@proxy-checkout/stripe-server-js 0.1.0-prx-146.161.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
@@ -7,15 +7,10 @@
7
7
  * metadata placement, idempotency, and binding reconciliation.
8
8
  */
9
9
  import { assertCartBuyerReference, ProxyCheckoutValidationError } from "@proxy-checkout/server-js";
10
- import { acquireStripeRoot, assertNoConnectRequestOptions, checkoutCartConfirmation, fingerprintHostedCheckoutProviderOptions, recordOrReconcileLegacyCheckoutBinding, retrieveCheckoutSessionWithLineEvidence, } from "./acquisition.js";
11
- import { assertHostedNoCostCompatibility, assertStripePathCompatibility, } from "./compatibility.js";
12
- import { StripeResponseShapeError, UnsupportedStripeOptionError } from "./errors.js";
13
- import { assertCheckoutSessionBelongsToProxy, assertCheckoutSessionMetadataPreflight, assertProviderObjectBelongsToAcquisition, injectRequiredCheckoutSessionMetadata, } from "./metadata.js";
10
+ import { ProxyStripeOpenCheckoutError } from "./errors.js";
11
+ import { assertCheckoutSessionBelongsToProxy, injectRequiredCheckoutSessionMetadata, } from "./metadata.js";
14
12
  import { normalizePsp } from "./psp.js";
15
13
  export async function openCheckout(options) {
16
- if (usesAcquisitionContract(options)) {
17
- return openAcquisitionCheckout(options);
18
- }
19
14
  // Normalize the same way the API does on store, so the binding is recorded
20
15
  // under the normalized psp and later syncCheckoutCart validations compare
21
16
  // normalized-to-normalized.
@@ -135,7 +130,7 @@ export async function openCheckout(options) {
135
130
  const checkoutSession = (await options.stripe.checkout.sessions.create(injectRequiredCheckoutSessionMetadata(params, proxySession.id), buildStripeRequestOptions(options, proxySession.id, psp)));
136
131
  // Fail closed if the merchant forgot to place proxy_session_id metadata.
137
132
  assertCheckoutSessionBelongsToProxy(checkoutSession, proxySession.id);
138
- const binding = await recordOrReconcileLegacyCheckoutBinding({
133
+ const binding = await recordOrReconcileProviderBinding({
139
134
  checkoutSessionId: checkoutSession.id,
140
135
  expectedCartVersion: proxySession.cartVersion,
141
136
  commercialMode,
@@ -157,310 +152,6 @@ export async function openCheckout(options) {
157
152
  proxySessionId: proxySession.id,
158
153
  };
159
154
  }
160
- async function openAcquisitionCheckout(options) {
161
- if (options.commercialMode !== "one_time" ||
162
- (options.uiMode !== "hosted" && options.uiMode !== "embedded" && options.uiMode !== "custom") ||
163
- options.pricingMode === undefined) {
164
- throw new UnsupportedStripeOptionError("The explicit Checkout acquisition contract supports one_time hosted, embedded, and custom presentations only.", "uiMode");
165
- }
166
- if (normalizePsp(options.psp ?? "stripe") !== "stripe") {
167
- throw new UnsupportedStripeOptionError("The Stripe acquisition helper cannot route provider creation through another PSP.", "psp");
168
- }
169
- assertNoConnectRequestOptions(options.stripeRequestOptions);
170
- const compatibility = compatibilityDescriptor(options);
171
- assertStripePathCompatibility(checkoutCompatibilityPath(options.uiMode), compatibility);
172
- let proxySession = await options.proxy.sessions.payerOpened(options.proxySessionId, {
173
- requestId: options.requestId,
174
- });
175
- let cart = parseCheckoutCart(options, proxySession);
176
- if (proxySession.presentationState !== "checkout_available") {
177
- return terminalResultFromSession(proxySession, cart);
178
- }
179
- let offer = options.validateCart
180
- ? await options.validateCart({ cart, session: proxySession })
181
- : undefined;
182
- const reconciledCart = await options.reconcileCart?.({ cart, offer, session: proxySession });
183
- if (reconciledCart) {
184
- if (options.getCartBuyerReference) {
185
- assertCartBuyerReference(options.getCartBuyerReference(reconciledCart.cart), proxySession);
186
- }
187
- try {
188
- const updated = await options.proxy.sessions.cart.set(proxySession.id, {
189
- amountMinor: reconciledCart.amountMinor,
190
- cartSnapshot: reconciledCart.cartSnapshot,
191
- currency: reconciledCart.currency,
192
- expectedCartVersion: proxySession.cartVersion,
193
- requestId: options.requestId,
194
- });
195
- proxySession = {
196
- ...proxySession,
197
- amountMinor: updated.amountMinor,
198
- cartSnapshot: updated.cartSnapshot,
199
- cartVersion: updated.cartVersion,
200
- currency: updated.currency,
201
- };
202
- }
203
- catch (cause) {
204
- if (!isProxyConflict(cause))
205
- throw cause;
206
- const joined = await options.proxy.sessions.payerOpened(proxySession.id, {
207
- requestId: options.requestId,
208
- });
209
- if (!sameCheckoutCart(joined, reconciledCart))
210
- throw cause;
211
- proxySession = joined;
212
- }
213
- cart = reconciledCart.cart;
214
- if (options.validateCart)
215
- offer = await options.validateCart({ cart, session: proxySession });
216
- }
217
- const current = await options.proxy.sessions.payerOpened(proxySession.id, {
218
- requestId: options.requestId,
219
- });
220
- if (current.presentationState !== "checkout_available") {
221
- return terminalResultFromSession(current, cart);
222
- }
223
- if (!sameCheckoutCart(current, proxySession)) {
224
- throw new ProxyCheckoutValidationError(`Proxy session ${proxySession.id} cart changed while Checkout was being prepared.`, { code: "cart_version_conflict", field: "cart_version" });
225
- }
226
- proxySession = current;
227
- const builtParams = await options.buildCheckoutSessionParams({
228
- cart,
229
- offer,
230
- proxySession,
231
- stripe: options.stripe,
232
- });
233
- validateAcquisitionCheckoutParams(builtParams, options, proxySession);
234
- assertCheckoutSessionMetadataPreflight(builtParams, proxySession.id);
235
- // Stripe 12 has no hosted ui_mode create field, while newer Stripe responses
236
- // name the same provider value hosted_page. Omit only the hosted provider
237
- // alias; client-secret paths must send their exact generation-specific mode.
238
- const params = options.uiMode === "hosted" ? withoutHostedProviderUiMode(builtParams) : builtParams;
239
- if (options.pricingMode === "fixed" && proxySession.amountMinor === 0) {
240
- assertHostedNoCostCompatibility(compatibility);
241
- }
242
- const providerOptionsFingerprint = fingerprintHostedCheckoutProviderOptions(params);
243
- const acquisitionResult = await acquireStripeRoot({
244
- checkoutUiMode: options.uiMode,
245
- commercialMode: "one_time",
246
- integrationPath: "checkout_session",
247
- objectType: "checkout_session",
248
- pricingMode: options.pricingMode,
249
- create: async (reservation) => {
250
- const created = (await options.stripe.checkout.sessions.create(injectRequiredCheckoutSessionMetadata(params, proxySession.id, {
251
- contractVersion: "2",
252
- id: reservation.acquisitionAttempt.id,
253
- }), {
254
- ...options.stripeRequestOptions,
255
- idempotencyKey: reservation.stripe.idempotencyKeys.root,
256
- }));
257
- return (await retrieveCheckoutSessionWithLineEvidence({
258
- id: created.id,
259
- initial: created,
260
- requestOptions: options.stripeRequestOptions,
261
- stripe: options.stripe,
262
- }));
263
- },
264
- providerOptionsFingerprint,
265
- proxy: options.proxy,
266
- proxySession,
267
- requestId: options.requestId,
268
- retrieve: async (id) => (await retrieveCheckoutSessionWithLineEvidence({
269
- id,
270
- requestOptions: options.stripeRequestOptions,
271
- stripe: options.stripe,
272
- })),
273
- toConfirmation: (session) => checkoutCartConfirmation({
274
- cartVersion: proxySession.cartVersion,
275
- checkoutSession: session,
276
- expectedLineItems: params.line_items,
277
- }),
278
- validateObject: (session, acquisition) => {
279
- assertProviderObjectBelongsToAcquisition(session, {
280
- acquisitionAttemptId: acquisition.id,
281
- integrationPath: "checkout_session",
282
- proxySessionId: proxySession.id,
283
- });
284
- assertCheckoutProviderCart(session, proxySession, options.pricingMode ?? "fixed", params.line_items, options.uiMode ?? "hosted");
285
- },
286
- });
287
- if (acquisitionResult.outcome === "terminal") {
288
- return terminalResultFromSession(acquisitionResult.proxySession, cart);
289
- }
290
- const acquired = acquisitionResult;
291
- const presentation = checkoutPresentation(acquired.object, options.uiMode);
292
- return {
293
- acquisitionAttemptId: acquired.acquisition.id,
294
- binding: {
295
- providerCheckoutSessionId: acquired.object.id,
296
- proxySessionId: proxySession.id,
297
- psp: "stripe",
298
- updatedAt: acquired.acquisition.updatedAt,
299
- },
300
- cart,
301
- checkoutSession: acquired.object,
302
- checkoutSessionId: acquired.object.id,
303
- clientSecret: presentation.kind === "client_secret" ? presentation.clientSecret : null,
304
- offer,
305
- outcome: "ready",
306
- presentation,
307
- proxySession,
308
- proxySessionId: proxySession.id,
309
- };
310
- }
311
- function usesAcquisitionContract(options) {
312
- const values = [options.commercialMode, options.pricingMode, options.uiMode];
313
- const count = values.filter((value) => value !== undefined).length;
314
- if (count !== 0 && count !== values.length) {
315
- throw new ProxyCheckoutValidationError("commercialMode, pricingMode, and uiMode must be supplied together for the acquisition contract.", { code: "unsupported_provider_option", field: "commercialMode" });
316
- }
317
- return count === values.length;
318
- }
319
- function parseCheckoutCart(options, proxySession) {
320
- return options.cartSchema || options.getCartBuyerReference
321
- ? options.proxy.sessions.parseCart(proxySession, options.cartSchema ?? ((value) => value), { getBuyerReference: options.getCartBuyerReference })
322
- : proxySession.cartSnapshot;
323
- }
324
- function compatibilityDescriptor(options) {
325
- if (!options.compatibility && !options.stripeApiVersion)
326
- return undefined;
327
- return {
328
- ...options.compatibility,
329
- ...(options.stripeApiVersion ? { apiVersion: options.stripeApiVersion } : {}),
330
- };
331
- }
332
- function validateAcquisitionCheckoutParams(params, options, proxySession) {
333
- assertCheckoutAutomaticRecoveryDisabled(params, proxySession.id);
334
- if (checkoutCommercialMode(params, proxySession.id) !== options.commercialMode) {
335
- throw new UnsupportedStripeOptionError("Stripe Checkout mode does not match commercialMode.", "mode");
336
- }
337
- const uiMode = normalizeProviderUiMode(params.ui_mode);
338
- if (uiMode !== options.uiMode) {
339
- throw new UnsupportedStripeOptionError(`Stripe Checkout ui_mode ${uiMode} does not match requested ${options.uiMode}.`, "ui_mode");
340
- }
341
- if (uiMode === "hosted" && !params.success_url) {
342
- throw new UnsupportedStripeOptionError("Hosted Stripe Checkout requires success_url.", "success_url");
343
- }
344
- if (uiMode === "hosted" && params.return_url !== undefined) {
345
- throw new UnsupportedStripeOptionError("Hosted Stripe Checkout uses success_url and must not set return_url.", "return_url");
346
- }
347
- if (uiMode !== "hosted" && !params.return_url) {
348
- throw new UnsupportedStripeOptionError(`${uiMode} Stripe Checkout requires return_url.`, "return_url");
349
- }
350
- if (uiMode !== "hosted" && params.success_url !== undefined) {
351
- throw new UnsupportedStripeOptionError(`${uiMode} Stripe Checkout uses return_url and must not set success_url.`, "success_url");
352
- }
353
- const paymentIntentData = objectRecord(params.payment_intent_data);
354
- for (const field of [
355
- "application_fee_amount",
356
- "application_fee_percent",
357
- "capture_method",
358
- "on_behalf_of",
359
- "transfer_data",
360
- "transfer_group",
361
- ]) {
362
- if (paymentIntentData[field] !== undefined) {
363
- throw new UnsupportedStripeOptionError(`One-time Checkout does not support payment_intent_data.${field}.`, `payment_intent_data.${field}`);
364
- }
365
- }
366
- if (options.pricingMode === "fixed") {
367
- for (const field of [
368
- "allow_promotion_codes",
369
- "shipping_address_collection",
370
- "shipping_options",
371
- ]) {
372
- const value = params[field];
373
- if (value !== undefined && value !== false) {
374
- throw new UnsupportedStripeOptionError(`Fixed-price Checkout cannot use provider-finalized option ${field}.`, field);
375
- }
376
- }
377
- for (const field of ["adaptive_pricing", "automatic_tax"]) {
378
- const value = objectRecord(params[field]);
379
- if (value.enabled === true) {
380
- throw new UnsupportedStripeOptionError(`Fixed-price Checkout cannot enable provider-finalized option ${field}.`, `${field}.enabled`);
381
- }
382
- }
383
- const lineItems = Array.isArray(params.line_items) ? params.line_items : [];
384
- if (lineItems.some((line) => objectRecord(objectRecord(line).adjustable_quantity).enabled === true)) {
385
- throw new UnsupportedStripeOptionError("Fixed-price Checkout cannot enable adjustable line-item quantities.", "line_items.adjustable_quantity.enabled");
386
- }
387
- }
388
- }
389
- function assertCheckoutProviderCart(checkoutSession, proxySession, pricingMode, expectedLineItems, requestedUiMode) {
390
- const confirmation = checkoutCartConfirmation({
391
- cartVersion: 0,
392
- checkoutSession,
393
- expectedLineItems,
394
- });
395
- if (checkoutSession.mode !== undefined &&
396
- checkoutSession.mode !== null &&
397
- checkoutSession.mode !== "payment") {
398
- throw new StripeResponseShapeError("Stripe Checkout response is not payment mode.", "mode");
399
- }
400
- if (normalizeProviderUiMode(checkoutSession.ui_mode) !== requestedUiMode) {
401
- throw new StripeResponseShapeError(`Stripe Checkout response is not ${requestedUiMode} UI mode.`, "ui_mode");
402
- }
403
- if (confirmation.currency !== proxySession.currency ||
404
- (pricingMode === "fixed" && confirmation.amountMinor !== proxySession.amountMinor)) {
405
- throw new ProxyCheckoutValidationError(`Stripe Checkout Session ${checkoutSession.id} does not represent the current Proxy cart amount and currency.`, { code: "cart_version_conflict", field: "provider_amount" });
406
- }
407
- }
408
- function normalizeProviderUiMode(value) {
409
- if (value === undefined || value === null || value === "hosted" || value === "hosted_page") {
410
- return "hosted";
411
- }
412
- if (value === "embedded" || value === "embedded_page")
413
- return "embedded";
414
- if (value === "custom" || value === "elements")
415
- return "custom";
416
- return value;
417
- }
418
- function checkoutCompatibilityPath(uiMode) {
419
- return `one_time.checkout.${uiMode}`;
420
- }
421
- function checkoutPresentation(checkoutSession, uiMode) {
422
- const clientSecret = checkoutSession.client_secret;
423
- const url = checkoutSession.url;
424
- if (uiMode === "hosted") {
425
- if (!isSafeHostedCheckoutUrl(url)) {
426
- throw new StripeResponseShapeError("Hosted Stripe Checkout response did not contain a safe HTTPS redirect URL.", "url");
427
- }
428
- if (clientSecret !== undefined && clientSecret !== null) {
429
- throw new StripeResponseShapeError("Hosted Stripe Checkout response unexpectedly contained a client secret.", "client_secret");
430
- }
431
- return { kind: "redirect", url };
432
- }
433
- if (!isCheckoutSessionClientSecret(clientSecret)) {
434
- throw new StripeResponseShapeError(`${uiMode} Stripe Checkout response did not contain a valid Checkout Session client secret.`, "client_secret");
435
- }
436
- if (url !== undefined && url !== null) {
437
- throw new StripeResponseShapeError(`${uiMode} Stripe Checkout response unexpectedly contained a hosted redirect URL.`, "url");
438
- }
439
- return { clientSecret, kind: "client_secret", uiMode };
440
- }
441
- function isCheckoutSessionClientSecret(value) {
442
- return typeof value === "string" && /^cs_(?:test|live)_[A-Za-z0-9_]+$/.test(value);
443
- }
444
- function isSafeHostedCheckoutUrl(value) {
445
- if (typeof value !== "string" || value !== value.trim())
446
- return false;
447
- try {
448
- const parsed = new URL(value);
449
- return parsed.protocol === "https:" && parsed.username === "" && parsed.password === "";
450
- }
451
- catch {
452
- return false;
453
- }
454
- }
455
- function withoutHostedProviderUiMode(params) {
456
- if (params.ui_mode === undefined)
457
- return params;
458
- const { ui_mode: _providerUiMode, ...providerParams } = params;
459
- return providerParams;
460
- }
461
- function objectRecord(value) {
462
- return typeof value === "object" && value !== null ? value : {};
463
- }
464
155
  function terminalResultFromSession(proxySession, cart) {
465
156
  const common = {
466
157
  cart,
@@ -520,6 +211,31 @@ function bindingFromSession(proxySession) {
520
211
  updatedAt: proxySession.updatedAt,
521
212
  };
522
213
  }
214
+ async function recordOrReconcileProviderBinding(input) {
215
+ try {
216
+ return await input.proxy.sessions.recordProviderBinding(input.proxySessionId, {
217
+ commercialMode: input.commercialMode,
218
+ expectedCartVersion: input.expectedCartVersion,
219
+ providerCheckoutSessionId: input.checkoutSessionId,
220
+ psp: input.psp,
221
+ requestId: input.requestId,
222
+ });
223
+ }
224
+ catch (cause) {
225
+ const reconciled = await readReconciledBinding(input).catch(() => undefined);
226
+ if (reconciled) {
227
+ return reconciled;
228
+ }
229
+ if (isProxyConflict(cause)) {
230
+ throw cause;
231
+ }
232
+ throw new ProxyStripeOpenCheckoutError("Stripe Checkout Session was created but Proxy provider binding could not be recorded.", {
233
+ cause,
234
+ code: "provider_binding_failed",
235
+ providerCheckoutSessionId: input.checkoutSessionId,
236
+ });
237
+ }
238
+ }
523
239
  function sameCheckoutCart(actual, expected) {
524
240
  return (actual.amountMinor === expected.amountMinor &&
525
241
  actual.currency === expected.currency &&
@@ -551,6 +267,18 @@ function assertCheckoutAutomaticRecoveryDisabled(params, proxySessionId) {
551
267
  throw new ProxyCheckoutValidationError(`Proxy session ${proxySessionId} does not allow Stripe Checkout automatic recovery because replacement roots require explicit Proxy supersession.`, { code: "unsupported_provider_option", field: "after_expiration.recovery.enabled" });
552
268
  }
553
269
  }
270
+ async function readReconciledBinding(input) {
271
+ const current = await input.proxy.sessions.retrieve(input.proxySessionId, {
272
+ requestId: input.requestId,
273
+ });
274
+ if (current.providerCheckoutSessionId !== input.checkoutSessionId ||
275
+ current.providerCheckoutSessionPsp !== input.psp) {
276
+ return undefined;
277
+ }
278
+ const checkoutSession = await input.stripe.checkout.sessions.retrieve(input.checkoutSessionId, undefined, input.stripeRequestOptions);
279
+ assertCheckoutSessionBelongsToProxy(checkoutSession, input.proxySessionId);
280
+ return bindingFromSession(current);
281
+ }
554
282
  function isProxyConflict(error) {
555
283
  return (typeof error === "object" &&
556
284
  error !== null &&
@@ -6,7 +6,7 @@
6
6
  * safest order with deterministic rollback/reconciliation on failure.
7
7
  */
8
8
  import { type JsonObject, type MerchantProxySession, type ProxyCartValidator, type ProxyCheckoutServerClient } from "@proxy-checkout/server-js";
9
- import type { StripeCheckoutSessionLike, StripeMutableCheckoutClientLike, StripeRequestOptionsWithoutIdempotencyLike } from "./types.js";
9
+ import type { StripeCheckoutSessionLike, StripeClientLike, StripeRequestOptionsWithoutIdempotencyLike } from "./types.js";
10
10
  /** The next cart, expressed both as Proxy state and the typed merchant cart. */
11
11
  export interface SyncedCart<TCart> {
12
12
  readonly amountMinor: number;
@@ -14,7 +14,7 @@ export interface SyncedCart<TCart> {
14
14
  readonly cartSnapshot: JsonObject;
15
15
  readonly currency: string;
16
16
  }
17
- export interface SyncCheckoutCartOptions<TStripe extends StripeMutableCheckoutClientLike, TCart, TInput> {
17
+ export interface SyncCheckoutCartOptions<TStripe extends StripeClientLike, TCart, TInput> {
18
18
  /** Compute the next cart from current state and the merchant input. */
19
19
  readonly buildNextCart: (input: {
20
20
  currentCart: TCart;
@@ -44,4 +44,4 @@ export interface SyncCheckoutCartResult<TCart> {
44
44
  readonly checkoutSession: StripeCheckoutSessionLike;
45
45
  readonly currency: string;
46
46
  }
47
- export declare function syncCheckoutCart<TStripe extends StripeMutableCheckoutClientLike, TCart, TInput>(options: SyncCheckoutCartOptions<TStripe, TCart, TInput>): Promise<SyncCheckoutCartResult<TCart>>;
47
+ export declare function syncCheckoutCart<TStripe extends StripeClientLike, TCart, TInput>(options: SyncCheckoutCartOptions<TStripe, TCart, TInput>): Promise<SyncCheckoutCartResult<TCart>>;
@@ -6,9 +6,8 @@
6
6
  * safest order with deterministic rollback/reconciliation on failure.
7
7
  */
8
8
  import { ProxyCheckoutValidationError, } from "@proxy-checkout/server-js";
9
- import { assertNoConnectRequestOptions, checkoutCartConfirmation, retrieveCheckoutSessionWithLineEvidence, } from "./acquisition.js";
10
9
  import { ProxyStripeCartSyncError } from "./errors.js";
11
- import { assertCheckoutSessionBelongsToProxy, assertProviderObjectBelongsToAcquisition, } from "./metadata.js";
10
+ import { assertCheckoutSessionBelongsToProxy } from "./metadata.js";
12
11
  import { normalizePsp } from "./psp.js";
13
12
  export async function syncCheckoutCart(options) {
14
13
  // Normalize the same way the API does on store, so the binding comparison is
@@ -18,32 +17,11 @@ export async function syncCheckoutCart(options) {
18
17
  const session = await options.proxy.sessions.retrieve(options.proxySessionId, {
19
18
  requestId: options.requestId,
20
19
  });
21
- // Ownership: validate the stored binding and, for contract-v2 objects, the
22
- // exact acquisition before any Stripe read or mutation.
20
+ // Ownership: validate against the stored binding first, then defensively
21
+ // against the Stripe object's own metadata.
23
22
  assertSessionBoundTo(session, options.checkoutSessionId, psp);
24
- const acquisition = session.currentAcquisitionAttempt === null
25
- ? null
26
- : await resolveExplicitCheckoutAcquisition(options, session);
27
- if (acquisition)
28
- assertNoConnectRequestOptions(options.stripeRequestOptions);
29
- if (acquisition?.checkoutUiMode === "hosted") {
30
- throw new ProxyStripeCartSyncError("Hosted Stripe Checkout Sessions do not support line-item mutation. Expire the bound Session, wait for Proxy to observe provider-confirmed expiry, update the versioned Proxy cart, supersede the terminal acquisition, and call openCheckout again.", {
31
- cause: new Error("Stripe hosted Checkout requires provider-confirmed replacement"),
32
- code: "hosted_replacement_required",
33
- reconciled: true,
34
- });
35
- }
36
23
  const stripeSession = await options.stripe.checkout.sessions.retrieve(options.checkoutSessionId, undefined, options.stripeRequestOptions);
37
- if (acquisition) {
38
- assertProviderObjectBelongsToAcquisition(stripeSession, {
39
- acquisitionAttemptId: acquisition.id,
40
- integrationPath: "checkout_session",
41
- proxySessionId: options.proxySessionId,
42
- });
43
- }
44
- else {
45
- assertCheckoutSessionBelongsToProxy(stripeSession, options.proxySessionId);
46
- }
24
+ assertCheckoutSessionBelongsToProxy(stripeSession, options.proxySessionId);
47
25
  const currentCart = options.cartSchema
48
26
  ? options.proxy.sessions.parseCart(session, options.cartSchema)
49
27
  : session.cartSnapshot;
@@ -52,6 +30,12 @@ export async function syncCheckoutCart(options) {
52
30
  currentSession: session,
53
31
  input: options.input,
54
32
  });
33
+ const prior = {
34
+ amountMinor: session.amountMinor,
35
+ cartSnapshot: session.cartSnapshot,
36
+ cartVersion: session.cartVersion,
37
+ currency: session.currency,
38
+ };
55
39
  const stripeUpdateParams = {
56
40
  line_items: options.buildStripeLineItems(next.cart),
57
41
  };
@@ -73,22 +57,19 @@ export async function syncCheckoutCart(options) {
73
57
  updatedCheckout = await options.stripe.checkout.sessions.update(options.checkoutSessionId, stripeUpdateParams, stripeUpdateOptions);
74
58
  }
75
59
  catch (retryCause) {
76
- throw new ProxyStripeCartSyncError("Stripe checkout update failed after an idempotent retry. The versioned Proxy cart remains committed so another concurrent viewer cannot be orphaned; provider success is gated until that revision is confirmed.", { cause: retryCause, code: "stripe_update_failed", reconciled: false });
77
- }
78
- }
79
- if (acquisition) {
80
- try {
81
- await confirmCheckoutCartRevision(options, {
82
- acquisition,
83
- cartVersion: proxyCartUpdate.cartVersion,
84
- checkoutSession: updatedCheckout,
85
- currency: proxyCartUpdate.currency,
86
- expectedAmountMinor: proxyCartUpdate.amountMinor,
87
- expectedLineItems: stripeUpdateParams.line_items,
88
- });
89
- }
90
- catch (cause) {
91
- throw new ProxyStripeCartSyncError("Stripe checkout was updated, but Proxy could not confirm the provider-authored cart revision; payment remains non-provisionable until reconciliation succeeds.", { cause, code: "reconciliation_failed", reconciled: false });
60
+ try {
61
+ await options.proxy.sessions.cart.set(options.proxySessionId, {
62
+ amountMinor: prior.amountMinor,
63
+ cartSnapshot: prior.cartSnapshot,
64
+ currency: prior.currency,
65
+ expectedCartVersion: proxyCartUpdate.cartVersion,
66
+ requestId: options.requestId,
67
+ });
68
+ }
69
+ catch (rollbackCause) {
70
+ throw new ProxyStripeCartSyncError("Stripe checkout update failed after an idempotent retry and the Proxy cart rollback failed; carts may be out of sync.", { cause: retryCause, code: "reconciliation_failed", reconciled: false, rollbackCause });
71
+ }
72
+ throw new ProxyStripeCartSyncError("Stripe checkout update failed after an idempotent retry; the Proxy cart rollback was attempted, but Stripe state may still be ambiguous.", { cause: retryCause, code: "stripe_update_failed", reconciled: false });
92
73
  }
93
74
  }
94
75
  return {
@@ -99,57 +80,6 @@ export async function syncCheckoutCart(options) {
99
80
  currency: proxyCartUpdate.currency,
100
81
  };
101
82
  }
102
- async function confirmCheckoutCartRevision(options, input) {
103
- const checkoutSession = await retrieveCheckoutSessionWithLineEvidence({
104
- id: options.checkoutSessionId,
105
- initial: input.checkoutSession,
106
- requestOptions: options.stripeRequestOptions,
107
- stripe: options.stripe,
108
- });
109
- const confirmation = checkoutCartConfirmation({
110
- cartVersion: input.cartVersion,
111
- checkoutSession,
112
- expectedLineItems: input.expectedLineItems,
113
- });
114
- const acquisition = input.acquisition;
115
- if (acquisition.commercialMode !== "one_time" ||
116
- acquisition.integrationPath !== "checkout_session") {
117
- return;
118
- }
119
- if (acquisition.pricingMode === "fixed" &&
120
- confirmation.amountMinor !== input.expectedAmountMinor) {
121
- throw new Error("Stripe Checkout total does not match the fixed Proxy cart amount.");
122
- }
123
- if (confirmation.currency !== input.currency) {
124
- throw new Error("Stripe Checkout currency does not match the Proxy cart currency.");
125
- }
126
- await options.proxy.providerAcquisitions.confirmProviderAcquisitionCart(options.proxySessionId, acquisition.id, {
127
- ...confirmation,
128
- expectedVersion: acquisition.version,
129
- requestId: options.requestId,
130
- });
131
- }
132
- async function resolveExplicitCheckoutAcquisition(options, session) {
133
- const summary = session.currentAcquisitionAttempt;
134
- if (summary === null || summary.integrationPath !== "checkout_session") {
135
- throw acquisitionBindingMismatch(session.id, "current_acquisition_attempt.integration_path", "The current Proxy acquisition is not a Checkout Session acquisition.");
136
- }
137
- const acquisitions = await options.proxy.providerAcquisitions.listProviderAcquisitions(session.id, { requestId: options.requestId });
138
- const acquisition = acquisitions.find((candidate) => candidate.id === summary.id);
139
- if (acquisition?.psp !== "stripe" ||
140
- acquisition.integrationPath !== "checkout_session" ||
141
- acquisition.providerRootObjectType !== "checkout_session" ||
142
- acquisition.providerRootObjectId !== options.checkoutSessionId) {
143
- throw acquisitionBindingMismatch(session.id, "current_acquisition_attempt", "The current Proxy acquisition is not bound to the requested Stripe Checkout Session.");
144
- }
145
- return acquisition;
146
- }
147
- function acquisitionBindingMismatch(proxySessionId, field, message) {
148
- return new ProxyCheckoutValidationError(`${message} Proxy session: ${proxySessionId}.`, {
149
- code: "provider_binding_mismatch",
150
- field,
151
- });
152
- }
153
83
  function buildStripeUpdateRequestOptions(options, cartVersion) {
154
84
  return {
155
85
  ...options.stripeRequestOptions,