@proxy-checkout/stripe-server-js 0.1.0-prx-146.162.1 → 0.1.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.
Files changed (65) hide show
  1. package/README.md +15 -113
  2. package/dist/cjs/errors.d.cts +1 -68
  3. package/dist/cjs/errors.d.ts +1 -68
  4. package/dist/cjs/errors.js +1 -86
  5. package/dist/cjs/index.d.cts +4 -10
  6. package/dist/cjs/index.d.ts +4 -10
  7. package/dist/cjs/index.js +1 -24
  8. package/dist/cjs/metadata.d.cts +2 -26
  9. package/dist/cjs/metadata.d.ts +2 -26
  10. package/dist/cjs/metadata.js +3 -73
  11. package/dist/cjs/open-checkout.d.cts +0 -21
  12. package/dist/cjs/open-checkout.d.ts +0 -21
  13. package/dist/cjs/open-checkout.js +38 -310
  14. package/dist/cjs/sync-checkout-cart.d.cts +3 -3
  15. package/dist/cjs/sync-checkout-cart.d.ts +3 -3
  16. package/dist/cjs/sync-checkout-cart.js +22 -92
  17. package/dist/cjs/types.d.cts +0 -199
  18. package/dist/cjs/types.d.ts +0 -199
  19. package/dist/cjs/version.d.cts +1 -1
  20. package/dist/cjs/version.d.ts +1 -1
  21. package/dist/cjs/version.js +1 -1
  22. package/dist/esm/errors.d.ts +1 -68
  23. package/dist/esm/errors.js +0 -80
  24. package/dist/esm/index.d.ts +4 -10
  25. package/dist/esm/index.js +2 -7
  26. package/dist/esm/metadata.d.ts +2 -26
  27. package/dist/esm/metadata.js +2 -69
  28. package/dist/esm/open-checkout.d.ts +0 -21
  29. package/dist/esm/open-checkout.js +40 -312
  30. package/dist/esm/sync-checkout-cart.d.ts +3 -3
  31. package/dist/esm/sync-checkout-cart.js +23 -93
  32. package/dist/esm/types.d.ts +0 -199
  33. package/dist/esm/version.d.ts +1 -1
  34. package/dist/esm/version.js +1 -1
  35. package/package.json +3 -3
  36. package/dist/cjs/acquisition.d.cts +0 -189
  37. package/dist/cjs/acquisition.d.ts +0 -189
  38. package/dist/cjs/acquisition.js +0 -789
  39. package/dist/cjs/compatibility.d.cts +0 -66
  40. package/dist/cjs/compatibility.d.ts +0 -66
  41. package/dist/cjs/compatibility.js +0 -174
  42. package/dist/cjs/open-payment-intent.d.cts +0 -35
  43. package/dist/cjs/open-payment-intent.d.ts +0 -35
  44. package/dist/cjs/open-payment-intent.js +0 -157
  45. package/dist/cjs/open-setup-intent.d.cts +0 -49
  46. package/dist/cjs/open-setup-intent.d.ts +0 -49
  47. package/dist/cjs/open-setup-intent.js +0 -157
  48. package/dist/cjs/open-subscription.d.cts +0 -75
  49. package/dist/cjs/open-subscription.d.ts +0 -75
  50. package/dist/cjs/open-subscription.js +0 -307
  51. package/dist/cjs/subscription-params.d.cts +0 -33
  52. package/dist/cjs/subscription-params.d.ts +0 -33
  53. package/dist/cjs/subscription-params.js +0 -237
  54. package/dist/esm/acquisition.d.ts +0 -189
  55. package/dist/esm/acquisition.js +0 -763
  56. package/dist/esm/compatibility.d.ts +0 -66
  57. package/dist/esm/compatibility.js +0 -168
  58. package/dist/esm/open-payment-intent.d.ts +0 -35
  59. package/dist/esm/open-payment-intent.js +0 -154
  60. package/dist/esm/open-setup-intent.d.ts +0 -49
  61. package/dist/esm/open-setup-intent.js +0 -153
  62. package/dist/esm/open-subscription.d.ts +0 -75
  63. package/dist/esm/open-subscription.js +0 -304
  64. package/dist/esm/subscription-params.d.ts +0 -33
  65. package/dist/esm/subscription-params.js +0 -232
@@ -1,789 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.assertNoConnectRequestOptions = assertNoConnectRequestOptions;
4
- exports.terminalAcquisitionResult = terminalAcquisitionResult;
5
- exports.openSharedAcquisitionSession = openSharedAcquisitionSession;
6
- exports.acquireStripeRoot = acquireStripeRoot;
7
- exports.terminalAfterAcquisitionFailure = terminalAfterAcquisitionFailure;
8
- exports.recordOrReconcileLegacyCheckoutBinding = recordOrReconcileLegacyCheckoutBinding;
9
- exports.fingerprintStripeProviderOptions = fingerprintStripeProviderOptions;
10
- exports.hashProviderCartEvidence = hashProviderCartEvidence;
11
- exports.checkoutOptionsForFingerprint = checkoutOptionsForFingerprint;
12
- exports.fingerprintHostedCheckoutProviderOptions = fingerprintHostedCheckoutProviderOptions;
13
- exports.retrieveCheckoutSessionWithLineEvidence = retrieveCheckoutSessionWithLineEvidence;
14
- exports.checkoutCartConfirmation = checkoutCartConfirmation;
15
- exports.paymentIntentOptionsForFingerprint = paymentIntentOptionsForFingerprint;
16
- exports.acquisitionMetadata = acquisitionMetadata;
17
- exports.mergeAcquisitionMetadata = mergeAcquisitionMetadata;
18
- exports.assertMerchantDoesNotSetAcquisitionMetadata = assertMerchantDoesNotSetAcquisitionMetadata;
19
- exports.assertAcquisitionMetadata = assertAcquisitionMetadata;
20
- exports.reserveDirectSubscriptionAcquisition = reserveDirectSubscriptionAcquisition;
21
- exports.recoverSetupIntentReservation = recoverSetupIntentReservation;
22
- exports.attachAcquisitionObject = attachAcquisitionObject;
23
- exports.createOrRecoverStripeAcquisitionObject = createOrRecoverStripeAcquisitionObject;
24
- exports.stripeRequestOptions = stripeRequestOptions;
25
- exports.requireStripeObjectId = requireStripeObjectId;
26
- exports.assertDirectSubscriptionReservation = assertDirectSubscriptionReservation;
27
- const node_crypto_1 = require("node:crypto");
28
- const server_js_1 = require("@proxy-checkout/server-js");
29
- const errors_js_1 = require("./errors.js");
30
- const metadata_js_1 = require("./metadata.js");
31
- const MAX_STRIPE_METADATA_KEYS = 50;
32
- const MAX_STRIPE_METADATA_KEY_LENGTH = 40;
33
- const MAX_STRIPE_METADATA_VALUE_LENGTH = 500;
34
- /** Connect routing is outside every first-class Proxy acquisition path. */
35
- function assertNoConnectRequestOptions(options) {
36
- if (options && (options.stripeAccount !== undefined || options.stripe_account !== undefined)) {
37
- throw new errors_js_1.UnsupportedStripeOptionError("Stripe Connect account routing is outside this acquisition capability.", "stripeRequestOptions.stripeAccount");
38
- }
39
- }
40
- function terminalAcquisitionResult(proxySession, cart) {
41
- const common = { cart, proxySession, proxySessionId: proxySession.id };
42
- if (proxySession.presentationState === "already_paid" &&
43
- (proxySession.status === "paid" ||
44
- proxySession.status === "provisionable" ||
45
- proxySession.status === "provisioned" ||
46
- proxySession.status === "provisioning_failed")) {
47
- return { ...common, outcome: "already_paid", sessionStatus: proxySession.status };
48
- }
49
- if (proxySession.presentationState === "action_required" &&
50
- proxySession.status === "merchant_action_required") {
51
- return { ...common, outcome: "action_required", sessionStatus: proxySession.status };
52
- }
53
- if (proxySession.presentationState === "unavailable" &&
54
- (proxySession.status === "cancelled" ||
55
- proxySession.status === "created" ||
56
- proxySession.status === "expired" ||
57
- proxySession.status === "failed")) {
58
- return { ...common, outcome: "unavailable", sessionStatus: proxySession.status };
59
- }
60
- throw new server_js_1.ProxyCheckoutValidationError(`Proxy session ${proxySession.id} returned an inconsistent presentation and lifecycle status.`, { code: "invalid_session_presentation", field: "presentation_state" });
61
- }
62
- async function openSharedAcquisitionSession(options) {
63
- const proxySession = await options.proxy.sessions.payerOpened(options.proxySessionId, {
64
- requestId: options.requestId,
65
- });
66
- const cart = options.cartSchema || options.getCartBuyerReference
67
- ? options.proxy.sessions.parseCart(proxySession, options.cartSchema ?? ((value) => value), { getBuyerReference: options.getCartBuyerReference })
68
- : proxySession.cartSnapshot;
69
- if (proxySession.presentationState === "checkout_available") {
70
- return { cart, outcome: "active", proxySession, proxySessionId: proxySession.id };
71
- }
72
- return terminalAcquisitionResult(proxySession, cart);
73
- }
74
- /**
75
- * One order-scoped owner for reserve/join, deterministic provider creation,
76
- * null-or-same attachment, and exact provider/cart confirmation. There is no
77
- * payer lease: every identical caller can execute this function concurrently.
78
- */
79
- async function acquireStripeRoot(options) {
80
- const existing = await currentAcquisition(options);
81
- if (existing?.providerRootObjectId) {
82
- assertAcquisitionConfiguration(existing, options);
83
- const object = await retrieveKnownRoot(options, existing, existing.providerRootObjectId);
84
- options.validateObject(object, existing);
85
- assertProviderCustomer(existing, object);
86
- const acquisition = await confirmProviderCart(options, existing, options.toConfirmation(object));
87
- return { acquisition, object, outcome: "acquired", reservation: null };
88
- }
89
- let reservation;
90
- try {
91
- reservation = await options.proxy.providerAcquisitions.reserveProviderAcquisition(options.proxySession.id, {
92
- ...(options.checkoutUiMode === undefined ? {} : { checkoutUiMode: options.checkoutUiMode }),
93
- commercialMode: options.commercialMode,
94
- integrationPath: options.integrationPath,
95
- ...(options.directMerchandiseSubtotalMinor === undefined
96
- ? {}
97
- : { merchandiseSubtotalMinor: options.directMerchandiseSubtotalMinor }),
98
- pricingMode: options.pricingMode,
99
- providerOptionsFingerprint: options.providerOptionsFingerprint,
100
- requestId: options.requestId,
101
- });
102
- }
103
- catch (cause) {
104
- const proxySession = await terminalAfterAcquisitionFailure({
105
- cause,
106
- proxy: options.proxy,
107
- proxySessionId: options.proxySession.id,
108
- requestId: options.requestId,
109
- });
110
- return { outcome: "terminal", proxySession };
111
- }
112
- let acquisition = reservation.acquisitionAttempt;
113
- if (acquisition.providerRootObjectId) {
114
- const object = await retrieveKnownRoot(options, acquisition, acquisition.providerRootObjectId);
115
- options.validateObject(object, acquisition);
116
- assertProviderCustomer(acquisition, object);
117
- acquisition = await confirmProviderCart(options, acquisition, options.toConfirmation(object));
118
- return { acquisition, object, outcome: "acquired", reservation };
119
- }
120
- const replayed = await createOrRecoverStripeAcquisitionObject({
121
- acquisition,
122
- objectType: options.objectType,
123
- proxy: options.proxy,
124
- proxySessionId: options.proxySession.id,
125
- requestId: options.requestId,
126
- retrieve: options.retrieve,
127
- role: "root",
128
- create: () => options.create(reservation),
129
- });
130
- const { object } = replayed;
131
- acquisition = replayed.acquisition;
132
- options.validateObject(object, acquisition);
133
- assertProviderCustomer(acquisition, object);
134
- const confirmation = options.toConfirmation(object);
135
- if (!replayed.alreadyAttached) {
136
- acquisition = await attachAcquisitionObject({
137
- acquisitionAttempt: acquisition,
138
- objectId: object.id,
139
- objectType: options.objectType,
140
- providerConfirmation: confirmation,
141
- proxy: options.proxy,
142
- proxySessionId: options.proxySession.id,
143
- requestId: options.requestId,
144
- role: "root",
145
- });
146
- }
147
- acquisition = await confirmProviderCart(options, acquisition, confirmation);
148
- return { acquisition, object, outcome: "acquired", reservation };
149
- }
150
- /**
151
- * Re-read only a terminal reserve conflict. Failures after reserve are never
152
- * converted because a Stripe root may already exist and must remain visible.
153
- */
154
- async function terminalAfterAcquisitionFailure(input) {
155
- if (!isTerminalReserveConflict(input.cause))
156
- throw input.cause;
157
- const proxySession = await input.proxy.sessions.payerOpened(input.proxySessionId, {
158
- requestId: input.requestId,
159
- });
160
- if (proxySession.presentationState === "checkout_available")
161
- throw input.cause;
162
- return proxySession;
163
- }
164
- function isTerminalReserveConflict(cause) {
165
- if (!cause || typeof cause !== "object")
166
- return false;
167
- const error = cause;
168
- return (error.statusCode === 409 &&
169
- (error.code === "session_already_completed" || error.code === "session_not_actionable"));
170
- }
171
- /** Compatibility bridge for 0.0.x Checkout callers while the public helper
172
- * moves all provider-binding ownership out of open-checkout.ts. */
173
- async function recordOrReconcileLegacyCheckoutBinding(input) {
174
- try {
175
- return await input.proxy.sessions.recordProviderBinding(input.proxySessionId, {
176
- commercialMode: input.commercialMode,
177
- expectedCartVersion: input.expectedCartVersion,
178
- providerCheckoutSessionId: input.checkoutSessionId,
179
- psp: input.psp,
180
- requestId: input.requestId,
181
- });
182
- }
183
- catch (cause) {
184
- const reconciled = await readLegacyCheckoutBinding(input).catch(() => undefined);
185
- if (reconciled)
186
- return reconciled;
187
- if (isProxyConflict(cause))
188
- throw cause;
189
- throw new errors_js_1.ProxyStripeOpenCheckoutError("Stripe Checkout Session was created but Proxy provider binding could not be recorded.", {
190
- cause,
191
- code: "provider_binding_failed",
192
- providerCheckoutSessionId: input.checkoutSessionId,
193
- });
194
- }
195
- }
196
- function fingerprintStripeProviderOptions(path, params) {
197
- return hashEvidence(`proxy.stripe.provider_options.${path}.v1`, params);
198
- }
199
- function hashProviderCartEvidence(path, value) {
200
- return hashEvidence(`proxy.stripe.provider_cart.${path}.v1`, value);
201
- }
202
- function checkoutOptionsForFingerprint(params) {
203
- const { line_items: _lineItems, ...configuration } = params;
204
- return configuration;
205
- }
206
- /** Canonical provider-options identity used when replacing a hosted Checkout acquisition. */
207
- function fingerprintHostedCheckoutProviderOptions(params) {
208
- return fingerprintStripeProviderOptions("checkout_session", checkoutOptionsForFingerprint(params));
209
- }
210
- async function retrieveCheckoutSessionWithLineEvidence(input) {
211
- let session = input.initial;
212
- if (!hasCompleteCheckoutLineItems(session)) {
213
- session = await input.stripe.checkout.sessions.retrieve(input.id, { expand: ["line_items.data.price"] }, input.requestOptions);
214
- }
215
- if (hasCompleteCheckoutLineItems(session))
216
- return session;
217
- const listLineItems = input.stripe.checkout.sessions.listLineItems;
218
- if (!listLineItems) {
219
- throw new errors_js_1.StripeResponseShapeError("Stripe Checkout line-item evidence is incomplete; enable expanded line_items or provide checkout.sessions.listLineItems.", "line_items");
220
- }
221
- const data = [];
222
- let startingAfter;
223
- for (let pageNumber = 0; pageNumber < 100; pageNumber += 1) {
224
- const page = await listLineItems.call(input.stripe.checkout.sessions, input.id, {
225
- expand: ["data.price"],
226
- limit: 100,
227
- ...(startingAfter ? { starting_after: startingAfter } : {}),
228
- }, input.requestOptions);
229
- if (!Array.isArray(page.data)) {
230
- throw new errors_js_1.StripeResponseShapeError("Stripe Checkout line-item list response is missing data.", "line_items.data");
231
- }
232
- data.push(...page.data);
233
- if (page.has_more !== true) {
234
- return { ...session, line_items: { data, has_more: false } };
235
- }
236
- const cursor = page.data.at(-1)?.id;
237
- if (!cursor) {
238
- throw new errors_js_1.StripeResponseShapeError("Stripe Checkout line-item pagination is missing its cursor.", "line_items.data.id");
239
- }
240
- startingAfter = cursor;
241
- }
242
- throw new errors_js_1.StripeResponseShapeError("Stripe Checkout line-item evidence exceeded the bounded 10,000-line read.", "line_items");
243
- }
244
- function checkoutCartConfirmation(input) {
245
- const { checkoutSession } = input;
246
- if (!Number.isSafeInteger(checkoutSession.amount_total) ||
247
- (checkoutSession.amount_total ?? -1) < 0 ||
248
- !Number.isSafeInteger(checkoutSession.amount_subtotal) ||
249
- (checkoutSession.amount_subtotal ?? -1) < 0 ||
250
- typeof checkoutSession.currency !== "string") {
251
- throw new errors_js_1.StripeResponseShapeError("Stripe Checkout response is missing amount_total, amount_subtotal, or currency.", "amount_total");
252
- }
253
- const providerLines = normalizeProviderCheckoutLines(checkoutSession);
254
- const expectedLines = normalizeExpectedCheckoutLines(input.expectedLineItems);
255
- if (!checkoutLinesMatch(expectedLines, providerLines)) {
256
- throw new server_js_1.ProxyCheckoutValidationError(`Stripe Checkout Session ${checkoutSession.id} line identity, quantity, or recurring semantics do not match the requested cart revision.`, { code: "cart_version_conflict", field: "provider_line_items" });
257
- }
258
- const amountMinor = checkoutSession.amount_total;
259
- const merchandiseSubtotalMinor = checkoutSession.amount_subtotal;
260
- return {
261
- amountMinor,
262
- cartVersion: input.cartVersion,
263
- currency: checkoutSession.currency,
264
- evidenceSha256: hashProviderCartEvidence("checkout_session", {
265
- amount_subtotal: merchandiseSubtotalMinor,
266
- amount_total: amountMinor,
267
- cart_version: input.cartVersion,
268
- currency: checkoutSession.currency,
269
- id: checkoutSession.id,
270
- lines: providerLines,
271
- }),
272
- merchandiseSubtotalMinor,
273
- };
274
- }
275
- function paymentIntentOptionsForFingerprint(params) {
276
- const { amount: _amount, currency: _currency, ...configuration } = params;
277
- return configuration;
278
- }
279
- function acquisitionMetadata(reservation) {
280
- return {
281
- proxy_acquisition_attempt_id: reservation.stripe.metadata.proxyAcquisitionAttemptId,
282
- proxy_contract_version: reservation.stripe.metadata.proxyContractVersion,
283
- proxy_integration_path: reservation.acquisitionAttempt.integrationPath,
284
- proxy_session_id: reservation.stripe.metadata.proxySessionId,
285
- };
286
- }
287
- function mergeAcquisitionMetadata(existing, required, field = "metadata") {
288
- for (const [key, requiredValue] of Object.entries(required)) {
289
- const existingValue = existing?.[key];
290
- if (existingValue !== undefined && existingValue !== requiredValue) {
291
- throw new errors_js_1.ProxyMetadataConflictError(`${field}.${key}`);
292
- }
293
- }
294
- const merged = { ...existing, ...required };
295
- const entries = Object.entries(merged);
296
- if (entries.length > MAX_STRIPE_METADATA_KEYS) {
297
- throw new errors_js_1.ProxyStripeAcquisitionError(`Stripe ${field} has ${entries.length} keys after adding Proxy acquisition metadata; the supported maximum is ${MAX_STRIPE_METADATA_KEYS}.`, { code: "unsupported_provider_option", field });
298
- }
299
- for (const [key, value] of entries) {
300
- if (key.length === 0 || key.length > MAX_STRIPE_METADATA_KEY_LENGTH) {
301
- throw new errors_js_1.ProxyStripeAcquisitionError(`Stripe ${field} contains a metadata key outside the supported length.`, { code: "unsupported_provider_option", field: `${field}.${key}` });
302
- }
303
- if (value !== null && String(value).length > MAX_STRIPE_METADATA_VALUE_LENGTH) {
304
- throw new errors_js_1.ProxyStripeAcquisitionError(`Stripe ${field}.${key} exceeds the supported metadata value length.`, { code: "unsupported_provider_option", field: `${field}.${key}` });
305
- }
306
- }
307
- return merged;
308
- }
309
- function assertMerchantDoesNotSetAcquisitionMetadata(metadata, field = "metadata") {
310
- for (const key of [
311
- "proxy_acquisition_attempt_id",
312
- "proxy_contract_version",
313
- "proxy_integration_path",
314
- "proxy_session_id",
315
- ]) {
316
- if (metadata?.[key] !== undefined) {
317
- throw new errors_js_1.ProxyMetadataConflictError(`${field}.${key}`);
318
- }
319
- }
320
- }
321
- function assertAcquisitionMetadata(metadata, expected, objectLabel) {
322
- for (const [key, expectedValue] of Object.entries(expected)) {
323
- if (metadata?.[key] !== expectedValue) {
324
- throw new errors_js_1.ProxyStripeAcquisitionError(`${objectLabel} metadata.${key} does not match the Proxy acquisition.`, { code: "acquisition_mismatch", field: `metadata.${key}` });
325
- }
326
- }
327
- }
328
- async function reserveDirectSubscriptionAcquisition(input) {
329
- return input.proxy.providerAcquisitions.reserveProviderAcquisition(input.proxySessionId, {
330
- commercialMode: "subscription",
331
- directSubscriptionSource: input.directSubscriptionSource,
332
- integrationPath: "direct_subscription",
333
- pricingMode: "fixed",
334
- providerOptionsFingerprint: input.providerOptionsFingerprint,
335
- requestId: input.requestId,
336
- });
337
- }
338
- async function recoverSetupIntentReservation(input) {
339
- const reservation = await reserveDirectSubscriptionAcquisition({
340
- directSubscriptionSource: "setup_intent",
341
- providerOptionsFingerprint: input.providerOptionsFingerprint,
342
- proxy: input.proxy,
343
- proxySessionId: input.proxySessionId,
344
- requestId: input.requestId,
345
- });
346
- assertDirectSubscriptionReservation(reservation, "setup_intent", input.proxySessionId, input.providerOptionsFingerprint);
347
- if (reservation.acquisitionAttempt.id !== input.acquisitionAttemptId) {
348
- throw new errors_js_1.ProxyStripeAcquisitionError(`Proxy acquisition ${reservation.acquisitionAttempt.id} does not match requested acquisition ${input.acquisitionAttemptId}.`, { code: "acquisition_mismatch", field: "acquisitionAttemptId" });
349
- }
350
- if (reservation.acquisitionAttempt.providerSetupIntentId !== input.setupIntentId) {
351
- throw new errors_js_1.ProxyStripeAcquisitionError(`Stripe SetupIntent ${input.setupIntentId} is not attached to Proxy acquisition ${input.acquisitionAttemptId}.`, { code: "acquisition_mismatch", field: "setupIntentId" });
352
- }
353
- return reservation;
354
- }
355
- async function attachAcquisitionObject(input) {
356
- try {
357
- return await input.proxy.providerAcquisitions.attachProviderAcquisitionObject(input.proxySessionId, input.acquisitionAttempt.id, {
358
- expectedVersion: input.acquisitionAttempt.version,
359
- objectId: input.objectId,
360
- objectType: input.objectType,
361
- ...(input.providerConfirmation ? { providerConfirmation: input.providerConfirmation } : {}),
362
- ...(input.providerCustomerId ? { providerCustomerId: input.providerCustomerId } : {}),
363
- requestId: input.requestId,
364
- role: input.role,
365
- });
366
- }
367
- catch (cause) {
368
- const acquisitions = await input.proxy.providerAcquisitions
369
- .listProviderAcquisitions(input.proxySessionId, { requestId: input.requestId })
370
- .catch(() => []);
371
- const current = acquisitions.find((candidate) => candidate.id === input.acquisitionAttempt.id);
372
- const same = input.role === "setup_intent"
373
- ? current?.providerSetupIntentId === input.objectId
374
- : current?.providerRootObjectId === input.objectId &&
375
- current.providerRootObjectType === input.objectType;
376
- if (current && same)
377
- return current;
378
- throw new errors_js_1.ProxyStripeAcquisitionError("Stripe provider object exists but Proxy could not record or reconcile its attachment.", {
379
- acquisitionAttemptId: input.acquisitionAttempt.id,
380
- cause,
381
- code: "provider_attachment_failed",
382
- providerObjectId: input.objectId,
383
- });
384
- }
385
- }
386
- /**
387
- * Canonical deterministic-create replay used by every Stripe acquisition role.
388
- * A second create uses the same caller-owned idempotency key and therefore
389
- * recovers the first result; it never authorizes another physical object.
390
- */
391
- async function createOrRecoverStripeAcquisitionObject(input) {
392
- let acquisition = input.acquisition;
393
- const attachedObjectId = acquisitionObjectId(acquisition, input.role, input.objectType);
394
- if (attachedObjectId) {
395
- return {
396
- acquisition,
397
- alreadyAttached: true,
398
- object: await retrieveAcquisitionObject(input, acquisition, attachedObjectId),
399
- };
400
- }
401
- assertProviderCreateReplayWindowOpen(acquisition);
402
- try {
403
- return { acquisition, alreadyAttached: false, object: await input.create() };
404
- }
405
- catch (firstCause) {
406
- const current = await findAcquisitionById(input, acquisition.id);
407
- const recoveredObjectId = current
408
- ? acquisitionObjectId(current, input.role, input.objectType)
409
- : null;
410
- if (current && recoveredObjectId) {
411
- acquisition = current;
412
- return {
413
- acquisition,
414
- alreadyAttached: true,
415
- object: await retrieveAcquisitionObject(input, acquisition, recoveredObjectId),
416
- };
417
- }
418
- assertProviderCreateReplayWindowOpen(acquisition);
419
- try {
420
- return { acquisition, alreadyAttached: false, object: await input.create() };
421
- }
422
- catch (cause) {
423
- throw new errors_js_1.ProxyStripeAcquisitionError("Stripe provider creation remained ambiguous after the deterministic idempotent retry.", {
424
- acquisitionAttemptId: acquisition.id,
425
- cause: aggregateCauses(firstCause, cause),
426
- code: "provider_create_ambiguous",
427
- });
428
- }
429
- }
430
- }
431
- function assertProviderCreateReplayWindowOpen(acquisition) {
432
- const retryNotAfter = Date.parse(acquisition.providerRetryNotAfter);
433
- if (!Number.isFinite(retryNotAfter)) {
434
- throw new errors_js_1.StripeResponseShapeError(`Proxy acquisition ${acquisition.id} returned an invalid provider replay deadline.`, "acquisition.providerRetryNotAfter");
435
- }
436
- if (Date.now() >= retryNotAfter) {
437
- throw new errors_js_1.ProxyStripeAcquisitionError("Stripe provider creation remains ambiguous after the safe idempotency replay window.", {
438
- acquisitionAttemptId: acquisition.id,
439
- code: "provider_create_ambiguous",
440
- });
441
- }
442
- }
443
- function acquisitionObjectId(acquisition, role, objectType) {
444
- if (role === "setup_intent") {
445
- if (objectType !== "setup_intent") {
446
- throw new errors_js_1.ProxyStripeAcquisitionError(`Proxy acquisition ${acquisition.id} received an invalid SetupIntent role type.`, { code: "acquisition_mismatch", field: "objectType" });
447
- }
448
- return acquisition.providerSetupIntentId;
449
- }
450
- if (acquisition.providerRootObjectId !== null &&
451
- acquisition.providerRootObjectType !== objectType) {
452
- throw new errors_js_1.ProxyStripeAcquisitionError(`Proxy acquisition ${acquisition.id} is attached to a different provider root type.`, { code: "acquisition_mismatch", field: "providerRootObjectType" });
453
- }
454
- return acquisition.providerRootObjectId;
455
- }
456
- async function findAcquisitionById(input, acquisitionAttemptId) {
457
- const acquisitions = await input.proxy.providerAcquisitions.listProviderAcquisitions(input.proxySessionId, { requestId: input.requestId });
458
- return acquisitions.find((candidate) => candidate.id === acquisitionAttemptId) ?? null;
459
- }
460
- async function retrieveAcquisitionObject(input, acquisition, providerObjectId) {
461
- try {
462
- return await input.retrieve(providerObjectId);
463
- }
464
- catch (cause) {
465
- throw new errors_js_1.ProxyStripeAcquisitionError("Proxy knows the Stripe object but retrieval failed.", {
466
- acquisitionAttemptId: acquisition.id,
467
- cause,
468
- code: "provider_retrieve_failed",
469
- providerObjectId,
470
- });
471
- }
472
- }
473
- function stripeRequestOptions(merchantOptions, idempotencyKey) {
474
- return { ...merchantOptions, idempotencyKey };
475
- }
476
- function requireStripeObjectId(value, prefix, field) {
477
- const id = typeof value === "string"
478
- ? value
479
- : typeof value === "object" && value !== null && "id" in value
480
- ? value.id
481
- : undefined;
482
- if (typeof id !== "string" || !id.startsWith(prefix) || id.length <= prefix.length) {
483
- throw new errors_js_1.StripeResponseShapeError(`Stripe response field ${field} is missing or invalid.`, field);
484
- }
485
- return id;
486
- }
487
- function assertDirectSubscriptionReservation(reservation, source, proxySessionId, providerOptionsFingerprint) {
488
- const attempt = reservation.acquisitionAttempt;
489
- if (attempt.proxySessionId !== proxySessionId ||
490
- attempt.commercialMode !== "subscription" ||
491
- attempt.integrationPath !== "direct_subscription" ||
492
- attempt.pricingMode !== "fixed" ||
493
- attempt.directSubscriptionSource !== source ||
494
- (providerOptionsFingerprint !== undefined &&
495
- attempt.providerOptionsFingerprint !== providerOptionsFingerprint) ||
496
- reservation.stripe.metadata.proxyAcquisitionAttemptId !== attempt.id ||
497
- reservation.stripe.metadata.proxyContractVersion !== "2" ||
498
- reservation.stripe.metadata.proxySessionId !== proxySessionId) {
499
- throw new errors_js_1.ProxyStripeAcquisitionError(`Proxy acquisition ${attempt.id} does not match the requested direct Subscription contract.`, { code: "acquisition_mismatch", field: "acquisition" });
500
- }
501
- }
502
- async function currentAcquisition(options, expectedId) {
503
- const summary = options.proxySession.currentAcquisitionAttempt;
504
- const acquisitionId = expectedId ?? summary?.id;
505
- if (!acquisitionId)
506
- return null;
507
- const acquisitions = await options.proxy.providerAcquisitions.listProviderAcquisitions(options.proxySession.id, { requestId: options.requestId });
508
- return acquisitions.find((candidate) => candidate.id === acquisitionId) ?? null;
509
- }
510
- function assertAcquisitionConfiguration(acquisition, options) {
511
- const expectedSubtotal = options.directMerchandiseSubtotalMinor ?? null;
512
- if (acquisition.integrationPath !== options.integrationPath ||
513
- acquisition.commercialMode !== options.commercialMode ||
514
- acquisition.pricingMode !== options.pricingMode ||
515
- acquisition.checkoutUiMode !== (options.checkoutUiMode ?? null) ||
516
- acquisition.directMerchandiseSubtotalMinor !== expectedSubtotal ||
517
- acquisition.providerOptionsFingerprint !== options.providerOptionsFingerprint) {
518
- throw new server_js_1.ProxyCheckoutValidationError(`Proxy session ${options.proxySession.id} already has an acquisition with different immutable Stripe options.`, { code: "acquisition_provider_options_conflict", field: "provider_options_fingerprint" });
519
- }
520
- }
521
- async function retrieveKnownRoot(options, acquisition, providerObjectId) {
522
- try {
523
- return await options.retrieve(providerObjectId);
524
- }
525
- catch (cause) {
526
- throw new errors_js_1.ProxyStripeAcquisitionError("Proxy knows the Stripe root but retrieval failed.", {
527
- acquisitionAttemptId: acquisition.id,
528
- cause,
529
- code: "provider_retrieve_failed",
530
- providerObjectId,
531
- });
532
- }
533
- }
534
- async function confirmProviderCart(options, acquisition, confirmation) {
535
- if (sameConfirmation(acquisition, confirmation))
536
- return acquisition;
537
- try {
538
- return await options.proxy.providerAcquisitions.confirmProviderAcquisitionCart(options.proxySession.id, acquisition.id, {
539
- ...confirmation,
540
- expectedVersion: acquisition.version,
541
- requestId: options.requestId,
542
- });
543
- }
544
- catch (cause) {
545
- const refreshed = await currentAcquisition(options, acquisition.id).catch(() => null);
546
- if (refreshed?.id === acquisition.id && sameConfirmation(refreshed, confirmation)) {
547
- return refreshed;
548
- }
549
- throw new errors_js_1.ProxyStripeAcquisitionError("Stripe root exists but its exact cart revision could not be confirmed with Proxy.", {
550
- acquisitionAttemptId: acquisition.id,
551
- cause,
552
- code: "provider_cart_confirmation_failed",
553
- providerObjectId: acquisition.providerRootObjectId,
554
- });
555
- }
556
- }
557
- function sameConfirmation(acquisition, confirmation) {
558
- return (acquisition.providerConfirmedAmountMinor === confirmation.amountMinor &&
559
- acquisition.providerConfirmedCartVersion === confirmation.cartVersion &&
560
- acquisition.providerConfirmedCurrency === confirmation.currency &&
561
- acquisition.providerConfirmedEvidenceSha256 === confirmation.evidenceSha256 &&
562
- acquisition.providerConfirmedMerchandiseSubtotalMinor ===
563
- (confirmation.merchandiseSubtotalMinor ?? null));
564
- }
565
- function assertProviderCustomer(acquisition, object) {
566
- const providerCustomerId = stripeCustomerId(object);
567
- if (providerCustomerId &&
568
- acquisition.providerCustomerId &&
569
- acquisition.providerCustomerId !== providerCustomerId) {
570
- throw new errors_js_1.ProxyStripeAcquisitionError("Stripe root Customer disagrees with the immutable Proxy acquisition diagnostic.", {
571
- acquisitionAttemptId: acquisition.id,
572
- cause: undefined,
573
- code: "provider_attachment_failed",
574
- providerObjectId: object.id,
575
- });
576
- }
577
- }
578
- function stripeCustomerId(object) {
579
- const customer = object.customer;
580
- const id = typeof customer === "string" ? customer : customer?.id;
581
- return typeof id === "string" && /^cus_[A-Za-z0-9._:-]+$/.test(id) ? id : null;
582
- }
583
- async function readLegacyCheckoutBinding(input) {
584
- const current = await input.proxy.sessions.retrieve(input.proxySessionId, {
585
- requestId: input.requestId,
586
- });
587
- if (current.providerCheckoutSessionId !== input.checkoutSessionId ||
588
- current.providerCheckoutSessionPsp !== input.psp) {
589
- return undefined;
590
- }
591
- const checkoutSession = await input.stripe.checkout.sessions.retrieve(input.checkoutSessionId, undefined, input.stripeRequestOptions);
592
- (0, metadata_js_1.assertCheckoutSessionBelongsToProxy)(checkoutSession, input.proxySessionId);
593
- return {
594
- providerCheckoutSessionId: input.checkoutSessionId,
595
- proxySessionId: current.id,
596
- psp: input.psp,
597
- updatedAt: current.updatedAt,
598
- };
599
- }
600
- function isProxyConflict(error) {
601
- return (typeof error === "object" &&
602
- error !== null &&
603
- "statusCode" in error &&
604
- error.statusCode === 409);
605
- }
606
- function hasCompleteCheckoutLineItems(session) {
607
- return Array.isArray(session?.line_items?.data) && session.line_items?.has_more === false;
608
- }
609
- function normalizeProviderCheckoutLines(session) {
610
- if (!hasCompleteCheckoutLineItems(session)) {
611
- throw new errors_js_1.StripeResponseShapeError("Stripe Checkout response is missing complete provider-authored line-item evidence.", "line_items");
612
- }
613
- const lines = session.line_items.data.map((line) => {
614
- const price = objectRecord(line.price);
615
- const priceId = readNonemptyString(price.id);
616
- const quantity = readPositiveInteger(line.quantity);
617
- if (!priceId || !quantity) {
618
- throw new errors_js_1.StripeResponseShapeError("Stripe Checkout line-item evidence is missing Price identity or quantity.", "line_items.data.price");
619
- }
620
- const recurring = normalizeRecurringEvidence(price.recurring);
621
- const priceType = recurring ? "recurring" : "one_time";
622
- if ((price.type === "recurring" && !recurring) || (price.type === "one_time" && recurring)) {
623
- throw new errors_js_1.StripeResponseShapeError("Stripe Checkout Price type disagrees with recurring evidence.", "line_items.data.price.recurring");
624
- }
625
- const unitAmountMinor = readNonnegativeInteger(price.unit_amount);
626
- return {
627
- currency: readNonemptyString(price.currency)?.toLowerCase() ?? null,
628
- description: readNonemptyString(line.description),
629
- priceId,
630
- priceType,
631
- productId: readStripeObjectId(price.product),
632
- quantity,
633
- recurring,
634
- unitAmountDecimal: readStripeDecimal(price.unit_amount_decimal) ??
635
- (unitAmountMinor === null ? null : String(unitAmountMinor)),
636
- unitAmountMinor,
637
- };
638
- });
639
- return sortEvidence(lines);
640
- }
641
- function normalizeExpectedCheckoutLines(value) {
642
- if (!Array.isArray(value) || value.length === 0) {
643
- throw new server_js_1.ProxyCheckoutValidationError("Stripe Checkout requires line_items for exact cart-revision evidence.", { code: "cart_version_conflict", field: "line_items" });
644
- }
645
- const lines = value.map((rawLine) => {
646
- const line = objectRecord(rawLine);
647
- const quantity = readPositiveInteger(line.quantity) ?? 1;
648
- const priceId = readStripeObjectId(line.price);
649
- if (priceId) {
650
- return { kind: "price", priceId, quantity };
651
- }
652
- const priceData = objectRecord(line.price_data);
653
- const productData = objectRecord(priceData.product_data);
654
- const currency = readNonemptyString(priceData.currency)?.toLowerCase();
655
- const unitAmountMinor = readNonnegativeInteger(priceData.unit_amount);
656
- const unitAmountDecimal = readNonemptyString(priceData.unit_amount_decimal);
657
- const productId = readStripeObjectId(priceData.product);
658
- const productName = readNonemptyString(productData.name);
659
- if (!currency ||
660
- (!productId && !productName) ||
661
- (unitAmountMinor === null && unitAmountDecimal === null)) {
662
- throw new server_js_1.ProxyCheckoutValidationError("Stripe Checkout line_items must identify a Price or complete price_data.", { code: "cart_version_conflict", field: "line_items.price" });
663
- }
664
- return {
665
- currency,
666
- kind: "price_data",
667
- productId,
668
- productName,
669
- quantity,
670
- recurring: normalizeRecurringEvidence(priceData.recurring),
671
- unitAmountDecimal: unitAmountDecimal ?? String(unitAmountMinor),
672
- unitAmountMinor,
673
- };
674
- });
675
- return sortEvidence(lines);
676
- }
677
- function checkoutLinesMatch(expected, provider) {
678
- if (expected.length !== provider.length)
679
- return false;
680
- const remaining = [...provider];
681
- for (const expectedLine of expected) {
682
- const index = remaining.findIndex((providerLine) => expectedLine.kind === "price"
683
- ? expectedLine.priceId === providerLine.priceId &&
684
- expectedLine.quantity === providerLine.quantity
685
- : expectedLine.currency === providerLine.currency &&
686
- (expectedLine.productId
687
- ? expectedLine.productId === providerLine.productId
688
- : expectedLine.productName === providerLine.description) &&
689
- expectedLine.quantity === providerLine.quantity &&
690
- stableStringify(expectedLine.recurring) === stableStringify(providerLine.recurring) &&
691
- expectedLine.unitAmountDecimal === providerLine.unitAmountDecimal &&
692
- expectedLine.unitAmountMinor === providerLine.unitAmountMinor);
693
- if (index < 0)
694
- return false;
695
- remaining.splice(index, 1);
696
- }
697
- return remaining.length === 0;
698
- }
699
- function normalizeRecurringEvidence(value) {
700
- if (value === null || value === undefined)
701
- return null;
702
- const recurring = objectRecord(value);
703
- const interval = readNonemptyString(recurring.interval);
704
- const intervalCount = readPositiveInteger(recurring.interval_count) ?? 1;
705
- const usageType = readNonemptyString(recurring.usage_type) ?? "licensed";
706
- if (!interval) {
707
- throw new errors_js_1.StripeResponseShapeError("Stripe Checkout recurring Price evidence is missing its interval.", "line_items.data.price.recurring.interval");
708
- }
709
- return { interval, intervalCount, usageType };
710
- }
711
- function readNonemptyString(value) {
712
- return typeof value === "string" && value.length > 0 ? value : null;
713
- }
714
- function readStripeDecimal(value) {
715
- const rendered = typeof value === "string"
716
- ? value
717
- : typeof value === "number" && Number.isFinite(value)
718
- ? String(value)
719
- : typeof value === "object" &&
720
- value !== null &&
721
- "toString" in value &&
722
- typeof value.toString === "function"
723
- ? value.toString()
724
- : null;
725
- return rendered && /^\d+(?:\.\d+)?$/.test(rendered) ? rendered : null;
726
- }
727
- function readPositiveInteger(value) {
728
- return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
729
- }
730
- function readNonnegativeInteger(value) {
731
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
732
- }
733
- function readStripeObjectId(value) {
734
- if (typeof value === "string" && value.length > 0)
735
- return value;
736
- return readNonemptyString(objectRecord(value).id);
737
- }
738
- function objectRecord(value) {
739
- return typeof value === "object" && value !== null ? value : {};
740
- }
741
- function sortEvidence(values) {
742
- return [...values].sort((left, right) => stableStringify(left).localeCompare(stableStringify(right)));
743
- }
744
- function hashEvidence(domain, value) {
745
- return (0, node_crypto_1.createHash)("sha256")
746
- .update(domain, "utf8")
747
- .update("\0", "utf8")
748
- .update(stableStringify(value), "utf8")
749
- .digest("hex");
750
- }
751
- function stableStringify(value) {
752
- const normalized = normalizeJson(value, new Set());
753
- return JSON.stringify(normalized);
754
- }
755
- function normalizeJson(value, ancestors) {
756
- if (value === null || typeof value === "string" || typeof value === "boolean")
757
- return value;
758
- if (typeof value === "number") {
759
- if (!Number.isFinite(value))
760
- throw new TypeError("Stripe options must contain finite numbers.");
761
- return value;
762
- }
763
- if (Array.isArray(value)) {
764
- if (ancestors.has(value))
765
- throw new TypeError("Stripe options must not contain cycles.");
766
- ancestors.add(value);
767
- const result = value.map((item) => item === undefined ? null : normalizeJson(item, ancestors));
768
- ancestors.delete(value);
769
- return result;
770
- }
771
- if (typeof value === "object") {
772
- if (ancestors.has(value))
773
- throw new TypeError("Stripe options must not contain cycles.");
774
- ancestors.add(value);
775
- const result = {};
776
- for (const [key, item] of Object.entries(value).sort(([left], [right]) => left.localeCompare(right))) {
777
- if (item !== undefined)
778
- result[key] = normalizeJson(item, ancestors);
779
- }
780
- ancestors.delete(value);
781
- return result;
782
- }
783
- throw new TypeError("Stripe options must be JSON-compatible.");
784
- }
785
- function aggregateCauses(first, second) {
786
- return new Error("Both deterministic Stripe create attempts failed.", {
787
- cause: { first, second },
788
- });
789
- }