@cogenta/commerce 0.2.0 → 0.4.3

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 (95) hide show
  1. package/dist/admin/permissions.d.ts +8 -0
  2. package/dist/admin/permissions.d.ts.map +1 -1
  3. package/dist/admin/permissions.js +1 -0
  4. package/dist/admin/permissions.js.map +1 -1
  5. package/dist/admin/router.d.ts +47 -5
  6. package/dist/admin/router.d.ts.map +1 -1
  7. package/dist/admin/router.js +942 -24
  8. package/dist/admin/router.js.map +1 -1
  9. package/dist/cart/store.d.ts +23 -0
  10. package/dist/cart/store.d.ts.map +1 -1
  11. package/dist/cart/store.js +49 -2
  12. package/dist/cart/store.js.map +1 -1
  13. package/dist/catalog/csv.d.ts +29 -0
  14. package/dist/catalog/csv.d.ts.map +1 -0
  15. package/dist/catalog/csv.js +396 -0
  16. package/dist/catalog/csv.js.map +1 -0
  17. package/dist/catalog/store.d.ts +24 -4
  18. package/dist/catalog/store.d.ts.map +1 -1
  19. package/dist/catalog/store.js +238 -11
  20. package/dist/catalog/store.js.map +1 -1
  21. package/dist/catalog/types.d.ts +92 -0
  22. package/dist/catalog/types.d.ts.map +1 -1
  23. package/dist/catalog/types.js +20 -0
  24. package/dist/catalog/types.js.map +1 -1
  25. package/dist/coupon/store.d.ts +63 -5
  26. package/dist/coupon/store.d.ts.map +1 -1
  27. package/dist/coupon/store.js +140 -7
  28. package/dist/coupon/store.js.map +1 -1
  29. package/dist/customer/store.d.ts +17 -0
  30. package/dist/customer/store.d.ts.map +1 -1
  31. package/dist/customer/store.js +26 -0
  32. package/dist/customer/store.js.map +1 -1
  33. package/dist/index.d.ts +18 -8
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +9 -3
  36. package/dist/index.js.map +1 -1
  37. package/dist/invoice/credit-note.d.ts +45 -0
  38. package/dist/invoice/credit-note.d.ts.map +1 -0
  39. package/dist/invoice/credit-note.js +109 -0
  40. package/dist/invoice/credit-note.js.map +1 -0
  41. package/dist/invoice/sequence.d.ts +21 -0
  42. package/dist/invoice/sequence.d.ts.map +1 -0
  43. package/dist/invoice/sequence.js +64 -0
  44. package/dist/invoice/sequence.js.map +1 -0
  45. package/dist/invoice/store.d.ts +23 -1
  46. package/dist/invoice/store.d.ts.map +1 -1
  47. package/dist/invoice/store.js +23 -56
  48. package/dist/invoice/store.js.map +1 -1
  49. package/dist/order/csv.d.ts +24 -0
  50. package/dist/order/csv.d.ts.map +1 -0
  51. package/dist/order/csv.js +42 -0
  52. package/dist/order/csv.js.map +1 -0
  53. package/dist/order/notify.d.ts +65 -0
  54. package/dist/order/notify.d.ts.map +1 -0
  55. package/dist/order/notify.js +180 -0
  56. package/dist/order/notify.js.map +1 -0
  57. package/dist/order/store.d.ts +58 -1
  58. package/dist/order/store.d.ts.map +1 -1
  59. package/dist/order/store.js +145 -10
  60. package/dist/order/store.js.map +1 -1
  61. package/dist/order/types.d.ts +35 -1
  62. package/dist/order/types.d.ts.map +1 -1
  63. package/dist/order/types.js +2 -0
  64. package/dist/order/types.js.map +1 -1
  65. package/dist/payment/paypal.d.ts +9 -0
  66. package/dist/payment/paypal.d.ts.map +1 -0
  67. package/dist/payment/paypal.js +583 -0
  68. package/dist/payment/paypal.js.map +1 -0
  69. package/dist/payment/registry.d.ts +15 -6
  70. package/dist/payment/registry.d.ts.map +1 -1
  71. package/dist/payment/registry.js +17 -6
  72. package/dist/payment/registry.js.map +1 -1
  73. package/dist/payment/types.d.ts +6 -0
  74. package/dist/payment/types.d.ts.map +1 -1
  75. package/dist/shipping/store.d.ts +29 -3
  76. package/dist/shipping/store.d.ts.map +1 -1
  77. package/dist/shipping/store.js +68 -4
  78. package/dist/shipping/store.js.map +1 -1
  79. package/dist/subscription/renewal-notifier.d.ts +17 -0
  80. package/dist/subscription/renewal-notifier.d.ts.map +1 -0
  81. package/dist/subscription/renewal-notifier.js +33 -0
  82. package/dist/subscription/renewal-notifier.js.map +1 -0
  83. package/dist/subscription/store.d.ts +126 -3
  84. package/dist/subscription/store.d.ts.map +1 -1
  85. package/dist/subscription/store.js +348 -8
  86. package/dist/subscription/store.js.map +1 -1
  87. package/dist/tables.d.ts +17 -0
  88. package/dist/tables.d.ts.map +1 -1
  89. package/dist/tables.js +273 -0
  90. package/dist/tables.js.map +1 -1
  91. package/dist/tax/store.d.ts +20 -0
  92. package/dist/tax/store.d.ts.map +1 -1
  93. package/dist/tax/store.js +51 -7
  94. package/dist/tax/store.js.map +1 -1
  95. package/package.json +3 -2
@@ -0,0 +1,583 @@
1
+ import { createVerify } from 'node:crypto';
2
+ import { crc32 } from 'node:zlib';
3
+ import { CogentaError } from '@cogenta/core';
4
+ import { minorUnitExponent } from '../money.js';
5
+ /**
6
+ * A second real payment driver, proving the same registered-interface
7
+ * extensibility as `@cogenta/core`'s cache/queue/storage drivers: a shop is
8
+ * not stuck choosing between Stripe and bank transfer — a third gateway
9
+ * plugs into the exact same `PaymentDriver` interface (`start`/`fetch`/
10
+ * `refund`/`verifyEvent`), registers itself once (`registry.ts`), and the
11
+ * admin payment screen lists it without any change on its side.
12
+ *
13
+ * Written against PayPal's REST API (Orders v2 + Payments v2) with `fetch`,
14
+ * the same choice `stripe.ts` made and for the same reason (R9): the `paypal-
15
+ * server-sdk` package pulls a full generated client for four calls this
16
+ * driver makes.
17
+ *
18
+ * Two real differences from Stripe shape this file:
19
+ *
20
+ * - PayPal has no long-lived secret key. It authenticates with an OAuth2
21
+ * client-credentials exchange (`clientId`/`clientSecret` → a bearer token
22
+ * that expires), so this driver caches and refreshes a token rather than
23
+ * sending one credential on every call.
24
+ * - There is no single verb that both starts and settles a payment. An order
25
+ * is *created*, the shopper *approves* it on PayPal's site, and someone
26
+ * must *capture* it before money moves. This interface has no capture verb
27
+ * of its own, so `fetch()` — the same call the order store already makes to
28
+ * poll a payment after a shopper returns from a redirect — captures an
29
+ * approved order the moment it sees one. A second `fetch()` (or the
30
+ * confirming webhook) sees `COMPLETED` and does nothing further, because
31
+ * PayPal itself refuses a second capture on an already-captured order.
32
+ */
33
+ /**
34
+ * PayPal's live REST hostname. Unlike Stripe, sandbox and live are different
35
+ * hosts (`api-m.sandbox.paypal.com` vs `api-m.paypal.com`) rather than a
36
+ * prefix on the credential, so — exactly like the local HTTP stub `stripe.ts`
37
+ * tests point `apiBaseUrl` at — a site testing against PayPal's sandbox sets
38
+ * `payment.apiBaseUrl` explicitly. This default is the production host only.
39
+ */
40
+ const DEFAULT_API_BASE_URL = 'https://api-m.paypal.com';
41
+ /** No official window is documented; this matches Stripe's, a defensible middle ground against replay. */
42
+ const SIGNATURE_FRESHNESS_WINDOW_SECONDS = 5 * 60;
43
+ const REQUEST_TIMEOUT_MS = 20_000;
44
+ const AVAILABILITY_TIMEOUT_MS = 3_000;
45
+ /** A cached OAuth2 token is refreshed this long before it actually expires. */
46
+ const TOKEN_REFRESH_SKEW_MS = 30_000;
47
+ /**
48
+ * `GET /v2/checkout/orders/{id}` statuses, mapped onto this project's six.
49
+ * `APPROVED` is deliberately absent: `fetch()` never returns it verbatim, it
50
+ * always attempts a capture first and maps what capture produces instead —
51
+ * see the file comment.
52
+ */
53
+ const ORDER_STATUS_MAP = {
54
+ CREATED: 'pending',
55
+ SAVED: 'pending',
56
+ PAYER_ACTION_REQUIRED: 'pending',
57
+ VOIDED: 'cancelled',
58
+ COMPLETED: 'paid',
59
+ };
60
+ /** `POST /v2/payments/captures/{id}/refund` statuses, mapped onto the three a `DriverRefundResult` carries. */
61
+ const REFUND_STATUS_MAP = {
62
+ COMPLETED: 'refunded',
63
+ PENDING: 'pending',
64
+ CANCELLED: 'failed',
65
+ FAILED: 'failed',
66
+ };
67
+ /**
68
+ * The inbound webhook events this driver understands — every one a statement
69
+ * about a *capture*, PayPal's event vocabulary for money actually moving.
70
+ * `CHECKOUT.ORDER.APPROVED` is not here on purpose: approval is not payment,
71
+ * and mapping it to anything would be exactly the guess Stripe's driver
72
+ * refuses to make for an unrecognised intent status.
73
+ */
74
+ const EVENT_TYPE_STATUS = {
75
+ 'PAYMENT.CAPTURE.COMPLETED': 'paid',
76
+ 'PAYMENT.CAPTURE.DENIED': 'failed',
77
+ 'PAYMENT.CAPTURE.REFUNDED': 'refunded',
78
+ 'PAYMENT.CAPTURE.REVERSED': 'refunded',
79
+ };
80
+ function asRecord(value) {
81
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
82
+ ? value
83
+ : null;
84
+ }
85
+ function asArray(value) {
86
+ return Array.isArray(value) ? value : null;
87
+ }
88
+ function readString(source, key) {
89
+ const value = source?.[key];
90
+ return typeof value === 'string' && value !== '' ? value : null;
91
+ }
92
+ function readPath(source, ...path) {
93
+ let current = source;
94
+ for (const key of path.slice(0, -1)) {
95
+ current = asRecord(current?.[key]);
96
+ }
97
+ const last = path[path.length - 1];
98
+ return last === undefined ? null : readString(current, last);
99
+ }
100
+ function safeJsonParse(text) {
101
+ try {
102
+ return JSON.parse(text);
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ }
108
+ /** PayPal's amount is a decimal string in major units, unlike this project's integer minor units everywhere else. */
109
+ function toDecimalAmount(amountMinor, currency) {
110
+ const exponent = minorUnitExponent(currency);
111
+ return (amountMinor / 10 ** exponent).toFixed(exponent);
112
+ }
113
+ /** The inverse of `toDecimalAmount`. Null for anything that is not a finite decimal, never a guess. */
114
+ function fromDecimalAmount(value, currency) {
115
+ const parsed = Number(value);
116
+ if (!Number.isFinite(parsed))
117
+ return null;
118
+ const exponent = minorUnitExponent(currency);
119
+ return Math.round(parsed * 10 ** exponent);
120
+ }
121
+ /** The `rel: "approve"` link is what the shopper is redirected to. Absent once an order has been captured. */
122
+ function approveLink(order) {
123
+ for (const entry of asArray(order.links) ?? []) {
124
+ const link = asRecord(entry);
125
+ if (link !== null && readString(link, 'rel') === 'approve')
126
+ return readString(link, 'href');
127
+ }
128
+ return null;
129
+ }
130
+ /** The capture id under a captured order — needed to refund, since a refund targets a capture, not an order. */
131
+ function findCaptureId(order) {
132
+ const unit = asArray(order.purchase_units)?.[0];
133
+ const captures = asArray(asRecord(asRecord(unit)?.payments)?.captures);
134
+ const capture = asRecord(captures?.[0]);
135
+ return readString(capture, 'id');
136
+ }
137
+ function paymentFailed(message, hint, details, cause) {
138
+ return new CogentaError({
139
+ code: 'COMMERCE_PAYMENT_FAILED',
140
+ message,
141
+ hint,
142
+ details,
143
+ ...(cause === undefined ? {} : { cause }),
144
+ });
145
+ }
146
+ function signatureInvalid(reason) {
147
+ return new CogentaError({
148
+ code: 'COMMERCE_PAYMENT_SIGNATURE_INVALID',
149
+ message: `The PayPal webhook signature was refused: ${reason}.`,
150
+ hint: 'Check that payment.webhookId matches the webhook shown in the PayPal dashboard, that the raw request body is passed unparsed, and that the clock on this server is correct.',
151
+ details: { reason },
152
+ });
153
+ }
154
+ /** Header names arrive however the HTTP server spelled them; PayPal's are lower-case with hyphens. */
155
+ function headerValue(headers, name) {
156
+ for (const [key, value] of Object.entries(headers)) {
157
+ if (key.toLowerCase() === name)
158
+ return value;
159
+ }
160
+ return undefined;
161
+ }
162
+ /**
163
+ * Is this certificate URL one this driver should ever fetch and trust?
164
+ *
165
+ * A forged `paypal-cert-url` header pointing at an attacker's own server,
166
+ * paired with a signature made from the attacker's own key, would otherwise
167
+ * "verify" against a certificate the attacker controls — the classic webhook-
168
+ * spoofing vector. Trusted origins are: the exact host this driver is already
169
+ * talking to (what makes a local test stub, serving both the API and a
170
+ * fixture certificate, workable — the same escape hatch `apiBaseUrl` already
171
+ * grants Stripe's tests), or any real PayPal host over HTTPS.
172
+ */
173
+ function isTrustedCertUrl(certUrl, baseUrl) {
174
+ let cert;
175
+ let base;
176
+ try {
177
+ cert = new URL(certUrl);
178
+ base = new URL(baseUrl);
179
+ }
180
+ catch {
181
+ return false;
182
+ }
183
+ if (cert.origin === base.origin)
184
+ return true;
185
+ return (cert.protocol === 'https:' &&
186
+ (cert.hostname === 'paypal.com' || cert.hostname.endsWith('.paypal.com')));
187
+ }
188
+ function createPayPalGateway(clientId, clientSecret, baseUrl, webhookId, options) {
189
+ const now = options.now ?? Date.now;
190
+ const logger = options.logger;
191
+ let cachedToken;
192
+ const certCache = new Map();
193
+ async function ensureToken() {
194
+ if (cachedToken !== undefined && cachedToken.expiresAt - TOKEN_REFRESH_SKEW_MS > now()) {
195
+ return cachedToken.token;
196
+ }
197
+ const basic = Buffer.from(`${clientId}:${clientSecret}`, 'utf8').toString('base64');
198
+ let response;
199
+ try {
200
+ response = await fetch(`${baseUrl}/v1/oauth2/token`, {
201
+ method: 'POST',
202
+ headers: {
203
+ authorization: `Basic ${basic}`,
204
+ 'content-type': 'application/x-www-form-urlencoded',
205
+ },
206
+ body: 'grant_type=client_credentials',
207
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
208
+ });
209
+ }
210
+ catch (error) {
211
+ throw paymentFailed('PayPal could not be reached to obtain an access token.', 'Check outbound network access from this server, and whether PayPal is reporting an incident.', {}, error);
212
+ }
213
+ const text = await response.text();
214
+ const body = asRecord(text === '' ? null : safeJsonParse(text));
215
+ if (!response.ok) {
216
+ throw paymentFailed(`PayPal refused the OAuth2 token request with HTTP ${response.status}.`, 'Check that payment.clientId and payment.clientSecret are a valid PayPal REST app pair, and that they match this apiBaseUrl (sandbox credentials do not work against the live host, and vice versa).', { status: response.status });
217
+ }
218
+ const token = readString(body, 'access_token');
219
+ if (token === null) {
220
+ throw paymentFailed('PayPal answered the token request without an access_token.', 'Retry; if it recurs, check the PayPal REST app configuration.', { received: body === null ? [] : Object.keys(body) });
221
+ }
222
+ const expiresIn = typeof body?.expires_in === 'number' ? body.expires_in : 300;
223
+ cachedToken = { token, expiresAt: now() + expiresIn * 1000 };
224
+ return token;
225
+ }
226
+ async function call(path, init) {
227
+ const token = await ensureToken();
228
+ let response;
229
+ try {
230
+ response = await fetch(`${baseUrl}${path}`, {
231
+ method: init.method,
232
+ headers: {
233
+ authorization: `Bearer ${token}`,
234
+ ...(init.json === undefined ? {} : { 'content-type': 'application/json' }),
235
+ },
236
+ ...(init.json === undefined ? {} : { body: JSON.stringify(init.json) }),
237
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
238
+ });
239
+ }
240
+ catch (error) {
241
+ throw paymentFailed(`PayPal could not be reached for ${init.method} ${path}.`, 'Check outbound network access from this server, and whether PayPal is reporting an incident.', { path, method: init.method }, error);
242
+ }
243
+ const text = await response.text();
244
+ const parsed = text === '' ? null : safeJsonParse(text);
245
+ const body = asRecord(parsed);
246
+ if (!response.ok) {
247
+ const issue = readString(asRecord(asArray(body?.details)?.[0]), 'issue');
248
+ throw paymentFailed(`PayPal refused ${init.method} ${path} with HTTP ${response.status}: ${readString(body, 'message') ?? 'no reason given'}`, 'Check the amount, the currency and the order state in the PayPal dashboard; a 401 means the access token expired or the app credentials are wrong.', {
249
+ path,
250
+ method: init.method,
251
+ status: response.status,
252
+ // PayPal's own identifiers for the failure. Never the credentials.
253
+ paypalName: readString(body, 'name'),
254
+ paypalIssue: issue,
255
+ });
256
+ }
257
+ if (body === null) {
258
+ throw paymentFailed(`PayPal answered ${init.method} ${path} with something that is not a JSON object.`, 'This is usually a proxy or a captive portal answering instead of PayPal; check payment.apiBaseUrl and the outbound route.', { path, method: init.method, status: response.status });
259
+ }
260
+ return body;
261
+ }
262
+ function mapOrderStatus(status, externalId) {
263
+ const mapped = status === null ? undefined : ORDER_STATUS_MAP[status];
264
+ if (mapped !== undefined)
265
+ return mapped;
266
+ logger?.warn('PayPal reported an order status this driver does not know.', {
267
+ driver: 'paypal',
268
+ paypalStatus: status,
269
+ externalId,
270
+ mappedTo: 'failed',
271
+ });
272
+ return 'failed';
273
+ }
274
+ function toStartedPayment(order, knownId) {
275
+ const externalId = readString(order, 'id') ?? knownId ?? null;
276
+ if (externalId === null) {
277
+ throw paymentFailed('PayPal returned an order without an id.', 'Retry the payment; if it recurs, check the PayPal API version configured on the account.', { received: Object.keys(order) });
278
+ }
279
+ return {
280
+ externalId,
281
+ status: mapOrderStatus(readString(order, 'status'), externalId),
282
+ instructions: approveLink(order),
283
+ };
284
+ }
285
+ /**
286
+ * Captures an approved order, tolerating the one race this interface
287
+ * exposes: two concurrent `fetch()` calls (a shopper's browser polling
288
+ * while a webhook also arrives) can both see `APPROVED` and both attempt a
289
+ * capture. PayPal accepts the first and refuses the second with
290
+ * `ORDER_ALREADY_CAPTURED` — refused, not failed, so the true status is
291
+ * read back with a plain `GET` instead of surfacing that refusal to the
292
+ * order store.
293
+ */
294
+ async function captureOrRefetch(externalId) {
295
+ try {
296
+ return await call(`/v2/checkout/orders/${encodeURIComponent(externalId)}/capture`, {
297
+ method: 'POST',
298
+ });
299
+ }
300
+ catch (error) {
301
+ const issue = error instanceof CogentaError && typeof error.details?.paypalIssue === 'string'
302
+ ? error.details.paypalIssue
303
+ : null;
304
+ if (issue !== 'ORDER_ALREADY_CAPTURED')
305
+ throw error;
306
+ return call(`/v2/checkout/orders/${encodeURIComponent(externalId)}`, { method: 'GET' });
307
+ }
308
+ }
309
+ async function loadCert(certUrl) {
310
+ const cached = certCache.get(certUrl);
311
+ if (cached !== undefined)
312
+ return cached;
313
+ let response;
314
+ try {
315
+ response = await fetch(certUrl, { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
316
+ }
317
+ catch {
318
+ throw signatureInvalid(`the certificate at "${certUrl}" could not be fetched`);
319
+ }
320
+ if (!response.ok) {
321
+ throw signatureInvalid(`the certificate endpoint answered HTTP ${response.status}`);
322
+ }
323
+ const pem = await response.text();
324
+ certCache.set(certUrl, pem);
325
+ return pem;
326
+ }
327
+ return {
328
+ name: 'paypal',
329
+ settlesOffline: false,
330
+ start: async (request) => {
331
+ const order = await call('/v2/checkout/orders', {
332
+ method: 'POST',
333
+ json: {
334
+ intent: 'CAPTURE',
335
+ purchase_units: [
336
+ {
337
+ reference_id: request.orderReference,
338
+ ...(request.description === undefined ? {} : { description: request.description }),
339
+ amount: {
340
+ currency_code: request.currency.toUpperCase(),
341
+ value: toDecimalAmount(request.amountMinor, request.currency),
342
+ },
343
+ },
344
+ ],
345
+ ...(request.returnUrl === undefined
346
+ ? {}
347
+ : {
348
+ application_context: {
349
+ return_url: request.returnUrl,
350
+ cancel_url: request.returnUrl,
351
+ user_action: 'PAY_NOW',
352
+ },
353
+ }),
354
+ },
355
+ });
356
+ return toStartedPayment(order);
357
+ },
358
+ /**
359
+ * Re-reads the order, and — see the file comment — captures it first if
360
+ * the shopper has approved it but nobody has taken the money yet. This is
361
+ * the one call in the interface that both starts a network request and
362
+ * ends up moving funds, which is unusual for a `fetch`; it is unusual
363
+ * because PayPal's checkout has no other verb this narrow interface could
364
+ * call it from.
365
+ */
366
+ fetch: async (externalId) => {
367
+ const order = await call(`/v2/checkout/orders/${encodeURIComponent(externalId)}`, {
368
+ method: 'GET',
369
+ });
370
+ if (readString(order, 'status') !== 'APPROVED')
371
+ return toStartedPayment(order, externalId);
372
+ return toStartedPayment(await captureOrRefetch(externalId), externalId);
373
+ },
374
+ refund: async (request) => {
375
+ // `externalId` is the order id this driver stores throughout a
376
+ // payment's life (see the file comment); a refund targets the capture
377
+ // underneath it, which only exists once the order has settled.
378
+ const order = await call(`/v2/checkout/orders/${encodeURIComponent(request.externalId)}`, {
379
+ method: 'GET',
380
+ });
381
+ const captureId = findCaptureId(order);
382
+ if (captureId === null) {
383
+ throw paymentFailed(`PayPal order ${request.externalId} has not been captured yet, so there is nothing to refund.`, 'A payment can only be refunded once it has settled.', { externalId: request.externalId });
384
+ }
385
+ const refund = await call(`/v2/payments/captures/${encodeURIComponent(captureId)}/refund`, {
386
+ method: 'POST',
387
+ json: {
388
+ amount: {
389
+ currency_code: request.currency.toUpperCase(),
390
+ value: toDecimalAmount(request.amountMinor, request.currency),
391
+ },
392
+ ...(request.reason === undefined ? {} : { note_to_payer: request.reason }),
393
+ },
394
+ });
395
+ const status = readString(refund, 'status');
396
+ const mapped = status === null ? undefined : REFUND_STATUS_MAP[status];
397
+ if (mapped === undefined) {
398
+ logger?.warn('PayPal reported a refund status this driver does not know.', {
399
+ driver: 'paypal',
400
+ paypalStatus: status,
401
+ captureId,
402
+ mappedTo: 'failed',
403
+ });
404
+ }
405
+ return { externalId: readString(refund, 'id'), status: mapped ?? 'failed' };
406
+ },
407
+ verifyEvent: async (payload, headers) => {
408
+ if (webhookId === undefined || webhookId === '') {
409
+ throw signatureInvalid('no webhook id is configured for this site');
410
+ }
411
+ const transmissionId = headerValue(headers, 'paypal-transmission-id');
412
+ const transmissionTime = headerValue(headers, 'paypal-transmission-time');
413
+ const certUrl = headerValue(headers, 'paypal-cert-url');
414
+ const authAlgo = headerValue(headers, 'paypal-auth-algo');
415
+ const signature = headerValue(headers, 'paypal-transmission-sig');
416
+ if (transmissionId === undefined || transmissionId === '') {
417
+ throw signatureInvalid('no paypal-transmission-id header');
418
+ }
419
+ if (transmissionTime === undefined || transmissionTime === '') {
420
+ throw signatureInvalid('no paypal-transmission-time header');
421
+ }
422
+ if (certUrl === undefined || certUrl === '')
423
+ throw signatureInvalid('no paypal-cert-url header');
424
+ if (signature === undefined || signature === '') {
425
+ throw signatureInvalid('no paypal-transmission-sig header');
426
+ }
427
+ const transmittedAtMs = Date.parse(transmissionTime);
428
+ if (Number.isNaN(transmittedAtMs)) {
429
+ throw signatureInvalid('the paypal-transmission-time header carried no usable timestamp');
430
+ }
431
+ // Freshness before authenticity, same reasoning as Stripe's driver: a
432
+ // captured, genuinely-signed delivery stays genuinely signed forever, so
433
+ // the window is what stops a replay.
434
+ const ageSeconds = Math.abs(Math.floor(now() / 1000) - Math.floor(transmittedAtMs / 1000));
435
+ if (ageSeconds > SIGNATURE_FRESHNESS_WINDOW_SECONDS) {
436
+ throw signatureInvalid(`the transmission timestamp is ${ageSeconds}s away from now, outside the ${SIGNATURE_FRESHNESS_WINDOW_SECONDS}s window`);
437
+ }
438
+ if (!isTrustedCertUrl(certUrl, baseUrl)) {
439
+ throw signatureInvalid(`the certificate url "${certUrl}" is not a trusted PayPal host`);
440
+ }
441
+ const algorithm = authAlgo === undefined || authAlgo.toUpperCase() === 'SHA256WITHRSA' ? 'RSA-SHA256' : null;
442
+ if (algorithm === null)
443
+ throw signatureInvalid(`unsupported signing algorithm "${authAlgo}"`);
444
+ const cert = await loadCert(certUrl);
445
+ // PayPal's own documented scheme: the CRC32 of the raw body, as the
446
+ // unsigned decimal integer (never hex) it defines the string as.
447
+ const crc = (crc32(payload) >>> 0).toString(10);
448
+ const expectedMessage = `${transmissionId}|${transmissionTime}|${webhookId}|${crc}`;
449
+ let verified;
450
+ try {
451
+ verified = createVerify(algorithm).update(expectedMessage).verify(cert, signature, 'base64');
452
+ }
453
+ catch {
454
+ verified = false;
455
+ }
456
+ if (!verified)
457
+ throw signatureInvalid('the signature did not match the transmitted event');
458
+ const event = asRecord(safeJsonParse(payload));
459
+ const type = readString(event, 'event_type');
460
+ const status = type === null ? undefined : EVENT_TYPE_STATUS[type];
461
+ if (status === undefined) {
462
+ throw new CogentaError({
463
+ code: 'COMMERCE_PAYMENT_UNSUPPORTED',
464
+ message: `This driver has no reading for the PayPal event "${type ?? 'unknown'}".`,
465
+ hint: `Narrow the webhook in the PayPal dashboard to ${Object.keys(EVENT_TYPE_STATUS).join(', ')}.`,
466
+ details: { eventType: type },
467
+ });
468
+ }
469
+ const resource = asRecord(event?.resource);
470
+ // A capture event's `resource` is the Capture, not the Order — the
471
+ // order id this driver stores as `externalId` throughout a payment's
472
+ // life lives one level down, in `supplementary_data.related_ids`.
473
+ const orderId = readPath(resource, 'supplementary_data', 'related_ids', 'order_id');
474
+ if (orderId === null) {
475
+ throw paymentFailed(`The PayPal event "${type}" carried no order identifier.`, 'Check that the webhook is subscribed to capture events, which carry supplementary_data.related_ids.order_id.', { eventType: type });
476
+ }
477
+ const amount = asRecord(resource?.amount);
478
+ const currency = readString(amount, 'currency_code');
479
+ const rawAmount = readString(amount, 'value');
480
+ const amountMinor = currency === null || rawAmount === null ? null : fromDecimalAmount(rawAmount, currency);
481
+ return {
482
+ externalId: orderId,
483
+ // Unlike Stripe's payment intents, a capture webhook carries no
484
+ // reference back to `purchase_units[].reference_id` — only the order
485
+ // itself does. Honest null rather than a guess (same discipline as
486
+ // an unrecognised status mapping to `failed` above, not to `paid`).
487
+ orderReference: null,
488
+ status,
489
+ amountMinor,
490
+ currency: currency === null ? null : currency.toUpperCase(),
491
+ };
492
+ },
493
+ };
494
+ }
495
+ function hasCredentials(config) {
496
+ return (config.clientId !== undefined &&
497
+ config.clientId !== '' &&
498
+ config.clientSecret !== undefined &&
499
+ config.clientSecret !== '');
500
+ }
501
+ export function paypalPaymentDriver(options = {}) {
502
+ let gateway;
503
+ let probe;
504
+ const resolveBaseUrl = (config) => (config.apiBaseUrl ?? DEFAULT_API_BASE_URL).replace(/\/+$/, '');
505
+ async function probeToken(clientId, clientSecret, baseUrl) {
506
+ try {
507
+ const response = await fetch(`${baseUrl}/v1/oauth2/token`, {
508
+ method: 'POST',
509
+ headers: {
510
+ authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`, 'utf8').toString('base64')}`,
511
+ 'content-type': 'application/x-www-form-urlencoded',
512
+ },
513
+ body: 'grant_type=client_credentials',
514
+ signal: AbortSignal.timeout(AVAILABILITY_TIMEOUT_MS),
515
+ });
516
+ // The body is drained so the socket is released rather than left open
517
+ // for the timeout to reap.
518
+ await response.text();
519
+ return response.ok;
520
+ }
521
+ catch {
522
+ return false;
523
+ }
524
+ }
525
+ return {
526
+ name: 'paypal',
527
+ tier: 'optimal',
528
+ /**
529
+ * Does PayPal actually issue a token for this client id/secret pair? Not
530
+ * "are both fields set?" — same discipline as Stripe's `available()`, and
531
+ * for the same reason: the registry reads a `false` as "fall through",
532
+ * so this must never throw and never hang a startup.
533
+ */
534
+ available: async (config) => {
535
+ if (!hasCredentials(config))
536
+ return false;
537
+ return probeToken(config.clientId, config.clientSecret, resolveBaseUrl(config));
538
+ },
539
+ init: async (config) => {
540
+ if (!hasCredentials(config)) {
541
+ throw new CogentaError({
542
+ code: 'CONFIG_INVALID',
543
+ message: 'The PayPal payment driver needs a client id and a client secret.',
544
+ hint: 'Set payment.clientId and payment.clientSecret (or the COGENTA_PAYMENT_PAYPAL_CLIENT_ID / COGENTA_PAYMENT_PAYPAL_CLIENT_SECRET environment variables), or leave payment.driver unset to be paid by bank transfer.',
545
+ });
546
+ }
547
+ const baseUrl = resolveBaseUrl(config);
548
+ probe = { clientId: config.clientId, clientSecret: config.clientSecret, baseUrl };
549
+ gateway ??= createPayPalGateway(config.clientId, config.clientSecret, baseUrl, config.webhookId, options);
550
+ return gateway;
551
+ },
552
+ dispose: async () => {
553
+ // Nothing to close: every call is a one-shot `fetch`; the cached OAuth2
554
+ // token dies with the gateway closure.
555
+ gateway = undefined;
556
+ probe = undefined;
557
+ },
558
+ health: async () => {
559
+ if (probe === undefined) {
560
+ return { status: 'down', driver: 'paypal', tier: 'optimal', message: 'Not configured.' };
561
+ }
562
+ const startedAt = Date.now();
563
+ const ok = await probeToken(probe.clientId, probe.clientSecret, probe.baseUrl);
564
+ return ok
565
+ ? {
566
+ status: 'ok',
567
+ driver: 'paypal',
568
+ tier: 'optimal',
569
+ latencyMs: Date.now() - startedAt,
570
+ // The credentials are never named, not even partially.
571
+ message: 'PayPal answered.',
572
+ }
573
+ : {
574
+ status: 'down',
575
+ driver: 'paypal',
576
+ tier: 'optimal',
577
+ latencyMs: Date.now() - startedAt,
578
+ message: 'PayPal did not answer the OAuth2 token request.',
579
+ };
580
+ },
581
+ };
582
+ }
583
+ //# sourceMappingURL=paypal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paypal.js","sourceRoot":"","sources":["../../src/payment/paypal.ts"],"names":[],"mappings":"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"}
@@ -7,17 +7,26 @@ export interface PaymentRegistryOptions {
7
7
  * The payment gateways Cogenta ships.
8
8
  *
9
9
  * Registered in tier order, exactly like cache, queue, storage and database:
10
- * Stripe is `optimal` and answers only when a key is configured and the API
11
- * responds; bank transfer is `degraded` and always answers. So a site with no
12
- * Stripe account does not fail to start and does not fall over at checkout —
13
- * it takes bank transfers, which is a real way to be paid, not a placeholder.
10
+ * Stripe and PayPal are both `optimal` and answer only when real credentials
11
+ * are configured and the gateway's own API accepts them; bank transfer is
12
+ * `degraded` and always answers. So a site with no gateway account does not
13
+ * fail to start and does not fall over at checkout it takes bank transfers,
14
+ * which is a real way to be paid, not a placeholder.
15
+ *
16
+ * This is also the concrete answer to "what if I want a payment method that
17
+ * isn't Stripe?": `PaymentGateway` (`types.ts`) is a registered interface,
18
+ * exactly like cache/queue/storage, and PayPal is a second, independent
19
+ * implementation of it added without touching Stripe, `manual.ts`, the order
20
+ * store, or the admin payment screen — that screen already renders whatever
21
+ * `registry.list()` returns, so a third driver appears there for free.
14
22
  *
15
23
  * The registry's two rules apply here with more force than anywhere else in
16
- * the project. If the configuration **names** Stripe, a Stripe outage is fatal
24
+ * the project. If the configuration **names** a driver, its outage is fatal
17
25
  * rather than a silent downgrade: quietly switching a shop that expects card
18
26
  * payments over to "please make a transfer" would change what customers are
19
27
  * asked to do without anybody deciding to. If the configuration names nothing,
20
- * the fall-through is the point.
28
+ * the fall-through is the point — Stripe is tried before PayPal only because
29
+ * it was registered first, not because of any ranking between the two.
21
30
  */
22
31
  export declare function createPaymentRegistry(options?: PaymentRegistryOptions): DriverRegistry<PaymentGateway, PaymentConfig>;
23
32
  //# sourceMappingURL=registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/payment/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,MAAM,eAAe,CAAA;AAGtF,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE/D,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,sBAA2B,GACnC,cAAc,CAAC,cAAc,EAAE,aAAa,CAAC,CAW/C"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/payment/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,MAAM,eAAe,CAAA;AAItF,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE/D,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,sBAA2B,GACnC,cAAc,CAAC,cAAc,EAAE,aAAa,CAAC,CAY/C"}