@tagadapay/headless-sdk 1.3.2 → 1.5.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/README.md +84 -7
- package/dist/core/client.d.ts +6 -0
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +6 -0
- package/dist/core/client.js.map +1 -1
- package/dist/core/http.js +2 -2
- package/dist/core/http.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/modules/checkout.d.ts +113 -12
- package/dist/core/modules/checkout.d.ts.map +1 -1
- package/dist/core/modules/checkout.js +201 -55
- package/dist/core/modules/checkout.js.map +1 -1
- package/dist/core/modules/payment.d.ts +120 -4
- package/dist/core/modules/payment.d.ts.map +1 -1
- package/dist/core/modules/payment.js +336 -7
- package/dist/core/modules/payment.js.map +1 -1
- package/dist/core/types.d.ts +112 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/react/hooks/useCheckout.d.ts.map +1 -1
- package/dist/react/hooks/useCheckout.js +9 -15
- package/dist/react/hooks/useCheckout.js.map +1 -1
- package/dist/react/hooks/usePayment.d.ts +58 -13
- package/dist/react/hooks/usePayment.d.ts.map +1 -1
- package/dist/react/hooks/usePayment.js +124 -11
- package/dist/react/hooks/usePayment.js.map +1 -1
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.d.ts.map +1 -1
- package/dist/stripe/StripeExpressButton.d.ts +30 -0
- package/dist/stripe/StripeExpressButton.d.ts.map +1 -0
- package/dist/stripe/StripeExpressButton.js +32 -0
- package/dist/stripe/StripeExpressButton.js.map +1 -0
- package/dist/stripe/StripeExpressCheckout.d.ts +98 -0
- package/dist/stripe/StripeExpressCheckout.d.ts.map +1 -0
- package/dist/stripe/StripeExpressCheckout.js +198 -0
- package/dist/stripe/StripeExpressCheckout.js.map +1 -0
- package/dist/stripe/index.d.ts +8 -0
- package/dist/stripe/index.d.ts.map +1 -0
- package/dist/stripe/index.js +15 -0
- package/dist/stripe/index.js.map +1 -0
- package/dist/stripe/useStripeExpress.d.ts +54 -0
- package/dist/stripe/useStripeExpress.d.ts.map +1 -0
- package/dist/stripe/useStripeExpress.js +126 -0
- package/dist/stripe/useStripeExpress.js.map +1 -0
- package/dist/tagada-headless-react.min.js +2 -2
- package/dist/tagada-headless-react.min.js.map +3 -3
- package/dist/tagada-headless.js +483 -66
- package/dist/tagada-headless.min.js +2 -2
- package/dist/tagada-headless.min.js.map +3 -3
- package/package.json +9 -3
package/dist/tagada-headless.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @tagadapay/headless-sdk v1.
|
|
2
|
+
* @tagadapay/headless-sdk v1.5.0
|
|
3
3
|
* Headless checkout, payments, offers, and CRM
|
|
4
4
|
* @license MIT
|
|
5
5
|
*/
|
|
@@ -123,8 +123,8 @@ var TagadaHeadlessBundle = (() => {
|
|
|
123
123
|
|
|
124
124
|
// src/core/http.ts
|
|
125
125
|
var BASE_URLS = {
|
|
126
|
-
production: "https://
|
|
127
|
-
development: "https://
|
|
126
|
+
production: "https://api.tagada.io",
|
|
127
|
+
development: "https://api.tagada.dev",
|
|
128
128
|
local: "http://localhost:3000"
|
|
129
129
|
};
|
|
130
130
|
var HttpClient = class {
|
|
@@ -241,20 +241,27 @@ var TagadaHeadlessBundle = (() => {
|
|
|
241
241
|
*
|
|
242
242
|
* @param checkoutToken The checkout token (from URL param `checkoutToken`).
|
|
243
243
|
* @param sessionToken The JWT session token (from URL param `token`). Required for auth.
|
|
244
|
+
* @param opts.currency Optional presentment currency override (e.g. 'EUR').
|
|
245
|
+
* Only applied when the session has no currency set yet (first load).
|
|
246
|
+
* Once set, currency is locked to the session and this param is ignored.
|
|
244
247
|
*
|
|
245
|
-
* Both
|
|
248
|
+
* Both `checkoutToken` and `sessionToken` come from the redirect URL returned by `createSession()`.
|
|
246
249
|
* Example URL: `/checkout?checkoutToken=abc123&token=eyJ...`
|
|
247
250
|
*/
|
|
248
|
-
async loadSession(checkoutToken, sessionToken) {
|
|
251
|
+
async loadSession(checkoutToken, sessionToken, opts) {
|
|
249
252
|
var _a;
|
|
250
253
|
if (sessionToken) {
|
|
251
254
|
this.sessionToken = sessionToken;
|
|
252
255
|
(_a = this.onTokenChange) == null ? void 0 : _a.call(this, sessionToken);
|
|
253
256
|
}
|
|
257
|
+
const params = { skipAsyncWait: "false" };
|
|
258
|
+
if (opts == null ? void 0 : opts.currency) {
|
|
259
|
+
params.currency = opts.currency;
|
|
260
|
+
}
|
|
254
261
|
const raw = await this.http.get(
|
|
255
262
|
`/api/v1/checkout-sessions/${checkoutToken}/v2`,
|
|
256
263
|
{
|
|
257
|
-
params
|
|
264
|
+
params,
|
|
258
265
|
token: this.sessionToken
|
|
259
266
|
}
|
|
260
267
|
);
|
|
@@ -277,17 +284,18 @@ var TagadaHeadlessBundle = (() => {
|
|
|
277
284
|
storeId: this.storeId,
|
|
278
285
|
lineItems
|
|
279
286
|
};
|
|
280
|
-
if (opts.currency)
|
|
281
|
-
body.currency = opts.currency;
|
|
282
287
|
if (opts.returnUrl)
|
|
283
288
|
body.returnUrl = opts.returnUrl;
|
|
289
|
+
const customerData = {};
|
|
290
|
+
if (opts.currency)
|
|
291
|
+
customerData.currency = opts.currency;
|
|
284
292
|
if (opts.customerEmail) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
293
|
+
customerData.email = opts.customerEmail;
|
|
294
|
+
customerData.firstName = opts.customerFirstName;
|
|
295
|
+
customerData.lastName = opts.customerLastName;
|
|
296
|
+
}
|
|
297
|
+
if (Object.keys(customerData).length > 0) {
|
|
298
|
+
body.customer = customerData;
|
|
291
299
|
}
|
|
292
300
|
const res = await this.http.post("/api/v1/checkout/session/init", body);
|
|
293
301
|
const url = new URL(res.checkoutUrl);
|
|
@@ -302,7 +310,18 @@ var TagadaHeadlessBundle = (() => {
|
|
|
302
310
|
customerId: res.customerId
|
|
303
311
|
};
|
|
304
312
|
}
|
|
305
|
-
/**
|
|
313
|
+
/**
|
|
314
|
+
* Replace the cart items for an existing session.
|
|
315
|
+
*
|
|
316
|
+
* Returns a server-side mutation summary (counts of created/updated rows).
|
|
317
|
+
* It does NOT return the updated `CheckoutSession` — the underlying
|
|
318
|
+
* `POST /checkout-sessions/:id/line-items` endpoint never has and the
|
|
319
|
+
* previous typing was a lie that quietly assigned `{ success, updated, ... }`
|
|
320
|
+
* into a `CheckoutSession` slot in `useCheckout`.
|
|
321
|
+
*
|
|
322
|
+
* Call `loadSession()` after this if you need the refreshed session
|
|
323
|
+
* (the React `useCheckout` hook does this for you).
|
|
324
|
+
*/
|
|
306
325
|
async updateCart(sessionId, items) {
|
|
307
326
|
return this.http.post(
|
|
308
327
|
`/api/v1/checkout-sessions/${sessionId}/line-items`,
|
|
@@ -311,59 +330,117 @@ var TagadaHeadlessBundle = (() => {
|
|
|
311
330
|
);
|
|
312
331
|
}
|
|
313
332
|
/**
|
|
314
|
-
* Update customer
|
|
315
|
-
*
|
|
333
|
+
* Update customer identity (email, name, phone).
|
|
334
|
+
*
|
|
335
|
+
* Call this before `updateAddress()` — the SDK remembers the values and
|
|
336
|
+
* includes them automatically when addresses are submitted.
|
|
337
|
+
*
|
|
338
|
+
* If you prefer a single call, use `updateCustomerAndAddress()` instead.
|
|
316
339
|
*/
|
|
317
340
|
async updateCustomer(sessionId, customer) {
|
|
341
|
+
if (customer.email)
|
|
342
|
+
this.customerEmail = customer.email;
|
|
343
|
+
if (customer.firstName)
|
|
344
|
+
this.customerFirstName = customer.firstName;
|
|
345
|
+
if (customer.lastName)
|
|
346
|
+
this.customerLastName = customer.lastName;
|
|
347
|
+
if (customer.phone)
|
|
348
|
+
this.customerPhone = customer.phone;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Update shipping and/or billing address on the session.
|
|
352
|
+
*
|
|
353
|
+
* Automatically includes customer name/phone stored from `updateCustomer()`.
|
|
354
|
+
* Uses the same atomic API call as the production checkout, so email,
|
|
355
|
+
* customer name, and addresses are all persisted together.
|
|
356
|
+
*
|
|
357
|
+
* **Shipped products**: pass `shippingAddress`. If `billingAddress` is omitted,
|
|
358
|
+
* the shipping address is automatically used as billing.
|
|
359
|
+
*
|
|
360
|
+
* **Digital products**: pass only `billingAddress` (no shipping needed).
|
|
361
|
+
*
|
|
362
|
+
* @example
|
|
363
|
+
* // Shipped product — billing = shipping automatically
|
|
364
|
+
* await checkout.updateAddress(sessionId, {
|
|
365
|
+
* shippingAddress: { line1: '10 Rue de la Paix', city: 'Paris', postalCode: '75002', country: 'FR' },
|
|
366
|
+
* });
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* // Digital product — billing only
|
|
370
|
+
* await checkout.updateAddress(sessionId, {
|
|
371
|
+
* billingAddress: { country: 'FR' },
|
|
372
|
+
* });
|
|
373
|
+
*/
|
|
374
|
+
async updateAddress(sessionId, addresses) {
|
|
375
|
+
const shipping = this.mapAddress(addresses.shippingAddress);
|
|
376
|
+
const billing = this.mapAddress(addresses.billingAddress);
|
|
377
|
+
const effectiveShipping = shipping ?? billing ?? {};
|
|
378
|
+
const effectiveBilling = billing ?? shipping;
|
|
379
|
+
const differentBilling = !!(addresses.billingAddress && addresses.shippingAddress);
|
|
318
380
|
await this.http.post(
|
|
319
381
|
`/api/v1/checkout-sessions/${sessionId}/customer-and-session-info`,
|
|
320
382
|
{
|
|
321
383
|
checkoutSessionId: sessionId,
|
|
322
|
-
partial: true,
|
|
323
384
|
customerData: {
|
|
324
|
-
email:
|
|
325
|
-
},
|
|
326
|
-
shippingAddress: {
|
|
327
|
-
firstName: customer.firstName,
|
|
328
|
-
lastName: customer.lastName,
|
|
329
|
-
phone: customer.phone
|
|
385
|
+
email: this.customerEmail
|
|
330
386
|
},
|
|
331
|
-
|
|
332
|
-
|
|
387
|
+
shippingAddress: effectiveShipping,
|
|
388
|
+
billingAddress: effectiveBilling ?? {},
|
|
389
|
+
differentBillingAddress: differentBilling
|
|
333
390
|
},
|
|
334
391
|
{ token: this.sessionToken }
|
|
335
392
|
);
|
|
336
393
|
}
|
|
337
394
|
/**
|
|
338
|
-
*
|
|
395
|
+
* Set customer info + addresses in one call (mirrors production checkout).
|
|
339
396
|
*
|
|
340
|
-
*
|
|
397
|
+
* Equivalent to calling `updateCustomer()` then `updateAddress()`, but
|
|
398
|
+
* in a single network request.
|
|
341
399
|
*/
|
|
342
|
-
async
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
400
|
+
async updateCustomerAndAddress(sessionId, data) {
|
|
401
|
+
if (data.customer.email)
|
|
402
|
+
this.customerEmail = data.customer.email;
|
|
403
|
+
if (data.customer.firstName)
|
|
404
|
+
this.customerFirstName = data.customer.firstName;
|
|
405
|
+
if (data.customer.lastName)
|
|
406
|
+
this.customerLastName = data.customer.lastName;
|
|
407
|
+
if (data.customer.phone)
|
|
408
|
+
this.customerPhone = data.customer.phone;
|
|
409
|
+
const shipping = this.mapAddress(data.shippingAddress);
|
|
410
|
+
const billing = this.mapAddress(data.billingAddress);
|
|
411
|
+
const effectiveShipping = shipping ?? billing ?? {};
|
|
412
|
+
const effectiveBilling = billing ?? shipping;
|
|
413
|
+
const differentBilling = !!(data.billingAddress && data.shippingAddress);
|
|
355
414
|
await this.http.post(
|
|
356
|
-
`/api/v1/checkout-sessions/${sessionId}/
|
|
415
|
+
`/api/v1/checkout-sessions/${sessionId}/customer-and-session-info`,
|
|
357
416
|
{
|
|
358
417
|
checkoutSessionId: sessionId,
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
418
|
+
customerData: {
|
|
419
|
+
email: this.customerEmail
|
|
420
|
+
},
|
|
421
|
+
shippingAddress: effectiveShipping,
|
|
422
|
+
billingAddress: effectiveBilling ?? {},
|
|
423
|
+
differentBillingAddress: differentBilling
|
|
363
424
|
},
|
|
364
425
|
{ token: this.sessionToken }
|
|
365
426
|
);
|
|
366
427
|
}
|
|
428
|
+
/** Map an SDK Address to the API shape, injecting stored customer fields. */
|
|
429
|
+
mapAddress(a) {
|
|
430
|
+
if (!a)
|
|
431
|
+
return void 0;
|
|
432
|
+
return stripEmpty({
|
|
433
|
+
firstName: a.firstName ?? this.customerFirstName,
|
|
434
|
+
lastName: a.lastName ?? this.customerLastName,
|
|
435
|
+
phone: a.phone ?? this.customerPhone,
|
|
436
|
+
address1: a.line1,
|
|
437
|
+
address2: a.line2,
|
|
438
|
+
city: a.city,
|
|
439
|
+
state: a.state,
|
|
440
|
+
postal: a.postalCode,
|
|
441
|
+
country: a.country
|
|
442
|
+
});
|
|
443
|
+
}
|
|
367
444
|
/** Apply a promotion code to the session. */
|
|
368
445
|
async applyPromoCode(sessionId, code) {
|
|
369
446
|
await this.http.post(
|
|
@@ -380,7 +457,13 @@ var TagadaHeadlessBundle = (() => {
|
|
|
380
457
|
{ token: this.sessionToken }
|
|
381
458
|
);
|
|
382
459
|
}
|
|
383
|
-
/**
|
|
460
|
+
/**
|
|
461
|
+
* Fetch available shipping rates for the session's address.
|
|
462
|
+
* Only relevant for **shipped products** — digital products can skip this step.
|
|
463
|
+
*
|
|
464
|
+
* Requires a shipping address to be set first via `updateAddress()`.
|
|
465
|
+
* Returns an empty array if no rates are configured or address is missing.
|
|
466
|
+
*/
|
|
384
467
|
async getShippingRates(sessionId) {
|
|
385
468
|
const res = await this.http.get(
|
|
386
469
|
`/api/v1/checkout-sessions/${sessionId}/shipping-rates`,
|
|
@@ -388,7 +471,10 @@ var TagadaHeadlessBundle = (() => {
|
|
|
388
471
|
);
|
|
389
472
|
return res.rates ?? [];
|
|
390
473
|
}
|
|
391
|
-
/**
|
|
474
|
+
/**
|
|
475
|
+
* Select a shipping rate for the session.
|
|
476
|
+
* Only relevant for **shipped products** — digital products can skip this step.
|
|
477
|
+
*/
|
|
392
478
|
async selectShippingRate(sessionId, rateId) {
|
|
393
479
|
await this.http.post(
|
|
394
480
|
`/api/v1/checkout-sessions/${sessionId}/shipping-rate`,
|
|
@@ -401,17 +487,20 @@ var TagadaHeadlessBundle = (() => {
|
|
|
401
487
|
* The raw response has nested `checkoutSession` + `summary` -- we flatten it.
|
|
402
488
|
*/
|
|
403
489
|
normalizeSession(raw) {
|
|
490
|
+
var _a;
|
|
404
491
|
const cs = raw.checkoutSession;
|
|
405
492
|
const orderSummaries = cs.orderSummaries ?? [];
|
|
406
493
|
const summary = raw.summary ?? cs.summary ?? orderSummaries[0] ?? {};
|
|
407
494
|
const lineItems = cs.sessionLineItems ?? [];
|
|
408
495
|
const customer = cs.customer;
|
|
496
|
+
const sessionCurrency = (_a = summary.currency ?? cs.selectedPresentmentCurrency) == null ? void 0 : _a.toUpperCase();
|
|
409
497
|
const items = lineItems.map((li) => {
|
|
410
498
|
const price = li.price;
|
|
411
499
|
const variant = (price == null ? void 0 : price.variant) ?? {};
|
|
412
500
|
const product = variant.product ?? {};
|
|
413
501
|
const currencyOpts = (price == null ? void 0 : price.currencyOptions) ?? {};
|
|
414
|
-
const
|
|
502
|
+
const priceEntry = (sessionCurrency ? currencyOpts[sessionCurrency] : void 0) ?? currencyOpts[Object.keys(currencyOpts)[0]];
|
|
503
|
+
const amount = (priceEntry == null ? void 0 : priceEntry.amount) ?? 0;
|
|
415
504
|
return {
|
|
416
505
|
id: li.id,
|
|
417
506
|
variantId: li.variantId,
|
|
@@ -419,9 +508,9 @@ var TagadaHeadlessBundle = (() => {
|
|
|
419
508
|
productName: product.name ?? "",
|
|
420
509
|
variantName: variant.name ?? "",
|
|
421
510
|
quantity: li.quantity ?? 1,
|
|
422
|
-
unitAmount:
|
|
423
|
-
totalAmount:
|
|
424
|
-
currency:
|
|
511
|
+
unitAmount: amount,
|
|
512
|
+
totalAmount: amount * (li.quantity ?? 1),
|
|
513
|
+
currency: sessionCurrency ?? "USD",
|
|
425
514
|
imageUrl: variant.imageUrl ?? ""
|
|
426
515
|
};
|
|
427
516
|
});
|
|
@@ -430,7 +519,7 @@ var TagadaHeadlessBundle = (() => {
|
|
|
430
519
|
id: cs.id,
|
|
431
520
|
storeId: cs.storeId,
|
|
432
521
|
checkoutToken: cs.checkoutToken,
|
|
433
|
-
currency:
|
|
522
|
+
currency: sessionCurrency ?? "USD",
|
|
434
523
|
items,
|
|
435
524
|
totals: {
|
|
436
525
|
subtotal: summary.subtotalAmount ?? 0,
|
|
@@ -438,20 +527,55 @@ var TagadaHeadlessBundle = (() => {
|
|
|
438
527
|
shipping: summary.shippingCost ?? 0,
|
|
439
528
|
tax: summary.totalTaxAmount ?? 0,
|
|
440
529
|
total: summary.totalAmount ?? summaryItems.reduce((s, i) => s + (i.amount ?? 0), 0),
|
|
441
|
-
currency:
|
|
530
|
+
currency: sessionCurrency ?? "USD"
|
|
442
531
|
},
|
|
443
532
|
customer: customer ? {
|
|
444
533
|
id: customer.id,
|
|
445
534
|
email: customer.email,
|
|
446
535
|
firstName: customer.firstName,
|
|
447
|
-
lastName: customer.lastName
|
|
536
|
+
lastName: customer.lastName,
|
|
537
|
+
phone: customer.phone
|
|
448
538
|
} : void 0,
|
|
449
|
-
shippingAddress: cs.shippingAddress,
|
|
450
|
-
billingAddress: cs.billingAddress,
|
|
539
|
+
shippingAddress: this.normalizeAddress(cs.shippingAddress),
|
|
540
|
+
billingAddress: this.normalizeAddress(cs.billingAddress),
|
|
541
|
+
selectedShippingRateId: cs.shippingRateId ?? null,
|
|
451
542
|
metadata: cs.metadata
|
|
452
543
|
};
|
|
453
544
|
}
|
|
545
|
+
/**
|
|
546
|
+
* Map the raw API address shape (`address1`, `address2`, `postal`) onto
|
|
547
|
+
* the SDK's `Address` shape (`line1`, `line2`, `postalCode`). Without
|
|
548
|
+
* this, `session.shippingAddress.line1` is silently undefined after a
|
|
549
|
+
* reload — the type was lying about the actual key names — which is
|
|
550
|
+
* what makes the demo lose form state on refresh.
|
|
551
|
+
*/
|
|
552
|
+
normalizeAddress(raw) {
|
|
553
|
+
if (!raw || typeof raw !== "object")
|
|
554
|
+
return void 0;
|
|
555
|
+
const a = raw;
|
|
556
|
+
const out = {
|
|
557
|
+
firstName: a.firstName,
|
|
558
|
+
lastName: a.lastName,
|
|
559
|
+
line1: a.line1 ?? a.address1,
|
|
560
|
+
line2: a.line2 ?? a.address2,
|
|
561
|
+
city: a.city,
|
|
562
|
+
state: a.state,
|
|
563
|
+
postalCode: a.postalCode ?? a.postal,
|
|
564
|
+
country: a.country,
|
|
565
|
+
phone: a.phone
|
|
566
|
+
};
|
|
567
|
+
return Object.values(out).some((v) => v !== void 0 && v !== "") ? out : void 0;
|
|
568
|
+
}
|
|
454
569
|
};
|
|
570
|
+
function stripEmpty(obj) {
|
|
571
|
+
const result = {};
|
|
572
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
573
|
+
if (v !== void 0 && v !== null && v !== "") {
|
|
574
|
+
result[k] = v;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
578
|
+
}
|
|
455
579
|
|
|
456
580
|
// src/core/modules/payment.ts
|
|
457
581
|
var PaymentModule = class {
|
|
@@ -467,8 +591,20 @@ var TagadaHeadlessBundle = (() => {
|
|
|
467
591
|
* Get the full payment setup config for a checkout session.
|
|
468
592
|
* Returns a map of payment methods with their configuration
|
|
469
593
|
* (card, apple_pay, google_pay, klarna, etc.).
|
|
594
|
+
*
|
|
595
|
+
* Priority chain:
|
|
596
|
+
* 1. Explicit `paymentSetupConfig` parameter (developer override)
|
|
597
|
+
* 2. `window.__TGD_STEP_CONFIG__` injection (Tagada routing infra)
|
|
598
|
+
* 3. API fallback (`GET /api/v1/payment-methods`)
|
|
470
599
|
*/
|
|
471
|
-
async getPaymentSetup(checkoutSessionId) {
|
|
600
|
+
async getPaymentSetup(checkoutSessionId, opts) {
|
|
601
|
+
if (opts == null ? void 0 : opts.paymentSetupConfig) {
|
|
602
|
+
return opts.paymentSetupConfig;
|
|
603
|
+
}
|
|
604
|
+
const injected = this.readInjectedStepConfig();
|
|
605
|
+
if (injected) {
|
|
606
|
+
return injected;
|
|
607
|
+
}
|
|
472
608
|
const methods = await this.http.get(
|
|
473
609
|
"/api/v1/payment-methods",
|
|
474
610
|
{ params: { checkoutSessionId } }
|
|
@@ -484,12 +620,39 @@ var TagadaHeadlessBundle = (() => {
|
|
|
484
620
|
* Get express payment methods (Apple Pay, Google Pay, PayPal, etc.)
|
|
485
621
|
* filtered to only those available in the current browser.
|
|
486
622
|
*/
|
|
487
|
-
async getExpressMethods(checkoutSessionId) {
|
|
488
|
-
var _a, _b;
|
|
489
|
-
const setup = await this.getPaymentSetup(checkoutSessionId);
|
|
623
|
+
async getExpressMethods(checkoutSessionId, opts) {
|
|
624
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
625
|
+
const setup = await this.getPaymentSetup(checkoutSessionId, opts);
|
|
490
626
|
const result = {};
|
|
491
|
-
for (const [, config] of Object.entries(setup)) {
|
|
492
|
-
if (!config.enabled
|
|
627
|
+
for (const [key, config] of Object.entries(setup)) {
|
|
628
|
+
if (!config.enabled)
|
|
629
|
+
continue;
|
|
630
|
+
if (key.startsWith("express_checkout:") && config.methods) {
|
|
631
|
+
const base2 = {
|
|
632
|
+
available: true,
|
|
633
|
+
processorId: config.processorId,
|
|
634
|
+
publishableKey: config.publishableKey,
|
|
635
|
+
metadata: config.metadata
|
|
636
|
+
};
|
|
637
|
+
if ((_a = config.methods.apple_pay) == null ? void 0 : _a.enabled) {
|
|
638
|
+
const hasApplePay = typeof window !== "undefined" && "ApplePaySession" in window && ((_c = (_b = window.ApplePaySession) == null ? void 0 : _b.canMakePayments) == null ? void 0 : _c.call(_b));
|
|
639
|
+
result.applePay = { ...base2, type: "apple_pay", available: !!hasApplePay };
|
|
640
|
+
}
|
|
641
|
+
if ((_d = config.methods.google_pay) == null ? void 0 : _d.enabled) {
|
|
642
|
+
result.googlePay = { ...base2, type: "google_pay" };
|
|
643
|
+
}
|
|
644
|
+
if ((_e = config.methods.paypal) == null ? void 0 : _e.enabled) {
|
|
645
|
+
result.paypal = { ...base2, type: "paypal" };
|
|
646
|
+
}
|
|
647
|
+
if ((_f = config.methods.link) == null ? void 0 : _f.enabled) {
|
|
648
|
+
result.link = { ...base2, type: "link" };
|
|
649
|
+
}
|
|
650
|
+
if ((_g = config.methods.klarna_express) == null ? void 0 : _g.enabled) {
|
|
651
|
+
result.klarna = { ...base2, type: "klarna_express" };
|
|
652
|
+
}
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
if (!config.express)
|
|
493
656
|
continue;
|
|
494
657
|
const method = config.method;
|
|
495
658
|
const base = {
|
|
@@ -500,7 +663,7 @@ var TagadaHeadlessBundle = (() => {
|
|
|
500
663
|
metadata: config.metadata
|
|
501
664
|
};
|
|
502
665
|
if (method === "apple_pay" || config.type === "apple_pay") {
|
|
503
|
-
const hasApplePay = typeof window !== "undefined" && "ApplePaySession" in window && ((
|
|
666
|
+
const hasApplePay = typeof window !== "undefined" && "ApplePaySession" in window && ((_i = (_h = window.ApplePaySession) == null ? void 0 : _h.canMakePayments) == null ? void 0 : _i.call(_h));
|
|
504
667
|
result.applePay = { ...base, available: !!hasApplePay };
|
|
505
668
|
} else if (method === "google_pay" || config.type === "google_pay") {
|
|
506
669
|
result.googlePay = base;
|
|
@@ -527,8 +690,8 @@ var TagadaHeadlessBundle = (() => {
|
|
|
527
690
|
/**
|
|
528
691
|
* Convenience: return only enabled payment method keys.
|
|
529
692
|
*/
|
|
530
|
-
async getEnabledMethods(checkoutSessionId) {
|
|
531
|
-
const setup = await this.getPaymentSetup(checkoutSessionId);
|
|
693
|
+
async getEnabledMethods(checkoutSessionId, opts) {
|
|
694
|
+
const setup = await this.getPaymentSetup(checkoutSessionId, opts);
|
|
532
695
|
return Object.entries(setup).filter(([, c]) => c.enabled).map(([key]) => key);
|
|
533
696
|
}
|
|
534
697
|
// ---------------------------------------------------------------------------
|
|
@@ -538,6 +701,14 @@ var TagadaHeadlessBundle = (() => {
|
|
|
538
701
|
* Tokenize a card using @tagadapay/core-js.
|
|
539
702
|
* Requires `@tagadapay/core-js` to be installed as a peer dependency.
|
|
540
703
|
*
|
|
704
|
+
* Card data is sent directly to Basis Theory for tokenization and never
|
|
705
|
+
* reaches TagadaPay servers. The returned `tagadaToken` is safe to pass
|
|
706
|
+
* to the TagadaPay API.
|
|
707
|
+
*
|
|
708
|
+
* **PCI note:** Using this method means card data passes through your
|
|
709
|
+
* JavaScript environment. You are responsible for PCI DSS SAQ-A-EP
|
|
710
|
+
* compliance. See https://docs.tagada.io/developer-tools/headless-sdk/pci-compliance
|
|
711
|
+
*
|
|
541
712
|
* @returns tagadaToken (base64 string) and rawToken with SCA metadata.
|
|
542
713
|
*/
|
|
543
714
|
async tokenizeCard(card) {
|
|
@@ -598,6 +769,7 @@ var TagadaHeadlessBundle = (() => {
|
|
|
598
769
|
return this.http.post("/api/v1/payment/pay-with-token", {
|
|
599
770
|
tagadaToken: opts.tagadaToken,
|
|
600
771
|
checkoutSessionId: opts.checkoutSessionId,
|
|
772
|
+
returnUrl: opts.returnUrl,
|
|
601
773
|
paymentFlowId: opts.paymentFlowId,
|
|
602
774
|
processorId: opts.processorId,
|
|
603
775
|
paymentMethod: opts.paymentMethod,
|
|
@@ -681,6 +853,85 @@ var TagadaHeadlessBundle = (() => {
|
|
|
681
853
|
paymentFlowId: opts.paymentFlowId
|
|
682
854
|
});
|
|
683
855
|
}
|
|
856
|
+
/**
|
|
857
|
+
* Initiate a Stripe Express Checkout payment (Apple Pay, Google Pay, Link, etc.).
|
|
858
|
+
*
|
|
859
|
+
* Calls `pay-v2` with `isExpress: true`, which returns a `clientSecret` in
|
|
860
|
+
* `payment.requireActionData.metadata.stripeExpressCheckout.clientSecret`.
|
|
861
|
+
* The caller must then call `stripe.confirmPayment()` with that secret.
|
|
862
|
+
*
|
|
863
|
+
* Used by the `StripeExpressCheckout` class and `useStripeExpress` hook.
|
|
864
|
+
* Not typically called directly — use the Stripe integration classes instead.
|
|
865
|
+
*/
|
|
866
|
+
async processStripeExpress(opts) {
|
|
867
|
+
return this.payDirect({
|
|
868
|
+
checkoutSessionId: opts.checkoutSessionId,
|
|
869
|
+
processorId: opts.processorId,
|
|
870
|
+
paymentMethod: opts.paymentMethod,
|
|
871
|
+
isExpress: true
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
// ---------------------------------------------------------------------------
|
|
875
|
+
// High-level orchestrator — the ONE function beginners should call
|
|
876
|
+
// ---------------------------------------------------------------------------
|
|
877
|
+
/**
|
|
878
|
+
* Process a payment end-to-end: pay, handle 3DS / redirects, poll until done.
|
|
879
|
+
*
|
|
880
|
+
* This is the **recommended** way to process payments. It wraps `pay()`, detects
|
|
881
|
+
* `requireAction`, runs 3DS challenges (if `@tagadapay/core-js` is installed),
|
|
882
|
+
* and polls until a terminal status.
|
|
883
|
+
*
|
|
884
|
+
* Returns a discriminated union so you can pattern-match on `result.status`:
|
|
885
|
+
*
|
|
886
|
+
* @example
|
|
887
|
+
* ```ts
|
|
888
|
+
* const result = await payment.processPayment({
|
|
889
|
+
* checkoutSessionId: session.id,
|
|
890
|
+
* tagadaToken,
|
|
891
|
+
* });
|
|
892
|
+
*
|
|
893
|
+
* switch (result.status) {
|
|
894
|
+
* case 'succeeded':
|
|
895
|
+
* console.log('Paid!', result.payment.id);
|
|
896
|
+
* break;
|
|
897
|
+
* case 'requires_redirect':
|
|
898
|
+
* // In browser: window.location.href = result.redirectUrl
|
|
899
|
+
* // The React hook handles this automatically.
|
|
900
|
+
* break;
|
|
901
|
+
* case 'failed':
|
|
902
|
+
* console.error(result.error);
|
|
903
|
+
* break;
|
|
904
|
+
* }
|
|
905
|
+
* ```
|
|
906
|
+
*/
|
|
907
|
+
async processPayment(opts) {
|
|
908
|
+
var _a, _b;
|
|
909
|
+
if (!opts.returnUrl && typeof window !== "undefined") {
|
|
910
|
+
opts = { ...opts, returnUrl: window.location.href };
|
|
911
|
+
}
|
|
912
|
+
const payResult = await this.pay(opts);
|
|
913
|
+
const payment = payResult.payment;
|
|
914
|
+
if (payment.status === "succeeded") {
|
|
915
|
+
return { status: "succeeded", payment, order: payResult.order };
|
|
916
|
+
}
|
|
917
|
+
if (payment.status === "failed" || payment.status === "declined") {
|
|
918
|
+
const msg = ((_a = payment.requireActionData) == null ? void 0 : _a.message) || ((_b = payment.requireActionData) == null ? void 0 : _b.errorCode) || `Payment ${payment.status}`;
|
|
919
|
+
return { status: "failed", error: msg, payment };
|
|
920
|
+
}
|
|
921
|
+
if (payment.requireAction && payment.requireAction !== "none" && payment.requireActionData) {
|
|
922
|
+
return this.handlePaymentAction(payment, opts);
|
|
923
|
+
}
|
|
924
|
+
return this.pollUntilDone(payment.id);
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Resume a payment after the user returns from a redirect (3DS, processor auth, APM).
|
|
928
|
+
*
|
|
929
|
+
* Called automatically by `usePayment()` in React when it detects return-URL params.
|
|
930
|
+
* You can also call it manually in non-React apps.
|
|
931
|
+
*/
|
|
932
|
+
async resumeAfterRedirect(paymentId) {
|
|
933
|
+
return this.pollUntilDone(paymentId);
|
|
934
|
+
}
|
|
684
935
|
// ---------------------------------------------------------------------------
|
|
685
936
|
// Status polling
|
|
686
937
|
// ---------------------------------------------------------------------------
|
|
@@ -711,9 +962,175 @@ var TagadaHeadlessBundle = (() => {
|
|
|
711
962
|
paymentId
|
|
712
963
|
});
|
|
713
964
|
}
|
|
965
|
+
/** Tell the server this action has been seen / is being processed. */
|
|
966
|
+
async markActionProcessed(paymentId) {
|
|
967
|
+
try {
|
|
968
|
+
await this.http.post("/api/v1/payments/require-action/processed", {
|
|
969
|
+
paymentId
|
|
970
|
+
});
|
|
971
|
+
} catch {
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
// ---------------------------------------------------------------------------
|
|
975
|
+
// Internal: action routing
|
|
976
|
+
// ---------------------------------------------------------------------------
|
|
977
|
+
/**
|
|
978
|
+
* Route a payment action to the appropriate handler.
|
|
979
|
+
* Returns `requires_redirect` for browser-level redirects,
|
|
980
|
+
* or attempts 3DS inline and polls to completion.
|
|
981
|
+
*/
|
|
982
|
+
async handlePaymentAction(payment, opts) {
|
|
983
|
+
const actionData = payment.requireActionData;
|
|
984
|
+
await this.markActionProcessed(payment.id);
|
|
985
|
+
const actionType = actionData.type;
|
|
986
|
+
switch (actionType) {
|
|
987
|
+
case "threeds_auth":
|
|
988
|
+
return this.handleThreedsAuth(payment, opts);
|
|
989
|
+
case "processor_auth":
|
|
990
|
+
case "redirect_to_payment":
|
|
991
|
+
return this.extractRedirect(payment, actionData);
|
|
992
|
+
case "redirect":
|
|
993
|
+
return this.extractRedirect(payment, actionData);
|
|
994
|
+
case "error":
|
|
995
|
+
return {
|
|
996
|
+
status: "failed",
|
|
997
|
+
error: actionData.message || actionData.errorCode || "Payment failed",
|
|
998
|
+
payment
|
|
999
|
+
};
|
|
1000
|
+
default:
|
|
1001
|
+
return this.extractRedirect(payment, actionData);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Extract redirect info from actionData.
|
|
1006
|
+
* Works for processor_auth (GET or POST), redirect, and any unknown type with a URL.
|
|
1007
|
+
*/
|
|
1008
|
+
extractRedirect(payment, actionData) {
|
|
1009
|
+
var _a, _b;
|
|
1010
|
+
const redirect = (_a = actionData.metadata) == null ? void 0 : _a.redirect;
|
|
1011
|
+
const url = (redirect == null ? void 0 : redirect.redirectUrl) || actionData.redirectUrl;
|
|
1012
|
+
if (!url) {
|
|
1013
|
+
return {
|
|
1014
|
+
status: "failed",
|
|
1015
|
+
error: `Payment requires action (${actionData.type}) but no redirect URL was provided`,
|
|
1016
|
+
payment
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
return {
|
|
1020
|
+
status: "requires_redirect",
|
|
1021
|
+
redirectUrl: url,
|
|
1022
|
+
method: ((_b = redirect == null ? void 0 : redirect.method) == null ? void 0 : _b.toUpperCase()) || "GET",
|
|
1023
|
+
postData: redirect == null ? void 0 : redirect.data,
|
|
1024
|
+
paymentId: payment.id
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Run a 3DS challenge using @tagadapay/core-js.
|
|
1029
|
+
* If core-js is not installed, falls back to redirect.
|
|
1030
|
+
*/
|
|
1031
|
+
async handleThreedsAuth(payment, opts) {
|
|
1032
|
+
var _a, _b;
|
|
1033
|
+
const session = (_b = (_a = payment.requireActionData) == null ? void 0 : _a.metadata) == null ? void 0 : _b.threedsSession;
|
|
1034
|
+
if (!(session == null ? void 0 : session.externalSessionId)) {
|
|
1035
|
+
return this.extractRedirect(payment, payment.requireActionData);
|
|
1036
|
+
}
|
|
1037
|
+
let coreJs;
|
|
1038
|
+
try {
|
|
1039
|
+
coreJs = await import("@tagadapay/core-js");
|
|
1040
|
+
} catch {
|
|
1041
|
+
if (session.acsChallengeUrl) {
|
|
1042
|
+
return {
|
|
1043
|
+
status: "requires_redirect",
|
|
1044
|
+
redirectUrl: session.acsChallengeUrl,
|
|
1045
|
+
paymentId: payment.id
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
return this.extractRedirect(payment, payment.requireActionData);
|
|
1049
|
+
}
|
|
1050
|
+
try {
|
|
1051
|
+
const manager = new coreJs.ThreedsManager({ provider: "basis_theory" });
|
|
1052
|
+
await manager.initialize({ apiKey: void 0 });
|
|
1053
|
+
await manager.startChallenge({
|
|
1054
|
+
sessionId: session.externalSessionId,
|
|
1055
|
+
acsChallengeUrl: session.acsChallengeUrl,
|
|
1056
|
+
acsTransactionId: session.acsTransID,
|
|
1057
|
+
threeDSVersion: session.messageVersion
|
|
1058
|
+
});
|
|
1059
|
+
await this.completeAfter3ds(payment.id);
|
|
1060
|
+
return this.pollUntilDone(payment.id);
|
|
1061
|
+
} catch {
|
|
1062
|
+
if (session.acsChallengeUrl) {
|
|
1063
|
+
return {
|
|
1064
|
+
status: "requires_redirect",
|
|
1065
|
+
redirectUrl: session.acsChallengeUrl,
|
|
1066
|
+
paymentId: payment.id
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
return {
|
|
1070
|
+
status: "failed",
|
|
1071
|
+
error: "3D Secure authentication failed",
|
|
1072
|
+
payment
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Poll until the payment reaches a terminal status, then return a ProcessPaymentResult.
|
|
1078
|
+
* Also handles new requireAction that may appear during polling.
|
|
1079
|
+
*/
|
|
1080
|
+
async pollUntilDone(paymentId, opts) {
|
|
1081
|
+
const interval = (opts == null ? void 0 : opts.intervalMs) ?? 1500;
|
|
1082
|
+
const timeout = (opts == null ? void 0 : opts.timeoutMs) ?? 6e4;
|
|
1083
|
+
const start = Date.now();
|
|
1084
|
+
let consecutiveErrors = 0;
|
|
1085
|
+
while (Date.now() - start < timeout) {
|
|
1086
|
+
let status;
|
|
1087
|
+
try {
|
|
1088
|
+
status = await this.getPaymentStatus(paymentId);
|
|
1089
|
+
consecutiveErrors = 0;
|
|
1090
|
+
} catch {
|
|
1091
|
+
consecutiveErrors++;
|
|
1092
|
+
if (consecutiveErrors >= 3) {
|
|
1093
|
+
return { status: "failed", error: "Payment verification failed due to network errors" };
|
|
1094
|
+
}
|
|
1095
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
1098
|
+
if (status.status === "succeeded" || status.status === "pending" && status.subStatus === "authorized") {
|
|
1099
|
+
return { status: "succeeded", payment: status, order: status.order };
|
|
1100
|
+
}
|
|
1101
|
+
if (status.status === "failed" || status.status === "declined") {
|
|
1102
|
+
return { status: "failed", error: status.status, payment: status };
|
|
1103
|
+
}
|
|
1104
|
+
if (status.requireAction && status.requireAction !== "none" && status.requireActionData && !status.requireActionData.processed) {
|
|
1105
|
+
return this.handlePaymentAction(
|
|
1106
|
+
status,
|
|
1107
|
+
{}
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
1111
|
+
}
|
|
1112
|
+
return { status: "pending", paymentId };
|
|
1113
|
+
}
|
|
714
1114
|
// ---------------------------------------------------------------------------
|
|
715
1115
|
// Internal helpers
|
|
716
1116
|
// ---------------------------------------------------------------------------
|
|
1117
|
+
/**
|
|
1118
|
+
* Read paymentSetupConfig from the Tagada routing injection.
|
|
1119
|
+
* Only available when running on Tagada infra (not on external hosting like Vercel).
|
|
1120
|
+
*/
|
|
1121
|
+
readInjectedStepConfig() {
|
|
1122
|
+
if (typeof window === "undefined")
|
|
1123
|
+
return null;
|
|
1124
|
+
const raw = window.__TGD_STEP_CONFIG__;
|
|
1125
|
+
if (!raw)
|
|
1126
|
+
return null;
|
|
1127
|
+
try {
|
|
1128
|
+
const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
1129
|
+
return (parsed == null ? void 0 : parsed.paymentSetupConfig) ?? null;
|
|
1130
|
+
} catch {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
717
1134
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
718
1135
|
async loadCoreJs() {
|
|
719
1136
|
try {
|