@tagadapay/plugin-sdk 4.0.0 → 4.0.2
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/README.md +1129 -1129
- package/build-cdn.js +499 -499
- package/dist/external-tracker.js +156 -2
- package/dist/external-tracker.min.js +2 -2
- package/dist/external-tracker.min.js.map +4 -4
- package/dist/react/providers/TagadaProvider.js +5 -5
- package/dist/tagada-react-sdk-minimal.min.js +2 -2
- package/dist/tagada-react-sdk-minimal.min.js.map +4 -4
- package/dist/tagada-react-sdk.js +696 -245
- package/dist/tagada-react-sdk.min.js +2 -2
- package/dist/tagada-react-sdk.min.js.map +4 -4
- package/dist/tagada-sdk.js +2908 -94
- package/dist/tagada-sdk.min.js +2 -2
- package/dist/tagada-sdk.min.js.map +4 -4
- package/dist/v2/core/funnelClient.d.ts +40 -0
- package/dist/v2/core/funnelClient.js +30 -0
- package/dist/v2/core/pixelTracker.d.ts +51 -0
- package/dist/v2/core/pixelTracker.js +425 -0
- package/dist/v2/core/resources/checkout.d.ts +45 -1
- package/dist/v2/core/resources/checkout.js +13 -3
- package/dist/v2/core/resources/offers.d.ts +1 -1
- package/dist/v2/core/resources/offers.js +3 -1
- package/dist/v2/core/resources/promotionEvents.d.ts +5 -0
- package/dist/v2/core/resources/promotionEvents.js +2 -0
- package/dist/v2/core/resources/promotions.d.ts +6 -1
- package/dist/v2/core/resources/promotions.js +6 -1
- package/dist/v2/core/resources/shippingRates.d.ts +18 -0
- package/dist/v2/core/resources/shippingRates.js +18 -0
- package/dist/v2/core/utils/clickIdResolver.d.ts +79 -0
- package/dist/v2/core/utils/clickIdResolver.js +169 -0
- package/dist/v2/core/utils/index.d.ts +2 -0
- package/dist/v2/core/utils/index.js +4 -0
- package/dist/v2/core/utils/metaEventId.d.ts +14 -0
- package/dist/v2/core/utils/metaEventId.js +16 -0
- package/dist/v2/core/utils/previewModeIndicator.js +101 -101
- package/dist/v2/index.d.ts +7 -0
- package/dist/v2/index.js +10 -0
- package/dist/v2/react/components/ApplePayButton.js +50 -0
- package/dist/v2/react/components/FunnelScriptInjector.js +9 -9
- package/dist/v2/react/components/GooglePayButton.js +39 -1
- package/dist/v2/react/components/StripeExpressButton.js +54 -2
- package/dist/v2/react/hooks/payment-actions/useNgeniusThreedsAction.js +11 -11
- package/dist/v2/react/hooks/useCheckoutQuery.js +41 -29
- package/dist/v2/react/hooks/useDiscountsQuery.js +4 -0
- package/dist/v2/react/hooks/useFunnel.d.ts +7 -0
- package/dist/v2/react/hooks/useFunnel.js +2 -1
- package/dist/v2/react/hooks/usePixelTracking.d.ts +10 -5
- package/dist/v2/react/hooks/usePixelTracking.js +32 -374
- package/dist/v2/react/hooks/usePreviewOffer.d.ts +3 -1
- package/dist/v2/react/hooks/usePreviewOffer.js +8 -2
- package/dist/v2/react/hooks/usePromotionsQuery.js +9 -3
- package/dist/v2/react/hooks/useShippingRatesQuery.js +36 -21
- package/dist/v2/react/hooks/useStepConfig.d.ts +9 -0
- package/dist/v2/react/hooks/useStepConfig.js +5 -1
- package/dist/v2/react/index.d.ts +4 -0
- package/dist/v2/react/index.js +8 -0
- package/dist/v2/react/providers/TagadaProvider.js +18 -5
- package/dist/v2/standalone/apple-pay-service.d.ts +1 -1
- package/dist/v2/standalone/index.d.ts +3 -0
- package/dist/v2/standalone/index.js +23 -0
- package/dist/v2/standalone/payment-service.d.ts +54 -1
- package/dist/v2/standalone/payment-service.js +228 -61
- package/package.json +115 -115
|
@@ -221,13 +221,40 @@ export class PaymentService {
|
|
|
221
221
|
});
|
|
222
222
|
this.callbacks.onCurrentPaymentId?.(response.payment?.id || null);
|
|
223
223
|
if (response.payment.requireAction !== 'none') {
|
|
224
|
-
|
|
225
|
-
|
|
224
|
+
// Pre-empt terminal failure surfaced through requireAction='error' (e.g.
|
|
225
|
+
// Stripe rejecting an APM that isn't enabled on the account) or status
|
|
226
|
+
// already declined/failed. Don't even invoke the action handler — for
|
|
227
|
+
// 'error' it would just fire onError, for declined+redirect it might
|
|
228
|
+
// navigate to a stale URL. Surface the failure so the button resets.
|
|
229
|
+
if (response.payment.requireAction === 'error'
|
|
230
|
+
|| response.payment.status === 'declined'
|
|
231
|
+
|| response.payment.status === 'failed') {
|
|
232
|
+
const msg = response.payment.requireActionData?.message
|
|
233
|
+
|| response.payment.error?.message
|
|
234
|
+
|| 'Payment declined';
|
|
235
|
+
this.callbacks.onError?.(msg);
|
|
236
|
+
this.callbacks.onProcessing?.(false);
|
|
237
|
+
return { success: false, error: msg, payment: response.payment, order: response.order };
|
|
238
|
+
}
|
|
239
|
+
// Run the action handler; propagate its outcome (covers radar declines,
|
|
240
|
+
// missing redirect URLs, etc. — not just the headline 'error' case).
|
|
241
|
+
const outcome = await this.handlePaymentAction(response.payment);
|
|
242
|
+
const settled = this.settleActionOutcome(outcome, response.payment, response.order);
|
|
243
|
+
if (settled)
|
|
244
|
+
return settled;
|
|
245
|
+
// 'pending' falls through to polling
|
|
226
246
|
}
|
|
227
247
|
if (response.payment.status === 'succeeded') {
|
|
228
248
|
this.callbacks.onProcessing?.(false);
|
|
229
249
|
return { success: true, payment: response.payment, order: response.order };
|
|
230
250
|
}
|
|
251
|
+
if (response.payment.status === 'declined'
|
|
252
|
+
|| response.payment.status === 'failed') {
|
|
253
|
+
const msg = response.payment.error?.message || 'Payment declined';
|
|
254
|
+
this.callbacks.onError?.(msg);
|
|
255
|
+
this.callbacks.onProcessing?.(false);
|
|
256
|
+
return { success: false, error: msg, payment: response.payment, order: response.order };
|
|
257
|
+
}
|
|
231
258
|
return new Promise((resolve) => {
|
|
232
259
|
this.startPolling(response.payment.id, {
|
|
233
260
|
onSuccess: (payment) => {
|
|
@@ -239,14 +266,42 @@ export class PaymentService {
|
|
|
239
266
|
this.callbacks.onProcessing?.(false);
|
|
240
267
|
resolve({ success: false, error });
|
|
241
268
|
},
|
|
242
|
-
onRequireAction: (payment) => {
|
|
243
|
-
|
|
269
|
+
onRequireAction: async (payment) => {
|
|
270
|
+
// Polling found a new requireAction mid-flight. Run the handler and
|
|
271
|
+
// resolve the outer promise with its outcome — otherwise the caller
|
|
272
|
+
// hangs forever waiting on a polling loop that already stopped.
|
|
273
|
+
const outcome = await this.handlePaymentAction(payment);
|
|
274
|
+
const settled = this.settleActionOutcome(outcome, payment, response.order);
|
|
275
|
+
if (settled) {
|
|
276
|
+
this.callbacks.onProcessing?.(false);
|
|
277
|
+
resolve(settled);
|
|
278
|
+
}
|
|
279
|
+
// 'pending' outcome means the action handler started its own polling;
|
|
280
|
+
// the outer promise stays open and will resolve via that path.
|
|
244
281
|
},
|
|
245
282
|
});
|
|
246
283
|
});
|
|
247
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* Translate an ActionOutcome into a PaymentResult, or null when the outcome
|
|
287
|
+
* is 'pending' (caller should keep polling instead of resolving).
|
|
288
|
+
*/
|
|
289
|
+
settleActionOutcome(outcome, payment, order) {
|
|
290
|
+
switch (outcome.kind) {
|
|
291
|
+
case 'redirected':
|
|
292
|
+
return { success: true, payment, order, redirecting: true };
|
|
293
|
+
case 'completed':
|
|
294
|
+
return { success: true, payment: outcome.payment, order };
|
|
295
|
+
case 'failed':
|
|
296
|
+
return { success: false, error: outcome.error, payment: outcome.payment ?? payment, order };
|
|
297
|
+
case 'pending':
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
248
301
|
/**
|
|
249
302
|
* After radar / completePaymentAfterAction, handle the resumed payment.
|
|
303
|
+
* Fires callbacks for side effects AND returns an outcome so the caller
|
|
304
|
+
* (handlePaymentAction) can propagate failure into PaymentResult.
|
|
250
305
|
*/
|
|
251
306
|
async handleResumedPayment(resumedPayment) {
|
|
252
307
|
if (resumedPayment.status === 'declined' || resumedPayment.status === 'failed') {
|
|
@@ -256,18 +311,17 @@ export class PaymentService {
|
|
|
256
311
|
this.callbacks.onError?.(errorMsg);
|
|
257
312
|
this.callbacks.onProcessing?.(false);
|
|
258
313
|
this.callbacks.onFailure?.(errorMsg);
|
|
259
|
-
return;
|
|
314
|
+
return { kind: 'failed', error: errorMsg, payment: resumedPayment };
|
|
260
315
|
}
|
|
261
316
|
if (resumedPayment.status === 'succeeded') {
|
|
262
317
|
this.callbacks.onProcessing?.(false);
|
|
263
318
|
this.callbacks.onSuccess?.(resumedPayment);
|
|
264
|
-
return;
|
|
319
|
+
return { kind: 'completed', payment: resumedPayment };
|
|
265
320
|
}
|
|
266
321
|
if (resumedPayment.requireAction !== 'none' && resumedPayment.requireActionData) {
|
|
267
|
-
|
|
268
|
-
return;
|
|
322
|
+
return this.handlePaymentAction(resumedPayment);
|
|
269
323
|
}
|
|
270
|
-
// Start polling for final status
|
|
324
|
+
// Start polling for final status — outcome will arrive via callbacks.
|
|
271
325
|
this.startPolling(resumedPayment.id, {
|
|
272
326
|
onSuccess: (p) => {
|
|
273
327
|
this.callbacks.onProcessing?.(false);
|
|
@@ -279,18 +333,19 @@ export class PaymentService {
|
|
|
279
333
|
},
|
|
280
334
|
onRequireAction: (p) => { void this.handlePaymentAction(p); },
|
|
281
335
|
});
|
|
336
|
+
return { kind: 'pending' };
|
|
282
337
|
}
|
|
283
338
|
// ==========================================================================
|
|
284
339
|
// PAYMENT ACTION HANDLER (mirrors usePaymentActionHandler)
|
|
285
340
|
// ==========================================================================
|
|
286
341
|
async handlePaymentAction(payment) {
|
|
287
342
|
if (payment.requireAction === 'none')
|
|
288
|
-
return;
|
|
343
|
+
return { kind: 'pending' };
|
|
289
344
|
if (payment.requireActionData?.processed)
|
|
290
|
-
return;
|
|
345
|
+
return { kind: 'pending' };
|
|
291
346
|
const actionData = payment.requireActionData;
|
|
292
347
|
if (!actionData)
|
|
293
|
-
return;
|
|
348
|
+
return { kind: 'pending' };
|
|
294
349
|
try {
|
|
295
350
|
await this.paymentsResource.markPaymentActionProcessed(payment.id);
|
|
296
351
|
}
|
|
@@ -306,50 +361,55 @@ export class PaymentService {
|
|
|
306
361
|
await this.callbacks.onBeforeRedirect(payment, redirectUrl);
|
|
307
362
|
}
|
|
308
363
|
window.location.href = redirectUrl;
|
|
364
|
+
return { kind: 'redirected' };
|
|
309
365
|
}
|
|
310
|
-
|
|
366
|
+
if (payment.status === 'succeeded') {
|
|
311
367
|
this.callbacks.onProcessing?.(false);
|
|
312
368
|
this.callbacks.onSuccess?.(payment);
|
|
369
|
+
return { kind: 'completed', payment };
|
|
313
370
|
}
|
|
314
|
-
|
|
371
|
+
// Action said redirect but no URL and payment not succeeded — broken
|
|
372
|
+
// response from backend. Surface as failure rather than hanging.
|
|
373
|
+
const noUrlMsg = 'Payment redirect URL missing';
|
|
374
|
+
this.callbacks.onError?.(noUrlMsg);
|
|
375
|
+
this.callbacks.onProcessing?.(false);
|
|
376
|
+
return { kind: 'failed', error: noUrlMsg, payment };
|
|
315
377
|
}
|
|
316
378
|
case 'threeds_auth': {
|
|
317
379
|
const session = actionData.metadata?.threedsSession;
|
|
318
380
|
if (session?.acsChallengeUrl) {
|
|
319
381
|
console.log('[PaymentService] 3DS challenge redirect:', session.acsChallengeUrl);
|
|
320
382
|
window.location.href = session.acsChallengeUrl;
|
|
383
|
+
return { kind: 'redirected' };
|
|
321
384
|
}
|
|
322
|
-
|
|
385
|
+
const noUrlMsg = '3DS challenge URL missing';
|
|
386
|
+
this.callbacks.onError?.(noUrlMsg);
|
|
387
|
+
this.callbacks.onProcessing?.(false);
|
|
388
|
+
return { kind: 'failed', error: noUrlMsg, payment };
|
|
323
389
|
}
|
|
324
390
|
case 'error': {
|
|
325
391
|
const msg = actionData.message || 'Payment action failed';
|
|
326
392
|
this.callbacks.onError?.(msg);
|
|
327
393
|
this.callbacks.onProcessing?.(false);
|
|
328
|
-
|
|
394
|
+
return { kind: 'failed', error: msg, payment };
|
|
329
395
|
}
|
|
330
396
|
case 'kesspay_auth':
|
|
331
|
-
this.handleKessPayAuth(actionData);
|
|
332
|
-
break;
|
|
397
|
+
return this.handleKessPayAuth(actionData);
|
|
333
398
|
case 'trustflow_auth':
|
|
334
|
-
this.handleTrustFlowAuth(actionData);
|
|
335
|
-
break;
|
|
399
|
+
return this.handleTrustFlowAuth(actionData);
|
|
336
400
|
case 'finix_radar':
|
|
337
|
-
|
|
338
|
-
break;
|
|
401
|
+
return this.handleFinixRadar(payment, actionData);
|
|
339
402
|
case 'stripe_radar':
|
|
340
|
-
|
|
341
|
-
break;
|
|
403
|
+
return this.handleStripeRadar(payment, actionData);
|
|
342
404
|
case 'radar':
|
|
343
405
|
if (actionData.metadata?.provider === 'airwallex') {
|
|
344
|
-
|
|
406
|
+
return this.handleAirwallexRadar(payment, actionData);
|
|
345
407
|
}
|
|
346
|
-
|
|
408
|
+
return { kind: 'pending' };
|
|
347
409
|
case 'mastercard_auth':
|
|
348
|
-
|
|
349
|
-
break;
|
|
410
|
+
return this.handleMasterCardAuth(payment, actionData);
|
|
350
411
|
case 'ngenius_3ds':
|
|
351
|
-
|
|
352
|
-
break;
|
|
412
|
+
return this.handleNgeniusThreeds(payment, actionData);
|
|
353
413
|
default: {
|
|
354
414
|
console.log('[PaymentService] Unhandled action, starting polling:', actionData.type);
|
|
355
415
|
this.startPolling(payment.id, {
|
|
@@ -357,7 +417,7 @@ export class PaymentService {
|
|
|
357
417
|
onFailure: (e) => { this.callbacks.onError?.(e); this.callbacks.onProcessing?.(false); },
|
|
358
418
|
onRequireAction: (p) => { void this.handlePaymentAction(p); },
|
|
359
419
|
});
|
|
360
|
-
|
|
420
|
+
return { kind: 'pending' };
|
|
361
421
|
}
|
|
362
422
|
}
|
|
363
423
|
}
|
|
@@ -367,9 +427,10 @@ export class PaymentService {
|
|
|
367
427
|
handleKessPayAuth(actionData) {
|
|
368
428
|
const threeDSData = actionData?.metadata?.threeds;
|
|
369
429
|
if (!threeDSData?.challengeHtml) {
|
|
370
|
-
|
|
430
|
+
const msg = 'Missing KessPay 3DS challenge HTML';
|
|
431
|
+
this.callbacks.onError?.(msg);
|
|
371
432
|
this.callbacks.onProcessing?.(false);
|
|
372
|
-
return;
|
|
433
|
+
return { kind: 'failed', error: msg };
|
|
373
434
|
}
|
|
374
435
|
try {
|
|
375
436
|
this.callbacks.onProcessing?.(false);
|
|
@@ -396,10 +457,13 @@ export class PaymentService {
|
|
|
396
457
|
document.write(threeDSData.challengeHtml);
|
|
397
458
|
document.close();
|
|
398
459
|
}
|
|
460
|
+
return { kind: 'redirected' };
|
|
399
461
|
}
|
|
400
462
|
catch (error) {
|
|
401
|
-
|
|
463
|
+
const msg = error instanceof Error ? error.message : 'KessPay 3DS failed';
|
|
464
|
+
this.callbacks.onError?.(msg);
|
|
402
465
|
this.callbacks.onProcessing?.(false);
|
|
466
|
+
return { kind: 'failed', error: msg };
|
|
403
467
|
}
|
|
404
468
|
}
|
|
405
469
|
// --------------------------------------------------------------------------
|
|
@@ -408,9 +472,10 @@ export class PaymentService {
|
|
|
408
472
|
handleTrustFlowAuth(actionData) {
|
|
409
473
|
const authData = actionData?.metadata?.trustflow;
|
|
410
474
|
if (!authData?.appId || !authData?.txnId || !authData?.hash) {
|
|
411
|
-
|
|
475
|
+
const msg = 'Missing Trust Flow 3DS data';
|
|
476
|
+
this.callbacks.onError?.(msg);
|
|
412
477
|
this.callbacks.onProcessing?.(false);
|
|
413
|
-
return;
|
|
478
|
+
return { kind: 'failed', error: msg };
|
|
414
479
|
}
|
|
415
480
|
try {
|
|
416
481
|
this.callbacks.onProcessing?.(false);
|
|
@@ -427,10 +492,13 @@ export class PaymentService {
|
|
|
427
492
|
}
|
|
428
493
|
document.body.appendChild(form);
|
|
429
494
|
form.submit();
|
|
495
|
+
return { kind: 'redirected' };
|
|
430
496
|
}
|
|
431
497
|
catch (error) {
|
|
432
|
-
|
|
498
|
+
const msg = error instanceof Error ? error.message : 'Trust Flow 3DS failed';
|
|
499
|
+
this.callbacks.onError?.(msg);
|
|
433
500
|
this.callbacks.onProcessing?.(false);
|
|
501
|
+
return { kind: 'failed', error: msg };
|
|
434
502
|
}
|
|
435
503
|
}
|
|
436
504
|
// --------------------------------------------------------------------------
|
|
@@ -439,9 +507,10 @@ export class PaymentService {
|
|
|
439
507
|
async handleFinixRadar(payment, actionData) {
|
|
440
508
|
const radarConfig = actionData.metadata?.radar;
|
|
441
509
|
if (!radarConfig) {
|
|
442
|
-
|
|
510
|
+
const msg = 'Finix radar config missing';
|
|
511
|
+
this.callbacks.onError?.(msg);
|
|
443
512
|
this.callbacks.onProcessing?.(false);
|
|
444
|
-
return;
|
|
513
|
+
return { kind: 'failed', error: msg, payment };
|
|
445
514
|
}
|
|
446
515
|
try {
|
|
447
516
|
await this.loadScript('https://js.finix.com/v/1/finix.js', () => typeof window.Finix?.Auth === 'function');
|
|
@@ -469,11 +538,13 @@ export class PaymentService {
|
|
|
469
538
|
},
|
|
470
539
|
});
|
|
471
540
|
const resumed = await this.paymentsResource.completePaymentAfterAction(payment.id);
|
|
472
|
-
await this.handleResumedPayment(resumed);
|
|
541
|
+
return await this.handleResumedPayment(resumed);
|
|
473
542
|
}
|
|
474
543
|
catch (error) {
|
|
475
|
-
|
|
544
|
+
const msg = error instanceof Error ? error.message : 'Finix radar failed';
|
|
545
|
+
this.callbacks.onError?.(msg);
|
|
476
546
|
this.callbacks.onProcessing?.(false);
|
|
547
|
+
return { kind: 'failed', error: msg, payment };
|
|
477
548
|
}
|
|
478
549
|
}
|
|
479
550
|
// --------------------------------------------------------------------------
|
|
@@ -482,9 +553,10 @@ export class PaymentService {
|
|
|
482
553
|
async handleStripeRadar(payment, actionData) {
|
|
483
554
|
const radarConfig = actionData.metadata?.radar;
|
|
484
555
|
if (!radarConfig?.publishableKey) {
|
|
485
|
-
|
|
556
|
+
const msg = 'Stripe radar config missing';
|
|
557
|
+
this.callbacks.onError?.(msg);
|
|
486
558
|
this.callbacks.onProcessing?.(false);
|
|
487
|
-
return;
|
|
559
|
+
return { kind: 'failed', error: msg, payment };
|
|
488
560
|
}
|
|
489
561
|
try {
|
|
490
562
|
await this.loadScript('https://js.stripe.com/v3/', () => typeof window.Stripe === 'function');
|
|
@@ -500,11 +572,13 @@ export class PaymentService {
|
|
|
500
572
|
stripeRadarSessionData: result.radarSession,
|
|
501
573
|
});
|
|
502
574
|
const resumed = await this.paymentsResource.completePaymentAfterAction(payment.id);
|
|
503
|
-
await this.handleResumedPayment(resumed);
|
|
575
|
+
return await this.handleResumedPayment(resumed);
|
|
504
576
|
}
|
|
505
577
|
catch (error) {
|
|
506
|
-
|
|
578
|
+
const msg = error instanceof Error ? error.message : 'Stripe radar failed';
|
|
579
|
+
this.callbacks.onError?.(msg);
|
|
507
580
|
this.callbacks.onProcessing?.(false);
|
|
581
|
+
return { kind: 'failed', error: msg, payment };
|
|
508
582
|
}
|
|
509
583
|
}
|
|
510
584
|
// --------------------------------------------------------------------------
|
|
@@ -513,9 +587,10 @@ export class PaymentService {
|
|
|
513
587
|
async handleNgeniusThreeds(payment, actionData) {
|
|
514
588
|
const sdk = actionData.metadata?.sdk;
|
|
515
589
|
if (!sdk?.paymentResponse || !sdk.orderReference || !sdk.paymentReference) {
|
|
516
|
-
|
|
590
|
+
const msg = 'N-Genius 3DS: missing SDK metadata';
|
|
591
|
+
this.callbacks.onError?.(msg);
|
|
517
592
|
this.callbacks.onProcessing?.(false);
|
|
518
|
-
return;
|
|
593
|
+
return { kind: 'failed', error: msg, payment };
|
|
519
594
|
}
|
|
520
595
|
try {
|
|
521
596
|
const sdkUrl = sdk.isSandboxed
|
|
@@ -543,13 +618,14 @@ export class PaymentService {
|
|
|
543
618
|
orderReference: sdk.orderReference,
|
|
544
619
|
paymentReference: sdk.paymentReference,
|
|
545
620
|
});
|
|
546
|
-
await this.handleResumedPayment(completedPayment);
|
|
621
|
+
return await this.handleResumedPayment(completedPayment);
|
|
547
622
|
}
|
|
548
623
|
catch (error) {
|
|
549
624
|
const msg = error instanceof Error ? error.message : 'N-Genius 3DS failed';
|
|
550
625
|
console.error('[N-Genius 3DS] Error:', error);
|
|
551
626
|
this.callbacks.onError?.(msg);
|
|
552
627
|
this.callbacks.onProcessing?.(false);
|
|
628
|
+
return { kind: 'failed', error: msg, payment };
|
|
553
629
|
}
|
|
554
630
|
}
|
|
555
631
|
// --------------------------------------------------------------------------
|
|
@@ -560,9 +636,10 @@ export class PaymentService {
|
|
|
560
636
|
const orderId = payment.order?.id;
|
|
561
637
|
const checkoutSessionId = payment.order?.checkoutSessionId;
|
|
562
638
|
if (!orderId || !checkoutSessionId) {
|
|
563
|
-
|
|
639
|
+
const msg = 'Missing order info for Airwallex radar';
|
|
640
|
+
this.callbacks.onError?.(msg);
|
|
564
641
|
this.callbacks.onProcessing?.(false);
|
|
565
|
-
return;
|
|
642
|
+
return { kind: 'failed', error: msg, payment };
|
|
566
643
|
}
|
|
567
644
|
try {
|
|
568
645
|
const sessionId = crypto.randomUUID();
|
|
@@ -591,11 +668,13 @@ export class PaymentService {
|
|
|
591
668
|
airwallexRadarSessionId: sessionId,
|
|
592
669
|
});
|
|
593
670
|
const resumed = await this.paymentsResource.completePaymentAfterAction(payment.id);
|
|
594
|
-
await this.handleResumedPayment(resumed);
|
|
671
|
+
return await this.handleResumedPayment(resumed);
|
|
595
672
|
}
|
|
596
673
|
catch (error) {
|
|
597
|
-
|
|
674
|
+
const msg = error instanceof Error ? error.message : 'Airwallex radar failed';
|
|
675
|
+
this.callbacks.onError?.(msg);
|
|
598
676
|
this.callbacks.onProcessing?.(false);
|
|
677
|
+
return { kind: 'failed', error: msg, payment };
|
|
599
678
|
}
|
|
600
679
|
}
|
|
601
680
|
// --------------------------------------------------------------------------
|
|
@@ -604,9 +683,10 @@ export class PaymentService {
|
|
|
604
683
|
async handleMasterCardAuth(payment, actionData) {
|
|
605
684
|
const threeDSData = actionData?.metadata?.threeds;
|
|
606
685
|
if (!threeDSData?.sessionId || !threeDSData?.merchantId) {
|
|
607
|
-
|
|
686
|
+
const msg = 'Missing MasterCard 3DS data';
|
|
687
|
+
this.callbacks.onError?.(msg);
|
|
608
688
|
this.callbacks.onProcessing?.(false);
|
|
609
|
-
return;
|
|
689
|
+
return { kind: 'failed', error: msg, payment };
|
|
610
690
|
}
|
|
611
691
|
try {
|
|
612
692
|
this.callbacks.onProcessing?.(false);
|
|
@@ -659,21 +739,26 @@ export class PaymentService {
|
|
|
659
739
|
document.write(challengeHtml);
|
|
660
740
|
document.close();
|
|
661
741
|
}
|
|
742
|
+
return { kind: 'redirected' };
|
|
662
743
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
744
|
+
// Frictionless — complete immediately via resumed payment
|
|
745
|
+
if (threeDSData.paymentId) {
|
|
746
|
+
const resumed = await this.paymentsResource.completePaymentAfterAction(threeDSData.paymentId);
|
|
747
|
+
const cleanup = document.getElementById(containerId);
|
|
748
|
+
if (cleanup)
|
|
749
|
+
cleanup.remove();
|
|
750
|
+
return await this.handleResumedPayment(resumed);
|
|
669
751
|
}
|
|
670
752
|
const cleanup = document.getElementById(containerId);
|
|
671
753
|
if (cleanup)
|
|
672
754
|
cleanup.remove();
|
|
755
|
+
return { kind: 'pending' };
|
|
673
756
|
}
|
|
674
757
|
catch (error) {
|
|
675
|
-
|
|
758
|
+
const msg = error instanceof Error ? error.message : 'MasterCard 3DS failed';
|
|
759
|
+
this.callbacks.onError?.(msg);
|
|
676
760
|
this.callbacks.onProcessing?.(false);
|
|
761
|
+
return { kind: 'failed', error: msg, payment };
|
|
677
762
|
}
|
|
678
763
|
}
|
|
679
764
|
// --------------------------------------------------------------------------
|
|
@@ -979,4 +1064,86 @@ export class PaymentService {
|
|
|
979
1064
|
return { success: false, error: msg };
|
|
980
1065
|
}
|
|
981
1066
|
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Stripe Express Checkout Element payment.
|
|
1069
|
+
*
|
|
1070
|
+
* Mirrors the inline flow from `react/components/StripeExpressButton.onConfirm`:
|
|
1071
|
+
* 1. processPaymentDirect with isExpress=true → returns clientSecret
|
|
1072
|
+
* 2. stripe.confirmPayment(elements, clientSecret) — must run while wallet sheet is open
|
|
1073
|
+
* 3. Poll until webhook marks payment succeeded
|
|
1074
|
+
*
|
|
1075
|
+
* Used for ECE methods Stripe surfaces in one element: apple_pay, google_pay, link, klarna.
|
|
1076
|
+
* The `stripe` and `elements` refs come from Stripe React hooks at the call site.
|
|
1077
|
+
*/
|
|
1078
|
+
async processStripeExpressPayment(checkoutSessionId, paymentMethod, processorId, stripe, elements, options) {
|
|
1079
|
+
this.callbacks.onProcessing?.(true);
|
|
1080
|
+
this.callbacks.onError?.(null);
|
|
1081
|
+
try {
|
|
1082
|
+
const paymentFlowId = getAssignedPaymentFlowId();
|
|
1083
|
+
const response = await this.paymentsResource.processPaymentDirect(checkoutSessionId, '', undefined, {
|
|
1084
|
+
processorId,
|
|
1085
|
+
paymentMethod,
|
|
1086
|
+
isExpress: true,
|
|
1087
|
+
paymentFlowId,
|
|
1088
|
+
shippingRateId: options?.shippingRateId,
|
|
1089
|
+
});
|
|
1090
|
+
this.callbacks.onCurrentPaymentId?.(response.payment?.id || null);
|
|
1091
|
+
const clientSecret = response?.payment?.requireActionData?.metadata?.stripeExpressCheckout?.clientSecret;
|
|
1092
|
+
if (!clientSecret) {
|
|
1093
|
+
const msg = 'Express checkout configuration missing — no client secret returned';
|
|
1094
|
+
this.callbacks.onError?.(msg);
|
|
1095
|
+
this.callbacks.onProcessing?.(false);
|
|
1096
|
+
return { success: false, error: msg, payment: response.payment, order: response.order };
|
|
1097
|
+
}
|
|
1098
|
+
const { error: confirmError } = await stripe.confirmPayment({
|
|
1099
|
+
elements,
|
|
1100
|
+
clientSecret,
|
|
1101
|
+
confirmParams: { return_url: window.location.href },
|
|
1102
|
+
redirect: 'if_required',
|
|
1103
|
+
});
|
|
1104
|
+
if (confirmError) {
|
|
1105
|
+
const msg = confirmError.message ?? 'Payment confirmation failed';
|
|
1106
|
+
this.callbacks.onError?.(msg);
|
|
1107
|
+
this.callbacks.onProcessing?.(false);
|
|
1108
|
+
return { success: false, error: msg, payment: response.payment, order: response.order };
|
|
1109
|
+
}
|
|
1110
|
+
// Poll for webhook completion. Ignore stripe_express_checkout require-actions
|
|
1111
|
+
// (already handled by stripe.confirmPayment above) and keep polling.
|
|
1112
|
+
const paymentId = response.payment.id;
|
|
1113
|
+
return await new Promise((resolve) => {
|
|
1114
|
+
const tick = async () => {
|
|
1115
|
+
try {
|
|
1116
|
+
const payment = await this.paymentsResource.getPaymentStatus(paymentId);
|
|
1117
|
+
if (payment.status === 'succeeded' ||
|
|
1118
|
+
(payment.status === 'pending' && payment.subStatus === 'authorized')) {
|
|
1119
|
+
this.callbacks.onProcessing?.(false);
|
|
1120
|
+
resolve({ success: true, payment, order: response.order });
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
if (payment.status !== 'succeeded' && payment.status !== 'pending') {
|
|
1124
|
+
const msg = payment.status || 'Payment failed';
|
|
1125
|
+
this.callbacks.onError?.(msg);
|
|
1126
|
+
this.callbacks.onProcessing?.(false);
|
|
1127
|
+
resolve({ success: false, error: msg, payment, order: response.order });
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
// Pending — keep polling. Ignore stripe_express_checkout require-action
|
|
1131
|
+
// (it's just the original intent metadata; we already handled it).
|
|
1132
|
+
setTimeout(tick, 1500);
|
|
1133
|
+
}
|
|
1134
|
+
catch {
|
|
1135
|
+
// Network blip — try again. Bail after a few failures handled by getPaymentStatus retries upstream.
|
|
1136
|
+
setTimeout(tick, 1500);
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
void tick();
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
catch (error) {
|
|
1143
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1144
|
+
this.callbacks.onError?.(msg);
|
|
1145
|
+
this.callbacks.onProcessing?.(false);
|
|
1146
|
+
return { success: false, error: msg };
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
982
1149
|
}
|