@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.
- package/dist/admin/permissions.d.ts +8 -0
- package/dist/admin/permissions.d.ts.map +1 -1
- package/dist/admin/permissions.js +1 -0
- package/dist/admin/permissions.js.map +1 -1
- package/dist/admin/router.d.ts +47 -5
- package/dist/admin/router.d.ts.map +1 -1
- package/dist/admin/router.js +942 -24
- package/dist/admin/router.js.map +1 -1
- package/dist/cart/store.d.ts +23 -0
- package/dist/cart/store.d.ts.map +1 -1
- package/dist/cart/store.js +49 -2
- package/dist/cart/store.js.map +1 -1
- package/dist/catalog/csv.d.ts +29 -0
- package/dist/catalog/csv.d.ts.map +1 -0
- package/dist/catalog/csv.js +396 -0
- package/dist/catalog/csv.js.map +1 -0
- package/dist/catalog/store.d.ts +24 -4
- package/dist/catalog/store.d.ts.map +1 -1
- package/dist/catalog/store.js +238 -11
- package/dist/catalog/store.js.map +1 -1
- package/dist/catalog/types.d.ts +92 -0
- package/dist/catalog/types.d.ts.map +1 -1
- package/dist/catalog/types.js +20 -0
- package/dist/catalog/types.js.map +1 -1
- package/dist/coupon/store.d.ts +63 -5
- package/dist/coupon/store.d.ts.map +1 -1
- package/dist/coupon/store.js +140 -7
- package/dist/coupon/store.js.map +1 -1
- package/dist/customer/store.d.ts +17 -0
- package/dist/customer/store.d.ts.map +1 -1
- package/dist/customer/store.js +26 -0
- package/dist/customer/store.js.map +1 -1
- package/dist/index.d.ts +18 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/invoice/credit-note.d.ts +45 -0
- package/dist/invoice/credit-note.d.ts.map +1 -0
- package/dist/invoice/credit-note.js +109 -0
- package/dist/invoice/credit-note.js.map +1 -0
- package/dist/invoice/sequence.d.ts +21 -0
- package/dist/invoice/sequence.d.ts.map +1 -0
- package/dist/invoice/sequence.js +64 -0
- package/dist/invoice/sequence.js.map +1 -0
- package/dist/invoice/store.d.ts +23 -1
- package/dist/invoice/store.d.ts.map +1 -1
- package/dist/invoice/store.js +23 -56
- package/dist/invoice/store.js.map +1 -1
- package/dist/order/csv.d.ts +24 -0
- package/dist/order/csv.d.ts.map +1 -0
- package/dist/order/csv.js +42 -0
- package/dist/order/csv.js.map +1 -0
- package/dist/order/notify.d.ts +65 -0
- package/dist/order/notify.d.ts.map +1 -0
- package/dist/order/notify.js +180 -0
- package/dist/order/notify.js.map +1 -0
- package/dist/order/store.d.ts +58 -1
- package/dist/order/store.d.ts.map +1 -1
- package/dist/order/store.js +145 -10
- package/dist/order/store.js.map +1 -1
- package/dist/order/types.d.ts +35 -1
- package/dist/order/types.d.ts.map +1 -1
- package/dist/order/types.js +2 -0
- package/dist/order/types.js.map +1 -1
- package/dist/payment/paypal.d.ts +9 -0
- package/dist/payment/paypal.d.ts.map +1 -0
- package/dist/payment/paypal.js +583 -0
- package/dist/payment/paypal.js.map +1 -0
- package/dist/payment/registry.d.ts +15 -6
- package/dist/payment/registry.d.ts.map +1 -1
- package/dist/payment/registry.js +17 -6
- package/dist/payment/registry.js.map +1 -1
- package/dist/payment/types.d.ts +6 -0
- package/dist/payment/types.d.ts.map +1 -1
- package/dist/shipping/store.d.ts +29 -3
- package/dist/shipping/store.d.ts.map +1 -1
- package/dist/shipping/store.js +68 -4
- package/dist/shipping/store.js.map +1 -1
- package/dist/subscription/renewal-notifier.d.ts +17 -0
- package/dist/subscription/renewal-notifier.d.ts.map +1 -0
- package/dist/subscription/renewal-notifier.js +33 -0
- package/dist/subscription/renewal-notifier.js.map +1 -0
- package/dist/subscription/store.d.ts +126 -3
- package/dist/subscription/store.d.ts.map +1 -1
- package/dist/subscription/store.js +348 -8
- package/dist/subscription/store.js.map +1 -1
- package/dist/tables.d.ts +17 -0
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +273 -0
- package/dist/tables.js.map +1 -1
- package/dist/tax/store.d.ts +20 -0
- package/dist/tax/store.d.ts.map +1 -1
- package/dist/tax/store.js +51 -7
- package/dist/tax/store.js.map +1 -1
- 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
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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**
|
|
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;
|
|
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"}
|