@tonder.io/ionic-full-sdk 0.0.14-beta → 0.0.16-beta
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/dist/classes/3dsHandler.d.ts +10 -2
- package/dist/classes/inlineCheckout.d.ts +3 -0
- package/dist/helpers/utils.d.ts +9 -0
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/classes/3dsHandler.ts +143 -17
- package/src/classes/inlineCheckout.ts +47 -5
- package/src/helpers/skyflow.ts +16 -21
- package/src/helpers/utils.ts +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tonder.io/ionic-full-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16-beta",
|
|
4
4
|
"description": "Tonder ionic full SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.js",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"author": "",
|
|
11
11
|
"license": "ISC",
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@tonder.io/ionic-lite-sdk": "^0.0.
|
|
13
|
+
"@tonder.io/ionic-lite-sdk": "^0.0.25-beta",
|
|
14
14
|
"crypto-js": "^4.1.1",
|
|
15
15
|
"skyflow-js": "^1.34.1"
|
|
16
16
|
},
|
|
@@ -27,7 +27,45 @@ export class ThreeDSHandler {
|
|
|
27
27
|
saveVerifyTransactionUrl() {
|
|
28
28
|
const url = this.payload?.next_action?.redirect_to_url?.verify_transaction_status_url
|
|
29
29
|
if (url) {
|
|
30
|
-
|
|
30
|
+
this.saveUrlWithExpiration(url)
|
|
31
|
+
} else {
|
|
32
|
+
const url = this.payload?.next_action?.iframe_resources?.verify_transaction_status_url
|
|
33
|
+
if (url) {
|
|
34
|
+
this.saveUrlWithExpiration(url)
|
|
35
|
+
} else {
|
|
36
|
+
console.log('No verify_transaction_status_url found');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
saveUrlWithExpiration(url: string) {
|
|
42
|
+
try {
|
|
43
|
+
const now = new Date()
|
|
44
|
+
const item = {
|
|
45
|
+
url: url,
|
|
46
|
+
// Expires after 20 minutes
|
|
47
|
+
expires: now.getTime() + 20 * 60 * 1000
|
|
48
|
+
}
|
|
49
|
+
localStorage.setItem('verify_transaction_status', JSON.stringify(item))
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.log('error: ', error)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getUrlWithExpiration() {
|
|
56
|
+
const status = localStorage.getItem("verify_transaction_status");
|
|
57
|
+
if(status) {
|
|
58
|
+
const item = JSON.parse(status)
|
|
59
|
+
if (!item) return
|
|
60
|
+
const now = new Date()
|
|
61
|
+
if (now.getTime() > item.expires) {
|
|
62
|
+
this.removeVerifyTransactionUrl()
|
|
63
|
+
return null
|
|
64
|
+
} else {
|
|
65
|
+
return item.url
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
return null
|
|
31
69
|
}
|
|
32
70
|
}
|
|
33
71
|
|
|
@@ -39,17 +77,54 @@ export class ThreeDSHandler {
|
|
|
39
77
|
return localStorage.getItem("verify_transaction_status_url")
|
|
40
78
|
}
|
|
41
79
|
|
|
42
|
-
|
|
43
|
-
const
|
|
80
|
+
loadIframe() {
|
|
81
|
+
const iframe = this.payload?.next_action?.iframe_resources?.iframe
|
|
82
|
+
|
|
83
|
+
if (iframe) {
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
const iframe = this.payload?.next_action?.iframe_resources?.iframe
|
|
86
|
+
|
|
87
|
+
if (iframe) {
|
|
88
|
+
this.saveVerifyTransactionUrl()
|
|
89
|
+
const container = document.createElement('div')
|
|
90
|
+
container.innerHTML = iframe
|
|
91
|
+
document.body.appendChild(container)
|
|
92
|
+
|
|
93
|
+
// Create and append the script tag manually
|
|
94
|
+
const script = document.createElement('script')
|
|
95
|
+
script.textContent = 'document.getElementById("tdsMmethodForm").submit();'
|
|
96
|
+
container.appendChild(script)
|
|
97
|
+
|
|
98
|
+
// Resolve the promise when the iframe is loaded
|
|
99
|
+
const iframeElement = document.getElementById('tdsMmethodTgtFrame')
|
|
100
|
+
if(iframeElement) {
|
|
101
|
+
iframeElement.onload = () => resolve(true)
|
|
102
|
+
} else {
|
|
103
|
+
console.log('No redirection found');
|
|
104
|
+
reject(false)
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
console.log('No redirection found');
|
|
108
|
+
reject(false)
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
getRedirectUrl() {
|
|
115
|
+
return this.payload?.next_action?.redirect_to_url?.url
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
redirectToChallenge() {
|
|
119
|
+
const url = this.getRedirectUrl()
|
|
44
120
|
if (url) {
|
|
45
|
-
this.saveVerifyTransactionUrl()
|
|
46
|
-
|
|
121
|
+
this.saveVerifyTransactionUrl()
|
|
122
|
+
window.location = url;
|
|
47
123
|
} else {
|
|
48
124
|
console.log('No redirection found');
|
|
49
|
-
return false
|
|
50
125
|
}
|
|
51
126
|
}
|
|
52
|
-
|
|
127
|
+
|
|
53
128
|
// Returns an object
|
|
54
129
|
// https://example.com/?name=John&age=30&city=NewYork
|
|
55
130
|
// { name: "John", age: "30", city: "NewYork" }
|
|
@@ -64,8 +139,63 @@ export class ThreeDSHandler {
|
|
|
64
139
|
return parameters;
|
|
65
140
|
}
|
|
66
141
|
|
|
142
|
+
handleSuccessTransaction(response: any) {
|
|
143
|
+
this.removeVerifyTransactionUrl();
|
|
144
|
+
if(this.successUrl) {
|
|
145
|
+
window.location = this.successUrl as Location;
|
|
146
|
+
}
|
|
147
|
+
console.log('Transacción autorizada exitosamente.');
|
|
148
|
+
return response;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
handleDeclinedTransaction(response: any) {
|
|
152
|
+
this.removeVerifyTransactionUrl();
|
|
153
|
+
console.log('Transacción rechazada.');
|
|
154
|
+
throw new Error("Transacción rechazada.");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// TODO: the method below needs to be tested with a real 3DS challenge
|
|
158
|
+
// since we couldn't get a test card that works with this feature
|
|
159
|
+
async handle3dsChallenge(response_json: any) {
|
|
160
|
+
// Create the form element:
|
|
161
|
+
const form = document.createElement('form');
|
|
162
|
+
form.name = 'frm';
|
|
163
|
+
form.method = 'POST';
|
|
164
|
+
form.action = response_json.redirect_post_url;
|
|
165
|
+
|
|
166
|
+
// Add hidden fields:
|
|
167
|
+
const creqInput = document.createElement('input');
|
|
168
|
+
creqInput.type = 'hidden';
|
|
169
|
+
creqInput.name = response_json.creq;
|
|
170
|
+
creqInput.value = response_json.creq;
|
|
171
|
+
form.appendChild(creqInput);
|
|
172
|
+
|
|
173
|
+
const termUrlInput = document.createElement('input');
|
|
174
|
+
termUrlInput.type = 'hidden';
|
|
175
|
+
termUrlInput.name = response_json.term_url;
|
|
176
|
+
termUrlInput.value = response_json.TermUrl;
|
|
177
|
+
form.appendChild(termUrlInput);
|
|
178
|
+
|
|
179
|
+
// Append the form to the body:
|
|
180
|
+
document.body.appendChild(form);
|
|
181
|
+
form.submit();
|
|
182
|
+
|
|
183
|
+
await this.verifyTransactionStatus();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async handleTransactionResponse(response: any) {
|
|
187
|
+
const response_json = await response.json();
|
|
188
|
+
if (response_json.status === "Pending") {
|
|
189
|
+
return await this.handle3dsChallenge(response_json);
|
|
190
|
+
} else if (["Success", "Authorized"].includes(response_json.status)) {
|
|
191
|
+
return this.handleSuccessTransaction(response);
|
|
192
|
+
} else {
|
|
193
|
+
return this.handleDeclinedTransaction(response);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
67
197
|
async verifyTransactionStatus() {
|
|
68
|
-
const verifyUrl = this.
|
|
198
|
+
const verifyUrl = this.getUrlWithExpiration();
|
|
69
199
|
|
|
70
200
|
if (verifyUrl) {
|
|
71
201
|
const url = `${this.baseUrl}${verifyUrl}`;
|
|
@@ -79,23 +209,19 @@ export class ThreeDSHandler {
|
|
|
79
209
|
// body: JSON.stringify(data),
|
|
80
210
|
});
|
|
81
211
|
|
|
82
|
-
if (response.status
|
|
83
|
-
this.removeVerifyTransactionUrl();
|
|
84
|
-
//@ts-ignore
|
|
85
|
-
window.location = this.successUrl
|
|
86
|
-
console.log('La transacción se verificó con éxito.');
|
|
87
|
-
return response;
|
|
88
|
-
} else {
|
|
212
|
+
if (response.status !== 200) {
|
|
89
213
|
console.error('La verificación de la transacción falló.');
|
|
90
|
-
return
|
|
214
|
+
return
|
|
91
215
|
}
|
|
216
|
+
|
|
217
|
+
return await this.handleTransactionResponse(response);
|
|
92
218
|
} catch (error) {
|
|
93
219
|
console.error('Error al verificar la transacción:', error);
|
|
94
220
|
return error;
|
|
95
221
|
}
|
|
96
222
|
} else {
|
|
97
223
|
console.log('No verify_transaction_status_url found');
|
|
98
|
-
return null;
|
|
99
224
|
}
|
|
100
225
|
}
|
|
226
|
+
|
|
101
227
|
}
|
|
@@ -3,7 +3,8 @@ import { LiteCheckout } from '@tonder.io/ionic-lite-sdk';
|
|
|
3
3
|
import {
|
|
4
4
|
showError,
|
|
5
5
|
showMessage,
|
|
6
|
-
mapCards
|
|
6
|
+
mapCards,
|
|
7
|
+
getBrowserInfo
|
|
7
8
|
} from '../helpers/utils';
|
|
8
9
|
import { initSkyflow } from '../helpers/skyflow'
|
|
9
10
|
import { ThreeDSHandler } from './3dsHandler';
|
|
@@ -62,6 +63,9 @@ export class InlineCheckout {
|
|
|
62
63
|
radioChecked: string | null
|
|
63
64
|
fetchingPayment: boolean
|
|
64
65
|
isOpenPaySandbox: boolean = true
|
|
66
|
+
metadata = {}
|
|
67
|
+
card = {}
|
|
68
|
+
currency: string = ""
|
|
65
69
|
|
|
66
70
|
constructor ({
|
|
67
71
|
apiKey,
|
|
@@ -155,12 +159,27 @@ export class InlineCheckout {
|
|
|
155
159
|
}
|
|
156
160
|
}
|
|
157
161
|
|
|
162
|
+
#handleMetadata(data: { metadata: any }) {
|
|
163
|
+
this.metadata = data?.metadata
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#handleCurrency(data: { currency: string }) {
|
|
167
|
+
this.currency = data?.currency
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#handleCard(data: { card: string }) {
|
|
171
|
+
this.card = data?.card
|
|
172
|
+
}
|
|
173
|
+
|
|
158
174
|
payment(data: any) {
|
|
159
175
|
return new Promise(async (resolve, reject) => {
|
|
160
176
|
try {
|
|
161
177
|
this.#handleCustomer(data.customer)
|
|
162
178
|
this.setCartTotal(data.cart?.total)
|
|
163
179
|
this.setCartItems(data.cart?.items)
|
|
180
|
+
this.#handleMetadata(data)
|
|
181
|
+
this.#handleCurrency(data)
|
|
182
|
+
this.#handleCard(data)
|
|
164
183
|
const response: ErrorResponse | StartCheckoutResponse | false | undefined = await this.#checkout()
|
|
165
184
|
if (response) {
|
|
166
185
|
const process3ds = new ThreeDSHandler({
|
|
@@ -169,11 +188,30 @@ export class InlineCheckout {
|
|
|
169
188
|
apiKey: this.apiKeyTonder,
|
|
170
189
|
successUrl: this.successUrl
|
|
171
190
|
});
|
|
191
|
+
console.log("response", response);
|
|
172
192
|
this.callBack(response);
|
|
173
|
-
if
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
193
|
+
if("next_action" in response) {
|
|
194
|
+
const iframe = response?.next_action?.iframe_resources?.iframe
|
|
195
|
+
if (iframe) {
|
|
196
|
+
process3ds.loadIframe()?.then(() => {
|
|
197
|
+
//TODO: Check if this will be necessary on the frontend side
|
|
198
|
+
// after some the tests in production, since the 3DS process
|
|
199
|
+
// doesn't works properly on the sandbox environment
|
|
200
|
+
// setTimeout(() => {
|
|
201
|
+
// process3ds.verifyTransactionStatus();
|
|
202
|
+
// }, 10000);
|
|
203
|
+
process3ds.verifyTransactionStatus();
|
|
204
|
+
}).catch((error) => {
|
|
205
|
+
console.log('Error loading iframe:', error)
|
|
206
|
+
})
|
|
207
|
+
} else {
|
|
208
|
+
const redirectUrl = process3ds.getRedirectUrl()
|
|
209
|
+
if (redirectUrl) {
|
|
210
|
+
process3ds.redirectToChallenge()
|
|
211
|
+
} else {
|
|
212
|
+
resolve(response);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
177
215
|
}
|
|
178
216
|
}
|
|
179
217
|
} catch (error) {
|
|
@@ -547,11 +585,15 @@ export class InlineCheckout {
|
|
|
547
585
|
business_id: business.pk,
|
|
548
586
|
payment_id: ("pk" in jsonResponsePayment) && jsonResponsePayment.pk,
|
|
549
587
|
source: 'sdk',
|
|
588
|
+
metadata: this.metadata,
|
|
589
|
+
browser_info: getBrowserInfo(),
|
|
590
|
+
currency: this.currency
|
|
550
591
|
};
|
|
551
592
|
|
|
552
593
|
const jsonResponseRouter = await this.liteCheckout.startCheckoutRouter(
|
|
553
594
|
routerItems
|
|
554
595
|
);
|
|
596
|
+
|
|
555
597
|
if (jsonResponseRouter) {
|
|
556
598
|
try {
|
|
557
599
|
const selector: any = document.querySelector(`#${this.collectorIds.tonderPayButton}`);
|
package/src/helpers/skyflow.ts
CHANGED
|
@@ -63,9 +63,20 @@ export async function initSkyflow(
|
|
|
63
63
|
type: Skyflow.ValidationRuleType.LENGTH_MATCH_RULE,
|
|
64
64
|
params: {
|
|
65
65
|
max: 70,
|
|
66
|
+
error: "El campo nombre del titular debe tener menos de 70 caracteres"
|
|
66
67
|
},
|
|
67
68
|
};
|
|
68
69
|
|
|
70
|
+
const regexEmpty = RegExp("^(?!\s*$).+");
|
|
71
|
+
|
|
72
|
+
const regexMatchRule = {
|
|
73
|
+
type: Skyflow.ValidationRuleType.REGEX_MATCH_RULE,
|
|
74
|
+
params: {
|
|
75
|
+
regex: regexEmpty,
|
|
76
|
+
error: "El campo es requerido" // Optional, default error is 'VALIDATION FAILED'.
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
69
80
|
const cardHolderNameElement: CollectElement | RevealElement | ComposableElement = await collectContainer.create({
|
|
70
81
|
table: "cards",
|
|
71
82
|
column: "cardholder_name",
|
|
@@ -73,13 +84,9 @@ export async function initSkyflow(
|
|
|
73
84
|
label: "Titular de la tarjeta",
|
|
74
85
|
placeholder: "Nombre como aparece en la tarjeta",
|
|
75
86
|
type: Skyflow.ElementType.CARDHOLDER_NAME,
|
|
76
|
-
validations: [lengthMatchRule],
|
|
87
|
+
validations: [regexMatchRule, lengthMatchRule],
|
|
77
88
|
});
|
|
78
89
|
|
|
79
|
-
if("setError" in cardHolderNameElement) {
|
|
80
|
-
cardHolderNameElement.setError('Inválido')
|
|
81
|
-
}
|
|
82
|
-
|
|
83
90
|
// Create collect elements.
|
|
84
91
|
const cardNumberElement: CollectElement | RevealElement | ComposableElement = await collectContainer.create({
|
|
85
92
|
table: "cards",
|
|
@@ -92,12 +99,9 @@ export async function initSkyflow(
|
|
|
92
99
|
label: "Número de tarjeta",
|
|
93
100
|
placeholder: "1234 1234 1234 1234",
|
|
94
101
|
type: Skyflow.ElementType.CARD_NUMBER,
|
|
102
|
+
validations: [regexMatchRule],
|
|
95
103
|
});
|
|
96
104
|
|
|
97
|
-
if("setError" in cardNumberElement) {
|
|
98
|
-
cardNumberElement.setError('Inválido')
|
|
99
|
-
}
|
|
100
|
-
|
|
101
105
|
const cvvElement: CollectElement | RevealElement | ComposableElement = await collectContainer.create({
|
|
102
106
|
table: "cards",
|
|
103
107
|
column: "cvv",
|
|
@@ -105,11 +109,8 @@ export async function initSkyflow(
|
|
|
105
109
|
label: "CVC/CVV",
|
|
106
110
|
placeholder: "3-4 dígitos",
|
|
107
111
|
type: Skyflow.ElementType.CVV,
|
|
112
|
+
validations: [regexMatchRule],
|
|
108
113
|
});
|
|
109
|
-
|
|
110
|
-
if("setError" in cvvElement) {
|
|
111
|
-
cvvElement.setError('Inválido')
|
|
112
|
-
}
|
|
113
114
|
|
|
114
115
|
const expiryMonthElement: CollectElement | RevealElement | ComposableElement = await collectContainer.create({
|
|
115
116
|
table: "cards",
|
|
@@ -118,12 +119,9 @@ export async function initSkyflow(
|
|
|
118
119
|
label: "Fecha de expiración",
|
|
119
120
|
placeholder: "MM",
|
|
120
121
|
type: Skyflow.ElementType.EXPIRATION_MONTH,
|
|
122
|
+
validations: [regexMatchRule],
|
|
121
123
|
});
|
|
122
124
|
|
|
123
|
-
if("setError" in expiryMonthElement) {
|
|
124
|
-
expiryMonthElement.setError('Inválido')
|
|
125
|
-
}
|
|
126
|
-
|
|
127
125
|
const expiryYearElement: CollectElement | RevealElement | ComposableElement = await collectContainer.create({
|
|
128
126
|
table: "cards",
|
|
129
127
|
column: "expiration_year",
|
|
@@ -131,12 +129,9 @@ export async function initSkyflow(
|
|
|
131
129
|
label: "",
|
|
132
130
|
placeholder: "AA",
|
|
133
131
|
type: Skyflow.ElementType.EXPIRATION_YEAR,
|
|
132
|
+
validations: [regexMatchRule],
|
|
134
133
|
});
|
|
135
134
|
|
|
136
|
-
if("setError" in expiryYearElement) {
|
|
137
|
-
expiryYearElement.setError('Inválido')
|
|
138
|
-
}
|
|
139
|
-
|
|
140
135
|
await mountElements(
|
|
141
136
|
{ id: collectorIds.cardNumber, element: cardNumberElement },
|
|
142
137
|
{ id: collectorIds.cvv, element: cvvElement },
|
package/src/helpers/utils.ts
CHANGED
|
@@ -133,4 +133,17 @@ export const mapCards = (card: CardResponse) => {
|
|
|
133
133
|
const last = carArr[carArr.length - 1];
|
|
134
134
|
newCard.card_number = `••••${last}`;
|
|
135
135
|
return newCard;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const getBrowserInfo = () => {
|
|
139
|
+
const browserInfo = {
|
|
140
|
+
javascript_enabled: true, // Assumed since JavaScript is running
|
|
141
|
+
time_zone: new Date().getTimezoneOffset(),
|
|
142
|
+
language: navigator.language || 'en-US', // Fallback to 'en-US'
|
|
143
|
+
color_depth: window.screen ? window.screen.colorDepth : null,
|
|
144
|
+
screen_width: window.screen ? window.screen.width * window.devicePixelRatio || window.screen.width : null,
|
|
145
|
+
screen_height: window.screen ? window.screen.height * window.devicePixelRatio || window.screen.height : null,
|
|
146
|
+
user_agent: navigator.userAgent,
|
|
147
|
+
};
|
|
148
|
+
return browserInfo;
|
|
136
149
|
}
|