@tagadapay/plugin-sdk 3.1.25 → 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.
Files changed (81) hide show
  1. package/dist/external-tracker.js +160 -6
  2. package/dist/external-tracker.min.js +2 -2
  3. package/dist/external-tracker.min.js.map +4 -4
  4. package/dist/react/config/payment.d.ts +2 -2
  5. package/dist/react/config/payment.js +5 -5
  6. package/dist/react/hooks/usePayment.d.ts +7 -0
  7. package/dist/react/hooks/usePayment.js +1 -0
  8. package/dist/tagada-react-sdk-minimal.min.js +2 -2
  9. package/dist/tagada-react-sdk-minimal.min.js.map +4 -4
  10. package/dist/tagada-react-sdk.js +2220 -1428
  11. package/dist/tagada-react-sdk.min.js +2 -2
  12. package/dist/tagada-react-sdk.min.js.map +4 -4
  13. package/dist/tagada-sdk.js +3784 -128
  14. package/dist/tagada-sdk.min.js +2 -2
  15. package/dist/tagada-sdk.min.js.map +4 -4
  16. package/dist/v2/core/config/environment.d.ts +3 -3
  17. package/dist/v2/core/config/environment.js +7 -7
  18. package/dist/v2/core/funnelClient.d.ts +42 -0
  19. package/dist/v2/core/funnelClient.js +30 -0
  20. package/dist/v2/core/pixelTracker.d.ts +51 -0
  21. package/dist/v2/core/pixelTracker.js +425 -0
  22. package/dist/v2/core/resources/checkout.d.ts +45 -1
  23. package/dist/v2/core/resources/checkout.js +13 -3
  24. package/dist/v2/core/resources/funnel.d.ts +1 -1
  25. package/dist/v2/core/resources/geo.d.ts +50 -0
  26. package/dist/v2/core/resources/geo.js +35 -0
  27. package/dist/v2/core/resources/offers.d.ts +1 -1
  28. package/dist/v2/core/resources/offers.js +3 -1
  29. package/dist/v2/core/resources/payments.d.ts +19 -1
  30. package/dist/v2/core/resources/payments.js +8 -0
  31. package/dist/v2/core/resources/promotionEvents.d.ts +5 -0
  32. package/dist/v2/core/resources/promotionEvents.js +2 -0
  33. package/dist/v2/core/resources/promotions.d.ts +6 -1
  34. package/dist/v2/core/resources/promotions.js +6 -1
  35. package/dist/v2/core/resources/shippingRates.d.ts +18 -0
  36. package/dist/v2/core/resources/shippingRates.js +18 -0
  37. package/dist/v2/core/utils/clickIdResolver.d.ts +79 -0
  38. package/dist/v2/core/utils/clickIdResolver.js +169 -0
  39. package/dist/v2/core/utils/index.d.ts +2 -0
  40. package/dist/v2/core/utils/index.js +4 -0
  41. package/dist/v2/core/utils/metaEventId.d.ts +14 -0
  42. package/dist/v2/core/utils/metaEventId.js +16 -0
  43. package/dist/v2/index.d.ts +7 -0
  44. package/dist/v2/index.js +10 -0
  45. package/dist/v2/react/components/ApplePayButton.js +50 -0
  46. package/dist/v2/react/components/FunnelScriptInjector.js +158 -10
  47. package/dist/v2/react/components/GooglePayButton.js +39 -1
  48. package/dist/v2/react/components/StripeExpressButton.d.ts +8 -0
  49. package/dist/v2/react/components/StripeExpressButton.js +76 -3
  50. package/dist/v2/react/hooks/payment-actions/useNgeniusThreedsAction.d.ts +15 -0
  51. package/dist/v2/react/hooks/payment-actions/useNgeniusThreedsAction.js +166 -0
  52. package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.js +12 -0
  53. package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.js +1 -0
  54. package/dist/v2/react/hooks/useCheckoutQuery.js +41 -29
  55. package/dist/v2/react/hooks/useDiscountsQuery.js +4 -0
  56. package/dist/v2/react/hooks/useFunnel.d.ts +7 -0
  57. package/dist/v2/react/hooks/useFunnel.js +2 -1
  58. package/dist/v2/react/hooks/useISOData.js +25 -7
  59. package/dist/v2/react/hooks/usePaymentPolling.d.ts +1 -1
  60. package/dist/v2/react/hooks/usePixelTracking.d.ts +10 -5
  61. package/dist/v2/react/hooks/usePixelTracking.js +32 -374
  62. package/dist/v2/react/hooks/usePreviewOffer.d.ts +3 -1
  63. package/dist/v2/react/hooks/usePreviewOffer.js +8 -2
  64. package/dist/v2/react/hooks/usePromotionsQuery.js +9 -3
  65. package/dist/v2/react/hooks/useShippingRatesQuery.js +36 -21
  66. package/dist/v2/react/hooks/useStepConfig.d.ts +9 -0
  67. package/dist/v2/react/hooks/useStepConfig.js +5 -1
  68. package/dist/v2/react/index.d.ts +4 -0
  69. package/dist/v2/react/index.js +8 -0
  70. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +12 -4
  71. package/dist/v2/react/providers/TagadaProvider.js +13 -0
  72. package/dist/v2/standalone/apple-pay-service.d.ts +12 -0
  73. package/dist/v2/standalone/apple-pay-service.js +12 -0
  74. package/dist/v2/standalone/external-tracker.d.ts +1 -1
  75. package/dist/v2/standalone/google-pay-service.d.ts +9 -0
  76. package/dist/v2/standalone/google-pay-service.js +9 -0
  77. package/dist/v2/standalone/index.d.ts +11 -1
  78. package/dist/v2/standalone/index.js +30 -0
  79. package/dist/v2/standalone/payment-service.d.ts +72 -6
  80. package/dist/v2/standalone/payment-service.js +285 -65
  81. package/package.json +2 -1
@@ -212,6 +212,7 @@ export class PaymentService {
212
212
  paymentFlowId,
213
213
  processorId: extra?.processorId,
214
214
  paymentMethod: extra?.paymentMethod,
215
+ shippingRateId: extra?.shippingRateId,
215
216
  });
216
217
  console.log('[PaymentService] Payment response:', {
217
218
  paymentId: response.payment?.id,
@@ -220,13 +221,40 @@ export class PaymentService {
220
221
  });
221
222
  this.callbacks.onCurrentPaymentId?.(response.payment?.id || null);
222
223
  if (response.payment.requireAction !== 'none') {
223
- await this.handlePaymentAction(response.payment);
224
- return { success: true, payment: response.payment, order: response.order, redirecting: true };
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
225
246
  }
226
247
  if (response.payment.status === 'succeeded') {
227
248
  this.callbacks.onProcessing?.(false);
228
249
  return { success: true, payment: response.payment, order: response.order };
229
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
+ }
230
258
  return new Promise((resolve) => {
231
259
  this.startPolling(response.payment.id, {
232
260
  onSuccess: (payment) => {
@@ -238,14 +266,42 @@ export class PaymentService {
238
266
  this.callbacks.onProcessing?.(false);
239
267
  resolve({ success: false, error });
240
268
  },
241
- onRequireAction: (payment) => {
242
- void this.handlePaymentAction(payment);
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.
243
281
  },
244
282
  });
245
283
  });
246
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
+ }
247
301
  /**
248
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.
249
305
  */
250
306
  async handleResumedPayment(resumedPayment) {
251
307
  if (resumedPayment.status === 'declined' || resumedPayment.status === 'failed') {
@@ -255,18 +311,17 @@ export class PaymentService {
255
311
  this.callbacks.onError?.(errorMsg);
256
312
  this.callbacks.onProcessing?.(false);
257
313
  this.callbacks.onFailure?.(errorMsg);
258
- return;
314
+ return { kind: 'failed', error: errorMsg, payment: resumedPayment };
259
315
  }
260
316
  if (resumedPayment.status === 'succeeded') {
261
317
  this.callbacks.onProcessing?.(false);
262
318
  this.callbacks.onSuccess?.(resumedPayment);
263
- return;
319
+ return { kind: 'completed', payment: resumedPayment };
264
320
  }
265
321
  if (resumedPayment.requireAction !== 'none' && resumedPayment.requireActionData) {
266
- await this.handlePaymentAction(resumedPayment);
267
- return;
322
+ return this.handlePaymentAction(resumedPayment);
268
323
  }
269
- // Start polling for final status
324
+ // Start polling for final status — outcome will arrive via callbacks.
270
325
  this.startPolling(resumedPayment.id, {
271
326
  onSuccess: (p) => {
272
327
  this.callbacks.onProcessing?.(false);
@@ -278,18 +333,19 @@ export class PaymentService {
278
333
  },
279
334
  onRequireAction: (p) => { void this.handlePaymentAction(p); },
280
335
  });
336
+ return { kind: 'pending' };
281
337
  }
282
338
  // ==========================================================================
283
339
  // PAYMENT ACTION HANDLER (mirrors usePaymentActionHandler)
284
340
  // ==========================================================================
285
341
  async handlePaymentAction(payment) {
286
342
  if (payment.requireAction === 'none')
287
- return;
343
+ return { kind: 'pending' };
288
344
  if (payment.requireActionData?.processed)
289
- return;
345
+ return { kind: 'pending' };
290
346
  const actionData = payment.requireActionData;
291
347
  if (!actionData)
292
- return;
348
+ return { kind: 'pending' };
293
349
  try {
294
350
  await this.paymentsResource.markPaymentActionProcessed(payment.id);
295
351
  }
@@ -301,48 +357,59 @@ export class PaymentService {
301
357
  const redirectUrl = actionData.metadata?.redirect?.redirectUrl || actionData.redirectUrl || actionData.url;
302
358
  if (redirectUrl) {
303
359
  console.log('[PaymentService] Redirecting:', redirectUrl);
360
+ if (this.callbacks.onBeforeRedirect) {
361
+ await this.callbacks.onBeforeRedirect(payment, redirectUrl);
362
+ }
304
363
  window.location.href = redirectUrl;
364
+ return { kind: 'redirected' };
305
365
  }
306
- else if (payment.status === 'succeeded') {
366
+ if (payment.status === 'succeeded') {
307
367
  this.callbacks.onProcessing?.(false);
308
368
  this.callbacks.onSuccess?.(payment);
369
+ return { kind: 'completed', payment };
309
370
  }
310
- break;
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 };
311
377
  }
312
378
  case 'threeds_auth': {
313
379
  const session = actionData.metadata?.threedsSession;
314
380
  if (session?.acsChallengeUrl) {
315
381
  console.log('[PaymentService] 3DS challenge redirect:', session.acsChallengeUrl);
316
382
  window.location.href = session.acsChallengeUrl;
383
+ return { kind: 'redirected' };
317
384
  }
318
- break;
385
+ const noUrlMsg = '3DS challenge URL missing';
386
+ this.callbacks.onError?.(noUrlMsg);
387
+ this.callbacks.onProcessing?.(false);
388
+ return { kind: 'failed', error: noUrlMsg, payment };
319
389
  }
320
390
  case 'error': {
321
391
  const msg = actionData.message || 'Payment action failed';
322
392
  this.callbacks.onError?.(msg);
323
393
  this.callbacks.onProcessing?.(false);
324
- break;
394
+ return { kind: 'failed', error: msg, payment };
325
395
  }
326
396
  case 'kesspay_auth':
327
- this.handleKessPayAuth(actionData);
328
- break;
397
+ return this.handleKessPayAuth(actionData);
329
398
  case 'trustflow_auth':
330
- this.handleTrustFlowAuth(actionData);
331
- break;
399
+ return this.handleTrustFlowAuth(actionData);
332
400
  case 'finix_radar':
333
- await this.handleFinixRadar(payment, actionData);
334
- break;
401
+ return this.handleFinixRadar(payment, actionData);
335
402
  case 'stripe_radar':
336
- await this.handleStripeRadar(payment, actionData);
337
- break;
403
+ return this.handleStripeRadar(payment, actionData);
338
404
  case 'radar':
339
405
  if (actionData.metadata?.provider === 'airwallex') {
340
- await this.handleAirwallexRadar(payment, actionData);
406
+ return this.handleAirwallexRadar(payment, actionData);
341
407
  }
342
- break;
408
+ return { kind: 'pending' };
343
409
  case 'mastercard_auth':
344
- await this.handleMasterCardAuth(payment, actionData);
345
- break;
410
+ return this.handleMasterCardAuth(payment, actionData);
411
+ case 'ngenius_3ds':
412
+ return this.handleNgeniusThreeds(payment, actionData);
346
413
  default: {
347
414
  console.log('[PaymentService] Unhandled action, starting polling:', actionData.type);
348
415
  this.startPolling(payment.id, {
@@ -350,7 +417,7 @@ export class PaymentService {
350
417
  onFailure: (e) => { this.callbacks.onError?.(e); this.callbacks.onProcessing?.(false); },
351
418
  onRequireAction: (p) => { void this.handlePaymentAction(p); },
352
419
  });
353
- break;
420
+ return { kind: 'pending' };
354
421
  }
355
422
  }
356
423
  }
@@ -360,9 +427,10 @@ export class PaymentService {
360
427
  handleKessPayAuth(actionData) {
361
428
  const threeDSData = actionData?.metadata?.threeds;
362
429
  if (!threeDSData?.challengeHtml) {
363
- this.callbacks.onError?.('Missing KessPay 3DS challenge HTML');
430
+ const msg = 'Missing KessPay 3DS challenge HTML';
431
+ this.callbacks.onError?.(msg);
364
432
  this.callbacks.onProcessing?.(false);
365
- return;
433
+ return { kind: 'failed', error: msg };
366
434
  }
367
435
  try {
368
436
  this.callbacks.onProcessing?.(false);
@@ -389,10 +457,13 @@ export class PaymentService {
389
457
  document.write(threeDSData.challengeHtml);
390
458
  document.close();
391
459
  }
460
+ return { kind: 'redirected' };
392
461
  }
393
462
  catch (error) {
394
- this.callbacks.onError?.(error instanceof Error ? error.message : 'KessPay 3DS failed');
463
+ const msg = error instanceof Error ? error.message : 'KessPay 3DS failed';
464
+ this.callbacks.onError?.(msg);
395
465
  this.callbacks.onProcessing?.(false);
466
+ return { kind: 'failed', error: msg };
396
467
  }
397
468
  }
398
469
  // --------------------------------------------------------------------------
@@ -401,9 +472,10 @@ export class PaymentService {
401
472
  handleTrustFlowAuth(actionData) {
402
473
  const authData = actionData?.metadata?.trustflow;
403
474
  if (!authData?.appId || !authData?.txnId || !authData?.hash) {
404
- this.callbacks.onError?.('Missing Trust Flow 3DS data');
475
+ const msg = 'Missing Trust Flow 3DS data';
476
+ this.callbacks.onError?.(msg);
405
477
  this.callbacks.onProcessing?.(false);
406
- return;
478
+ return { kind: 'failed', error: msg };
407
479
  }
408
480
  try {
409
481
  this.callbacks.onProcessing?.(false);
@@ -420,10 +492,13 @@ export class PaymentService {
420
492
  }
421
493
  document.body.appendChild(form);
422
494
  form.submit();
495
+ return { kind: 'redirected' };
423
496
  }
424
497
  catch (error) {
425
- this.callbacks.onError?.(error instanceof Error ? error.message : 'Trust Flow 3DS failed');
498
+ const msg = error instanceof Error ? error.message : 'Trust Flow 3DS failed';
499
+ this.callbacks.onError?.(msg);
426
500
  this.callbacks.onProcessing?.(false);
501
+ return { kind: 'failed', error: msg };
427
502
  }
428
503
  }
429
504
  // --------------------------------------------------------------------------
@@ -432,9 +507,10 @@ export class PaymentService {
432
507
  async handleFinixRadar(payment, actionData) {
433
508
  const radarConfig = actionData.metadata?.radar;
434
509
  if (!radarConfig) {
435
- this.callbacks.onError?.('Finix radar config missing');
510
+ const msg = 'Finix radar config missing';
511
+ this.callbacks.onError?.(msg);
436
512
  this.callbacks.onProcessing?.(false);
437
- return;
513
+ return { kind: 'failed', error: msg, payment };
438
514
  }
439
515
  try {
440
516
  await this.loadScript('https://js.finix.com/v/1/finix.js', () => typeof window.Finix?.Auth === 'function');
@@ -462,11 +538,13 @@ export class PaymentService {
462
538
  },
463
539
  });
464
540
  const resumed = await this.paymentsResource.completePaymentAfterAction(payment.id);
465
- await this.handleResumedPayment(resumed);
541
+ return await this.handleResumedPayment(resumed);
466
542
  }
467
543
  catch (error) {
468
- this.callbacks.onError?.(error instanceof Error ? error.message : 'Finix radar failed');
544
+ const msg = error instanceof Error ? error.message : 'Finix radar failed';
545
+ this.callbacks.onError?.(msg);
469
546
  this.callbacks.onProcessing?.(false);
547
+ return { kind: 'failed', error: msg, payment };
470
548
  }
471
549
  }
472
550
  // --------------------------------------------------------------------------
@@ -475,9 +553,10 @@ export class PaymentService {
475
553
  async handleStripeRadar(payment, actionData) {
476
554
  const radarConfig = actionData.metadata?.radar;
477
555
  if (!radarConfig?.publishableKey) {
478
- this.callbacks.onError?.('Stripe radar config missing');
556
+ const msg = 'Stripe radar config missing';
557
+ this.callbacks.onError?.(msg);
479
558
  this.callbacks.onProcessing?.(false);
480
- return;
559
+ return { kind: 'failed', error: msg, payment };
481
560
  }
482
561
  try {
483
562
  await this.loadScript('https://js.stripe.com/v3/', () => typeof window.Stripe === 'function');
@@ -493,11 +572,60 @@ export class PaymentService {
493
572
  stripeRadarSessionData: result.radarSession,
494
573
  });
495
574
  const resumed = await this.paymentsResource.completePaymentAfterAction(payment.id);
496
- await this.handleResumedPayment(resumed);
575
+ return await this.handleResumedPayment(resumed);
497
576
  }
498
577
  catch (error) {
499
- this.callbacks.onError?.(error instanceof Error ? error.message : 'Stripe radar failed');
578
+ const msg = error instanceof Error ? error.message : 'Stripe radar failed';
579
+ this.callbacks.onError?.(msg);
500
580
  this.callbacks.onProcessing?.(false);
581
+ return { kind: 'failed', error: msg, payment };
582
+ }
583
+ }
584
+ // --------------------------------------------------------------------------
585
+ // N-Genius 3DS (WebSDK)
586
+ // --------------------------------------------------------------------------
587
+ async handleNgeniusThreeds(payment, actionData) {
588
+ const sdk = actionData.metadata?.sdk;
589
+ if (!sdk?.paymentResponse || !sdk.orderReference || !sdk.paymentReference) {
590
+ const msg = 'N-Genius 3DS: missing SDK metadata';
591
+ this.callbacks.onError?.(msg);
592
+ this.callbacks.onProcessing?.(false);
593
+ return { kind: 'failed', error: msg, payment };
594
+ }
595
+ try {
596
+ const sdkUrl = sdk.isSandboxed
597
+ ? 'https://paypage.sandbox.ngenius-payments.com/hosted-sessions/sdk.js'
598
+ : 'https://paypage.ngenius-payments.com/hosted-sessions/sdk.js';
599
+ if (!document.getElementById('ngenius-websdk')) {
600
+ await new Promise((resolve, reject) => {
601
+ const script = document.createElement('script');
602
+ script.id = 'ngenius-websdk';
603
+ script.src = sdkUrl;
604
+ script.onload = () => resolve();
605
+ script.onerror = () => reject(new Error('Failed to load N-Genius WebSDK'));
606
+ document.head.appendChild(script);
607
+ });
608
+ }
609
+ const NI = window.NI;
610
+ if (!NI?.handlePaymentResponse) {
611
+ throw new Error('N-Genius WebSDK did not expose window.NI.handlePaymentResponse');
612
+ }
613
+ console.log('[N-Genius 3DS] Starting WebSDK challenge');
614
+ const outcome = await NI.handlePaymentResponse(sdk.paymentResponse, { mountId: 'ngenius-3ds-container', style: { width: '100%', height: 500 } });
615
+ console.log('[N-Genius 3DS] WebSDK outcome:', outcome.status);
616
+ const completedPayment = await this.paymentsResource.ngeniusThreedsComplete({
617
+ paymentId: payment.id,
618
+ orderReference: sdk.orderReference,
619
+ paymentReference: sdk.paymentReference,
620
+ });
621
+ return await this.handleResumedPayment(completedPayment);
622
+ }
623
+ catch (error) {
624
+ const msg = error instanceof Error ? error.message : 'N-Genius 3DS failed';
625
+ console.error('[N-Genius 3DS] Error:', error);
626
+ this.callbacks.onError?.(msg);
627
+ this.callbacks.onProcessing?.(false);
628
+ return { kind: 'failed', error: msg, payment };
501
629
  }
502
630
  }
503
631
  // --------------------------------------------------------------------------
@@ -508,9 +636,10 @@ export class PaymentService {
508
636
  const orderId = payment.order?.id;
509
637
  const checkoutSessionId = payment.order?.checkoutSessionId;
510
638
  if (!orderId || !checkoutSessionId) {
511
- this.callbacks.onError?.('Missing order info for Airwallex radar');
639
+ const msg = 'Missing order info for Airwallex radar';
640
+ this.callbacks.onError?.(msg);
512
641
  this.callbacks.onProcessing?.(false);
513
- return;
642
+ return { kind: 'failed', error: msg, payment };
514
643
  }
515
644
  try {
516
645
  const sessionId = crypto.randomUUID();
@@ -539,11 +668,13 @@ export class PaymentService {
539
668
  airwallexRadarSessionId: sessionId,
540
669
  });
541
670
  const resumed = await this.paymentsResource.completePaymentAfterAction(payment.id);
542
- await this.handleResumedPayment(resumed);
671
+ return await this.handleResumedPayment(resumed);
543
672
  }
544
673
  catch (error) {
545
- this.callbacks.onError?.(error instanceof Error ? error.message : 'Airwallex radar failed');
674
+ const msg = error instanceof Error ? error.message : 'Airwallex radar failed';
675
+ this.callbacks.onError?.(msg);
546
676
  this.callbacks.onProcessing?.(false);
677
+ return { kind: 'failed', error: msg, payment };
547
678
  }
548
679
  }
549
680
  // --------------------------------------------------------------------------
@@ -552,9 +683,10 @@ export class PaymentService {
552
683
  async handleMasterCardAuth(payment, actionData) {
553
684
  const threeDSData = actionData?.metadata?.threeds;
554
685
  if (!threeDSData?.sessionId || !threeDSData?.merchantId) {
555
- this.callbacks.onError?.('Missing MasterCard 3DS data');
686
+ const msg = 'Missing MasterCard 3DS data';
687
+ this.callbacks.onError?.(msg);
556
688
  this.callbacks.onProcessing?.(false);
557
- return;
689
+ return { kind: 'failed', error: msg, payment };
558
690
  }
559
691
  try {
560
692
  this.callbacks.onProcessing?.(false);
@@ -607,21 +739,26 @@ export class PaymentService {
607
739
  document.write(challengeHtml);
608
740
  document.close();
609
741
  }
742
+ return { kind: 'redirected' };
610
743
  }
611
- else {
612
- // Frictionless — complete immediately
613
- if (threeDSData.paymentId) {
614
- const resumed = await this.paymentsResource.completePaymentAfterAction(threeDSData.paymentId);
615
- await this.handleResumedPayment(resumed);
616
- }
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);
617
751
  }
618
752
  const cleanup = document.getElementById(containerId);
619
753
  if (cleanup)
620
754
  cleanup.remove();
755
+ return { kind: 'pending' };
621
756
  }
622
757
  catch (error) {
623
- this.callbacks.onError?.(error instanceof Error ? error.message : 'MasterCard 3DS failed');
758
+ const msg = error instanceof Error ? error.message : 'MasterCard 3DS failed';
759
+ this.callbacks.onError?.(msg);
624
760
  this.callbacks.onProcessing?.(false);
761
+ return { kind: 'failed', error: msg, payment };
625
762
  }
626
763
  }
627
764
  // --------------------------------------------------------------------------
@@ -835,7 +972,7 @@ export class PaymentService {
835
972
  // ==========================================================================
836
973
  // PAYMENT PROCESSING (all methods)
837
974
  // ==========================================================================
838
- async processCardPayment(checkoutSessionId, cardData) {
975
+ async processCardPayment(checkoutSessionId, cardData, options) {
839
976
  this.callbacks.onProcessing?.(true);
840
977
  this.callbacks.onError?.(null);
841
978
  try {
@@ -858,7 +995,7 @@ export class PaymentService {
858
995
  // Continue without 3DS
859
996
  }
860
997
  }
861
- return await this.processAndHandle(checkoutSessionId, instrument.id, threedsSessionId);
998
+ return await this.processAndHandle(checkoutSessionId, instrument.id, threedsSessionId, { shippingRateId: options?.shippingRateId });
862
999
  }
863
1000
  catch (error) {
864
1001
  const msg = error instanceof Error ? error.message : String(error);
@@ -867,12 +1004,12 @@ export class PaymentService {
867
1004
  return { success: false, error: msg };
868
1005
  }
869
1006
  }
870
- async processApplePayPayment(checkoutSessionId, applePayToken) {
1007
+ async processApplePayPayment(checkoutSessionId, applePayToken, options) {
871
1008
  this.callbacks.onProcessing?.(true);
872
1009
  this.callbacks.onError?.(null);
873
1010
  try {
874
1011
  const instrument = await this.createApplePayPaymentInstrument(applePayToken);
875
- return await this.processAndHandle(checkoutSessionId, instrument.id);
1012
+ return await this.processAndHandle(checkoutSessionId, instrument.id, undefined, { shippingRateId: options?.shippingRateId });
876
1013
  }
877
1014
  catch (error) {
878
1015
  const msg = error instanceof Error ? error.message : String(error);
@@ -881,12 +1018,12 @@ export class PaymentService {
881
1018
  return { success: false, error: msg };
882
1019
  }
883
1020
  }
884
- async processGooglePayPayment(checkoutSessionId, googlePayToken) {
1021
+ async processGooglePayPayment(checkoutSessionId, googlePayToken, options) {
885
1022
  this.callbacks.onProcessing?.(true);
886
1023
  this.callbacks.onError?.(null);
887
1024
  try {
888
1025
  const instrument = await this.createGooglePayPaymentInstrument(googlePayToken);
889
- return await this.processAndHandle(checkoutSessionId, instrument.id);
1026
+ return await this.processAndHandle(checkoutSessionId, instrument.id, undefined, { shippingRateId: options?.shippingRateId });
890
1027
  }
891
1028
  catch (error) {
892
1029
  const msg = error instanceof Error ? error.message : String(error);
@@ -895,11 +1032,11 @@ export class PaymentService {
895
1032
  return { success: false, error: msg };
896
1033
  }
897
1034
  }
898
- async processPaymentWithInstrument(checkoutSessionId, paymentInstrumentId) {
1035
+ async processPaymentWithInstrument(checkoutSessionId, paymentInstrumentId, options) {
899
1036
  this.callbacks.onProcessing?.(true);
900
1037
  this.callbacks.onError?.(null);
901
1038
  try {
902
- return await this.processAndHandle(checkoutSessionId, paymentInstrumentId);
1039
+ return await this.processAndHandle(checkoutSessionId, paymentInstrumentId, undefined, { shippingRateId: options?.shippingRateId });
903
1040
  }
904
1041
  catch (error) {
905
1042
  const msg = error instanceof Error ? error.message : String(error);
@@ -908,7 +1045,7 @@ export class PaymentService {
908
1045
  return { success: false, error: msg };
909
1046
  }
910
1047
  }
911
- async processApmPayment(checkoutSessionId, apmData) {
1048
+ async processApmPayment(checkoutSessionId, apmData, options) {
912
1049
  this.callbacks.onProcessing?.(true);
913
1050
  this.callbacks.onError?.(null);
914
1051
  try {
@@ -917,6 +1054,89 @@ export class PaymentService {
917
1054
  paymentMethod: apmData.paymentMethod,
918
1055
  initiatedBy: apmData.initiatedBy,
919
1056
  source: apmData.source,
1057
+ shippingRateId: options?.shippingRateId,
1058
+ });
1059
+ }
1060
+ catch (error) {
1061
+ const msg = error instanceof Error ? error.message : String(error);
1062
+ this.callbacks.onError?.(msg);
1063
+ this.callbacks.onProcessing?.(false);
1064
+ return { success: false, error: msg };
1065
+ }
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();
920
1140
  });
921
1141
  }
922
1142
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tagadapay/plugin-sdk",
3
- "version": "3.1.25",
3
+ "version": "4.0.2",
4
4
  "description": "Modern React SDK for building Tagada Pay plugins",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -76,6 +76,7 @@
76
76
  "@google-pay/button-react": "^3.0.10",
77
77
  "@stripe/react-stripe-js": "^5.6.1",
78
78
  "@stripe/stripe-js": "^8.11.0",
79
+ "@tagadapay/core-js": "workspace:*",
79
80
  "@tagadapay/plugin-sdk": "link:",
80
81
  "@tanstack/react-query": "^5.90.2",
81
82
  "@whop/checkout": "^0.0.40",