@proxy-checkout/stripe-server-js 0.0.1 → 0.1.0-prx-124.137.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ Server-side Stripe adapter for Proxy Checkout. It owns the Stripe-specific integ
4
4
 
5
5
  - **`requiredMetadata(proxySession)`** — the `proxy_session_id` metadata Proxy needs on the Checkout Session, Subscription, and PaymentIntent.
6
6
  - **`assertCheckoutSessionBelongsToProxy` / `assertSubscriptionBelongsToProxy` / `assertPaymentIntentBelongsToProxy`** — provider-object ownership checks.
7
- - **`openCheckout(...)`** — records payer-opened, validates/reconciles the typed cart with rollback, injects required metadata, creates the Stripe Checkout Session with deterministic idempotency, records the Proxy provider binding, and returns the client secret. The merchant only supplies app-specific Stripe params.
7
+ - **`openCheckout(...)`** — records payer-opened, validates/reconciles the typed cart with rollback, injects required metadata, creates or joins the Stripe Checkout Session with deterministic idempotency, and records the Proxy provider binding. It returns provider-create material only for a `ready` outcome; terminal outcomes retain the shared cart without starting Stripe.
8
8
  - **`syncCheckoutCart(...)`** — validates the stored Proxy provider binding, then updates the Proxy cart and the Stripe Checkout `line_items` in the safest order with deterministic rollback/reconciliation (`ProxyStripeCartSyncError`).
9
9
 
10
10
  PSP-agnostic primitives (sessions, subscriptions, webhooks, lifecycle) live in [`@proxy-checkout/server-js`](https://www.npmjs.com/package/@proxy-checkout/server-js). This package is an additive adapter on top and is dependency-free: you inject your own `Stripe` instance and your `@proxy-checkout/server-js` client.
@@ -38,19 +38,23 @@ const checkout = await openCheckout({
38
38
  ui_mode: "custom",
39
39
  }),
40
40
  });
41
- // checkout.clientSecret -> render Stripe Elements
42
-
43
- // Cart edits go through the binding-validated sync helper.
44
- await syncCheckoutCart({
45
- proxy,
46
- stripe,
47
- proxySessionId,
48
- checkoutSessionId: checkout.checkoutSession.id,
49
- cartSchema,
50
- input: { interval: "year" },
51
- buildNextCart: ({ currentCart, input }) => buildMerchantCart(currentCart, input),
52
- buildStripeLineItems: (cart) => cart.lineItems,
53
- });
41
+ if (checkout.outcome === "ready") {
42
+ // checkout.clientSecret -> render Stripe Elements
43
+
44
+ // Cart edits go through the binding-validated sync helper.
45
+ await syncCheckoutCart({
46
+ proxy,
47
+ stripe,
48
+ proxySessionId,
49
+ checkoutSessionId: checkout.checkoutSession.id,
50
+ cartSchema,
51
+ input: { interval: "year" },
52
+ buildNextCart: ({ currentCart, input }) => buildMerchantCart(currentCart, input),
53
+ buildStripeLineItems: (cart) => cart.lineItems,
54
+ });
55
+ } else {
56
+ // Render checkout.cart plus checkout.outcome/sessionStatus. Do not start Stripe.
57
+ }
54
58
  ```
55
59
 
56
60
  Use the delegated-checkout guide from your Proxy onboarding materials for the
@@ -1,6 +1,6 @@
1
1
  export { type ProxyStripeCartSyncCode, ProxyStripeCartSyncError, type ProxyStripeOpenCheckoutCode, ProxyStripeOpenCheckoutError, } from "./errors.js";
2
2
  export { assertCheckoutSessionBelongsToProxy, assertPaymentIntentBelongsToProxy, assertSubscriptionBelongsToProxy, injectRequiredCheckoutSessionMetadata, PROXY_SESSION_METADATA_KEY, type ProxySessionMetadata, type ProxyStripeRequiredMetadata, requiredMetadata, } from "./metadata.js";
3
- export { type OpenCheckoutOptions, type OpenCheckoutReconciledCart, type OpenCheckoutResult, openCheckout, } from "./open-checkout.js";
3
+ export { type OpenCheckoutOptions, type OpenCheckoutReconciledCart, type OpenCheckoutResponse, type OpenCheckoutResult, type OpenCheckoutTerminalResult, openCheckout, } from "./open-checkout.js";
4
4
  export { normalizePsp } from "./psp.js";
5
5
  export { type SyncCheckoutCartOptions, type SyncCheckoutCartResult, type SyncedCart, syncCheckoutCart, } from "./sync-checkout-cart.js";
6
6
  export type { StripeCheckoutSessionCreateParams, StripeCheckoutSessionLike, StripeCheckoutSessionsApiLike, StripeCheckoutSessionUpdateParams, StripeClientLike, StripeMetadata, StripePaymentIntentLike, StripeRequestOptionsLike, StripeSubscriptionLike, } from "./types.js";
@@ -1,6 +1,6 @@
1
1
  export { type ProxyStripeCartSyncCode, ProxyStripeCartSyncError, type ProxyStripeOpenCheckoutCode, ProxyStripeOpenCheckoutError, } from "./errors.js";
2
2
  export { assertCheckoutSessionBelongsToProxy, assertPaymentIntentBelongsToProxy, assertSubscriptionBelongsToProxy, injectRequiredCheckoutSessionMetadata, PROXY_SESSION_METADATA_KEY, type ProxySessionMetadata, type ProxyStripeRequiredMetadata, requiredMetadata, } from "./metadata.js";
3
- export { type OpenCheckoutOptions, type OpenCheckoutReconciledCart, type OpenCheckoutResult, openCheckout, } from "./open-checkout.js";
3
+ export { type OpenCheckoutOptions, type OpenCheckoutReconciledCart, type OpenCheckoutResponse, type OpenCheckoutResult, type OpenCheckoutTerminalResult, openCheckout, } from "./open-checkout.js";
4
4
  export { normalizePsp } from "./psp.js";
5
5
  export { type SyncCheckoutCartOptions, type SyncCheckoutCartResult, type SyncedCart, syncCheckoutCart, } from "./sync-checkout-cart.js";
6
6
  export type { StripeCheckoutSessionCreateParams, StripeCheckoutSessionLike, StripeCheckoutSessionsApiLike, StripeCheckoutSessionUpdateParams, StripeClientLike, StripeMetadata, StripePaymentIntentLike, StripeRequestOptionsLike, StripeSubscriptionLike, } from "./types.js";
@@ -54,7 +54,28 @@ export interface OpenCheckoutResult<TCart, TOffer, TSession> {
54
54
  readonly checkoutSession: TSession;
55
55
  readonly clientSecret: string | null;
56
56
  readonly offer: TOffer;
57
+ readonly outcome: "ready";
57
58
  readonly proxySession: PayerOpenedResult;
58
59
  readonly proxySessionId: string;
59
60
  }
60
- export declare function openCheckout<TStripe extends StripeClientLike, TCart = unknown, TOffer = undefined, TSession extends StripeCheckoutSessionLike = StripeCheckoutSessionLike>(options: OpenCheckoutOptions<TStripe, TCart, TOffer>): Promise<OpenCheckoutResult<TCart, TOffer, TSession>>;
61
+ export type OpenCheckoutTerminalResult<TCart> = {
62
+ readonly cart: TCart;
63
+ readonly outcome: "already_paid";
64
+ readonly proxySession: PayerOpenedResult;
65
+ readonly proxySessionId: string;
66
+ readonly sessionStatus: "paid" | "provisionable" | "provisioned" | "provisioning_failed";
67
+ } | {
68
+ readonly cart: TCart;
69
+ readonly outcome: "action_required";
70
+ readonly proxySession: PayerOpenedResult;
71
+ readonly proxySessionId: string;
72
+ readonly sessionStatus: "merchant_action_required";
73
+ } | {
74
+ readonly cart: TCart;
75
+ readonly outcome: "unavailable";
76
+ readonly proxySession: PayerOpenedResult;
77
+ readonly proxySessionId: string;
78
+ readonly sessionStatus: "cancelled" | "created" | "expired" | "failed";
79
+ };
80
+ export type OpenCheckoutResponse<TCart, TOffer, TSession> = OpenCheckoutResult<TCart, TOffer, TSession> | OpenCheckoutTerminalResult<TCart>;
81
+ export declare function openCheckout<TStripe extends StripeClientLike, TCart = unknown, TOffer = undefined, TSession extends StripeCheckoutSessionLike = StripeCheckoutSessionLike>(options: OpenCheckoutOptions<TStripe, TCart, TOffer>): Promise<OpenCheckoutResponse<TCart, TOffer, TSession>>;
@@ -54,7 +54,28 @@ export interface OpenCheckoutResult<TCart, TOffer, TSession> {
54
54
  readonly checkoutSession: TSession;
55
55
  readonly clientSecret: string | null;
56
56
  readonly offer: TOffer;
57
+ readonly outcome: "ready";
57
58
  readonly proxySession: PayerOpenedResult;
58
59
  readonly proxySessionId: string;
59
60
  }
60
- export declare function openCheckout<TStripe extends StripeClientLike, TCart = unknown, TOffer = undefined, TSession extends StripeCheckoutSessionLike = StripeCheckoutSessionLike>(options: OpenCheckoutOptions<TStripe, TCart, TOffer>): Promise<OpenCheckoutResult<TCart, TOffer, TSession>>;
61
+ export type OpenCheckoutTerminalResult<TCart> = {
62
+ readonly cart: TCart;
63
+ readonly outcome: "already_paid";
64
+ readonly proxySession: PayerOpenedResult;
65
+ readonly proxySessionId: string;
66
+ readonly sessionStatus: "paid" | "provisionable" | "provisioned" | "provisioning_failed";
67
+ } | {
68
+ readonly cart: TCart;
69
+ readonly outcome: "action_required";
70
+ readonly proxySession: PayerOpenedResult;
71
+ readonly proxySessionId: string;
72
+ readonly sessionStatus: "merchant_action_required";
73
+ } | {
74
+ readonly cart: TCart;
75
+ readonly outcome: "unavailable";
76
+ readonly proxySession: PayerOpenedResult;
77
+ readonly proxySessionId: string;
78
+ readonly sessionStatus: "cancelled" | "created" | "expired" | "failed";
79
+ };
80
+ export type OpenCheckoutResponse<TCart, TOffer, TSession> = OpenCheckoutResult<TCart, TOffer, TSession> | OpenCheckoutTerminalResult<TCart>;
81
+ export declare function openCheckout<TStripe extends StripeClientLike, TCart = unknown, TOffer = undefined, TSession extends StripeCheckoutSessionLike = StripeCheckoutSessionLike>(options: OpenCheckoutOptions<TStripe, TCart, TOffer>): Promise<OpenCheckoutResponse<TCart, TOffer, TSession>>;
@@ -27,6 +27,13 @@ async function openCheckout(options) {
27
27
  let cart = options.cartSchema || options.getCartBuyerReference
28
28
  ? options.proxy.sessions.parseCart(proxySession, options.cartSchema ?? ((value) => value), { getBuyerReference: options.getCartBuyerReference })
29
29
  : proxySession.cartSnapshot;
30
+ if (proxySession.presentationState !== "checkout_available") {
31
+ return terminalResultFromSession(proxySession, cart);
32
+ }
33
+ if (proxySession.status === "payment_pending" &&
34
+ proxySession.providerCheckoutSessionId === null) {
35
+ throw new server_js_1.ProxyCheckoutValidationError(`Proxy session ${proxySession.id} already has payment preparation in progress without a bound Stripe Checkout Session.`, { code: "provider_binding_missing", field: "provider_checkout_session_id" });
36
+ }
30
37
  let offer = options.validateCart
31
38
  ? await options.validateCart({ cart, session: proxySession })
32
39
  : undefined;
@@ -40,10 +47,14 @@ async function openCheckout(options) {
40
47
  checkoutSession,
41
48
  clientSecret: checkoutSession.client_secret ?? null,
42
49
  offer,
50
+ outcome: "ready",
43
51
  proxySession,
44
52
  proxySessionId: proxySession.id,
45
53
  };
46
54
  }
55
+ if (proxySession.currentAcquisitionAttempt !== null) {
56
+ throw new server_js_1.ProxyCheckoutValidationError(`Proxy session ${proxySession.id} already has acquisition ${proxySession.currentAcquisitionAttempt.id} in progress; openCheckout will not create an unrelated Stripe Checkout Session.`, { code: "provider_acquisition_in_progress", field: "current_acquisition_attempt" });
57
+ }
47
58
  let rollbackCart;
48
59
  const reconciledCart = await options.reconcileCart?.({ cart, offer, session: proxySession });
49
60
  if (reconciledCart) {
@@ -87,11 +98,14 @@ async function openCheckout(options) {
87
98
  proxySession,
88
99
  stripe: options.stripe,
89
100
  });
101
+ assertCheckoutAutomaticRecoveryDisabled(params, proxySession.id);
102
+ const commercialMode = checkoutCommercialMode(params, proxySession.id);
90
103
  const checkoutSession = (await options.stripe.checkout.sessions.create((0, metadata_js_1.injectRequiredCheckoutSessionMetadata)(params, proxySession.id), buildStripeRequestOptions(options, proxySession.id, psp)));
91
104
  // Fail closed if the merchant forgot to place proxy_session_id metadata.
92
105
  (0, metadata_js_1.assertCheckoutSessionBelongsToProxy)(checkoutSession, proxySession.id);
93
106
  const binding = await recordOrReconcileProviderBinding({
94
107
  checkoutSessionId: checkoutSession.id,
108
+ commercialMode,
95
109
  proxy: options.proxy,
96
110
  proxySessionId: proxySession.id,
97
111
  psp,
@@ -105,6 +119,7 @@ async function openCheckout(options) {
105
119
  checkoutSession,
106
120
  clientSecret: checkoutSession.client_secret ?? null,
107
121
  offer,
122
+ outcome: "ready",
108
123
  proxySession,
109
124
  proxySessionId: proxySession.id,
110
125
  };
@@ -122,6 +137,39 @@ async function openCheckout(options) {
122
137
  throw cause;
123
138
  }
124
139
  }
140
+ function terminalResultFromSession(proxySession, cart) {
141
+ const common = {
142
+ cart,
143
+ proxySession,
144
+ proxySessionId: proxySession.id,
145
+ };
146
+ switch (proxySession.presentationState) {
147
+ case "already_paid":
148
+ if (proxySession.status !== "paid" &&
149
+ proxySession.status !== "provisionable" &&
150
+ proxySession.status !== "provisioned" &&
151
+ proxySession.status !== "provisioning_failed") {
152
+ break;
153
+ }
154
+ return { ...common, outcome: "already_paid", sessionStatus: proxySession.status };
155
+ case "action_required":
156
+ if (proxySession.status !== "merchant_action_required") {
157
+ break;
158
+ }
159
+ return { ...common, outcome: "action_required", sessionStatus: proxySession.status };
160
+ case "unavailable":
161
+ if (proxySession.status !== "cancelled" &&
162
+ proxySession.status !== "created" &&
163
+ proxySession.status !== "expired" &&
164
+ proxySession.status !== "failed") {
165
+ break;
166
+ }
167
+ return { ...common, outcome: "unavailable", sessionStatus: proxySession.status };
168
+ case "checkout_available":
169
+ break;
170
+ }
171
+ throw new server_js_1.ProxyCheckoutValidationError(`Proxy session ${proxySession.id} returned an inconsistent presentation and lifecycle status.`, { code: "invalid_session_presentation", field: "presentation_state" });
172
+ }
125
173
  function buildStripeRequestOptions(options, proxySessionId, psp) {
126
174
  return {
127
175
  ...options.stripeRequestOptions,
@@ -170,6 +218,7 @@ async function rollbackReconciledCart(input) {
170
218
  async function recordOrReconcileProviderBinding(input) {
171
219
  try {
172
220
  return await input.proxy.sessions.recordProviderBinding(input.proxySessionId, {
221
+ commercialMode: input.commercialMode,
173
222
  providerCheckoutSessionId: input.checkoutSessionId,
174
223
  psp: input.psp,
175
224
  requestId: input.requestId,
@@ -190,6 +239,20 @@ async function recordOrReconcileProviderBinding(input) {
190
239
  });
191
240
  }
192
241
  }
242
+ function checkoutCommercialMode(params, proxySessionId) {
243
+ if (params.mode === "payment") {
244
+ return "one_time";
245
+ }
246
+ if (params.mode === "subscription") {
247
+ return "subscription";
248
+ }
249
+ throw new server_js_1.ProxyCheckoutValidationError(`Proxy session ${proxySessionId} requires Stripe Checkout mode payment or subscription.`, { code: "unsupported_provider_option", field: "mode" });
250
+ }
251
+ function assertCheckoutAutomaticRecoveryDisabled(params, proxySessionId) {
252
+ if (params.after_expiration?.recovery?.enabled === true) {
253
+ throw new server_js_1.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" });
254
+ }
255
+ }
193
256
  async function readReconciledBinding(input) {
194
257
  const current = await input.proxy.sessions.retrieve(input.proxySessionId, {
195
258
  requestId: input.requestId,
@@ -27,6 +27,11 @@ export interface StripeCheckoutSessionUpdateParams {
27
27
  readonly metadata?: StripeMetadataParam;
28
28
  }
29
29
  export type StripeCheckoutSessionCreateParams = object & {
30
+ readonly after_expiration?: {
31
+ readonly recovery?: {
32
+ readonly enabled?: boolean;
33
+ };
34
+ };
30
35
  readonly metadata?: StripeMetadataParam;
31
36
  readonly mode?: "payment" | "setup" | "subscription";
32
37
  readonly payment_intent_data?: unknown;
@@ -27,6 +27,11 @@ export interface StripeCheckoutSessionUpdateParams {
27
27
  readonly metadata?: StripeMetadataParam;
28
28
  }
29
29
  export type StripeCheckoutSessionCreateParams = object & {
30
+ readonly after_expiration?: {
31
+ readonly recovery?: {
32
+ readonly enabled?: boolean;
33
+ };
34
+ };
30
35
  readonly metadata?: StripeMetadataParam;
31
36
  readonly mode?: "payment" | "setup" | "subscription";
32
37
  readonly payment_intent_data?: unknown;
@@ -1,2 +1,2 @@
1
1
  export declare const proxyCheckoutStripeServerSdkName = "@proxy-checkout/stripe-server-js";
2
- export declare const proxyCheckoutStripeServerSdkVersion = "0.0.1";
2
+ export declare const proxyCheckoutStripeServerSdkVersion = "0.1.0-prx-124.137.1";
@@ -1,2 +1,2 @@
1
1
  export declare const proxyCheckoutStripeServerSdkName = "@proxy-checkout/stripe-server-js";
2
- export declare const proxyCheckoutStripeServerSdkVersion = "0.0.1";
2
+ export declare const proxyCheckoutStripeServerSdkVersion = "0.1.0-prx-124.137.1";
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.proxyCheckoutStripeServerSdkVersion = exports.proxyCheckoutStripeServerSdkName = void 0;
4
4
  exports.proxyCheckoutStripeServerSdkName = "@proxy-checkout/stripe-server-js";
5
- exports.proxyCheckoutStripeServerSdkVersion = "0.0.1";
5
+ exports.proxyCheckoutStripeServerSdkVersion = "0.1.0-prx-124.137.1";
@@ -1,6 +1,6 @@
1
1
  export { type ProxyStripeCartSyncCode, ProxyStripeCartSyncError, type ProxyStripeOpenCheckoutCode, ProxyStripeOpenCheckoutError, } from "./errors.js";
2
2
  export { assertCheckoutSessionBelongsToProxy, assertPaymentIntentBelongsToProxy, assertSubscriptionBelongsToProxy, injectRequiredCheckoutSessionMetadata, PROXY_SESSION_METADATA_KEY, type ProxySessionMetadata, type ProxyStripeRequiredMetadata, requiredMetadata, } from "./metadata.js";
3
- export { type OpenCheckoutOptions, type OpenCheckoutReconciledCart, type OpenCheckoutResult, openCheckout, } from "./open-checkout.js";
3
+ export { type OpenCheckoutOptions, type OpenCheckoutReconciledCart, type OpenCheckoutResponse, type OpenCheckoutResult, type OpenCheckoutTerminalResult, openCheckout, } from "./open-checkout.js";
4
4
  export { normalizePsp } from "./psp.js";
5
5
  export { type SyncCheckoutCartOptions, type SyncCheckoutCartResult, type SyncedCart, syncCheckoutCart, } from "./sync-checkout-cart.js";
6
6
  export type { StripeCheckoutSessionCreateParams, StripeCheckoutSessionLike, StripeCheckoutSessionsApiLike, StripeCheckoutSessionUpdateParams, StripeClientLike, StripeMetadata, StripePaymentIntentLike, StripeRequestOptionsLike, StripeSubscriptionLike, } from "./types.js";
@@ -54,7 +54,28 @@ export interface OpenCheckoutResult<TCart, TOffer, TSession> {
54
54
  readonly checkoutSession: TSession;
55
55
  readonly clientSecret: string | null;
56
56
  readonly offer: TOffer;
57
+ readonly outcome: "ready";
57
58
  readonly proxySession: PayerOpenedResult;
58
59
  readonly proxySessionId: string;
59
60
  }
60
- export declare function openCheckout<TStripe extends StripeClientLike, TCart = unknown, TOffer = undefined, TSession extends StripeCheckoutSessionLike = StripeCheckoutSessionLike>(options: OpenCheckoutOptions<TStripe, TCart, TOffer>): Promise<OpenCheckoutResult<TCart, TOffer, TSession>>;
61
+ export type OpenCheckoutTerminalResult<TCart> = {
62
+ readonly cart: TCart;
63
+ readonly outcome: "already_paid";
64
+ readonly proxySession: PayerOpenedResult;
65
+ readonly proxySessionId: string;
66
+ readonly sessionStatus: "paid" | "provisionable" | "provisioned" | "provisioning_failed";
67
+ } | {
68
+ readonly cart: TCart;
69
+ readonly outcome: "action_required";
70
+ readonly proxySession: PayerOpenedResult;
71
+ readonly proxySessionId: string;
72
+ readonly sessionStatus: "merchant_action_required";
73
+ } | {
74
+ readonly cart: TCart;
75
+ readonly outcome: "unavailable";
76
+ readonly proxySession: PayerOpenedResult;
77
+ readonly proxySessionId: string;
78
+ readonly sessionStatus: "cancelled" | "created" | "expired" | "failed";
79
+ };
80
+ export type OpenCheckoutResponse<TCart, TOffer, TSession> = OpenCheckoutResult<TCart, TOffer, TSession> | OpenCheckoutTerminalResult<TCart>;
81
+ export declare function openCheckout<TStripe extends StripeClientLike, TCart = unknown, TOffer = undefined, TSession extends StripeCheckoutSessionLike = StripeCheckoutSessionLike>(options: OpenCheckoutOptions<TStripe, TCart, TOffer>): Promise<OpenCheckoutResponse<TCart, TOffer, TSession>>;
@@ -24,6 +24,13 @@ export async function openCheckout(options) {
24
24
  let cart = options.cartSchema || options.getCartBuyerReference
25
25
  ? options.proxy.sessions.parseCart(proxySession, options.cartSchema ?? ((value) => value), { getBuyerReference: options.getCartBuyerReference })
26
26
  : proxySession.cartSnapshot;
27
+ if (proxySession.presentationState !== "checkout_available") {
28
+ return terminalResultFromSession(proxySession, cart);
29
+ }
30
+ if (proxySession.status === "payment_pending" &&
31
+ proxySession.providerCheckoutSessionId === null) {
32
+ throw new ProxyCheckoutValidationError(`Proxy session ${proxySession.id} already has payment preparation in progress without a bound Stripe Checkout Session.`, { code: "provider_binding_missing", field: "provider_checkout_session_id" });
33
+ }
27
34
  let offer = options.validateCart
28
35
  ? await options.validateCart({ cart, session: proxySession })
29
36
  : undefined;
@@ -37,10 +44,14 @@ export async function openCheckout(options) {
37
44
  checkoutSession,
38
45
  clientSecret: checkoutSession.client_secret ?? null,
39
46
  offer,
47
+ outcome: "ready",
40
48
  proxySession,
41
49
  proxySessionId: proxySession.id,
42
50
  };
43
51
  }
52
+ if (proxySession.currentAcquisitionAttempt !== null) {
53
+ throw new ProxyCheckoutValidationError(`Proxy session ${proxySession.id} already has acquisition ${proxySession.currentAcquisitionAttempt.id} in progress; openCheckout will not create an unrelated Stripe Checkout Session.`, { code: "provider_acquisition_in_progress", field: "current_acquisition_attempt" });
54
+ }
44
55
  let rollbackCart;
45
56
  const reconciledCart = await options.reconcileCart?.({ cart, offer, session: proxySession });
46
57
  if (reconciledCart) {
@@ -84,11 +95,14 @@ export async function openCheckout(options) {
84
95
  proxySession,
85
96
  stripe: options.stripe,
86
97
  });
98
+ assertCheckoutAutomaticRecoveryDisabled(params, proxySession.id);
99
+ const commercialMode = checkoutCommercialMode(params, proxySession.id);
87
100
  const checkoutSession = (await options.stripe.checkout.sessions.create(injectRequiredCheckoutSessionMetadata(params, proxySession.id), buildStripeRequestOptions(options, proxySession.id, psp)));
88
101
  // Fail closed if the merchant forgot to place proxy_session_id metadata.
89
102
  assertCheckoutSessionBelongsToProxy(checkoutSession, proxySession.id);
90
103
  const binding = await recordOrReconcileProviderBinding({
91
104
  checkoutSessionId: checkoutSession.id,
105
+ commercialMode,
92
106
  proxy: options.proxy,
93
107
  proxySessionId: proxySession.id,
94
108
  psp,
@@ -102,6 +116,7 @@ export async function openCheckout(options) {
102
116
  checkoutSession,
103
117
  clientSecret: checkoutSession.client_secret ?? null,
104
118
  offer,
119
+ outcome: "ready",
105
120
  proxySession,
106
121
  proxySessionId: proxySession.id,
107
122
  };
@@ -119,6 +134,39 @@ export async function openCheckout(options) {
119
134
  throw cause;
120
135
  }
121
136
  }
137
+ function terminalResultFromSession(proxySession, cart) {
138
+ const common = {
139
+ cart,
140
+ proxySession,
141
+ proxySessionId: proxySession.id,
142
+ };
143
+ switch (proxySession.presentationState) {
144
+ case "already_paid":
145
+ if (proxySession.status !== "paid" &&
146
+ proxySession.status !== "provisionable" &&
147
+ proxySession.status !== "provisioned" &&
148
+ proxySession.status !== "provisioning_failed") {
149
+ break;
150
+ }
151
+ return { ...common, outcome: "already_paid", sessionStatus: proxySession.status };
152
+ case "action_required":
153
+ if (proxySession.status !== "merchant_action_required") {
154
+ break;
155
+ }
156
+ return { ...common, outcome: "action_required", sessionStatus: proxySession.status };
157
+ case "unavailable":
158
+ if (proxySession.status !== "cancelled" &&
159
+ proxySession.status !== "created" &&
160
+ proxySession.status !== "expired" &&
161
+ proxySession.status !== "failed") {
162
+ break;
163
+ }
164
+ return { ...common, outcome: "unavailable", sessionStatus: proxySession.status };
165
+ case "checkout_available":
166
+ break;
167
+ }
168
+ throw new ProxyCheckoutValidationError(`Proxy session ${proxySession.id} returned an inconsistent presentation and lifecycle status.`, { code: "invalid_session_presentation", field: "presentation_state" });
169
+ }
122
170
  function buildStripeRequestOptions(options, proxySessionId, psp) {
123
171
  return {
124
172
  ...options.stripeRequestOptions,
@@ -167,6 +215,7 @@ async function rollbackReconciledCart(input) {
167
215
  async function recordOrReconcileProviderBinding(input) {
168
216
  try {
169
217
  return await input.proxy.sessions.recordProviderBinding(input.proxySessionId, {
218
+ commercialMode: input.commercialMode,
170
219
  providerCheckoutSessionId: input.checkoutSessionId,
171
220
  psp: input.psp,
172
221
  requestId: input.requestId,
@@ -187,6 +236,20 @@ async function recordOrReconcileProviderBinding(input) {
187
236
  });
188
237
  }
189
238
  }
239
+ function checkoutCommercialMode(params, proxySessionId) {
240
+ if (params.mode === "payment") {
241
+ return "one_time";
242
+ }
243
+ if (params.mode === "subscription") {
244
+ return "subscription";
245
+ }
246
+ throw new ProxyCheckoutValidationError(`Proxy session ${proxySessionId} requires Stripe Checkout mode payment or subscription.`, { code: "unsupported_provider_option", field: "mode" });
247
+ }
248
+ function assertCheckoutAutomaticRecoveryDisabled(params, proxySessionId) {
249
+ if (params.after_expiration?.recovery?.enabled === true) {
250
+ 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" });
251
+ }
252
+ }
190
253
  async function readReconciledBinding(input) {
191
254
  const current = await input.proxy.sessions.retrieve(input.proxySessionId, {
192
255
  requestId: input.requestId,
@@ -27,6 +27,11 @@ export interface StripeCheckoutSessionUpdateParams {
27
27
  readonly metadata?: StripeMetadataParam;
28
28
  }
29
29
  export type StripeCheckoutSessionCreateParams = object & {
30
+ readonly after_expiration?: {
31
+ readonly recovery?: {
32
+ readonly enabled?: boolean;
33
+ };
34
+ };
30
35
  readonly metadata?: StripeMetadataParam;
31
36
  readonly mode?: "payment" | "setup" | "subscription";
32
37
  readonly payment_intent_data?: unknown;
@@ -1,2 +1,2 @@
1
1
  export declare const proxyCheckoutStripeServerSdkName = "@proxy-checkout/stripe-server-js";
2
- export declare const proxyCheckoutStripeServerSdkVersion = "0.0.1";
2
+ export declare const proxyCheckoutStripeServerSdkVersion = "0.1.0-prx-124.137.1";
@@ -1,2 +1,2 @@
1
1
  export const proxyCheckoutStripeServerSdkName = "@proxy-checkout/stripe-server-js";
2
- export const proxyCheckoutStripeServerSdkVersion = "0.0.1";
2
+ export const proxyCheckoutStripeServerSdkVersion = "0.1.0-prx-124.137.1";
package/package.json CHANGED
@@ -7,14 +7,14 @@
7
7
  "sideEffects": false,
8
8
  "type": "module",
9
9
  "types": "./dist/esm/index.d.ts",
10
- "version": "0.0.1",
10
+ "version": "0.1.0-prx-124.137.1",
11
11
  "devDependencies": {
12
12
  "@types/node": "^24.12.4",
13
13
  "@vitest/coverage-v8": "4.1.7",
14
14
  "stripe": "^21.0.1",
15
15
  "typescript": "^6.0.3",
16
16
  "vitest": "4.1.7",
17
- "@proxy-checkout/server-js": "0.0.4"
17
+ "@proxy-checkout/server-js": "0.1.0-prx-124.137.1"
18
18
  },
19
19
  "exports": {
20
20
  ".": {
@@ -41,7 +41,7 @@
41
41
  "sdk"
42
42
  ],
43
43
  "peerDependencies": {
44
- "@proxy-checkout/server-js": ">=0.0.4-0 <0.1.0"
44
+ "@proxy-checkout/server-js": ">=0.1.0-0 <0.2.0"
45
45
  },
46
46
  "publishConfig": {
47
47
  "access": "public"
@@ -54,6 +54,7 @@
54
54
  "scripts": {
55
55
  "build": "node ../../scripts/sdk/build-dual-package.mjs",
56
56
  "test:all": "vitest run",
57
+ "test:ci": "pnpm run test:coverage",
57
58
  "test:coverage": "vitest run --coverage.enabled --coverage.reportsDirectory=coverage/all",
58
59
  "test:coverage:changed": "vitest run --coverage.enabled --coverage.reportsDirectory=coverage/changed",
59
60
  "test:coverage:integration": "vitest run --project integration --coverage.enabled --coverage.reportsDirectory=coverage/integration",