@voxepay/checkout 0.1.0 → 0.2.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/dist/components/modal.d.ts +49 -1
- package/dist/index.cjs.js +402 -77
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +402 -77
- package/dist/index.esm.js.map +1 -1
- package/dist/types.d.ts +25 -0
- package/dist/voxepay-checkout.min.js +1 -1
- package/dist/voxepay-checkout.min.js.map +1 -1
- package/package.json +2 -2
package/dist/index.esm.js
CHANGED
|
@@ -227,6 +227,18 @@ const ICONS = {
|
|
|
227
227
|
error: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20" stroke="currentColor" stroke-width="2">
|
|
228
228
|
<path stroke-linecap="round" stroke-linejoin="round" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" />
|
|
229
229
|
</svg>`,
|
|
230
|
+
copy: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
231
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
|
232
|
+
</svg>`,
|
|
233
|
+
bank: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
234
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M3 21h18M3 10h18M5 6l7-3 7 3M4 10v11M20 10v11M8 14v3M12 14v3M16 14v3" />
|
|
235
|
+
</svg>`,
|
|
236
|
+
card: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
237
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
|
|
238
|
+
</svg>`,
|
|
239
|
+
clock: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
240
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
241
|
+
</svg>`,
|
|
230
242
|
};
|
|
231
243
|
// Card brand logos
|
|
232
244
|
const CARD_BRAND_DISPLAY = {
|
|
@@ -247,6 +259,7 @@ class VoxePayModal {
|
|
|
247
259
|
}
|
|
248
260
|
};
|
|
249
261
|
this.options = options;
|
|
262
|
+
const methods = options.paymentMethods || ['card', 'bank_transfer'];
|
|
250
263
|
this.state = {
|
|
251
264
|
cardNumber: '',
|
|
252
265
|
expiry: '',
|
|
@@ -259,6 +272,10 @@ class VoxePayModal {
|
|
|
259
272
|
otpTimer: 60,
|
|
260
273
|
canResendOtp: false,
|
|
261
274
|
otpTimerInterval: null,
|
|
275
|
+
paymentMethod: methods[0],
|
|
276
|
+
transferTimer: 0,
|
|
277
|
+
transferTimerInterval: null,
|
|
278
|
+
bankTransferDetails: options.bankTransferDetails || null,
|
|
262
279
|
};
|
|
263
280
|
}
|
|
264
281
|
/**
|
|
@@ -279,6 +296,7 @@ class VoxePayModal {
|
|
|
279
296
|
*/
|
|
280
297
|
close() {
|
|
281
298
|
var _a;
|
|
299
|
+
this.stopTransferTimer();
|
|
282
300
|
(_a = this.overlay) === null || _a === void 0 ? void 0 : _a.classList.remove('voxepay-visible');
|
|
283
301
|
setTimeout(() => {
|
|
284
302
|
var _a, _b, _c;
|
|
@@ -309,11 +327,20 @@ class VoxePayModal {
|
|
|
309
327
|
document.body.appendChild(this.container);
|
|
310
328
|
this.overlay = this.container.querySelector('.voxepay-overlay');
|
|
311
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* Get available payment methods
|
|
332
|
+
*/
|
|
333
|
+
getPaymentMethods() {
|
|
334
|
+
return this.options.paymentMethods || ['card', 'bank_transfer'];
|
|
335
|
+
}
|
|
312
336
|
/**
|
|
313
337
|
* Get modal HTML
|
|
314
338
|
*/
|
|
315
339
|
getModalHTML() {
|
|
316
340
|
const formattedAmount = formatAmount(this.options.amount, this.options.currency);
|
|
341
|
+
const methods = this.getPaymentMethods();
|
|
342
|
+
const showTabs = methods.length > 1;
|
|
343
|
+
const isCard = this.state.paymentMethod === 'card';
|
|
317
344
|
return `
|
|
318
345
|
<div class="voxepay-overlay">
|
|
319
346
|
<div class="voxepay-modal" role="dialog" aria-modal="true" aria-labelledby="voxepay-title">
|
|
@@ -329,71 +356,21 @@ class VoxePayModal {
|
|
|
329
356
|
${ICONS.close}
|
|
330
357
|
</button>
|
|
331
358
|
</div>
|
|
359
|
+
|
|
360
|
+
${showTabs ? `
|
|
361
|
+
<div class="voxepay-method-tabs">
|
|
362
|
+
${methods.includes('card') ? `
|
|
363
|
+
<button class="voxepay-method-tab ${isCard ? 'active' : ''}" data-method="card">
|
|
364
|
+
${ICONS.card} <span>Card</span>
|
|
365
|
+
</button>` : ''}
|
|
366
|
+
${methods.includes('bank_transfer') ? `
|
|
367
|
+
<button class="voxepay-method-tab ${!isCard ? 'active' : ''}" data-method="bank_transfer">
|
|
368
|
+
${ICONS.bank} <span>Bank Transfer</span>
|
|
369
|
+
</button>` : ''}
|
|
370
|
+
</div>` : ''}
|
|
332
371
|
|
|
333
372
|
<div class="voxepay-body" id="voxepay-form-container">
|
|
334
|
-
|
|
335
|
-
<div class="voxepay-form-group">
|
|
336
|
-
<label class="voxepay-label">
|
|
337
|
-
<span class="voxepay-label-icon">💳</span>
|
|
338
|
-
Card Number
|
|
339
|
-
</label>
|
|
340
|
-
<div class="voxepay-card-input-wrapper">
|
|
341
|
-
<input
|
|
342
|
-
type="text"
|
|
343
|
-
class="voxepay-input"
|
|
344
|
-
id="voxepay-card-number"
|
|
345
|
-
name="cardNumber"
|
|
346
|
-
placeholder="1234 5678 9012 3456"
|
|
347
|
-
autocomplete="cc-number"
|
|
348
|
-
inputmode="numeric"
|
|
349
|
-
/>
|
|
350
|
-
<div class="voxepay-card-brand" id="voxepay-card-brand"></div>
|
|
351
|
-
</div>
|
|
352
|
-
<div class="voxepay-error-message" id="voxepay-card-error" style="display: none;"></div>
|
|
353
|
-
</div>
|
|
354
|
-
|
|
355
|
-
<div class="voxepay-row">
|
|
356
|
-
<div class="voxepay-form-group">
|
|
357
|
-
<label class="voxepay-label">
|
|
358
|
-
<span class="voxepay-label-icon">📅</span>
|
|
359
|
-
Expiry
|
|
360
|
-
</label>
|
|
361
|
-
<input
|
|
362
|
-
type="text"
|
|
363
|
-
class="voxepay-input"
|
|
364
|
-
id="voxepay-expiry"
|
|
365
|
-
name="expiry"
|
|
366
|
-
placeholder="MM/YY"
|
|
367
|
-
autocomplete="cc-exp"
|
|
368
|
-
inputmode="numeric"
|
|
369
|
-
maxlength="5"
|
|
370
|
-
/>
|
|
371
|
-
<div class="voxepay-error-message" id="voxepay-expiry-error" style="display: none;"></div>
|
|
372
|
-
</div>
|
|
373
|
-
|
|
374
|
-
<div class="voxepay-form-group">
|
|
375
|
-
<label class="voxepay-label">
|
|
376
|
-
<span class="voxepay-label-icon">🔒</span>
|
|
377
|
-
CVV
|
|
378
|
-
</label>
|
|
379
|
-
<input
|
|
380
|
-
type="text"
|
|
381
|
-
class="voxepay-input"
|
|
382
|
-
id="voxepay-cvv"
|
|
383
|
-
name="cvv"
|
|
384
|
-
placeholder="•••"
|
|
385
|
-
autocomplete="cc-csc"
|
|
386
|
-
inputmode="numeric"
|
|
387
|
-
maxlength="4"
|
|
388
|
-
/>
|
|
389
|
-
<div class="voxepay-error-message" id="voxepay-cvv-error" style="display: none;"></div>
|
|
390
|
-
</div>
|
|
391
|
-
</div>
|
|
392
|
-
|
|
393
|
-
<button type="submit" class="voxepay-submit-btn" id="voxepay-submit">
|
|
394
|
-
<span>Pay Now ${formattedAmount}</span>
|
|
395
|
-
</button>
|
|
396
|
-
</form>
|
|
373
|
+
${isCard ? this.getCardFormHTML(formattedAmount) : this.getBankTransferHTML(formattedAmount)}
|
|
397
374
|
</div>
|
|
398
375
|
|
|
399
376
|
<div class="voxepay-footer">
|
|
@@ -406,6 +383,118 @@ class VoxePayModal {
|
|
|
406
383
|
</div>
|
|
407
384
|
`;
|
|
408
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* Get card form HTML
|
|
388
|
+
*/
|
|
389
|
+
getCardFormHTML(formattedAmount) {
|
|
390
|
+
return `
|
|
391
|
+
<form id="voxepay-payment-form" novalidate>
|
|
392
|
+
<div class="voxepay-form-group">
|
|
393
|
+
<label class="voxepay-label">
|
|
394
|
+
<span class="voxepay-label-icon">💳</span>
|
|
395
|
+
Card Number
|
|
396
|
+
</label>
|
|
397
|
+
<div class="voxepay-card-input-wrapper">
|
|
398
|
+
<input type="text" class="voxepay-input" id="voxepay-card-number" name="cardNumber"
|
|
399
|
+
placeholder="1234 5678 9012 3456" autocomplete="cc-number" inputmode="numeric" />
|
|
400
|
+
<div class="voxepay-card-brand" id="voxepay-card-brand"></div>
|
|
401
|
+
</div>
|
|
402
|
+
<div class="voxepay-error-message" id="voxepay-card-error" style="display: none;"></div>
|
|
403
|
+
</div>
|
|
404
|
+
|
|
405
|
+
<div class="voxepay-row">
|
|
406
|
+
<div class="voxepay-form-group">
|
|
407
|
+
<label class="voxepay-label"><span class="voxepay-label-icon">📅</span> Expiry</label>
|
|
408
|
+
<input type="text" class="voxepay-input" id="voxepay-expiry" name="expiry"
|
|
409
|
+
placeholder="MM/YY" autocomplete="cc-exp" inputmode="numeric" maxlength="5" />
|
|
410
|
+
<div class="voxepay-error-message" id="voxepay-expiry-error" style="display: none;"></div>
|
|
411
|
+
</div>
|
|
412
|
+
<div class="voxepay-form-group">
|
|
413
|
+
<label class="voxepay-label"><span class="voxepay-label-icon">🔒</span> CVV</label>
|
|
414
|
+
<input type="text" class="voxepay-input" id="voxepay-cvv" name="cvv"
|
|
415
|
+
placeholder="•••" autocomplete="cc-csc" inputmode="numeric" maxlength="4" />
|
|
416
|
+
<div class="voxepay-error-message" id="voxepay-cvv-error" style="display: none;"></div>
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
<button type="submit" class="voxepay-submit-btn" id="voxepay-submit">
|
|
421
|
+
<span>Pay Now ${formattedAmount}</span>
|
|
422
|
+
</button>
|
|
423
|
+
</form>
|
|
424
|
+
`;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Get bank transfer HTML
|
|
428
|
+
*/
|
|
429
|
+
getBankTransferHTML(formattedAmount) {
|
|
430
|
+
const details = this.state.bankTransferDetails;
|
|
431
|
+
if (!details) {
|
|
432
|
+
return `
|
|
433
|
+
<div class="voxepay-transfer-view">
|
|
434
|
+
<div class="voxepay-transfer-loading">
|
|
435
|
+
<div class="voxepay-spinner"></div>
|
|
436
|
+
<p style="margin-top: 16px; color: var(--voxepay-text-muted);">Generating account details...</p>
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
`;
|
|
440
|
+
}
|
|
441
|
+
return `
|
|
442
|
+
<div class="voxepay-transfer-view">
|
|
443
|
+
<div class="voxepay-transfer-instruction">
|
|
444
|
+
<p>Transfer <strong>${formattedAmount}</strong> to the account below</p>
|
|
445
|
+
</div>
|
|
446
|
+
|
|
447
|
+
<div class="voxepay-transfer-details">
|
|
448
|
+
<div class="voxepay-transfer-detail">
|
|
449
|
+
<span class="voxepay-transfer-label">Account Number</span>
|
|
450
|
+
<div class="voxepay-transfer-value-row">
|
|
451
|
+
<span class="voxepay-transfer-value voxepay-transfer-account" id="voxepay-account-number">${details.accountNumber}</span>
|
|
452
|
+
<button class="voxepay-copy-btn" id="voxepay-copy-btn" data-copy="${details.accountNumber}" title="Copy">
|
|
453
|
+
${ICONS.copy}
|
|
454
|
+
</button>
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
457
|
+
|
|
458
|
+
<div class="voxepay-transfer-detail">
|
|
459
|
+
<span class="voxepay-transfer-label">Bank Name</span>
|
|
460
|
+
<span class="voxepay-transfer-value">${details.bankName}</span>
|
|
461
|
+
</div>
|
|
462
|
+
|
|
463
|
+
<div class="voxepay-transfer-detail">
|
|
464
|
+
<span class="voxepay-transfer-label">Account Name</span>
|
|
465
|
+
<span class="voxepay-transfer-value">${details.accountName}</span>
|
|
466
|
+
</div>
|
|
467
|
+
|
|
468
|
+
<div class="voxepay-transfer-detail">
|
|
469
|
+
<span class="voxepay-transfer-label">Amount</span>
|
|
470
|
+
<span class="voxepay-transfer-value voxepay-transfer-amount">${formattedAmount}</span>
|
|
471
|
+
</div>
|
|
472
|
+
|
|
473
|
+
<div class="voxepay-transfer-detail">
|
|
474
|
+
<span class="voxepay-transfer-label">Reference</span>
|
|
475
|
+
<span class="voxepay-transfer-value" style="font-family: monospace; letter-spacing: 1px;">${details.reference}</span>
|
|
476
|
+
</div>
|
|
477
|
+
</div>
|
|
478
|
+
|
|
479
|
+
<div class="voxepay-transfer-timer" id="voxepay-transfer-timer">
|
|
480
|
+
${ICONS.clock}
|
|
481
|
+
<span>Account expires in <strong id="voxepay-transfer-countdown">${this.formatCountdown(details.expiresIn)}</strong></span>
|
|
482
|
+
</div>
|
|
483
|
+
|
|
484
|
+
<button type="button" class="voxepay-submit-btn" id="voxepay-transfer-confirm">
|
|
485
|
+
<span>I've sent the money</span>
|
|
486
|
+
</button>
|
|
487
|
+
</div>
|
|
488
|
+
`;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Format countdown seconds to MM:SS
|
|
492
|
+
*/
|
|
493
|
+
formatCountdown(seconds) {
|
|
494
|
+
const m = Math.floor(seconds / 60);
|
|
495
|
+
const s = seconds % 60;
|
|
496
|
+
return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
|
|
497
|
+
}
|
|
409
498
|
/**
|
|
410
499
|
* Render success view
|
|
411
500
|
*/
|
|
@@ -646,8 +735,8 @@ class VoxePayModal {
|
|
|
646
735
|
* Attach event listeners
|
|
647
736
|
*/
|
|
648
737
|
attachEventListeners() {
|
|
649
|
-
var _a, _b, _c, _d, _e, _f;
|
|
650
|
-
const closeBtn = (_a = this.container) === null || _a === void 0 ? void 0 : _a.querySelector('[data-action
|
|
738
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
739
|
+
const closeBtn = (_a = this.container) === null || _a === void 0 ? void 0 : _a.querySelector('[data-action=\"close\"]');
|
|
651
740
|
closeBtn === null || closeBtn === void 0 ? void 0 : closeBtn.addEventListener('click', () => this.close());
|
|
652
741
|
(_b = this.overlay) === null || _b === void 0 ? void 0 : _b.addEventListener('click', (e) => {
|
|
653
742
|
if (e.target === this.overlay) {
|
|
@@ -655,17 +744,221 @@ class VoxePayModal {
|
|
|
655
744
|
}
|
|
656
745
|
});
|
|
657
746
|
document.addEventListener('keydown', this.handleEscape);
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
747
|
+
// Payment method tabs
|
|
748
|
+
const tabs = (_c = this.container) === null || _c === void 0 ? void 0 : _c.querySelectorAll('.voxepay-method-tab');
|
|
749
|
+
tabs === null || tabs === void 0 ? void 0 : tabs.forEach(tab => {
|
|
750
|
+
tab.addEventListener('click', () => {
|
|
751
|
+
const method = tab.dataset.method;
|
|
752
|
+
if (method && method !== this.state.paymentMethod) {
|
|
753
|
+
this.switchPaymentMethod(method);
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
// Card-specific listeners
|
|
758
|
+
if (this.state.paymentMethod === 'card') {
|
|
759
|
+
const cardInput = (_d = this.container) === null || _d === void 0 ? void 0 : _d.querySelector('#voxepay-card-number');
|
|
760
|
+
cardInput === null || cardInput === void 0 ? void 0 : cardInput.addEventListener('input', (e) => this.handleCardInput(e));
|
|
761
|
+
cardInput === null || cardInput === void 0 ? void 0 : cardInput.addEventListener('blur', () => this.validateField('cardNumber'));
|
|
762
|
+
const expiryInput = (_e = this.container) === null || _e === void 0 ? void 0 : _e.querySelector('#voxepay-expiry');
|
|
763
|
+
expiryInput === null || expiryInput === void 0 ? void 0 : expiryInput.addEventListener('input', (e) => this.handleExpiryInput(e));
|
|
764
|
+
expiryInput === null || expiryInput === void 0 ? void 0 : expiryInput.addEventListener('blur', () => this.validateField('expiry'));
|
|
765
|
+
const cvvInput = (_f = this.container) === null || _f === void 0 ? void 0 : _f.querySelector('#voxepay-cvv');
|
|
766
|
+
cvvInput === null || cvvInput === void 0 ? void 0 : cvvInput.addEventListener('input', (e) => this.handleCVVInput(e));
|
|
767
|
+
cvvInput === null || cvvInput === void 0 ? void 0 : cvvInput.addEventListener('blur', () => this.validateField('cvv'));
|
|
768
|
+
const form = (_g = this.container) === null || _g === void 0 ? void 0 : _g.querySelector('#voxepay-payment-form');
|
|
769
|
+
form === null || form === void 0 ? void 0 : form.addEventListener('submit', (e) => this.handleSubmit(e));
|
|
770
|
+
}
|
|
771
|
+
// Bank transfer-specific listeners
|
|
772
|
+
if (this.state.paymentMethod === 'bank_transfer') {
|
|
773
|
+
this.attachBankTransferListeners();
|
|
774
|
+
// If no details yet, load them
|
|
775
|
+
if (!this.state.bankTransferDetails) {
|
|
776
|
+
this.loadBankTransferDetails();
|
|
777
|
+
}
|
|
778
|
+
else {
|
|
779
|
+
this.startTransferTimer();
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Attach bank transfer specific event listeners
|
|
785
|
+
*/
|
|
786
|
+
attachBankTransferListeners() {
|
|
787
|
+
var _a, _b;
|
|
788
|
+
const copyBtn = (_a = this.container) === null || _a === void 0 ? void 0 : _a.querySelector('#voxepay-copy-btn');
|
|
789
|
+
copyBtn === null || copyBtn === void 0 ? void 0 : copyBtn.addEventListener('click', () => {
|
|
790
|
+
const accountNum = copyBtn.dataset.copy || '';
|
|
791
|
+
this.copyToClipboard(accountNum);
|
|
792
|
+
});
|
|
793
|
+
const confirmBtn = (_b = this.container) === null || _b === void 0 ? void 0 : _b.querySelector('#voxepay-transfer-confirm');
|
|
794
|
+
confirmBtn === null || confirmBtn === void 0 ? void 0 : confirmBtn.addEventListener('click', () => this.handleTransferConfirm());
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Switch between payment methods
|
|
798
|
+
*/
|
|
799
|
+
switchPaymentMethod(method) {
|
|
800
|
+
var _a;
|
|
801
|
+
this.stopTransferTimer();
|
|
802
|
+
this.state.paymentMethod = method;
|
|
803
|
+
// Re-render the whole modal
|
|
804
|
+
if (this.container) {
|
|
805
|
+
this.container.innerHTML = this.getModalHTML();
|
|
806
|
+
this.overlay = this.container.querySelector('.voxepay-overlay');
|
|
807
|
+
(_a = this.overlay) === null || _a === void 0 ? void 0 : _a.classList.add('voxepay-visible');
|
|
808
|
+
this.attachEventListeners();
|
|
809
|
+
// Restore card values if switching back to card
|
|
810
|
+
if (method === 'card' && this.state.cardNumber) {
|
|
811
|
+
const cardInput = this.container.querySelector('#voxepay-card-number');
|
|
812
|
+
const expiryInput = this.container.querySelector('#voxepay-expiry');
|
|
813
|
+
const cvvInput = this.container.querySelector('#voxepay-cvv');
|
|
814
|
+
if (cardInput)
|
|
815
|
+
cardInput.value = formatCardNumber(this.state.cardNumber);
|
|
816
|
+
if (expiryInput)
|
|
817
|
+
expiryInput.value = this.state.expiry;
|
|
818
|
+
if (cvvInput)
|
|
819
|
+
cvvInput.value = this.state.cvv;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Load bank transfer details (from callback or generate mock)
|
|
825
|
+
*/
|
|
826
|
+
async loadBankTransferDetails() {
|
|
827
|
+
var _a;
|
|
828
|
+
try {
|
|
829
|
+
let details;
|
|
830
|
+
if (this.options.onBankTransferRequested) {
|
|
831
|
+
details = await this.options.onBankTransferRequested();
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
// Mock/default bank details for testing
|
|
835
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
836
|
+
details = {
|
|
837
|
+
accountNumber: '0123456789',
|
|
838
|
+
bankName: 'VoxePay Bank',
|
|
839
|
+
accountName: 'VoxePay Collections',
|
|
840
|
+
reference: `VP-${Date.now().toString(36).toUpperCase()}`,
|
|
841
|
+
expiresIn: 1800, // 30 minutes
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
this.state.bankTransferDetails = details;
|
|
845
|
+
// Re-render the bank transfer view
|
|
846
|
+
const formContainer = (_a = this.container) === null || _a === void 0 ? void 0 : _a.querySelector('#voxepay-form-container');
|
|
847
|
+
if (formContainer) {
|
|
848
|
+
const formattedAmount = formatAmount(this.options.amount, this.options.currency);
|
|
849
|
+
formContainer.innerHTML = this.getBankTransferHTML(formattedAmount);
|
|
850
|
+
this.attachBankTransferListeners();
|
|
851
|
+
this.startTransferTimer();
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
catch (error) {
|
|
855
|
+
this.options.onError({
|
|
856
|
+
code: 'BANK_TRANSFER_INIT_FAILED',
|
|
857
|
+
message: 'Could not generate bank transfer details. Please try again.',
|
|
858
|
+
recoverable: true,
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Copy text to clipboard with visual feedback
|
|
864
|
+
*/
|
|
865
|
+
async copyToClipboard(text) {
|
|
866
|
+
var _a;
|
|
867
|
+
try {
|
|
868
|
+
await navigator.clipboard.writeText(text);
|
|
869
|
+
const copyBtn = (_a = this.container) === null || _a === void 0 ? void 0 : _a.querySelector('#voxepay-copy-btn');
|
|
870
|
+
if (copyBtn) {
|
|
871
|
+
copyBtn.innerHTML = `${ICONS.check}`;
|
|
872
|
+
copyBtn.classList.add('voxepay-copied');
|
|
873
|
+
setTimeout(() => {
|
|
874
|
+
copyBtn.innerHTML = `${ICONS.copy}`;
|
|
875
|
+
copyBtn.classList.remove('voxepay-copied');
|
|
876
|
+
}, 2000);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
catch (_b) {
|
|
880
|
+
// Fallback for older browsers
|
|
881
|
+
const textarea = document.createElement('textarea');
|
|
882
|
+
textarea.value = text;
|
|
883
|
+
textarea.style.position = 'fixed';
|
|
884
|
+
textarea.style.opacity = '0';
|
|
885
|
+
document.body.appendChild(textarea);
|
|
886
|
+
textarea.select();
|
|
887
|
+
document.execCommand('copy');
|
|
888
|
+
document.body.removeChild(textarea);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Handle "I've sent the money" confirmation
|
|
893
|
+
*/
|
|
894
|
+
async handleTransferConfirm() {
|
|
895
|
+
var _a, _b;
|
|
896
|
+
const confirmBtn = (_a = this.container) === null || _a === void 0 ? void 0 : _a.querySelector('#voxepay-transfer-confirm');
|
|
897
|
+
if (confirmBtn) {
|
|
898
|
+
confirmBtn.disabled = true;
|
|
899
|
+
confirmBtn.innerHTML = '<div class="voxepay-spinner"></div><span>Confirming transfer...</span>';
|
|
900
|
+
}
|
|
901
|
+
this.stopTransferTimer();
|
|
902
|
+
try {
|
|
903
|
+
// Simulate transfer verification
|
|
904
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
905
|
+
const result = {
|
|
906
|
+
id: `pay_transfer_${Date.now()}`,
|
|
907
|
+
status: 'pending',
|
|
908
|
+
amount: this.options.amount,
|
|
909
|
+
currency: this.options.currency,
|
|
910
|
+
timestamp: new Date().toISOString(),
|
|
911
|
+
reference: (_b = this.state.bankTransferDetails) === null || _b === void 0 ? void 0 : _b.reference,
|
|
912
|
+
paymentMethod: 'bank_transfer',
|
|
913
|
+
};
|
|
914
|
+
this.state.isSuccess = true;
|
|
915
|
+
this.renderSuccessView();
|
|
916
|
+
this.options.onSuccess(result);
|
|
917
|
+
}
|
|
918
|
+
catch (error) {
|
|
919
|
+
if (confirmBtn) {
|
|
920
|
+
confirmBtn.disabled = false;
|
|
921
|
+
confirmBtn.innerHTML = "<span>I've sent the money</span>";
|
|
922
|
+
}
|
|
923
|
+
const paymentError = error;
|
|
924
|
+
this.options.onError(paymentError);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Start the transfer countdown timer
|
|
929
|
+
*/
|
|
930
|
+
startTransferTimer() {
|
|
931
|
+
if (!this.state.bankTransferDetails)
|
|
932
|
+
return;
|
|
933
|
+
this.state.transferTimer = this.state.bankTransferDetails.expiresIn;
|
|
934
|
+
this.state.transferTimerInterval = window.setInterval(() => {
|
|
935
|
+
var _a, _b, _c;
|
|
936
|
+
this.state.transferTimer--;
|
|
937
|
+
const countdownEl = (_a = this.container) === null || _a === void 0 ? void 0 : _a.querySelector('#voxepay-transfer-countdown');
|
|
938
|
+
if (countdownEl) {
|
|
939
|
+
countdownEl.textContent = this.formatCountdown(this.state.transferTimer);
|
|
940
|
+
}
|
|
941
|
+
if (this.state.transferTimer <= 0) {
|
|
942
|
+
this.stopTransferTimer();
|
|
943
|
+
// Show expired state
|
|
944
|
+
const timerEl = (_b = this.container) === null || _b === void 0 ? void 0 : _b.querySelector('#voxepay-transfer-timer');
|
|
945
|
+
if (timerEl) {
|
|
946
|
+
timerEl.innerHTML = `${ICONS.clock} <span style="color: var(--voxepay-error);">Account expired. Please try again.</span>`;
|
|
947
|
+
}
|
|
948
|
+
const confirmBtn = (_c = this.container) === null || _c === void 0 ? void 0 : _c.querySelector('#voxepay-transfer-confirm');
|
|
949
|
+
if (confirmBtn)
|
|
950
|
+
confirmBtn.disabled = true;
|
|
951
|
+
}
|
|
952
|
+
}, 1000);
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Stop the transfer countdown timer
|
|
956
|
+
*/
|
|
957
|
+
stopTransferTimer() {
|
|
958
|
+
if (this.state.transferTimerInterval) {
|
|
959
|
+
clearInterval(this.state.transferTimerInterval);
|
|
960
|
+
this.state.transferTimerInterval = null;
|
|
961
|
+
}
|
|
669
962
|
}
|
|
670
963
|
handleCardInput(e) {
|
|
671
964
|
var _a;
|
|
@@ -918,8 +1211,40 @@ class VoxePayModal {
|
|
|
918
1211
|
|
|
919
1212
|
.voxepay-back-btn { display: block; width: 100%; padding: 12px; background: transparent; border: none; color: var(--voxepay-text-muted); font-size: 0.875rem; cursor: pointer; margin-top: 12px; transition: color var(--voxepay-transition-fast); }
|
|
920
1213
|
.voxepay-back-btn:hover { color: var(--voxepay-primary); }
|
|
1214
|
+
|
|
1215
|
+
/* Payment Method Tabs */
|
|
1216
|
+
.voxepay-method-tabs { display: flex; border-bottom: 1px solid var(--voxepay-border); background: var(--voxepay-surface); }
|
|
1217
|
+
.voxepay-method-tab { flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 14px 16px; border: none; background: transparent; color: var(--voxepay-text-muted); font-size: 0.875rem; font-weight: 500; font-family: inherit; cursor: pointer; transition: all var(--voxepay-transition-fast); border-bottom: 2px solid transparent; }
|
|
1218
|
+
.voxepay-method-tab svg { width: 18px; height: 18px; }
|
|
1219
|
+
.voxepay-method-tab:hover { color: var(--voxepay-text); background: var(--voxepay-surface-hover); }
|
|
1220
|
+
.voxepay-method-tab.active { color: var(--voxepay-primary); border-bottom-color: var(--voxepay-primary); background: var(--voxepay-bg); }
|
|
1221
|
+
|
|
1222
|
+
/* Bank Transfer View */
|
|
1223
|
+
.voxepay-transfer-view { padding: 4px 0; }
|
|
1224
|
+
.voxepay-transfer-loading { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 48px 0; }
|
|
1225
|
+
.voxepay-transfer-instruction { text-align: center; margin-bottom: 20px; font-size: 0.938rem; color: var(--voxepay-text-muted); }
|
|
1226
|
+
.voxepay-transfer-instruction strong { color: var(--voxepay-text); font-size: 1.063rem; }
|
|
1227
|
+
.voxepay-transfer-details { background: var(--voxepay-surface); border: 1px solid var(--voxepay-border); border-radius: var(--voxepay-border-radius); overflow: hidden; margin-bottom: 16px; }
|
|
1228
|
+
.voxepay-transfer-detail { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border-bottom: 1px solid var(--voxepay-border); }
|
|
1229
|
+
.voxepay-transfer-detail:last-child { border-bottom: none; }
|
|
1230
|
+
.voxepay-transfer-label { font-size: 0.813rem; color: var(--voxepay-text-muted); font-weight: 500; }
|
|
1231
|
+
.voxepay-transfer-value { font-size: 0.938rem; font-weight: 600; color: var(--voxepay-text); }
|
|
1232
|
+
.voxepay-transfer-value-row { display: flex; align-items: center; gap: 8px; }
|
|
1233
|
+
.voxepay-transfer-account { font-size: 1.125rem; font-weight: 700; color: var(--voxepay-primary); letter-spacing: 1.5px; font-family: 'DM Sans', monospace; }
|
|
1234
|
+
.voxepay-transfer-amount { color: var(--voxepay-primary); }
|
|
1235
|
+
|
|
1236
|
+
/* Copy Button */
|
|
1237
|
+
.voxepay-copy-btn { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: 1px solid var(--voxepay-border); border-radius: 8px; background: var(--voxepay-surface-hover); color: var(--voxepay-text-muted); cursor: pointer; transition: all var(--voxepay-transition-fast); flex-shrink: 0; }
|
|
1238
|
+
.voxepay-copy-btn svg { width: 16px; height: 16px; }
|
|
1239
|
+
.voxepay-copy-btn:hover { border-color: var(--voxepay-primary); color: var(--voxepay-primary); background: rgba(0, 97, 255, 0.1); }
|
|
1240
|
+
.voxepay-copy-btn.voxepay-copied { border-color: var(--voxepay-success); color: var(--voxepay-success); background: var(--voxepay-success-bg); }
|
|
1241
|
+
|
|
1242
|
+
/* Transfer Timer */
|
|
1243
|
+
.voxepay-transfer-timer { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 10px 16px; background: var(--voxepay-surface); border: 1px solid var(--voxepay-border); border-radius: var(--voxepay-border-radius); margin-bottom: 16px; font-size: 0.875rem; color: var(--voxepay-text-muted); }
|
|
1244
|
+
.voxepay-transfer-timer svg { width: 16px; height: 16px; color: var(--voxepay-primary); flex-shrink: 0; }
|
|
1245
|
+
.voxepay-transfer-timer strong { color: var(--voxepay-primary); font-weight: 700; font-family: monospace; font-size: 0.938rem; }
|
|
921
1246
|
|
|
922
|
-
@media (max-width: 480px) { .voxepay-modal { max-width: 100%; max-height: 100%; border-radius: 0; height: 100%; } .voxepay-body { padding: 20px; } .voxepay-otp-digit { width: 42px; height: 50px; font-size: 1.25rem; } }
|
|
1247
|
+
@media (max-width: 480px) { .voxepay-modal { max-width: 100%; max-height: 100%; border-radius: 0; height: 100%; } .voxepay-body { padding: 20px; } .voxepay-otp-digit { width: 42px; height: 50px; font-size: 1.25rem; } .voxepay-transfer-detail { flex-direction: column; align-items: flex-start; gap: 4px; } }
|
|
923
1248
|
`;
|
|
924
1249
|
}
|
|
925
1250
|
}
|