@tagadapay/headless-sdk 1.9.0 → 1.12.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 +220 -216
- package/dist/core/modules/payment.d.ts +35 -0
- package/dist/core/modules/payment.d.ts.map +1 -1
- package/dist/core/modules/payment.js +70 -2
- package/dist/core/modules/payment.js.map +1 -1
- package/dist/react/hooks/usePayment.d.ts +4 -0
- package/dist/react/hooks/usePayment.d.ts.map +1 -1
- package/dist/react/hooks/usePayment.js.map +1 -1
- package/dist/tagada-headless-react.min.js +2 -2
- package/dist/tagada-headless-react.min.js.map +3 -3
- package/dist/tagada-headless.js +66 -4
- package/dist/tagada-headless.min.js +2 -2
- package/dist/tagada-headless.min.js.map +3 -3
- package/package.json +79 -79
package/README.md
CHANGED
|
@@ -1,216 +1,220 @@
|
|
|
1
|
-
# @tagadapay/headless-sdk
|
|
2
|
-
|
|
3
|
-
Use TagadaPay as a **headless checkout and CRM backend** from any website — React, Vue, vanilla JS, or plain HTML. Zero runtime dependencies in core.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
await
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
|
178
|
-
|
|
179
|
-
| `tagada.
|
|
180
|
-
| `tagada.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
- **
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
- **
|
|
205
|
-
- **
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
1
|
+
# @tagadapay/headless-sdk
|
|
2
|
+
|
|
3
|
+
Use TagadaPay as a **headless checkout and CRM backend** from any website — React, Vue, vanilla JS, or plain HTML. Zero runtime dependencies in core.
|
|
4
|
+
|
|
5
|
+
This package is for when **TagadaPay manages the cart**. If you already have the order (Medusa, WooCommerce, a custom order API), do not use this SDK — tokenize with `@tagadapay/core-js` and charge with Node `payments.process()`. See [Choose how you accept payments](https://docs.tagada.io/developer-tools/payments/choose-your-integration). Canonical example: [headless-vanilla](https://github.com/TagadaPay/examples/tree/main/headless-vanilla).
|
|
6
|
+
|
|
7
|
+
`processPayment()` pays a checkout **session**. Server `payments.process()` charges a vaulted **instrument**. They are not the same call.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @tagadapay/headless-sdk
|
|
13
|
+
# Optional: for card tokenization
|
|
14
|
+
npm install @tagadapay/core-js
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start (Vanilla JS)
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { createHeadlessClient } from '@tagadapay/headless-sdk';
|
|
21
|
+
|
|
22
|
+
const tagada = createHeadlessClient({
|
|
23
|
+
storeId: 'store_abc123',
|
|
24
|
+
environment: 'production',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Load checkout session
|
|
28
|
+
const session = await tagada.checkout.loadSession(checkoutToken, sessionToken);
|
|
29
|
+
|
|
30
|
+
// Set customer info (stored in memory, sent with address)
|
|
31
|
+
await tagada.checkout.updateCustomer(session.id, {
|
|
32
|
+
email: 'john@example.com',
|
|
33
|
+
firstName: 'John',
|
|
34
|
+
lastName: 'Doe',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Set address — customer fields are included automatically
|
|
38
|
+
await tagada.checkout.updateAddress(session.id, {
|
|
39
|
+
shippingAddress: { line1: '10 Rue de la Paix', city: 'Paris', postalCode: '75002', country: 'FR' },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Tokenize card + pay with automatic 3DS / redirect handling
|
|
43
|
+
const { tagadaToken } = await tagada.payment.tokenizeCard({
|
|
44
|
+
cardNumber: '4242424242424242',
|
|
45
|
+
expiryDate: '12/28',
|
|
46
|
+
cvc: '123',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const result = await tagada.payment.processPayment({
|
|
50
|
+
checkoutSessionId: session.id,
|
|
51
|
+
tagadaToken,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (result.status === 'succeeded') {
|
|
55
|
+
console.log('Payment succeeded!', result.order);
|
|
56
|
+
} else if (result.status === 'requires_redirect') {
|
|
57
|
+
// SDK handles this automatically in React; in vanilla JS, redirect manually:
|
|
58
|
+
window.location.href = result.redirectUrl;
|
|
59
|
+
} else if (result.status === 'failed') {
|
|
60
|
+
console.error(result.error);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Quick Start (React)
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { TagadaHeadlessProvider, useCheckout, usePayment } from '@tagadapay/headless-sdk/react';
|
|
68
|
+
|
|
69
|
+
function App() {
|
|
70
|
+
return (
|
|
71
|
+
<TagadaHeadlessProvider storeId="store_abc123" environment="production">
|
|
72
|
+
<CheckoutPage />
|
|
73
|
+
</TagadaHeadlessProvider>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function CheckoutPage() {
|
|
78
|
+
const { session, updateCustomer, updateAddress } = useCheckout(checkoutToken, sessionToken);
|
|
79
|
+
const { processPayment, isProcessing, tokenizeCard } = usePayment({
|
|
80
|
+
onPaymentSuccess: (result) => router.push('/thank-you'),
|
|
81
|
+
onPaymentFailed: (result) => alert(result.error),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const handlePay = async () => {
|
|
85
|
+
// Customer + address (single atomic API call under the hood)
|
|
86
|
+
await updateCustomer({ email, firstName, lastName });
|
|
87
|
+
await updateAddress({ shippingAddress: { line1, city, postalCode, country } });
|
|
88
|
+
|
|
89
|
+
// Tokenize + pay — 3DS redirects and polling are handled automatically
|
|
90
|
+
const { tagadaToken } = await tokenizeCard({ cardNumber, expiryDate, cvc });
|
|
91
|
+
await processPayment({ checkoutSessionId: session.id, tagadaToken });
|
|
92
|
+
// onPaymentSuccess / onPaymentFailed callbacks fire automatically
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return <button onClick={handlePay} disabled={isProcessing}>Pay Now</button>;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Payment Flow
|
|
100
|
+
|
|
101
|
+
`processPayment()` handles the full payment lifecycle automatically:
|
|
102
|
+
|
|
103
|
+
1. **Tokenize** → creates a payment instrument from the card
|
|
104
|
+
2. **Pay** → submits payment to the processor
|
|
105
|
+
3. **3DS / Redirect** → if the bank requires authentication, the SDK redirects the user and resumes automatically on return
|
|
106
|
+
4. **Poll** → waits for async processing to complete
|
|
107
|
+
5. **Result** → returns a typed `ProcessPaymentResult`
|
|
108
|
+
|
|
109
|
+
The return type is a discriminated union:
|
|
110
|
+
|
|
111
|
+
| Status | Meaning |
|
|
112
|
+
|--------|---------|
|
|
113
|
+
| `succeeded` | Payment confirmed. Show success screen. |
|
|
114
|
+
| `requires_redirect` | User must visit a URL (3DS, bank auth). Auto-handled in React. |
|
|
115
|
+
| `failed` | Payment declined or errored. `result.error` has details. |
|
|
116
|
+
| `pending` | Still processing (rare — polling timed out). |
|
|
117
|
+
|
|
118
|
+
## Address Handling
|
|
119
|
+
|
|
120
|
+
The SDK handles both **shipped** and **digital** products:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// Shipped product — billing defaults to shipping
|
|
124
|
+
await tagada.checkout.updateAddress(sessionId, {
|
|
125
|
+
shippingAddress: { line1: '...', city: '...', country: 'FR' },
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Digital product — billing only, no shipping needed
|
|
129
|
+
await tagada.checkout.updateAddress(sessionId, {
|
|
130
|
+
billingAddress: { country: 'FR' },
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Separate billing and shipping
|
|
134
|
+
await tagada.checkout.updateAddress(sessionId, {
|
|
135
|
+
shippingAddress: { line1: '...', city: '...', country: 'FR' },
|
|
136
|
+
billingAddress: { line1: '...', city: '...', country: 'DE' },
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Or use the combined method
|
|
140
|
+
await tagada.checkout.updateCustomerAndAddress(sessionId, {
|
|
141
|
+
customer: { email: 'john@example.com', firstName: 'John', lastName: 'Doe' },
|
|
142
|
+
shippingAddress: { line1: '...', city: '...', country: 'FR' },
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Funnel Navigation
|
|
147
|
+
|
|
148
|
+
Send a shopper into a TagadaPay funnel using the cart from the loaded checkout
|
|
149
|
+
session — same flow as the WooCommerce / Shopify plugins, in one call.
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
const tagada = createHeadlessClient({
|
|
153
|
+
storeId: 'store_abc123',
|
|
154
|
+
accountId: 'acc_abc123', // required for funnel.navigate()
|
|
155
|
+
environment: 'production',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Load (or create) a checkout session so the SDK has a cart.
|
|
159
|
+
await tagada.checkout.loadSession(checkoutToken, sessionToken);
|
|
160
|
+
|
|
161
|
+
// Get a funnel URL — caller decides how to redirect (SPA-friendly).
|
|
162
|
+
const { url } = await tagada.funnel.navigate({
|
|
163
|
+
funnelId: 'fnlv2_abc123',
|
|
164
|
+
stepId: 'step_abc123',
|
|
165
|
+
returnUrl: 'https://merchant.com/thank-you',
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
window.location.assign(url);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`funnel.navigate()` bootstraps an anonymous CMS session on first call and
|
|
172
|
+
caches it for subsequent calls in the same page. The cart, currency, customer
|
|
173
|
+
email, and promotion code are sourced from the loaded `CheckoutSession`.
|
|
174
|
+
|
|
175
|
+
## Modules
|
|
176
|
+
|
|
177
|
+
| Module | What it does |
|
|
178
|
+
|--------|-------------|
|
|
179
|
+
| `tagada.checkout` | Session management — load, update cart, promo codes, shipping |
|
|
180
|
+
| `tagada.payment` | Full payment lifecycle — APMs, tokenize, 3DS, pay, poll, express checkout |
|
|
181
|
+
| `tagada.offers` | Upsells & order bumps — preview, pay, post-purchase accept/decline |
|
|
182
|
+
| `tagada.customer` | CRM data — profile, orders, subscriptions |
|
|
183
|
+
| `tagada.catalog` | Product browsing — list products, variants, prices |
|
|
184
|
+
| `tagada.funnel` | Funnel navigation — bootstrap session and redirect into a funnel step |
|
|
185
|
+
|
|
186
|
+
## CDN Usage
|
|
187
|
+
|
|
188
|
+
```html
|
|
189
|
+
<script src="https://cdn.jsdelivr.net/npm/@tagadapay/headless-sdk/dist/tagada-headless.min.js"></script>
|
|
190
|
+
<script>
|
|
191
|
+
const tagada = TagadaHeadless.create({ storeId: 'store_abc123' });
|
|
192
|
+
</script>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Example
|
|
196
|
+
|
|
197
|
+
A complete React checkout app is available as a live demo and open-source reference:
|
|
198
|
+
|
|
199
|
+
- **Live demo**: [headless-react-eta.vercel.app](https://headless-react-eta.vercel.app)
|
|
200
|
+
- **Source code**: [github.com/TagadaPay/examples/tree/main/headless-react](https://github.com/TagadaPay/examples/tree/main/headless-react)
|
|
201
|
+
|
|
202
|
+
## Architecture
|
|
203
|
+
|
|
204
|
+
- **Zero dependencies** in core — native `fetch`, no axios
|
|
205
|
+
- **Tree-shakeable ESM** — unused modules are eliminated by bundlers
|
|
206
|
+
- **Framework-agnostic core** + thin React hooks layer
|
|
207
|
+
- **TypeScript first** — full type coverage, JSDoc on every method
|
|
208
|
+
- **Optional `@tagadapay/core-js`** peer — only needed for client-side card tokenization
|
|
209
|
+
- **Automatic 3DS** — handles redirects, iframe challenges, and polling out of the box
|
|
210
|
+
|
|
211
|
+
## Package Exports
|
|
212
|
+
|
|
213
|
+
| Import path | What you get |
|
|
214
|
+
|------------|--------------|
|
|
215
|
+
| `@tagadapay/headless-sdk` | Core client + all modules |
|
|
216
|
+
| `@tagadapay/headless-sdk/react` | React hooks + provider |
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
MIT
|
|
@@ -30,6 +30,12 @@ export declare class PaymentModule {
|
|
|
30
30
|
private readonly environment;
|
|
31
31
|
/** Used to persist wallet contacts onto the session before charging. */
|
|
32
32
|
private readonly checkout?;
|
|
33
|
+
/**
|
|
34
|
+
* Last payment setup seen by `getPaymentSetup` — lets `processApplePay`
|
|
35
|
+
* resolve the domain's Apple Pay registration (BYOK / platform-integrator)
|
|
36
|
+
* without a second network round-trip inside Apple's authorization window.
|
|
37
|
+
*/
|
|
38
|
+
private lastPaymentSetup;
|
|
33
39
|
constructor(http: HttpClient, storeId: string, environment: Environment,
|
|
34
40
|
/** Used to persist wallet contacts onto the session before charging. */
|
|
35
41
|
checkout?: CheckoutModule | undefined);
|
|
@@ -167,7 +173,36 @@ export declare class PaymentModule {
|
|
|
167
173
|
shippingContact?: ApplePayContact | null;
|
|
168
174
|
paymentFlowId?: string;
|
|
169
175
|
processorId?: string;
|
|
176
|
+
/**
|
|
177
|
+
* Basis Theory Apple Pay registration the checkout domain is bound to.
|
|
178
|
+
* Required when the domain runs merchant-scoped Apple Pay — BYOK or the
|
|
179
|
+
* platform-integrator (PPIID) system — because Apple encrypts the token
|
|
180
|
+
* to that registration's certificate. Resolved automatically from the
|
|
181
|
+
* payment setup (`applePayByok` domain map) when omitted; pass it only to
|
|
182
|
+
* override that resolution.
|
|
183
|
+
*/
|
|
184
|
+
merchantRegistrationId?: string;
|
|
170
185
|
}): Promise<PayResult>;
|
|
186
|
+
/**
|
|
187
|
+
* Resolve the Apple Pay registration binding for the domain the page is
|
|
188
|
+
* served from. Both merchant-certificate (BYOK) and platform-integrator
|
|
189
|
+
* (PPIID) Apple Pay attach a domain-keyed `applePayByok` map to the
|
|
190
|
+
* `apple_pay` method's metadata; a hit means every Basis Theory call —
|
|
191
|
+
* merchant session AND tokenization — must carry that
|
|
192
|
+
* `merchant_registration_id`. A miss (the default) means legacy platform
|
|
193
|
+
* Apple Pay: omit the id and behavior is unchanged.
|
|
194
|
+
*
|
|
195
|
+
* Sources, in order: the Tagada routing injection, the last payment setup
|
|
196
|
+
* seen by `getPaymentSetup`, then one API fetch when a `checkoutSessionId`
|
|
197
|
+
* is given and no setup was ever loaded.
|
|
198
|
+
*
|
|
199
|
+
* Use `displayName` (when present) as the `display_name` of the Basis
|
|
200
|
+
* Theory merchant session.
|
|
201
|
+
*/
|
|
202
|
+
resolveApplePayRegistration(checkoutSessionId?: string): Promise<{
|
|
203
|
+
merchantRegistrationId: string;
|
|
204
|
+
displayName?: string;
|
|
205
|
+
} | null>;
|
|
171
206
|
/**
|
|
172
207
|
* Process a Google Pay payment.
|
|
173
208
|
* Tokenizes the Google Pay token and processes payment in one call.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payment.d.ts","sourceRoot":"","sources":["../../../src/core/modules/payment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAEV,WAAW,EACX,kBAAkB,EAElB,cAAc,EACd,WAAW,EACX,SAAS,EACT,UAAU,EACV,gBAAgB,EAChB,uBAAuB,EACvB,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,aAAa;
|
|
1
|
+
{"version":3,"file":"payment.d.ts","sourceRoot":"","sources":["../../../src/core/modules/payment.ts"],"names":[],"mappings":"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"}
|
|
@@ -29,6 +29,12 @@ export class PaymentModule {
|
|
|
29
29
|
this.storeId = storeId;
|
|
30
30
|
this.environment = environment;
|
|
31
31
|
this.checkout = checkout;
|
|
32
|
+
/**
|
|
33
|
+
* Last payment setup seen by `getPaymentSetup` — lets `processApplePay`
|
|
34
|
+
* resolve the domain's Apple Pay registration (BYOK / platform-integrator)
|
|
35
|
+
* without a second network round-trip inside Apple's authorization window.
|
|
36
|
+
*/
|
|
37
|
+
this.lastPaymentSetup = null;
|
|
32
38
|
}
|
|
33
39
|
// ---------------------------------------------------------------------------
|
|
34
40
|
// Discovery: what payment methods are available?
|
|
@@ -45,10 +51,12 @@ export class PaymentModule {
|
|
|
45
51
|
*/
|
|
46
52
|
async getPaymentSetup(checkoutSessionId, opts) {
|
|
47
53
|
if (opts?.paymentSetupConfig) {
|
|
54
|
+
this.lastPaymentSetup = opts.paymentSetupConfig;
|
|
48
55
|
return opts.paymentSetupConfig;
|
|
49
56
|
}
|
|
50
57
|
const injected = this.readInjectedStepConfig();
|
|
51
58
|
if (injected) {
|
|
59
|
+
this.lastPaymentSetup = injected;
|
|
52
60
|
return injected;
|
|
53
61
|
}
|
|
54
62
|
const methods = await this.http.get('/api/v1/payment-methods', { params: { checkoutSessionId } });
|
|
@@ -57,6 +65,7 @@ export class PaymentModule {
|
|
|
57
65
|
const key = m.provider ? `${m.method}:${m.provider}` : m.method;
|
|
58
66
|
config[key] = m;
|
|
59
67
|
}
|
|
68
|
+
this.lastPaymentSetup = config;
|
|
60
69
|
return config;
|
|
61
70
|
}
|
|
62
71
|
/**
|
|
@@ -336,10 +345,17 @@ export class PaymentModule {
|
|
|
336
345
|
firstName: opts.shippingContact?.givenName ?? opts.billingContact?.givenName,
|
|
337
346
|
lastName: opts.shippingContact?.familyName ?? opts.billingContact?.familyName,
|
|
338
347
|
});
|
|
348
|
+
const merchantRegistrationId = opts.merchantRegistrationId ??
|
|
349
|
+
(await this.resolveApplePayRegistration(opts.checkoutSessionId))?.merchantRegistrationId;
|
|
339
350
|
const coreJs = await this.loadCoreJs();
|
|
340
351
|
const tokenizer = new coreJs.Tokenizer({ environment: this.environment });
|
|
341
352
|
await tokenizer.initialize();
|
|
342
|
-
|
|
353
|
+
// The tokenizer returns the normalized Basis Theory response — it carries
|
|
354
|
+
// no `tagadaToken` field. Wrap it into the standard base64 TagadaToken the
|
|
355
|
+
// pay endpoint expects (destructuring `{ tagadaToken }` here used to yield
|
|
356
|
+
// undefined and silently push the charge down the instrument-less path).
|
|
357
|
+
const rawToken = await tokenizer.tokenizeApplePay(opts.applePayToken, merchantRegistrationId);
|
|
358
|
+
const tagadaToken = btoa(JSON.stringify(coreJs.createTagadaDigitalWalletToken(rawToken, 'apple_pay')));
|
|
343
359
|
return this.pay({
|
|
344
360
|
checkoutSessionId: opts.checkoutSessionId,
|
|
345
361
|
tagadaToken,
|
|
@@ -349,6 +365,55 @@ export class PaymentModule {
|
|
|
349
365
|
isExpress: true,
|
|
350
366
|
});
|
|
351
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Resolve the Apple Pay registration binding for the domain the page is
|
|
370
|
+
* served from. Both merchant-certificate (BYOK) and platform-integrator
|
|
371
|
+
* (PPIID) Apple Pay attach a domain-keyed `applePayByok` map to the
|
|
372
|
+
* `apple_pay` method's metadata; a hit means every Basis Theory call —
|
|
373
|
+
* merchant session AND tokenization — must carry that
|
|
374
|
+
* `merchant_registration_id`. A miss (the default) means legacy platform
|
|
375
|
+
* Apple Pay: omit the id and behavior is unchanged.
|
|
376
|
+
*
|
|
377
|
+
* Sources, in order: the Tagada routing injection, the last payment setup
|
|
378
|
+
* seen by `getPaymentSetup`, then one API fetch when a `checkoutSessionId`
|
|
379
|
+
* is given and no setup was ever loaded.
|
|
380
|
+
*
|
|
381
|
+
* Use `displayName` (when present) as the `display_name` of the Basis
|
|
382
|
+
* Theory merchant session.
|
|
383
|
+
*/
|
|
384
|
+
async resolveApplePayRegistration(checkoutSessionId) {
|
|
385
|
+
if (typeof window === 'undefined')
|
|
386
|
+
return null;
|
|
387
|
+
let host = window.location.hostname.toLowerCase().split(':')[0];
|
|
388
|
+
if (host.startsWith('www.'))
|
|
389
|
+
host = host.slice(4);
|
|
390
|
+
const fromConfig = (setup) => {
|
|
391
|
+
for (const entry of Object.values(setup ?? {})) {
|
|
392
|
+
const map = entry?.metadata?.applePayByok;
|
|
393
|
+
if (!map || typeof map !== 'object')
|
|
394
|
+
continue;
|
|
395
|
+
const hit = map[host];
|
|
396
|
+
if (hit?.merchantRegistrationId) {
|
|
397
|
+
return { merchantRegistrationId: hit.merchantRegistrationId, displayName: hit.displayName };
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return null;
|
|
401
|
+
};
|
|
402
|
+
const local = fromConfig(this.readInjectedStepConfig()) ?? fromConfig(this.lastPaymentSetup);
|
|
403
|
+
if (local)
|
|
404
|
+
return local;
|
|
405
|
+
// A setup we already hold that carries no binding is authoritative — don't
|
|
406
|
+
// burn a network round-trip (inside Apple's authorization window) to
|
|
407
|
+
// re-learn that this is a legacy platform domain.
|
|
408
|
+
if (this.lastPaymentSetup || this.readInjectedStepConfig() || !checkoutSessionId)
|
|
409
|
+
return null;
|
|
410
|
+
try {
|
|
411
|
+
return fromConfig(await this.getPaymentSetup(checkoutSessionId));
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
352
417
|
/**
|
|
353
418
|
* Process a Google Pay payment.
|
|
354
419
|
* Tokenizes the Google Pay token and processes payment in one call.
|
|
@@ -368,7 +433,10 @@ export class PaymentModule {
|
|
|
368
433
|
const coreJs = await this.loadCoreJs();
|
|
369
434
|
const tokenizer = new coreJs.Tokenizer({ environment: this.environment });
|
|
370
435
|
await tokenizer.initialize();
|
|
371
|
-
|
|
436
|
+
// Same wrapping as Apple Pay above — the tokenizer's normalized response
|
|
437
|
+
// has no `tagadaToken` field.
|
|
438
|
+
const rawToken = await tokenizer.tokenizeGooglePay(opts.googlePayToken);
|
|
439
|
+
const tagadaToken = btoa(JSON.stringify(coreJs.createTagadaDigitalWalletToken(rawToken, 'google_pay')));
|
|
372
440
|
return this.pay({
|
|
373
441
|
checkoutSessionId: opts.checkoutSessionId,
|
|
374
442
|
tagadaToken,
|