@swirepay-developer/swirepay-payment-sdk 1.0.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/assets/imgs/GOOGLE_PAY.svg +6 -0
- package/index.html +1291 -0
- package/package.json +19 -0
- package/swirepayPaypalWalletPayment.js +742 -0
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
class PaypalPaymentMethod {
|
|
2
|
+
static paypal_sdk_url = "https://www.paypal.com/sdk/js";
|
|
3
|
+
static intent = "capture";
|
|
4
|
+
|
|
5
|
+
// CACHE: Stores active loading promises to prevent race conditions
|
|
6
|
+
static _scriptLoadingPromises = {};
|
|
7
|
+
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.mountId = config.mountId;
|
|
10
|
+
this.test = config.test;
|
|
11
|
+
this.apiKey = config.apiKey;
|
|
12
|
+
this.accountGid = config.accountGid;
|
|
13
|
+
this.currencyDetail = {
|
|
14
|
+
code: config.currencyCode,
|
|
15
|
+
countryAlpha2: config.countryAlpha2,
|
|
16
|
+
};
|
|
17
|
+
this.processStartCallback = config.processStartCallback;
|
|
18
|
+
this.failCallback = config.failCallback;
|
|
19
|
+
this.successCallback = config.successCallback;
|
|
20
|
+
this.unMountOnError = config.unMountOnError;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getEndpoints() {
|
|
24
|
+
if (this.test) {
|
|
25
|
+
return { gateway: 'https://staging-backend.swirepay.com' };
|
|
26
|
+
}
|
|
27
|
+
return { gateway: 'https://api.swirepay.com' };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
AmountDivideByHundred(amount) {
|
|
31
|
+
return (amount ? (amount / 100).toFixed(2) : amount);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getRobustPhoneNumber(countryCode, googlePhoneStr) {
|
|
35
|
+
if (!googlePhoneStr || !countryCode) return null;
|
|
36
|
+
const cleanPhone = typeof googlePhoneStr === 'string' ? googlePhoneStr.replace(/\D/g, '') : '';
|
|
37
|
+
const cleanCode = typeof countryCode === 'string' ? countryCode.replace(/\D/g, '') : '';
|
|
38
|
+
if (cleanCode && cleanPhone && cleanPhone.startsWith(cleanCode)) {
|
|
39
|
+
return `+${cleanPhone}`;
|
|
40
|
+
}
|
|
41
|
+
return cleanCode && cleanPhone ? `+${cleanCode}${cleanPhone}` : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ROBUST SCRIPT LOADER (Fixes Race Condition)
|
|
45
|
+
async script_to_head(attrs) {
|
|
46
|
+
const id = attrs.id;
|
|
47
|
+
|
|
48
|
+
// 1. If script is already in DOM, resolve immediately.
|
|
49
|
+
if (id && document.getElementById(id)) {
|
|
50
|
+
return Promise.resolve();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 2. If a request for this ID is already in progress, wait for it.
|
|
54
|
+
if (id && PaypalPaymentMethod._scriptLoadingPromises[id]) {
|
|
55
|
+
return PaypalPaymentMethod._scriptLoadingPromises[id];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 3. Create a new loading promise
|
|
59
|
+
const loadPromise = new Promise((resolve, reject) => {
|
|
60
|
+
const script = document.createElement("script");
|
|
61
|
+
Object.entries(attrs).forEach(([k, v]) => script.setAttribute(k, v));
|
|
62
|
+
|
|
63
|
+
script.onload = () => resolve();
|
|
64
|
+
script.onerror = (err) => {
|
|
65
|
+
delete PaypalPaymentMethod._scriptLoadingPromises[id]; // Cleanup on fail
|
|
66
|
+
reject(err);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
document.head.appendChild(script);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// 4. Cache the promise
|
|
73
|
+
if (id) {
|
|
74
|
+
PaypalPaymentMethod._scriptLoadingPromises[id] = loadPromise;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return loadPromise;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async getPayPalClientToken() {
|
|
81
|
+
const gateway = this.getEndpoints().gateway;
|
|
82
|
+
const config = {
|
|
83
|
+
method: 'GET',
|
|
84
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey },
|
|
85
|
+
};
|
|
86
|
+
const response = await fetch(`${gateway}/v1/paypal/${this.accountGid}/config`, config);
|
|
87
|
+
const responseData = await response.json();
|
|
88
|
+
return responseData?.message;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
Spinner({ size = '40px', color = '#007bff', thickness = '4px', speed = '1s', ...props }) {
|
|
92
|
+
const spinner = document.createElement('div');
|
|
93
|
+
spinner.style.width = size;
|
|
94
|
+
spinner.style.height = size;
|
|
95
|
+
spinner.style.border = `${thickness} solid #f3f3f3`;
|
|
96
|
+
spinner.style.borderTop = `${thickness} solid ${color}`;
|
|
97
|
+
spinner.style.borderRadius = '50%';
|
|
98
|
+
spinner.style.display = 'inline-block';
|
|
99
|
+
spinner.style.animation = `spin ${speed} linear infinite`;
|
|
100
|
+
|
|
101
|
+
if (!document.querySelector('#spinner-keyframes')) {
|
|
102
|
+
const style = document.createElement('style');
|
|
103
|
+
style.id = 'spinner-keyframes';
|
|
104
|
+
style.textContent = `@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }`;
|
|
105
|
+
document.head.appendChild(style);
|
|
106
|
+
}
|
|
107
|
+
Object.keys(props).forEach(key => {
|
|
108
|
+
if (key === 'className') spinner.className = props[key];
|
|
109
|
+
else if (key !== 'onClick') spinner.setAttribute(key, props[key]);
|
|
110
|
+
});
|
|
111
|
+
return spinner;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async findCustomer(name, email, phone) {
|
|
115
|
+
const gateway = this.getEndpoints().gateway;
|
|
116
|
+
let url = `${gateway}/v1/customer`;
|
|
117
|
+
const config = { method: 'GET', headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey } };
|
|
118
|
+
|
|
119
|
+
let params = [];
|
|
120
|
+
if(name) params.push(`name.EQ=${name}`);
|
|
121
|
+
if(email) params.push(`email.EQ=${email}`);
|
|
122
|
+
if(phone) params.push(`phoneNumber.EQ=${phone}`);
|
|
123
|
+
|
|
124
|
+
if(params.length > 0) url += `?${params.join('&')}`;
|
|
125
|
+
|
|
126
|
+
const response = await fetch(url, config);
|
|
127
|
+
return await response.json();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async createCustomer(name, email, phone) {
|
|
131
|
+
const gateway = this.getEndpoints().gateway;
|
|
132
|
+
let url = `${gateway}/v1/customer`;
|
|
133
|
+
const config = {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey },
|
|
136
|
+
body: JSON.stringify({ name, email, phoneNumber: phone }),
|
|
137
|
+
};
|
|
138
|
+
const response = await fetch(url, config);
|
|
139
|
+
return await response.json();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async addPaymentMethod(paymentMethod) {
|
|
143
|
+
const gateway = this.getEndpoints().gateway;
|
|
144
|
+
let url = `${gateway}/v1/payment-method`;
|
|
145
|
+
const config = {
|
|
146
|
+
method: 'POST',
|
|
147
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey },
|
|
148
|
+
body: JSON.stringify(paymentMethod),
|
|
149
|
+
};
|
|
150
|
+
const response = await fetch(url, config);
|
|
151
|
+
return await response.json();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async getMrn(psGid, psSecret) {
|
|
155
|
+
const gateway = this.getEndpoints().gateway;
|
|
156
|
+
let url = `${gateway}/v2/payment-session/${psGid}/mrn`;
|
|
157
|
+
const config = {
|
|
158
|
+
method: 'GET',
|
|
159
|
+
headers: { 'Content-Type': 'application/json', 'ps-client-secret': psSecret, 'x-api-key': this.apiKey },
|
|
160
|
+
};
|
|
161
|
+
const response = await fetch(url, config);
|
|
162
|
+
const data = await response.json();
|
|
163
|
+
return data?.message;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async action(psGid, psSecret) {
|
|
167
|
+
const gateway = this.getEndpoints().gateway;
|
|
168
|
+
let url = `${gateway}/v2/payment-session/${psGid}/action`;
|
|
169
|
+
const config = {
|
|
170
|
+
method: 'POST',
|
|
171
|
+
headers: { 'Content-Type': 'application/json', 'ps-client-secret': psSecret, 'x-api-key': this.apiKey },
|
|
172
|
+
body: JSON.stringify({}),
|
|
173
|
+
};
|
|
174
|
+
const response = await fetch(url, config);
|
|
175
|
+
const data = await response.json();
|
|
176
|
+
return data?.entity;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
removeAllChildFromMount() {
|
|
180
|
+
const mount = document.getElementById(this.mountId);
|
|
181
|
+
if (mount) mount.innerHTML = '';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
showLoading() {
|
|
185
|
+
const loadingSpinner = this.Spinner({ id: 'swirepay-loading-spinner' });
|
|
186
|
+
const mount = document.getElementById(this.mountId);
|
|
187
|
+
this.removeAllChildFromMount();
|
|
188
|
+
if (mount) mount.appendChild(loadingSpinner);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
showEligibilityIssue() {
|
|
192
|
+
this.removeAllChildFromMount();
|
|
193
|
+
if (!this.unMountOnError) {
|
|
194
|
+
const mount = document.getElementById(this.mountId);
|
|
195
|
+
const errorMsg = document.createElement('p');
|
|
196
|
+
errorMsg.innerText = 'Not Eligible';
|
|
197
|
+
if (mount) mount.appendChild(errorMsg);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
class PaypalGooglePay extends PaypalPaymentMethod {
|
|
203
|
+
static google_pay_sdk_url = "https://pay.google.com/gp/p/js/pay.js";
|
|
204
|
+
|
|
205
|
+
constructor(config) {
|
|
206
|
+
super(config);
|
|
207
|
+
this.googlePayPaymentsClient = null;
|
|
208
|
+
this.paypalGooglePayConfig = null;
|
|
209
|
+
this.countryCallingCode = config.countryCallingCode;
|
|
210
|
+
this.amount = null;
|
|
211
|
+
|
|
212
|
+
this.initGooglePay = this.initGooglePay.bind(this);
|
|
213
|
+
|
|
214
|
+
if (document.readyState === "complete" || document.readyState === "interactive") {
|
|
215
|
+
this.initGooglePay();
|
|
216
|
+
} else {
|
|
217
|
+
window.addEventListener('DOMContentLoaded', this.initGooglePay);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
GooglePayButton({ onClick, disabled = false, ...props }) {
|
|
222
|
+
const button = document.createElement('button');
|
|
223
|
+
if (props?.id) button.id = props.id;
|
|
224
|
+
|
|
225
|
+
const COLOR_PRIMARY = '#23262aff';
|
|
226
|
+
let isHovered = false;
|
|
227
|
+
|
|
228
|
+
const updateStyles = () => {
|
|
229
|
+
const btnStyles = {
|
|
230
|
+
backgroundColor: isHovered ? '#000000' : COLOR_PRIMARY,
|
|
231
|
+
borderColor: isHovered ? '#000000' : COLOR_PRIMARY,
|
|
232
|
+
color: '#fff',
|
|
233
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
234
|
+
opacity: disabled ? '0.6' : '1',
|
|
235
|
+
height: '32px',
|
|
236
|
+
padding: '4px 15px',
|
|
237
|
+
fontSize: '14px',
|
|
238
|
+
borderRadius: '6px',
|
|
239
|
+
border: '1px solid transparent',
|
|
240
|
+
outline: 'none',
|
|
241
|
+
display: 'inline-flex',
|
|
242
|
+
alignItems: 'center',
|
|
243
|
+
justifyContent: 'center',
|
|
244
|
+
gap: '8px',
|
|
245
|
+
transition: 'all 0.2s',
|
|
246
|
+
};
|
|
247
|
+
Object.keys(btnStyles).forEach(key => button.style[key] = btnStyles[key]);
|
|
248
|
+
};
|
|
249
|
+
updateStyles();
|
|
250
|
+
|
|
251
|
+
const googleIcon = document.createElement('img');
|
|
252
|
+
googleIcon.src = '/assets/imgs/GOOGLE_PAY.svg';
|
|
253
|
+
googleIcon.alt = 'Google Icon';
|
|
254
|
+
googleIcon.style.height = '20px';
|
|
255
|
+
googleIcon.style.width = 'auto';
|
|
256
|
+
|
|
257
|
+
const textSpan = document.createElement('span');
|
|
258
|
+
textSpan.textContent = ' Pay';
|
|
259
|
+
|
|
260
|
+
button.appendChild(googleIcon);
|
|
261
|
+
button.appendChild(textSpan);
|
|
262
|
+
button.disabled = disabled;
|
|
263
|
+
button.addEventListener('click', (e) => { if (!disabled && onClick) onClick(e); });
|
|
264
|
+
button.addEventListener('mouseenter', () => { if (!disabled) { isHovered = true; updateStyles(); } });
|
|
265
|
+
button.addEventListener('mouseleave', () => { isHovered = false; updateStyles(); });
|
|
266
|
+
|
|
267
|
+
Object.keys(props).forEach(key => {
|
|
268
|
+
if (key === 'className') button.className = props[key];
|
|
269
|
+
else if (key !== 'onClick') button.setAttribute(key, props[key]);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return button;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async handleGooglePayButtonClick() {
|
|
276
|
+
if (!this.googlePayPaymentsClient) {
|
|
277
|
+
console.error('no paymentsClient');
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (window.swirepayPayableAmount) {
|
|
282
|
+
this.amount = window.swirepayPayableAmount;
|
|
283
|
+
} else {
|
|
284
|
+
alert("Payment Error: Amount not set");
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (this.processStartCallback) this.processStartCallback();
|
|
289
|
+
|
|
290
|
+
const { paymentDataRequest } = this.constructGooglePayPaymentDataRequest();
|
|
291
|
+
try {
|
|
292
|
+
const paymentData = await this.googlePayPaymentsClient?.loadPaymentData(paymentDataRequest);
|
|
293
|
+
const value = await this.getPayPalClientToken();
|
|
294
|
+
const billingAddress = paymentData?.paymentMethodData?.info?.billingAddress;
|
|
295
|
+
const customerName = billingAddress?.name ?? null;
|
|
296
|
+
const customerEmail = paymentData?.email ?? null;
|
|
297
|
+
const customerPhone = this.getRobustPhoneNumber(this.countryCallingCode, billingAddress?.phoneNumber);
|
|
298
|
+
|
|
299
|
+
if (value) {
|
|
300
|
+
let customerGid;
|
|
301
|
+
const customerData = await this.findCustomer(customerName, customerEmail, customerPhone);
|
|
302
|
+
if (customerData.entity.content[0]?.gid) {
|
|
303
|
+
customerGid = customerData.entity.content[0].gid;
|
|
304
|
+
} else {
|
|
305
|
+
const newCustomer = await this.createCustomer(customerName, customerEmail, customerPhone);
|
|
306
|
+
customerGid = newCustomer.entity?.gid;
|
|
307
|
+
}
|
|
308
|
+
const paymentMethod = {
|
|
309
|
+
type: "WALLET",
|
|
310
|
+
subType: "GOOGLE_PAY_US",
|
|
311
|
+
googlePayUS: {
|
|
312
|
+
apiVersionMinor: paymentData.apiVersionMinor,
|
|
313
|
+
apiVersion: paymentData.apiVersion,
|
|
314
|
+
paymentMethodDescription: paymentData.paymentMethodData.description,
|
|
315
|
+
paymentMethodTokenizationDataType: paymentData.paymentMethodData.tokenizationData.type,
|
|
316
|
+
paymentMethodTokenizationDataToken: paymentData.paymentMethodData.tokenizationData.token,
|
|
317
|
+
paymentMethodType: paymentData.paymentMethodData.type,
|
|
318
|
+
paymentMethodInfo: paymentData.paymentMethodData.info
|
|
319
|
+
},
|
|
320
|
+
customerGid: customerGid,
|
|
321
|
+
}
|
|
322
|
+
let result = await this.addPaymentMethod(paymentMethod);
|
|
323
|
+
const gid = result?.entity?.gid;
|
|
324
|
+
|
|
325
|
+
const postData = {
|
|
326
|
+
amount: this.amount,
|
|
327
|
+
captureMethod: 'AUTOMATIC',
|
|
328
|
+
confirmMethod: 'AUTOMATIC',
|
|
329
|
+
currencyCode: this.currencyDetail?.code,
|
|
330
|
+
description: 'Online Payment',
|
|
331
|
+
paymentMethodGid: gid,
|
|
332
|
+
paymentMethodType: ['WALLET'],
|
|
333
|
+
receiptEmail: customerEmail || null,
|
|
334
|
+
receiptSms: customerPhone || null,
|
|
335
|
+
statementDescriptor: 'Google Pay Payment',
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const gateway = this.getEndpoints().gateway;
|
|
339
|
+
const res = await fetch(`${gateway}/v1/payment-session`, {
|
|
340
|
+
method: 'POST',
|
|
341
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey },
|
|
342
|
+
body: JSON.stringify(postData),
|
|
343
|
+
});
|
|
344
|
+
const resData = await res.json();
|
|
345
|
+
|
|
346
|
+
const orderId = await this.getMrn(resData?.entity?.gid, resData?.entity?.psClientSecret);
|
|
347
|
+
await window?.paypal?.Googlepay()?.confirmOrder({ orderId, paymentMethodData: paymentData?.paymentMethodData });
|
|
348
|
+
await this.action(resData?.entity?.gid, resData?.entity?.psClientSecret);
|
|
349
|
+
if (this.successCallback) await this.successCallback(resData?.entity);
|
|
350
|
+
}
|
|
351
|
+
} catch (err) {
|
|
352
|
+
console.error('Google Pay Error:', err);
|
|
353
|
+
if (this.failCallback) await this.failCallback(err);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
showGooglePayButton() {
|
|
358
|
+
const googlePayButton = this.GooglePayButton({
|
|
359
|
+
onClick: () => this.handleGooglePayButtonClick(),
|
|
360
|
+
disabled: false,
|
|
361
|
+
id: 'google-pay-btn'
|
|
362
|
+
});
|
|
363
|
+
const mount = document.getElementById(this.mountId);
|
|
364
|
+
this.removeAllChildFromMount();
|
|
365
|
+
if (mount) mount.appendChild(googlePayButton);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
getPaypalGooglePayConfig() {
|
|
369
|
+
const paymentsClient = this.getGooglePaymentsClient();
|
|
370
|
+
this.googlePayPaymentsClient = paymentsClient;
|
|
371
|
+
|
|
372
|
+
window.paypal?.Googlepay?.()?.config()?.then((currentPaypalGooglePayConfig) => {
|
|
373
|
+
this.paypalGooglePayConfig = currentPaypalGooglePayConfig;
|
|
374
|
+
const { allowedPaymentMethods, baseRequest } = this.constructGooglePayPaymentDataRequest();
|
|
375
|
+
|
|
376
|
+
paymentsClient?.isReadyToPay({ ...baseRequest, allowedPaymentMethods })
|
|
377
|
+
?.then((response) => {
|
|
378
|
+
if (response.result && currentPaypalGooglePayConfig.isEligible) {
|
|
379
|
+
this.showGooglePayButton();
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
this.showEligibilityIssue();
|
|
383
|
+
})?.catch((err) => { this.showEligibilityIssue(); });
|
|
384
|
+
})?.catch((err) => { this.showEligibilityIssue(); });
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
getGooglePaymentsClient() {
|
|
388
|
+
const usageMode = this.test ? 'TEST' : 'PRODUCTION';
|
|
389
|
+
return new window.google.payments.api.PaymentsClient({ environment: String(usageMode) });
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
constructGooglePayPaymentDataRequest() {
|
|
393
|
+
const currentAmount = window.swirepayPayableAmount || 0;
|
|
394
|
+
if (!this.paypalGooglePayConfig || !this.currencyDetail) return {};
|
|
395
|
+
|
|
396
|
+
const paymentDataRequest = {
|
|
397
|
+
apiVersion: this.paypalGooglePayConfig?.apiVersion,
|
|
398
|
+
apiVersionMinor: this.paypalGooglePayConfig?.apiVersionMinor,
|
|
399
|
+
emailRequired: true,
|
|
400
|
+
merchantInfo: {
|
|
401
|
+
merchantName: 'Swirepay',
|
|
402
|
+
merchantId: this.paypalGooglePayConfig?.merchantInfo?.merchantId,
|
|
403
|
+
},
|
|
404
|
+
allowedPaymentMethods: [{
|
|
405
|
+
type: this.paypalGooglePayConfig?.allowedPaymentMethods?.[0]?.type,
|
|
406
|
+
parameters: {
|
|
407
|
+
allowedAuthMethods: this.paypalGooglePayConfig?.allowedPaymentMethods?.[0]?.parameters?.allowedAuthMethods,
|
|
408
|
+
allowedCardNetworks: this.paypalGooglePayConfig?.allowedPaymentMethods?.[0]?.parameters?.allowedCardNetworks,
|
|
409
|
+
billingAddressRequired: this.paypalGooglePayConfig?.allowedPaymentMethods?.[0]?.parameters?.billingAddressRequired,
|
|
410
|
+
billingAddressParameters: {
|
|
411
|
+
format: this.paypalGooglePayConfig?.allowedPaymentMethods?.[0]?.parameters?.billingAddressParameters?.format,
|
|
412
|
+
phoneNumberRequired: true,
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
tokenizationSpecification: {
|
|
416
|
+
type: this.paypalGooglePayConfig?.allowedPaymentMethods?.[0]?.tokenizationSpecification?.type,
|
|
417
|
+
parameters: {
|
|
418
|
+
gateway: this.paypalGooglePayConfig?.allowedPaymentMethods?.[0]?.tokenizationSpecification?.parameters?.gateway,
|
|
419
|
+
gatewayMerchantId: this.paypalGooglePayConfig?.allowedPaymentMethods?.[0]?.tokenizationSpecification?.parameters?.gatewayMerchantId,
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
}],
|
|
423
|
+
transactionInfo: {
|
|
424
|
+
countryCode: this.currencyDetail?.countryAlpha2,
|
|
425
|
+
currencyCode: this.currencyDetail?.code,
|
|
426
|
+
totalPriceStatus: 'FINAL',
|
|
427
|
+
totalPrice: this.AmountDivideByHundred(currentAmount),
|
|
428
|
+
checkoutOption: 'COMPLETE_IMMEDIATE_PURCHASE',
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
return {
|
|
432
|
+
allowedPaymentMethods: this.paypalGooglePayConfig?.allowedPaymentMethods,
|
|
433
|
+
paymentDataRequest,
|
|
434
|
+
baseRequest: { apiVersion: this.paypalGooglePayConfig?.apiVersion, apiVersionMinor: this.paypalGooglePayConfig?.apiVersionMinor },
|
|
435
|
+
tokenizationSpecification: this.paypalGooglePayConfig?.allowedPaymentMethods?.[0]?.tokenizationSpecification,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async initGooglePay() {
|
|
440
|
+
if (!this.accountGid) return;
|
|
441
|
+
|
|
442
|
+
this.showLoading();
|
|
443
|
+
try {
|
|
444
|
+
const data = await this.getPayPalClientToken();
|
|
445
|
+
const value = atob(data);
|
|
446
|
+
const value_json = JSON.parse(value);
|
|
447
|
+
|
|
448
|
+
// Unified Request: buttons,googlepay,applepay
|
|
449
|
+
await this.script_to_head({
|
|
450
|
+
id: 'paypalSDK',
|
|
451
|
+
src: `${PaypalPaymentMethod.paypal_sdk_url}?client-id=${value_json?.clientId}¤cy=${this.currencyDetail?.code}&intent=${PaypalPaymentMethod.intent}&buyer-country=US&merchant-id=${value_json?.merchantId}&components=buttons,googlepay,applepay`, // Added applepay
|
|
452
|
+
"data-client-token": value_json?.token?.client_token,
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
await this.script_to_head({
|
|
456
|
+
id: 'googlePaySDK',
|
|
457
|
+
src: PaypalGooglePay.google_pay_sdk_url,
|
|
458
|
+
async: true,
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const checkGooglePaySDKInterval = setInterval(() => {
|
|
462
|
+
if (window.google?.payments?.api && window.paypal) {
|
|
463
|
+
this.getPaypalGooglePayConfig();
|
|
464
|
+
clearInterval(checkGooglePaySDKInterval);
|
|
465
|
+
}
|
|
466
|
+
}, 50);
|
|
467
|
+
} catch (err) {
|
|
468
|
+
console.error(err);
|
|
469
|
+
this.showEligibilityIssue();
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
class PaypalApplePay extends PaypalPaymentMethod {
|
|
475
|
+
constructor(config) {
|
|
476
|
+
super(config);
|
|
477
|
+
this.isApplePayEligible = false;
|
|
478
|
+
|
|
479
|
+
this.checkAppleEligible = this.checkAppleEligible.bind(this);
|
|
480
|
+
this.handleClick = this.handleClick.bind(this);
|
|
481
|
+
|
|
482
|
+
if (document.readyState === "complete" || document.readyState === "interactive") {
|
|
483
|
+
this.checkAppleEligible();
|
|
484
|
+
} else {
|
|
485
|
+
window.addEventListener('DOMContentLoaded', this.checkAppleEligible);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Helper to wait for specific PayPal components to initialize
|
|
490
|
+
waitForPayPalComponent(componentName, timeout = 5000) {
|
|
491
|
+
return new Promise((resolve, reject) => {
|
|
492
|
+
const startTime = Date.now();
|
|
493
|
+
|
|
494
|
+
const check = () => {
|
|
495
|
+
// Check if paypal exists AND the specific component exists
|
|
496
|
+
if (window.paypal && window.paypal[componentName]) {
|
|
497
|
+
resolve(window.paypal[componentName]);
|
|
498
|
+
} else if (Date.now() - startTime > timeout) {
|
|
499
|
+
reject(new Error(`PayPal component '${componentName}' failed to load within ${timeout}ms`));
|
|
500
|
+
} else {
|
|
501
|
+
// Check again in 50ms
|
|
502
|
+
setTimeout(check, 50);
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
check();
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async existsApplePayJsApi() {
|
|
511
|
+
try {
|
|
512
|
+
return !!(window.ApplePaySession && (await window.ApplePaySession.canMakePayments()));
|
|
513
|
+
} catch { return false; }
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async checkAppleEligible() {
|
|
517
|
+
this.showLoading();
|
|
518
|
+
const enabled = await this.existsApplePayJsApi();
|
|
519
|
+
if (!enabled || !this.accountGid) {
|
|
520
|
+
if(this.failCallback && !this.unMountOnError) this.failCallback('Payment method not supported...!');
|
|
521
|
+
this.showEligibilityIssue();
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
const data = await this.getPayPalClientToken();
|
|
527
|
+
const value = window.atob(data);
|
|
528
|
+
const value_json = JSON.parse(value);
|
|
529
|
+
|
|
530
|
+
// Unified Request: buttons,googlepay,applepay
|
|
531
|
+
await this.script_to_head({
|
|
532
|
+
id: 'paypalSDK',
|
|
533
|
+
src: `${PaypalPaymentMethod.paypal_sdk_url}?client-id=${value_json?.clientId}&enable-funding=venmo¤cy=${this.currencyDetail?.code}&intent=${PaypalPaymentMethod.intent}&merchant-id=${value_json?.merchantId}&components=buttons,googlepay,applepay`, // Added googlepay
|
|
534
|
+
"data-client-token": value_json?.token?.client_token,
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
// confirm the paypal sdk has loaded in before proceeding (5secs time limit)
|
|
539
|
+
try {
|
|
540
|
+
await this.waitForPayPalComponent('Applepay');
|
|
541
|
+
} catch (waitError) {
|
|
542
|
+
console.error(waitError);
|
|
543
|
+
this.showEligibilityIssue();
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const config = await window.paypal.Applepay().config();
|
|
548
|
+
this.isApplePayEligible = config.isEligible;
|
|
549
|
+
|
|
550
|
+
if (this.isApplePayEligible) {
|
|
551
|
+
this.renderApplePayButton();
|
|
552
|
+
} else {
|
|
553
|
+
this.showEligibilityIssue();
|
|
554
|
+
}
|
|
555
|
+
} catch (err) {
|
|
556
|
+
console.error("Apple Pay Init Error:", err);
|
|
557
|
+
this.showEligibilityIssue();
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
renderApplePayButton() {
|
|
562
|
+
if (!document.getElementById('apple-pay-style')) {
|
|
563
|
+
const style = document.createElement('style');
|
|
564
|
+
style.id = 'apple-pay-style';
|
|
565
|
+
style.innerHTML = `
|
|
566
|
+
.apple-pay-button {
|
|
567
|
+
display: inline-block;
|
|
568
|
+
-webkit-appearance: -apple-pay-button;
|
|
569
|
+
-apple-pay-button-type: buy;
|
|
570
|
+
-apple-pay-button-style: black;
|
|
571
|
+
width: 100%;
|
|
572
|
+
height: 32px;
|
|
573
|
+
border-radius: 4px;
|
|
574
|
+
cursor: pointer;
|
|
575
|
+
}
|
|
576
|
+
`;
|
|
577
|
+
document.head.appendChild(style);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const button = document.createElement('div');
|
|
581
|
+
button.className = 'apple-pay-button';
|
|
582
|
+
button.onclick = this.handleClick;
|
|
583
|
+
|
|
584
|
+
const mount = document.getElementById(this.mountId);
|
|
585
|
+
this.removeAllChildFromMount();
|
|
586
|
+
if (mount) mount.appendChild(button);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
getPaymentRequest(items, label, amountString) {
|
|
590
|
+
const paymentRequest = {
|
|
591
|
+
currencyCode: this.currencyDetail.code,
|
|
592
|
+
countryCode: this.currencyDetail.countryAlpha2,
|
|
593
|
+
lineItems: items,
|
|
594
|
+
total: {
|
|
595
|
+
label: label,
|
|
596
|
+
amount: amountString
|
|
597
|
+
},
|
|
598
|
+
supportedNetworks: ['masterCard', 'visa'],
|
|
599
|
+
merchantCapabilities: ['supports3DS', 'supportsCredit', 'supportsDebit'],
|
|
600
|
+
requiredShippingContactFields: ["postalAddress", "name", "phone", "email"],
|
|
601
|
+
requiredBillingContactFields: ["postalAddress", "email", "name", "phone"]
|
|
602
|
+
};
|
|
603
|
+
return paymentRequest;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
async performApplePayPayment(items, label) {
|
|
607
|
+
const payableAmount = this.AmountDivideByHundred(window.swirepayPayableAmount);
|
|
608
|
+
const paymentRequest = this.getPaymentRequest(items, label, payableAmount);
|
|
609
|
+
|
|
610
|
+
return new Promise((resolve, reject) => {
|
|
611
|
+
const session = new window.ApplePaySession(4, paymentRequest);
|
|
612
|
+
|
|
613
|
+
session.oncancel = (e) => reject("Payment cancelled");
|
|
614
|
+
|
|
615
|
+
session.onvalidatemerchant = async (event) => {
|
|
616
|
+
try {
|
|
617
|
+
const applepay = window.paypal.Applepay();
|
|
618
|
+
const payload = await applepay.validateMerchant({
|
|
619
|
+
validationUrl: event.validationURL,
|
|
620
|
+
displayName: "My Shop",
|
|
621
|
+
});
|
|
622
|
+
session.completeMerchantValidation(payload.merchantSession);
|
|
623
|
+
} catch (err) {
|
|
624
|
+
console.error("Merchant validation failed:", err);
|
|
625
|
+
session.abort();
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
session.onpaymentauthorized = async (event) => {
|
|
630
|
+
try {
|
|
631
|
+
const payment = event.payment;
|
|
632
|
+
const contact = payment.shippingContact || {};
|
|
633
|
+
const billing = payment.billingContact || {};
|
|
634
|
+
|
|
635
|
+
const customerName = (billing.givenName || "") + " " + (billing.familyName || "");
|
|
636
|
+
const customerEmail = contact.emailAddress || "";
|
|
637
|
+
const customerPhone = contact.phoneNumber ? `+1${contact.phoneNumber}` : "";
|
|
638
|
+
|
|
639
|
+
let customerGid;
|
|
640
|
+
const customerData = await this.findCustomer(customerName, customerEmail, customerPhone);
|
|
641
|
+
if (customerData.entity?.content?.[0]?.gid) {
|
|
642
|
+
customerGid = customerData.entity.content[0].gid;
|
|
643
|
+
} else {
|
|
644
|
+
const newCustomer = await this.createCustomer(customerName, customerEmail, customerPhone);
|
|
645
|
+
customerGid = newCustomer.entity?.gid;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const deviceData = `${payment.token.paymentData.data}&ectype=apple&ecsig=${payment.token.paymentData.signature}&eckey=${payment.token.paymentData.header.ephemeralPublicKey}&ectid=${payment.token.transactionIdentifier}&echash=&ecpublickeyhash=${payment.token.paymentData.header.publicKeyHash}`;
|
|
649
|
+
|
|
650
|
+
const paymentMethod = {
|
|
651
|
+
applePayUS: {
|
|
652
|
+
deviceData: deviceData,
|
|
653
|
+
cardBrand: payment.token.paymentMethod.network,
|
|
654
|
+
cardFundingType: payment.token.paymentMethod.type,
|
|
655
|
+
applePayDynamicLast4: payment.token.paymentMethod.displayName,
|
|
656
|
+
cardLast4: payment.token.paymentMethod.displayName
|
|
657
|
+
},
|
|
658
|
+
type: "WALLET",
|
|
659
|
+
subType: "APPLE_PAY_US",
|
|
660
|
+
postalCode: billing.postalCode,
|
|
661
|
+
paymentMethodBillingAddress: billing.locality ? {
|
|
662
|
+
city: billing.locality,
|
|
663
|
+
countryCode: billing.countryCode,
|
|
664
|
+
state: billing.administrativeArea,
|
|
665
|
+
street: billing.addressLines?.[0]
|
|
666
|
+
} : null,
|
|
667
|
+
customerGid: customerGid,
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
const pmResult = await this.addPaymentMethod(paymentMethod);
|
|
671
|
+
const gid = pmResult?.entity?.gid;
|
|
672
|
+
|
|
673
|
+
const postData = {
|
|
674
|
+
amount: window.swirepayPayableAmount,
|
|
675
|
+
captureMethod: 'AUTOMATIC',
|
|
676
|
+
confirmMethod: 'AUTOMATIC',
|
|
677
|
+
currencyCode: this.currencyDetail.code,
|
|
678
|
+
description: 'Online Payment',
|
|
679
|
+
paymentMethodGid: gid,
|
|
680
|
+
paymentMethodType: ['WALLET'],
|
|
681
|
+
receiptEmail: customerEmail || null,
|
|
682
|
+
receiptSms: customerPhone || null,
|
|
683
|
+
statementDescriptor: 'Apple Pay Payment',
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
const gateway = this.getEndpoints().gateway;
|
|
687
|
+
const res = await fetch(`${gateway}/v1/payment-session`, {
|
|
688
|
+
method: 'POST',
|
|
689
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey },
|
|
690
|
+
body: JSON.stringify(postData),
|
|
691
|
+
});
|
|
692
|
+
const resData = await res.json();
|
|
693
|
+
|
|
694
|
+
const orderId = await this.getMrn(resData?.entity?.gid, resData?.entity?.psClientSecret);
|
|
695
|
+
await window.paypal.Applepay().confirmOrder({
|
|
696
|
+
orderId,
|
|
697
|
+
token: payment.token,
|
|
698
|
+
billingContact: payment.billingContact,
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
await this.action(resData?.entity?.gid, resData?.entity?.psClientSecret);
|
|
702
|
+
|
|
703
|
+
session.completePayment(window.ApplePaySession.STATUS_SUCCESS);
|
|
704
|
+
resolve({ orderId, ...resData });
|
|
705
|
+
} catch (err) {
|
|
706
|
+
console.error("Apple Pay flow error:", err);
|
|
707
|
+
session.completePayment(window.ApplePaySession.STATUS_FAILURE);
|
|
708
|
+
reject(err);
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
try {
|
|
713
|
+
session.begin();
|
|
714
|
+
} catch (e) {
|
|
715
|
+
reject(e);
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
handleClick() {
|
|
721
|
+
if (!window.swirepayPayableAmount) {
|
|
722
|
+
alert('Amount not set');
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (this.isApplePayEligible) {
|
|
726
|
+
if (this.processStartCallback) this.processStartCallback();
|
|
727
|
+
|
|
728
|
+
const payableAmount = this.AmountDivideByHundred(window.swirepayPayableAmount);
|
|
729
|
+
|
|
730
|
+
this.performApplePayPayment(
|
|
731
|
+
[{ label: 'Total', amount: payableAmount }],
|
|
732
|
+
'Total'
|
|
733
|
+
).then((result) => {
|
|
734
|
+
if(this.successCallback) this.successCallback(result);
|
|
735
|
+
// console.log("Payment success:", result);
|
|
736
|
+
}).catch((err) => {
|
|
737
|
+
if(this.failCallback) this.failCallback(err);
|
|
738
|
+
console.error("Payment failed:", err);
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|