tonder-web-sdk 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env-example +1 -0
- package/.htaccess +1 -0
- package/README.md +204 -0
- package/cypress/e2e/1-getting-started/todo.cy.js +143 -0
- package/cypress/e2e/2-advanced-examples/actions.cy.js +299 -0
- package/cypress/e2e/2-advanced-examples/aliasing.cy.js +39 -0
- package/cypress/e2e/2-advanced-examples/assertions.cy.js +176 -0
- package/cypress/e2e/2-advanced-examples/connectors.cy.js +98 -0
- package/cypress/e2e/2-advanced-examples/cookies.cy.js +118 -0
- package/cypress/e2e/2-advanced-examples/cypress_api.cy.js +185 -0
- package/cypress/e2e/2-advanced-examples/files.cy.js +85 -0
- package/cypress/e2e/2-advanced-examples/location.cy.js +32 -0
- package/cypress/e2e/2-advanced-examples/misc.cy.js +104 -0
- package/cypress/e2e/2-advanced-examples/navigation.cy.js +56 -0
- package/cypress/e2e/2-advanced-examples/network_requests.cy.js +163 -0
- package/cypress/e2e/2-advanced-examples/querying.cy.js +114 -0
- package/cypress/e2e/2-advanced-examples/spies_stubs_clocks.cy.js +201 -0
- package/cypress/e2e/2-advanced-examples/storage.cy.js +110 -0
- package/cypress/e2e/2-advanced-examples/traversal.cy.js +121 -0
- package/cypress/e2e/2-advanced-examples/utilities.cy.js +108 -0
- package/cypress/e2e/2-advanced-examples/viewport.cy.js +58 -0
- package/cypress/e2e/2-advanced-examples/waiting.cy.js +30 -0
- package/cypress/e2e/2-advanced-examples/window.cy.js +22 -0
- package/cypress/fixtures/example.json +5 -0
- package/cypress/support/commands.js +25 -0
- package/cypress/support/e2e.js +20 -0
- package/cypress.config.js +9 -0
- package/index.html +178 -0
- package/index.js.example +50 -0
- package/package.json +29 -0
- package/samples/react/README.md +70 -0
- package/samples/react/build/asset-manifest.json +16 -0
- package/samples/react/build/favicon.ico +0 -0
- package/samples/react/build/index.html +1 -0
- package/samples/react/build/logo192.png +0 -0
- package/samples/react/build/logo512.png +0 -0
- package/samples/react/build/manifest.json +25 -0
- package/samples/react/build/robots.txt +3 -0
- package/samples/react/build/static/css/main.073c9b0a.css +2 -0
- package/samples/react/build/static/css/main.073c9b0a.css.map +1 -0
- package/samples/react/build/static/js/787.b83ed06f.chunk.js +2 -0
- package/samples/react/build/static/js/787.b83ed06f.chunk.js.map +1 -0
- package/samples/react/build/static/js/main.0a848807.js +3 -0
- package/samples/react/build/static/js/main.0a848807.js.LICENSE.txt +39 -0
- package/samples/react/build/static/js/main.0a848807.js.map +1 -0
- package/samples/react/build/static/media/sdk-icons.b491623214b2af4cccdb.png +0 -0
- package/samples/react/package-lock.json +28973 -0
- package/samples/react/package.json +44 -0
- package/samples/react/public/favicon.ico +0 -0
- package/samples/react/public/index.html +43 -0
- package/samples/react/public/logo192.png +0 -0
- package/samples/react/public/logo512.png +0 -0
- package/samples/react/public/manifest.json +25 -0
- package/samples/react/public/robots.txt +3 -0
- package/samples/react/src/App.css +38 -0
- package/samples/react/src/App.js +22 -0
- package/samples/react/src/App.test.js +8 -0
- package/samples/react/src/assets/img/sdk-icons.png +0 -0
- package/samples/react/src/components/Cart.js +29 -0
- package/samples/react/src/components/ProductCard.js +27 -0
- package/samples/react/src/context/CartContext.js +116 -0
- package/samples/react/src/index.css +13 -0
- package/samples/react/src/index.js +17 -0
- package/samples/react/src/logo.svg +1 -0
- package/samples/react/src/reportWebVitals.js +13 -0
- package/samples/react/src/screens/Checkout.js +82 -0
- package/samples/react/src/screens/Store.js +21 -0
- package/samples/react/src/setupTests.js +5 -0
- package/samples/react/src/storeProducts.js +30 -0
- package/src/classes/3dsHandler.js +203 -0
- package/src/classes/checkout.js +125 -0
- package/src/classes/inlineCheckout.js +349 -0
- package/src/data/api.js +109 -0
- package/src/helpers/skyflow.js +139 -0
- package/src/helpers/styles.js +61 -0
- package/src/helpers/template.js +112 -0
- package/src/helpers/utils.js +68 -0
- package/src/index-dev.js +129 -0
- package/src/index.html +58 -0
- package/src/index.js +7 -0
- package/success.html +22 -0
- package/v1/bundle.min.js +18 -0
- package/webpack.config.js +66 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
export class ThreeDSHandler {
|
|
2
|
+
constructor({
|
|
3
|
+
payload = null,
|
|
4
|
+
apiKey,
|
|
5
|
+
baseUrl,
|
|
6
|
+
successUrl
|
|
7
|
+
}) {
|
|
8
|
+
this.baseUrl = baseUrl,
|
|
9
|
+
this.apiKey = apiKey,
|
|
10
|
+
this.payload = payload,
|
|
11
|
+
this.successUrl = successUrl
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
saveVerifyTransactionUrl() {
|
|
15
|
+
const url = this.payload?.next_action?.redirect_to_url?.verify_transaction_status_url
|
|
16
|
+
if (url) {
|
|
17
|
+
this.saveUrlWithExpiration(url)
|
|
18
|
+
} else {
|
|
19
|
+
const url = this.payload?.next_action?.iframe_resources?.verify_transaction_status_url
|
|
20
|
+
if (url) {
|
|
21
|
+
this.saveUrlWithExpiration(url)
|
|
22
|
+
} else {
|
|
23
|
+
console.log('No verify_transaction_status_url found');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
saveUrlWithExpiration(url) {
|
|
29
|
+
try {
|
|
30
|
+
const now = new Date()
|
|
31
|
+
const item = {
|
|
32
|
+
url: url,
|
|
33
|
+
// Expires after 20 minutes
|
|
34
|
+
expires: now.getTime() + 20 * 60 * 1000
|
|
35
|
+
}
|
|
36
|
+
localStorage.setItem('verify_transaction_status', JSON.stringify(item))
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.log('error: ', error)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getUrlWithExpiration() {
|
|
43
|
+
const item = JSON.parse(localStorage.getItem("verify_transaction_status"))
|
|
44
|
+
if (!item) return
|
|
45
|
+
|
|
46
|
+
const now = new Date()
|
|
47
|
+
if (now.getTime() > item.expires) {
|
|
48
|
+
this.removeVerifyTransactionUrl()
|
|
49
|
+
return null
|
|
50
|
+
} else {
|
|
51
|
+
return item.url
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
removeVerifyTransactionUrl() {
|
|
56
|
+
localStorage.removeItem("verify_transaction_status")
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getVerifyTransactionUrl() {
|
|
60
|
+
return localStorage.getItem("verify_transaction_status")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
loadIframe() {
|
|
64
|
+
const iframe = this.payload?.next_action?.iframe_resources?.iframe
|
|
65
|
+
|
|
66
|
+
if (iframe) {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const iframe = this.payload?.next_action?.iframe_resources?.iframe
|
|
69
|
+
|
|
70
|
+
if (iframe) {
|
|
71
|
+
this.saveVerifyTransactionUrl()
|
|
72
|
+
const container = document.createElement('div')
|
|
73
|
+
container.innerHTML = iframe
|
|
74
|
+
document.body.appendChild(container)
|
|
75
|
+
|
|
76
|
+
// Create and append the script tag manually
|
|
77
|
+
const script = document.createElement('script')
|
|
78
|
+
script.textContent = 'document.getElementById("tdsMmethodForm").submit();'
|
|
79
|
+
container.appendChild(script)
|
|
80
|
+
|
|
81
|
+
// Resolve the promise when the iframe is loaded
|
|
82
|
+
const iframeElement = document.getElementById('tdsMmethodTgtFrame')
|
|
83
|
+
iframeElement.onload = () => resolve(true)
|
|
84
|
+
} else {
|
|
85
|
+
console.log('No redirection found');
|
|
86
|
+
reject(false)
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
getRedirectUrl() {
|
|
93
|
+
return this.payload?.next_action?.redirect_to_url?.url
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
redirectToChallenge() {
|
|
97
|
+
const url = this.getRedirectUrl()
|
|
98
|
+
if (url) {
|
|
99
|
+
this.saveVerifyTransactionUrl()
|
|
100
|
+
window.location = url;
|
|
101
|
+
} else {
|
|
102
|
+
console.log('No redirection found');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Returns an object
|
|
107
|
+
// https://example.com/?name=John&age=30&city=NewYork
|
|
108
|
+
// { name: "John", age: "30", city: "NewYork" }
|
|
109
|
+
getURLParameters() {
|
|
110
|
+
const parameters = {};
|
|
111
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
112
|
+
|
|
113
|
+
for (const [key, value] of urlParams) {
|
|
114
|
+
parameters[key] = value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return parameters;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
handleSuccessTransaction(response) {
|
|
121
|
+
this.removeVerifyTransactionUrl();
|
|
122
|
+
window.location = this.successUrl
|
|
123
|
+
console.log('Transacción autorizada exitosamente.');
|
|
124
|
+
return response;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
handleDeclinedTransaction(response) {
|
|
128
|
+
this.removeVerifyTransactionUrl();
|
|
129
|
+
console.log('Transacción rechazada.');
|
|
130
|
+
throw new Error("Transacción rechazada.");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// TODO: the method below needs to be tested with a real 3DS challenge
|
|
134
|
+
// since we couldn't get a test card that works with this feature
|
|
135
|
+
async handle3dsChallenge(response_json) {
|
|
136
|
+
// Create the form element:
|
|
137
|
+
const form = document.createElement('form');
|
|
138
|
+
form.name = 'frm';
|
|
139
|
+
form.method = 'POST';
|
|
140
|
+
form.action = response_json.redirect_post_url;
|
|
141
|
+
|
|
142
|
+
// Add hidden fields:
|
|
143
|
+
const creqInput = document.createElement('input');
|
|
144
|
+
creqInput.type = 'hidden';
|
|
145
|
+
creqInput.name = response_json.creq;
|
|
146
|
+
creqInput.value = response_json.creq;
|
|
147
|
+
form.appendChild(creqInput);
|
|
148
|
+
|
|
149
|
+
const termUrlInput = document.createElement('input');
|
|
150
|
+
termUrlInput.type = 'hidden';
|
|
151
|
+
termUrlInput.name = response_json.term_url;
|
|
152
|
+
termUrlInput.value = response_json.TermUrl;
|
|
153
|
+
form.appendChild(termUrlInput);
|
|
154
|
+
|
|
155
|
+
// Append the form to the body:
|
|
156
|
+
document.body.appendChild(form);
|
|
157
|
+
form.submit();
|
|
158
|
+
|
|
159
|
+
await this.verifyTransactionStatus();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async handleTransactionResponse(response) {
|
|
163
|
+
const response_json = await response.json();
|
|
164
|
+
|
|
165
|
+
if (response_json.status === "Pending") {
|
|
166
|
+
return await this.handle3dsChallenge(response_json);
|
|
167
|
+
} else if (["Success", "Authorized"].includes(response_json.status)) {
|
|
168
|
+
return this.handleSuccessTransaction(response);
|
|
169
|
+
} else {
|
|
170
|
+
return this.handleDeclinedTransaction(response);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async verifyTransactionStatus() {
|
|
175
|
+
const verifyUrl = this.getUrlWithExpiration();
|
|
176
|
+
|
|
177
|
+
if (verifyUrl) {
|
|
178
|
+
const url = `${this.baseUrl}${verifyUrl}`;
|
|
179
|
+
try {
|
|
180
|
+
const response = await fetch(url, {
|
|
181
|
+
method: "GET",
|
|
182
|
+
headers: {
|
|
183
|
+
"Content-Type": "application/json",
|
|
184
|
+
Authorization: `Token ${this.apiKey}`,
|
|
185
|
+
},
|
|
186
|
+
// body: JSON.stringify(data),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (response.status !== 200) {
|
|
190
|
+
console.error('La verificación de la transacción falló.');
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return await this.handleTransactionResponse(response);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error('Error al verificar la transacción:', error);
|
|
197
|
+
return error;
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
console.log('No verify_transaction_status_url found');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { AES } from "crypto-js";
|
|
2
|
+
|
|
3
|
+
export class Checkout {
|
|
4
|
+
constructor({
|
|
5
|
+
apiKey,
|
|
6
|
+
type = "payment",
|
|
7
|
+
backgroundColor="#141414",
|
|
8
|
+
color="#EBEBEB",
|
|
9
|
+
cb=()=>{},
|
|
10
|
+
url="http://checkout.tonder.io/#/"
|
|
11
|
+
}) {
|
|
12
|
+
this.url = url
|
|
13
|
+
this.apiKey = apiKey
|
|
14
|
+
this.type = type
|
|
15
|
+
this.backgroundColor = backgroundColor
|
|
16
|
+
this.color = color
|
|
17
|
+
this.params = ""
|
|
18
|
+
this.order = {}
|
|
19
|
+
this.buttonText = "Proceder al pago"
|
|
20
|
+
this.cb = cb
|
|
21
|
+
|
|
22
|
+
window.addEventListener("message", this.receiveMessage.bind(this), false);
|
|
23
|
+
}
|
|
24
|
+
generateButton = (buttonText) => {
|
|
25
|
+
this.buttonText = buttonText ? buttonText : this.buttonText
|
|
26
|
+
this.tonderButton = document.createElement('button');
|
|
27
|
+
this.tonderButton.innerHTML = this.buttonText;
|
|
28
|
+
this.stylishButton(this.tonderButton)
|
|
29
|
+
this.tonderButton.onclick = this.openCheckout
|
|
30
|
+
}
|
|
31
|
+
getButton = ({buttonText}) => {
|
|
32
|
+
this.generateButton(buttonText)
|
|
33
|
+
return this.tonderButton
|
|
34
|
+
}
|
|
35
|
+
mountButton = ({buttonText}) => {
|
|
36
|
+
this.generateButton(buttonText)
|
|
37
|
+
const entryPoint = document.getElementById("tonder-checkout")
|
|
38
|
+
try {
|
|
39
|
+
entryPoint.innerHTML = ""
|
|
40
|
+
entryPoint.append(this.tonderButton)
|
|
41
|
+
} catch(error) {
|
|
42
|
+
console.error(error)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
stylishButton = (element) => {
|
|
46
|
+
element.style.backgroundColor = this.backgroundColor
|
|
47
|
+
element.style.color = this.color
|
|
48
|
+
element.style.display = 'flex'
|
|
49
|
+
element.style.justifyContent = 'center'
|
|
50
|
+
element.style.border = 'none'
|
|
51
|
+
element.style.padding = '1rem'
|
|
52
|
+
element.style.borderRadius = '10px'
|
|
53
|
+
element.style.fontSize = '1rem'
|
|
54
|
+
element.style.width = '100%'
|
|
55
|
+
element.style.boxShadow = '0 3px 6px 0 rgba(0,0,0,0.16)'
|
|
56
|
+
}
|
|
57
|
+
setOrder = ({products, email, shippingCost }) => {
|
|
58
|
+
let _order = {}
|
|
59
|
+
if (products) _order.products = products
|
|
60
|
+
if (email) _order.email = email
|
|
61
|
+
if (shippingCost) _order.shippingCost = shippingCost
|
|
62
|
+
this.order = {...this.order, ..._order}
|
|
63
|
+
return this.order
|
|
64
|
+
}
|
|
65
|
+
openTabListener = (tab, button) => {
|
|
66
|
+
const tabInterval = setInterval(() => {
|
|
67
|
+
if (tab.closed) {
|
|
68
|
+
clearInterval(tabInterval);
|
|
69
|
+
button.disabled = false
|
|
70
|
+
button.innerHTML = this.buttonText
|
|
71
|
+
}
|
|
72
|
+
}, 500)
|
|
73
|
+
}
|
|
74
|
+
openCheckout = () => {
|
|
75
|
+
const queryString = this.getUrlParams()
|
|
76
|
+
const encrypted = AES.encrypt(queryString, 'url-params-encrypt').toString()
|
|
77
|
+
const encodedURL = encodeURIComponent(encrypted);
|
|
78
|
+
this.params = "?" + encodedURL;
|
|
79
|
+
const newWindow = window.open(this.url + this.params, '_blank', `width=1200,height=$800,left=0,top=0`);
|
|
80
|
+
this.tonderButton.disabled = true
|
|
81
|
+
this.tonderButton.innerHTML = `
|
|
82
|
+
<div class="loader"></div>
|
|
83
|
+
<style>
|
|
84
|
+
.loader {
|
|
85
|
+
border: 4px solid ${this.color};
|
|
86
|
+
border-radius: 50%;
|
|
87
|
+
border-top: 4px solid ${this.backgroundColor};
|
|
88
|
+
width: 0.625rem;
|
|
89
|
+
height: 0.625rem;
|
|
90
|
+
-webkit-animation: spin 2s linear infinite; /* Safari */
|
|
91
|
+
animation: spin 2s linear infinite;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Safari */
|
|
95
|
+
@-webkit-keyframes spin {
|
|
96
|
+
0% { -webkit-transform: rotate(0deg); }
|
|
97
|
+
100% { -webkit-transform: rotate(360deg); }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@keyframes spin {
|
|
101
|
+
0% { transform: rotate(0deg); }
|
|
102
|
+
100% { transform: rotate(360deg); }
|
|
103
|
+
}
|
|
104
|
+
</style>
|
|
105
|
+
`
|
|
106
|
+
this.openTabListener(newWindow, this.tonderButton)
|
|
107
|
+
}
|
|
108
|
+
getUrlParams = () => {
|
|
109
|
+
const params = { apiKey: this.apiKey, ...this.order, type: this.type}
|
|
110
|
+
if (params.products) {
|
|
111
|
+
params.products = JSON.stringify(params.products)
|
|
112
|
+
}
|
|
113
|
+
const queryString = new URLSearchParams(params).toString();
|
|
114
|
+
return queryString
|
|
115
|
+
}
|
|
116
|
+
receiveMessage(event) {
|
|
117
|
+
// Parse data if it is possible, in case of error it will return the raw data.
|
|
118
|
+
try {
|
|
119
|
+
const data = JSON.parse(event.data)
|
|
120
|
+
this.cb(data)
|
|
121
|
+
} catch(error) {
|
|
122
|
+
this.cb(event.data)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { cardTemplate } from '../helpers/template.js'
|
|
2
|
+
import {
|
|
3
|
+
getBusiness,
|
|
4
|
+
customerRegister,
|
|
5
|
+
createOrder,
|
|
6
|
+
createPayment,
|
|
7
|
+
startCheckoutRouter,
|
|
8
|
+
getOpenpayDeviceSessionID
|
|
9
|
+
} from '../data/api';
|
|
10
|
+
import {
|
|
11
|
+
showError,
|
|
12
|
+
getBrowserInfo,
|
|
13
|
+
} from '../helpers/utils';
|
|
14
|
+
import { initSkyflow } from '../helpers/skyflow'
|
|
15
|
+
import { ThreeDSHandler } from './3dsHandler.js';
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export class InlineCheckout {
|
|
19
|
+
static injected = false;
|
|
20
|
+
customer = {}
|
|
21
|
+
items = []
|
|
22
|
+
baseUrl = process.env.BASE_URL || "http://localhost:8000";
|
|
23
|
+
collectContainer = null
|
|
24
|
+
merchantData = {}
|
|
25
|
+
cartTotal = null
|
|
26
|
+
metadata = {}
|
|
27
|
+
card = {}
|
|
28
|
+
|
|
29
|
+
constructor({
|
|
30
|
+
apiKey,
|
|
31
|
+
returnUrl,
|
|
32
|
+
successUrl,
|
|
33
|
+
renderPaymentButton = false,
|
|
34
|
+
callBack = () => { },
|
|
35
|
+
styles,
|
|
36
|
+
}) {
|
|
37
|
+
this.apiKeyTonder = apiKey;
|
|
38
|
+
this.returnUrl = returnUrl;
|
|
39
|
+
this.successUrl = successUrl;
|
|
40
|
+
this.renderPaymentButton = renderPaymentButton;
|
|
41
|
+
this.callBack = callBack;
|
|
42
|
+
this.customStyles = styles
|
|
43
|
+
|
|
44
|
+
this.abortController = new AbortController()
|
|
45
|
+
this.process3ds = new ThreeDSHandler(
|
|
46
|
+
{ apiKey: apiKey, baseUrl: this.baseUrl, successUrl: successUrl }
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#mountPayButton() {
|
|
51
|
+
if (!this.renderPaymentButton) return;
|
|
52
|
+
|
|
53
|
+
const payButton = document.querySelector("#tonderPayButton");
|
|
54
|
+
if (!payButton) {
|
|
55
|
+
console.error("Pay button not found");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
payButton.style.display = "block";
|
|
60
|
+
payButton.textContent = `Pagar $${this.cartTotal}`;
|
|
61
|
+
payButton.onclick = async (event) => {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
await this.#handlePaymentClick(payButton);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async #handlePaymentClick(payButton) {
|
|
68
|
+
const prevButtonContent = payButton.innerHTML;
|
|
69
|
+
payButton.innerHTML = `<div class="lds-dual-ring"></div>`;
|
|
70
|
+
try {
|
|
71
|
+
const response = await this.payment(this.customer);
|
|
72
|
+
this.callBack(response);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error("Payment error:", error);
|
|
75
|
+
} finally {
|
|
76
|
+
payButton.innerHTML = prevButtonContent;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
payment(data) {
|
|
81
|
+
return new Promise(async (resolve, reject) => {
|
|
82
|
+
try {
|
|
83
|
+
this.#handleCustomer(data.customer)
|
|
84
|
+
this.setCartTotal(data.cart?.total)
|
|
85
|
+
this.setCartItems(data.cart?.items)
|
|
86
|
+
this.#handleMetadata(data)
|
|
87
|
+
this.#handleCurrency(data)
|
|
88
|
+
this.#handleCard(data)
|
|
89
|
+
const response = await this.#checkout()
|
|
90
|
+
if (response) {
|
|
91
|
+
const process3ds = new ThreeDSHandler({
|
|
92
|
+
baseUrl: this.baseUrl,
|
|
93
|
+
apiKey: this.apiKeyTonder,
|
|
94
|
+
payload: response,
|
|
95
|
+
});
|
|
96
|
+
this.callBack(response);
|
|
97
|
+
|
|
98
|
+
const iframe = response?.next_action?.iframe_resources?.iframe
|
|
99
|
+
|
|
100
|
+
if (iframe) {
|
|
101
|
+
process3ds.loadIframe().then(() => {
|
|
102
|
+
//TODO: Check if this will be necessary on the frontend side
|
|
103
|
+
// after some the tests in production, since the 3DS process
|
|
104
|
+
// doesn't works properly on the sandbox environment
|
|
105
|
+
// setTimeout(() => {
|
|
106
|
+
// process3ds.verifyTransactionStatus();
|
|
107
|
+
// }, 10000);
|
|
108
|
+
process3ds.verifyTransactionStatus();
|
|
109
|
+
}).catch((error) => {
|
|
110
|
+
console.log('Error loading iframe:', error)
|
|
111
|
+
})
|
|
112
|
+
} else {
|
|
113
|
+
const redirectUrl = process3ds.getRedirectUrl()
|
|
114
|
+
if (redirectUrl) {
|
|
115
|
+
process3ds.redirectToChallenge()
|
|
116
|
+
} else {
|
|
117
|
+
resolve(response);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
reject(error);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#handleCustomer(customer) {
|
|
128
|
+
console.log('customer: ', customer)
|
|
129
|
+
if (!customer) return
|
|
130
|
+
|
|
131
|
+
this.firstName = customer?.firstName
|
|
132
|
+
this.lastName = customer?.lastName
|
|
133
|
+
this.country = customer?.country
|
|
134
|
+
this.address = customer?.street
|
|
135
|
+
this.city = customer?.city
|
|
136
|
+
this.state = customer?.state
|
|
137
|
+
this.postCode = customer?.postCode
|
|
138
|
+
this.email = customer?.email
|
|
139
|
+
this.phone = customer?.phone
|
|
140
|
+
this.customer = customer
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#handleMetadata(data) {
|
|
144
|
+
this.metadata = data?.metadata
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#handleCurrency(data) {
|
|
148
|
+
this.currency = data?.currency
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#handleCard(data) {
|
|
152
|
+
this.card = data?.card
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
setCartItems(items) {
|
|
156
|
+
console.log('items: ', items)
|
|
157
|
+
this.cartItems = items
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
setCartTotal(total) {
|
|
161
|
+
console.log('total: ', total)
|
|
162
|
+
this.cartTotal = total
|
|
163
|
+
this.#updatePayButton()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#updatePayButton() {
|
|
167
|
+
const payButton = document.querySelector("#tonderPayButton");
|
|
168
|
+
if (!payButton) return
|
|
169
|
+
payButton.textContent = `Pagar $${this.cartTotal}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
setCallback(cb) {
|
|
173
|
+
this.cb = cb
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
injectCheckout() {
|
|
177
|
+
if (InlineCheckout.injected) return
|
|
178
|
+
this.process3ds.verifyTransactionStatus()
|
|
179
|
+
const injectInterval = setInterval(() => {
|
|
180
|
+
if (document.querySelector("#tonder-checkout")) {
|
|
181
|
+
document.querySelector("#tonder-checkout").innerHTML = cardTemplate;
|
|
182
|
+
this.#mountTonder();
|
|
183
|
+
clearInterval(injectInterval);
|
|
184
|
+
InlineCheckout.injected = true
|
|
185
|
+
}
|
|
186
|
+
}, 500);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async #fetchMerchantData() {
|
|
190
|
+
this.merchantData = await getBusiness(
|
|
191
|
+
this.baseUrl,
|
|
192
|
+
this.apiKeyTonder,
|
|
193
|
+
this.abortController.signal
|
|
194
|
+
);
|
|
195
|
+
return this.merchantData
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async getCustomer(customer, signal) {
|
|
199
|
+
return await customerRegister(this.baseUrl, this.apiKeyTonder, customer, signal);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async #mountTonder() {
|
|
203
|
+
this.#mountPayButton()
|
|
204
|
+
|
|
205
|
+
const {
|
|
206
|
+
vault_id,
|
|
207
|
+
vault_url,
|
|
208
|
+
} = await this.#fetchMerchantData();
|
|
209
|
+
|
|
210
|
+
this.collectContainer = await initSkyflow(
|
|
211
|
+
vault_id,
|
|
212
|
+
vault_url,
|
|
213
|
+
this.baseUrl,
|
|
214
|
+
this.apiKeyTonder,
|
|
215
|
+
this.abortController.signal,
|
|
216
|
+
this.customStyles,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
removeCheckout() {
|
|
221
|
+
InlineCheckout.injected = false
|
|
222
|
+
// Cancel all requests
|
|
223
|
+
this.abortController.abort();
|
|
224
|
+
this.abortController = new AbortController();
|
|
225
|
+
|
|
226
|
+
clearInterval(this.injectInterval);
|
|
227
|
+
console.log("InlineCheckout removed from DOM and cleaned up.");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async #getCardTokens() {
|
|
231
|
+
if (this.card?.skyflow_id) return this.card
|
|
232
|
+
try {
|
|
233
|
+
const collectResponse = await this.collectContainer.collect();
|
|
234
|
+
const cardTokens = await collectResponse["records"][0]["fields"];
|
|
235
|
+
return cardTokens;
|
|
236
|
+
} catch (error) {
|
|
237
|
+
showError("Por favor, verifica todos los campos de tu tarjeta")
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async #checkout() {
|
|
243
|
+
try {
|
|
244
|
+
document.querySelector("#tonderPayButton").disabled = true;
|
|
245
|
+
} catch (error) {
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const { openpay_keys, reference, business } = this.merchantData
|
|
249
|
+
const total = Number(this.cartTotal)
|
|
250
|
+
|
|
251
|
+
const cardTokens = await this.#getCardTokens();
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
let deviceSessionIdTonder;
|
|
255
|
+
if (openpay_keys.merchant_id && openpay_keys.public_key) {
|
|
256
|
+
deviceSessionIdTonder = await getOpenpayDeviceSessionID(
|
|
257
|
+
openpay_keys.merchant_id,
|
|
258
|
+
openpay_keys.public_key,
|
|
259
|
+
this.abortController.signal
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const { id, auth_token } = await this.getCustomer(
|
|
264
|
+
this.customer,
|
|
265
|
+
this.abortController.signal
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
var orderItems = {
|
|
269
|
+
business: this.apiKeyTonder,
|
|
270
|
+
client: auth_token,
|
|
271
|
+
billing_address_id: null,
|
|
272
|
+
shipping_address_id: null,
|
|
273
|
+
amount: total,
|
|
274
|
+
status: "A",
|
|
275
|
+
reference: reference,
|
|
276
|
+
is_oneclick: true,
|
|
277
|
+
items: this.cartItems,
|
|
278
|
+
};
|
|
279
|
+
console.log('orderItems: ', orderItems)
|
|
280
|
+
const jsonResponseOrder = await createOrder(
|
|
281
|
+
this.baseUrl,
|
|
282
|
+
this.apiKeyTonder,
|
|
283
|
+
orderItems
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
// Create payment
|
|
287
|
+
const now = new Date();
|
|
288
|
+
const dateString = now.toISOString();
|
|
289
|
+
|
|
290
|
+
var paymentItems = {
|
|
291
|
+
business_pk: business.pk,
|
|
292
|
+
client_id: id,
|
|
293
|
+
amount: total,
|
|
294
|
+
date: dateString,
|
|
295
|
+
order_id: jsonResponseOrder.id,
|
|
296
|
+
};
|
|
297
|
+
const jsonResponsePayment = await createPayment(
|
|
298
|
+
this.baseUrl,
|
|
299
|
+
this.apiKeyTonder,
|
|
300
|
+
paymentItems
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
// Checkout router
|
|
304
|
+
const routerItems = {
|
|
305
|
+
card: cardTokens,
|
|
306
|
+
name: this.firstName || "",
|
|
307
|
+
last_name: this.lastName || "",
|
|
308
|
+
email_client: this.email,
|
|
309
|
+
phone_number: this.phone,
|
|
310
|
+
return_url: this.returnUrl,
|
|
311
|
+
id_product: "no_id",
|
|
312
|
+
quantity_product: 1,
|
|
313
|
+
id_ship: "0",
|
|
314
|
+
instance_id_ship: "0",
|
|
315
|
+
amount: total,
|
|
316
|
+
title_ship: "shipping",
|
|
317
|
+
description: "transaction",
|
|
318
|
+
device_session_id: deviceSessionIdTonder ? deviceSessionIdTonder : null,
|
|
319
|
+
token_id: "",
|
|
320
|
+
order_id: jsonResponseOrder.id,
|
|
321
|
+
business_id: business.pk,
|
|
322
|
+
payment_id: jsonResponsePayment.pk,
|
|
323
|
+
source: 'sdk',
|
|
324
|
+
metadata: this.metadata,
|
|
325
|
+
browser_info: getBrowserInfo(),
|
|
326
|
+
currency: this.currency,
|
|
327
|
+
};
|
|
328
|
+
const jsonResponseRouter = await startCheckoutRouter(
|
|
329
|
+
this.baseUrl,
|
|
330
|
+
this.apiKeyTonder,
|
|
331
|
+
routerItems
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
if (jsonResponseRouter) {
|
|
335
|
+
try {
|
|
336
|
+
document.querySelector("#tonderPayButton").disabled = false;
|
|
337
|
+
} catch { }
|
|
338
|
+
return jsonResponseRouter;
|
|
339
|
+
} else {
|
|
340
|
+
showError("No se ha podido procesar el pago")
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
} catch (error) {
|
|
344
|
+
console.log(error);
|
|
345
|
+
showError("Ha ocurrido un error")
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
}
|