@proxy-checkout/stripe-server-js 0.0.0-pr-76.20.1 → 0.0.0-pr-76.21.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 the typed cart, 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 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.
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.
@@ -7,7 +7,7 @@
7
7
  * metadata placement, idempotency, and binding reconciliation.
8
8
  */
9
9
  import { ProxyCheckoutValidationError } from "@proxy-checkout/server-js";
10
- import { ProxyStripeOpenCheckoutError } from "./errors.js";
10
+ import { ProxyStripeCartSyncError, ProxyStripeOpenCheckoutError } from "./errors.js";
11
11
  import { assertCheckoutSessionBelongsToProxy, injectRequiredCheckoutSessionMetadata, } from "./metadata.js";
12
12
  import { normalizePsp } from "./psp.js";
13
13
  export async function openCheckout(options) {
@@ -27,8 +27,29 @@ export async function openCheckout(options) {
27
27
  const offer = options.validateCart
28
28
  ? await options.validateCart({ cart, session: proxySession })
29
29
  : undefined;
30
+ if (proxySession.providerCheckoutSessionId !== null) {
31
+ assertExistingBindingMatches(proxySession, psp);
32
+ const checkoutSession = (await options.stripe.checkout.sessions.retrieve(proxySession.providerCheckoutSessionId));
33
+ assertCheckoutSessionBelongsToProxy(checkoutSession, proxySession.id);
34
+ return {
35
+ binding: bindingFromSession(proxySession),
36
+ cart,
37
+ checkoutSession,
38
+ clientSecret: checkoutSession.client_secret ?? null,
39
+ offer,
40
+ proxySession,
41
+ proxySessionId: proxySession.id,
42
+ };
43
+ }
44
+ let rollbackCart;
30
45
  const reconciledCart = await options.reconcileCart?.({ cart, offer, session: proxySession });
31
46
  if (reconciledCart) {
47
+ const priorCart = {
48
+ amountMinor: proxySession.amountMinor,
49
+ cartSnapshot: proxySession.cartSnapshot,
50
+ cartVersion: proxySession.cartVersion,
51
+ currency: proxySession.currency,
52
+ };
32
53
  const updatedCart = await options.proxy.sessions.cart.set(proxySession.id, {
33
54
  amountMinor: reconciledCart.amountMinor,
34
55
  cartSnapshot: reconciledCart.cartSnapshot,
@@ -36,6 +57,10 @@ export async function openCheckout(options) {
36
57
  expectedCartVersion: proxySession.cartVersion,
37
58
  requestId: options.requestId,
38
59
  });
60
+ rollbackCart = {
61
+ ...priorCart,
62
+ expectedCartVersion: updatedCart.cartVersion,
63
+ };
39
64
  proxySession = {
40
65
  ...proxySession,
41
66
  amountMinor: updatedCart.amountMinor,
@@ -46,12 +71,26 @@ export async function openCheckout(options) {
46
71
  };
47
72
  cart = reconciledCart.cart;
48
73
  }
49
- if (proxySession.providerCheckoutSessionId !== null) {
50
- assertExistingBindingMatches(proxySession, psp);
51
- const checkoutSession = (await options.stripe.checkout.sessions.retrieve(proxySession.providerCheckoutSessionId));
74
+ try {
75
+ const params = await options.buildCheckoutSessionParams({
76
+ cart,
77
+ offer,
78
+ proxySession,
79
+ stripe: options.stripe,
80
+ });
81
+ const checkoutSession = (await options.stripe.checkout.sessions.create(injectRequiredCheckoutSessionMetadata(params, proxySession.id), buildStripeRequestOptions(options, proxySession.id, psp)));
82
+ // Fail closed if the merchant forgot to place proxy_session_id metadata.
52
83
  assertCheckoutSessionBelongsToProxy(checkoutSession, proxySession.id);
84
+ const binding = await recordOrReconcileProviderBinding({
85
+ checkoutSessionId: checkoutSession.id,
86
+ proxy: options.proxy,
87
+ proxySessionId: proxySession.id,
88
+ psp,
89
+ requestId: options.requestId,
90
+ stripe: options.stripe,
91
+ });
53
92
  return {
54
- binding: bindingFromSession(proxySession),
93
+ binding,
55
94
  cart,
56
95
  checkoutSession,
57
96
  clientSecret: checkoutSession.client_secret ?? null,
@@ -60,32 +99,18 @@ export async function openCheckout(options) {
60
99
  proxySessionId: proxySession.id,
61
100
  };
62
101
  }
63
- const params = await options.buildCheckoutSessionParams({
64
- cart,
65
- offer,
66
- proxySession,
67
- stripe: options.stripe,
68
- });
69
- const checkoutSession = (await options.stripe.checkout.sessions.create(injectRequiredCheckoutSessionMetadata(params, proxySession.id), buildStripeRequestOptions(options, proxySession.id, psp)));
70
- // Fail closed if the merchant forgot to place proxy_session_id metadata.
71
- assertCheckoutSessionBelongsToProxy(checkoutSession, proxySession.id);
72
- const binding = await recordOrReconcileProviderBinding({
73
- checkoutSessionId: checkoutSession.id,
74
- proxy: options.proxy,
75
- proxySessionId: proxySession.id,
76
- psp,
77
- requestId: options.requestId,
78
- stripe: options.stripe,
79
- });
80
- return {
81
- binding,
82
- cart,
83
- checkoutSession,
84
- clientSecret: checkoutSession.client_secret ?? null,
85
- offer,
86
- proxySession,
87
- proxySessionId: proxySession.id,
88
- };
102
+ catch (cause) {
103
+ if (rollbackCart) {
104
+ await rollbackReconciledCart({
105
+ cause,
106
+ proxy: options.proxy,
107
+ proxySessionId: proxySession.id,
108
+ requestId: options.requestId,
109
+ rollbackCart,
110
+ });
111
+ }
112
+ throw cause;
113
+ }
89
114
  }
90
115
  function buildStripeRequestOptions(options, proxySessionId, psp) {
91
116
  return {
@@ -113,6 +138,25 @@ function bindingFromSession(proxySession) {
113
138
  updatedAt: proxySession.updatedAt,
114
139
  };
115
140
  }
141
+ async function rollbackReconciledCart(input) {
142
+ try {
143
+ await input.proxy.sessions.cart.set(input.proxySessionId, {
144
+ amountMinor: input.rollbackCart.amountMinor,
145
+ cartSnapshot: input.rollbackCart.cartSnapshot,
146
+ currency: input.rollbackCart.currency,
147
+ expectedCartVersion: input.rollbackCart.expectedCartVersion,
148
+ requestId: input.requestId,
149
+ });
150
+ }
151
+ catch (rollbackCause) {
152
+ throw new ProxyStripeCartSyncError("openCheckout failed after reconciling the Proxy cart, and the rollback failed; carts may be out of sync.", {
153
+ cause: input.cause,
154
+ code: "reconciliation_failed",
155
+ reconciled: false,
156
+ rollbackCause,
157
+ });
158
+ }
159
+ }
116
160
  async function recordOrReconcileProviderBinding(input) {
117
161
  try {
118
162
  return await input.proxy.sessions.recordProviderBinding(input.proxySessionId, {
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export declare const proxyCheckoutStripeServerSdkName = "@proxy-checkout/stripe-server-js";
2
- export declare const proxyCheckoutStripeServerSdkVersion = "0.0.0-pr-76.20.1";
2
+ export declare const proxyCheckoutStripeServerSdkVersion = "0.0.0-pr-76.21.1";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  export const proxyCheckoutStripeServerSdkName = "@proxy-checkout/stripe-server-js";
2
- export const proxyCheckoutStripeServerSdkVersion = "0.0.0-pr-76.20.1";
2
+ export const proxyCheckoutStripeServerSdkVersion = "0.0.0-pr-76.21.1";
package/package.json CHANGED
@@ -7,13 +7,13 @@
7
7
  "sideEffects": false,
8
8
  "type": "module",
9
9
  "types": "./dist/index.d.ts",
10
- "version": "0.0.0-pr-76.20.1",
10
+ "version": "0.0.0-pr-76.21.1",
11
11
  "devDependencies": {
12
12
  "@types/node": "^24.12.4",
13
13
  "@vitest/coverage-v8": "4.1.7",
14
14
  "typescript": "^6.0.3",
15
15
  "vitest": "4.1.7",
16
- "@proxy-checkout/server-js": "0.0.4-pr-76.20.1"
16
+ "@proxy-checkout/server-js": "0.0.4-pr-76.21.1"
17
17
  },
18
18
  "exports": {
19
19
  ".": {