@flopay/node 1.2.1 → 1.2.4

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
@@ -127,7 +127,7 @@ new FloPay(secretKey: string, options?: FloPayNodeOptions)
127
127
 
128
128
  | Method | Returns | Description |
129
129
  |--------|---------|-------------|
130
- | `create(params)` | `Promise<CheckoutSessionResult>` | Creates a session via billing API (`POST /v1/checkouts/sessions`). Returns `{ status: 201, redirectUrl }` or `{ status: 204 }`. |
130
+ | `create(params)` | `Promise<CheckoutSessionResult>` | Creates a session via billing API (`POST /v1/checkouts/sessions`). Returns `{ status: 201, redirectUrl, nonce }` or `{ status: 204 }`. The `nonce` is the session-bound checkout token returned by the backend; forward it as `x-checkout-session-token` on every continuation call against the same session. |
131
131
  | `retrieve(id)` | `Promise<CheckoutSession>` | Retrieves a Stripe checkout session and normalizes it |
132
132
  | `expire(id)` | `Promise<CheckoutSession>` | Expires a Stripe checkout session |
133
133
  | `listLineItems(id)` | `Promise<LineItem[]>` | Lists line items for a Stripe checkout session |
@@ -152,7 +152,7 @@ new FloPay(secretKey: string, options?: FloPayNodeOptions)
152
152
  |------|-------------|
153
153
  | `FloPayNodeOptions` | Constructor options |
154
154
  | `CreateSessionParams` | Full session creation parameters (from `@flopay/shared`) |
155
- | `CheckoutSessionResult` | `{ status: 201, redirectUrl }` / `{ status: 204 }` / `{ status: number }` |
155
+ | `CheckoutSessionResult` | `{ status: 201, redirectUrl, nonce }` / `{ status: 204 }` / `{ status: number }` |
156
156
  | `CheckoutSession` | Normalized session: `id`, `clientSecret`, `mode`, `status`, `amount`, `currency`, `metadata` |
157
157
  | `Customer` | `id`, `email`, `firstName?`, `lastName?` |
158
158
  | `WebhookEvent` | `id`, `type`, `data`, `created` |
package/dist/index.cjs CHANGED
@@ -155,15 +155,23 @@ var FloPay = class {
155
155
  }
156
156
  if (status === 201) {
157
157
  const uuid = body?.data?.uuid;
158
+ const nonce = body?.data?.nonce;
158
159
  if (!uuid) {
159
160
  throw new Error("Checkout session created but no UUID was returned by the billing API");
160
161
  }
162
+ if (!nonce) {
163
+ throw new import_shared.FloPayError(
164
+ "Checkout session created but no `nonce` was returned by the billing API. Upgrade the billing service to TeamFloPay/backend#640 or later.",
165
+ "api_error",
166
+ { code: "MissingCheckoutSessionToken" }
167
+ );
168
+ }
161
169
  const redirectUrl = new URL(`${checkoutBaseUrl.replace(/\/+$/, "")}/secure`);
162
170
  redirectUrl.searchParams.set("id", uuid);
163
171
  for (const [key, value] of Object.entries(redirectParams)) {
164
172
  redirectUrl.searchParams.set(key, value);
165
173
  }
166
- return { status: 201, redirectUrl: redirectUrl.toString() };
174
+ return { status: 201, redirectUrl: redirectUrl.toString(), nonce };
167
175
  }
168
176
  if (status === 204) {
169
177
  return { status: 204 };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/flopay-node.ts"],"sourcesContent":["export { FloPay, STRIPE_API_VERSION } from './flopay-node.js';\nexport type { FloPayNodeOptions } from './flopay-node.js';\n","import Stripe from 'stripe';\nimport type {\n CheckoutSession,\n LineItem,\n CreateSessionParams,\n CheckoutSessionResult,\n CreateCustomerParams,\n UpdateCustomerParams,\n WebhookEvent,\n Customer,\n} from '@flopay/shared';\nimport {\n FloPayError,\n SDK_VERSION,\n buildProductPayload,\n foldIntoProducts,\n resolveSessionCurrency,\n} from '@flopay/shared';\n\nexport const STRIPE_API_VERSION = '2026-04-22.dahlia';\n\ntype StripeConfig = NonNullable<ConstructorParameters<typeof Stripe>[1]>;\n\n/** Options for the FloPay Node SDK constructor. */\nexport interface FloPayNodeOptions {\n apiVersion?: string;\n /** Stripe secret key, required when using Stripe-specific operations. */\n stripeSecretKey?: string;\n}\n\n/**\n * Server-side FloPay SDK.\n *\n * Supports two modes of operation:\n *\n * 1. **Billing API mode** — Creates checkout sessions via the FloPay/ClickTech\n * billing API (`POST /v1/checkouts/sessions`). This mirrors the\n * `createCheckoutSession` function from `clicktech-core-ui`.\n *\n * 2. **Stripe direct mode** — When a `stripeSecretKey` is provided, also\n * exposes direct Stripe operations (customers, webhooks).\n *\n * @example\n * ```ts\n * import { FloPay } from '@flopay/node';\n *\n * const flopay = new FloPay('sk_test_...');\n *\n * // Create session via billing API\n * const result = await flopay.checkout.sessions.create({\n * billingApiUrl: 'https://billing.example.com',\n * checkoutBaseUrl: 'https://checkout.example.com',\n * clientId: 'client_123',\n * currency: 'USD',\n * items: [{ code: 'prod_abc' }],\n * account: { userId: 'user_1', email: 'user@example.com' },\n * successUrl: '/success',\n * cancelUrl: '/cancel',\n * });\n * ```\n */\nexport class FloPay {\n private readonly stripe: Stripe;\n\n readonly checkout: {\n sessions: {\n /** Create a checkout session via the billing API. */\n create: (params: CreateSessionParams) => Promise<CheckoutSessionResult>;\n /** Retrieve a checkout session from Stripe. */\n retrieve: (id: string) => Promise<CheckoutSession>;\n /** Expire a checkout session on Stripe. */\n expire: (id: string) => Promise<CheckoutSession>;\n /** List line items for a Stripe checkout session. */\n listLineItems: (id: string) => Promise<LineItem[]>;\n };\n };\n\n readonly webhooks: {\n constructEvent: (\n payload: string | Buffer,\n signature: string,\n secret: string,\n ) => WebhookEvent;\n };\n\n readonly customers: {\n create: (params: CreateCustomerParams) => Promise<Customer>;\n retrieve: (id: string) => Promise<Customer>;\n update: (id: string, params: UpdateCustomerParams) => Promise<Customer>;\n };\n\n constructor(secretKey: string, options?: FloPayNodeOptions) {\n if (!secretKey) {\n throw new FloPayError(\n 'A secret key is required to initialize the FloPay server SDK.',\n 'authentication_error',\n );\n }\n\n this.stripe = new Stripe(options?.stripeSecretKey ?? secretKey, {\n apiVersion: (options?.apiVersion ?? STRIPE_API_VERSION) as StripeConfig['apiVersion'],\n appInfo: {\n name: 'flopay-node',\n version: SDK_VERSION,\n },\n });\n\n this.checkout = {\n sessions: {\n create: this.createSession.bind(this),\n retrieve: this.retrieveSession.bind(this),\n expire: this.expireSession.bind(this),\n listLineItems: this.listLineItems.bind(this),\n },\n };\n\n this.webhooks = {\n constructEvent: this.constructWebhookEvent.bind(this),\n };\n\n this.customers = {\n create: this.createCustomer.bind(this),\n retrieve: this.retrieveCustomer.bind(this),\n update: this.updateCustomer.bind(this),\n };\n }\n\n /**\n * Create a checkout session via the billing API.\n *\n * Mirrors `createCheckoutSession` from `clicktech-core-ui/modules`.\n * Posts to `{billingApiUrl}/v1/checkouts/sessions` and returns\n * the session UUID for redirect.\n */\n private async createSession(\n params: CreateSessionParams,\n ): Promise<CheckoutSessionResult> {\n const {\n billingApiUrl,\n checkoutBaseUrl,\n items = [],\n subscriptions = [],\n products,\n account,\n successUrl,\n cancelUrl,\n checkoutMode = 'confirm',\n couponCodes = [],\n tagsData,\n redirectParams = {},\n timeoutMs = 12000,\n clientId,\n currency,\n utmMetadata,\n } = params;\n\n const wireProducts = products ?? foldIntoProducts(items, subscriptions);\n const sessionCurrency = resolveSessionCurrency(currency, items, subscriptions, wireProducts);\n if (!sessionCurrency) {\n throw new FloPayError(\n 'currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.',\n 'validation_error',\n { code: 'CurrencyRequired', param: 'currency' },\n );\n }\n\n const payload: Record<string, unknown> = {\n clientId,\n checkoutVersion: SDK_VERSION,\n successUrl,\n cancelUrl,\n currency: sessionCurrency,\n checkoutMode,\n products: wireProducts.map((product) => buildProductPayload(product, sessionCurrency)),\n accountData: {\n userId: account.userId,\n firstName: account.firstName ?? null,\n lastName: account.lastName ?? null,\n email: account.email,\n country: account.country ?? null,\n gender: account.gender ?? null,\n city: account.city ?? null,\n state: account.state ?? null,\n zip: account.zip ?? null,\n },\n couponCodes,\n };\n\n if (tagsData) {\n payload['tagsData'] = tagsData;\n }\n\n if (utmMetadata?.length) {\n payload['utmMetadata'] = utmMetadata;\n }\n\n const url = `${billingApiUrl.replace(/\\/+$/, '')}/v1/checkouts/sessions`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n let status: number;\n let body: { data?: { uuid?: string } } | undefined;\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n\n status = response.status;\n\n try {\n body = await response.json() as { data?: { uuid?: string } };\n } catch {\n // 204 or empty body\n }\n } finally {\n clearTimeout(timer);\n }\n\n if (status === 201) {\n const uuid = body?.data?.uuid;\n\n if (!uuid) {\n throw new Error('Checkout session created but no UUID was returned by the billing API');\n }\n\n const redirectUrl = new URL(`${checkoutBaseUrl.replace(/\\/+$/, '')}/secure`);\n redirectUrl.searchParams.set('id', uuid);\n\n for (const [key, value] of Object.entries(redirectParams)) {\n redirectUrl.searchParams.set(key, value);\n }\n\n return { status: 201, redirectUrl: redirectUrl.toString() };\n }\n\n if (status === 204) {\n return { status: 204 };\n }\n\n return { status };\n }\n\n private normalizeStripeSession(\n session: Stripe.Checkout.Session,\n ): CheckoutSession {\n const statusMap: Record<string, CheckoutSession['status']> = {\n open: 'open',\n complete: 'complete',\n expired: 'expired',\n };\n\n return {\n id: session.id,\n clientSecret: session.client_secret ?? '',\n mode: session.mode as CheckoutSession['mode'],\n status: statusMap[session.status ?? 'open'] ?? 'open',\n amount: session.amount_total ?? 0,\n currency: session.currency ?? 'usd',\n metadata: (session.metadata as Record<string, string>) ?? {},\n };\n }\n\n private async retrieveSession(id: string): Promise<CheckoutSession> {\n const session = await this.stripe.checkout.sessions.retrieve(id);\n return this.normalizeStripeSession(session);\n }\n\n private async expireSession(id: string): Promise<CheckoutSession> {\n const session = await this.stripe.checkout.sessions.expire(id);\n return this.normalizeStripeSession(session);\n }\n\n private async listLineItems(id: string): Promise<LineItem[]> {\n const items = await this.stripe.checkout.sessions.listLineItems(id);\n return items.data.map((item) => ({\n price: item.price?.id,\n quantity: item.quantity ?? 1,\n }));\n }\n\n private constructWebhookEvent(\n payload: string | Buffer,\n signature: string,\n secret: string,\n ): WebhookEvent {\n const event = this.stripe.webhooks.constructEvent(payload, signature, secret);\n\n return {\n id: event.id,\n type: event.type,\n data: event.data as unknown as Record<string, unknown>,\n created: event.created,\n };\n }\n\n private async createCustomer(params: CreateCustomerParams): Promise<Customer> {\n const customer = await this.stripe.customers.create({\n email: params.email,\n name: params.name,\n metadata: params.metadata,\n });\n\n return {\n id: customer.id,\n email: customer.email ?? params.email,\n firstName: params.name?.split(' ')[0],\n lastName: params.name?.split(' ').slice(1).join(' '),\n };\n }\n\n private async retrieveCustomer(id: string): Promise<Customer> {\n const customer = await this.stripe.customers.retrieve(id);\n\n if (customer.deleted) {\n throw new FloPayError(\n `Customer ${id} has been deleted`,\n 'api_error',\n { code: 'resource_missing' },\n );\n }\n\n return {\n id: customer.id,\n email: customer.email ?? '',\n firstName: customer.name?.split(' ')[0],\n lastName: customer.name?.split(' ').slice(1).join(' '),\n };\n }\n\n private async updateCustomer(\n id: string,\n params: UpdateCustomerParams,\n ): Promise<Customer> {\n const customer = await this.stripe.customers.update(id, {\n email: params.email,\n name: params.name,\n metadata: params.metadata,\n });\n\n return {\n id: customer.id,\n email: customer.email ?? '',\n firstName: customer.name?.split(' ')[0],\n lastName: customer.name?.split(' ').slice(1).join(' '),\n };\n }\n}\n"],"mappings":"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","names":["Stripe"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/flopay-node.ts"],"sourcesContent":["export { FloPay, STRIPE_API_VERSION } from './flopay-node.js';\nexport type { FloPayNodeOptions } from './flopay-node.js';\n","import Stripe from 'stripe';\nimport type {\n CheckoutSession,\n LineItem,\n CreateSessionParams,\n CheckoutSessionResult,\n CreateCustomerParams,\n UpdateCustomerParams,\n WebhookEvent,\n Customer,\n} from '@flopay/shared';\nimport {\n FloPayError,\n SDK_VERSION,\n buildProductPayload,\n foldIntoProducts,\n resolveSessionCurrency,\n} from '@flopay/shared';\n\nexport const STRIPE_API_VERSION = '2026-04-22.dahlia';\n\ntype StripeConfig = NonNullable<ConstructorParameters<typeof Stripe>[1]>;\n\n/** Options for the FloPay Node SDK constructor. */\nexport interface FloPayNodeOptions {\n apiVersion?: string;\n /** Stripe secret key, required when using Stripe-specific operations. */\n stripeSecretKey?: string;\n}\n\n/**\n * Server-side FloPay SDK.\n *\n * Supports two modes of operation:\n *\n * 1. **Billing API mode** — Creates checkout sessions via the FloPay/ClickTech\n * billing API (`POST /v1/checkouts/sessions`). This mirrors the\n * `createCheckoutSession` function from `clicktech-core-ui`.\n *\n * 2. **Stripe direct mode** — When a `stripeSecretKey` is provided, also\n * exposes direct Stripe operations (customers, webhooks).\n *\n * @example\n * ```ts\n * import { FloPay } from '@flopay/node';\n *\n * const flopay = new FloPay('sk_test_...');\n *\n * // Create session via billing API\n * const result = await flopay.checkout.sessions.create({\n * billingApiUrl: 'https://billing.example.com',\n * checkoutBaseUrl: 'https://checkout.example.com',\n * clientId: 'client_123',\n * currency: 'USD',\n * items: [{ code: 'prod_abc' }],\n * account: { userId: 'user_1', email: 'user@example.com' },\n * successUrl: '/success',\n * cancelUrl: '/cancel',\n * });\n * ```\n */\nexport class FloPay {\n private readonly stripe: Stripe;\n\n readonly checkout: {\n sessions: {\n /** Create a checkout session via the billing API. */\n create: (params: CreateSessionParams) => Promise<CheckoutSessionResult>;\n /** Retrieve a checkout session from Stripe. */\n retrieve: (id: string) => Promise<CheckoutSession>;\n /** Expire a checkout session on Stripe. */\n expire: (id: string) => Promise<CheckoutSession>;\n /** List line items for a Stripe checkout session. */\n listLineItems: (id: string) => Promise<LineItem[]>;\n };\n };\n\n readonly webhooks: {\n constructEvent: (\n payload: string | Buffer,\n signature: string,\n secret: string,\n ) => WebhookEvent;\n };\n\n readonly customers: {\n create: (params: CreateCustomerParams) => Promise<Customer>;\n retrieve: (id: string) => Promise<Customer>;\n update: (id: string, params: UpdateCustomerParams) => Promise<Customer>;\n };\n\n constructor(secretKey: string, options?: FloPayNodeOptions) {\n if (!secretKey) {\n throw new FloPayError(\n 'A secret key is required to initialize the FloPay server SDK.',\n 'authentication_error',\n );\n }\n\n this.stripe = new Stripe(options?.stripeSecretKey ?? secretKey, {\n apiVersion: (options?.apiVersion ?? STRIPE_API_VERSION) as StripeConfig['apiVersion'],\n appInfo: {\n name: 'flopay-node',\n version: SDK_VERSION,\n },\n });\n\n this.checkout = {\n sessions: {\n create: this.createSession.bind(this),\n retrieve: this.retrieveSession.bind(this),\n expire: this.expireSession.bind(this),\n listLineItems: this.listLineItems.bind(this),\n },\n };\n\n this.webhooks = {\n constructEvent: this.constructWebhookEvent.bind(this),\n };\n\n this.customers = {\n create: this.createCustomer.bind(this),\n retrieve: this.retrieveCustomer.bind(this),\n update: this.updateCustomer.bind(this),\n };\n }\n\n /**\n * Create a checkout session via the billing API.\n *\n * Mirrors `createCheckoutSession` from `clicktech-core-ui/modules`.\n * Posts to `{billingApiUrl}/v1/checkouts/sessions` and returns\n * the session UUID for redirect.\n */\n private async createSession(\n params: CreateSessionParams,\n ): Promise<CheckoutSessionResult> {\n const {\n billingApiUrl,\n checkoutBaseUrl,\n items = [],\n subscriptions = [],\n products,\n account,\n successUrl,\n cancelUrl,\n checkoutMode = 'confirm',\n couponCodes = [],\n tagsData,\n redirectParams = {},\n timeoutMs = 12000,\n clientId,\n currency,\n utmMetadata,\n } = params;\n\n const wireProducts = products ?? foldIntoProducts(items, subscriptions);\n const sessionCurrency = resolveSessionCurrency(currency, items, subscriptions, wireProducts);\n if (!sessionCurrency) {\n throw new FloPayError(\n 'currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.',\n 'validation_error',\n { code: 'CurrencyRequired', param: 'currency' },\n );\n }\n\n const payload: Record<string, unknown> = {\n clientId,\n checkoutVersion: SDK_VERSION,\n successUrl,\n cancelUrl,\n currency: sessionCurrency,\n checkoutMode,\n products: wireProducts.map((product) => buildProductPayload(product, sessionCurrency)),\n accountData: {\n userId: account.userId,\n firstName: account.firstName ?? null,\n lastName: account.lastName ?? null,\n email: account.email,\n country: account.country ?? null,\n gender: account.gender ?? null,\n city: account.city ?? null,\n state: account.state ?? null,\n zip: account.zip ?? null,\n },\n couponCodes,\n };\n\n if (tagsData) {\n payload['tagsData'] = tagsData;\n }\n\n if (utmMetadata?.length) {\n payload['utmMetadata'] = utmMetadata;\n }\n\n const url = `${billingApiUrl.replace(/\\/+$/, '')}/v1/checkouts/sessions`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n let status: number;\n let body: { data?: { uuid?: string; nonce?: string } } | undefined;\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n\n status = response.status;\n\n try {\n body = await response.json() as { data?: { uuid?: string; nonce?: string } };\n } catch {\n // 204 or empty body\n }\n } finally {\n clearTimeout(timer);\n }\n\n if (status === 201) {\n const uuid = body?.data?.uuid;\n const nonce = body?.data?.nonce;\n\n if (!uuid) {\n throw new Error('Checkout session created but no UUID was returned by the billing API');\n }\n\n if (!nonce) {\n throw new FloPayError(\n 'Checkout session created but no `nonce` was returned by the billing API. Upgrade the billing service to TeamFloPay/backend#640 or later.',\n 'api_error',\n { code: 'MissingCheckoutSessionToken' },\n );\n }\n\n const redirectUrl = new URL(`${checkoutBaseUrl.replace(/\\/+$/, '')}/secure`);\n redirectUrl.searchParams.set('id', uuid);\n\n for (const [key, value] of Object.entries(redirectParams)) {\n redirectUrl.searchParams.set(key, value);\n }\n\n return { status: 201, redirectUrl: redirectUrl.toString(), nonce };\n }\n\n if (status === 204) {\n return { status: 204 };\n }\n\n return { status };\n }\n\n private normalizeStripeSession(\n session: Stripe.Checkout.Session,\n ): CheckoutSession {\n const statusMap: Record<string, CheckoutSession['status']> = {\n open: 'open',\n complete: 'complete',\n expired: 'expired',\n };\n\n return {\n id: session.id,\n clientSecret: session.client_secret ?? '',\n mode: session.mode as CheckoutSession['mode'],\n status: statusMap[session.status ?? 'open'] ?? 'open',\n amount: session.amount_total ?? 0,\n currency: session.currency ?? 'usd',\n metadata: (session.metadata as Record<string, string>) ?? {},\n };\n }\n\n private async retrieveSession(id: string): Promise<CheckoutSession> {\n const session = await this.stripe.checkout.sessions.retrieve(id);\n return this.normalizeStripeSession(session);\n }\n\n private async expireSession(id: string): Promise<CheckoutSession> {\n const session = await this.stripe.checkout.sessions.expire(id);\n return this.normalizeStripeSession(session);\n }\n\n private async listLineItems(id: string): Promise<LineItem[]> {\n const items = await this.stripe.checkout.sessions.listLineItems(id);\n return items.data.map((item) => ({\n price: item.price?.id,\n quantity: item.quantity ?? 1,\n }));\n }\n\n private constructWebhookEvent(\n payload: string | Buffer,\n signature: string,\n secret: string,\n ): WebhookEvent {\n const event = this.stripe.webhooks.constructEvent(payload, signature, secret);\n\n return {\n id: event.id,\n type: event.type,\n data: event.data as unknown as Record<string, unknown>,\n created: event.created,\n };\n }\n\n private async createCustomer(params: CreateCustomerParams): Promise<Customer> {\n const customer = await this.stripe.customers.create({\n email: params.email,\n name: params.name,\n metadata: params.metadata,\n });\n\n return {\n id: customer.id,\n email: customer.email ?? params.email,\n firstName: params.name?.split(' ')[0],\n lastName: params.name?.split(' ').slice(1).join(' '),\n };\n }\n\n private async retrieveCustomer(id: string): Promise<Customer> {\n const customer = await this.stripe.customers.retrieve(id);\n\n if (customer.deleted) {\n throw new FloPayError(\n `Customer ${id} has been deleted`,\n 'api_error',\n { code: 'resource_missing' },\n );\n }\n\n return {\n id: customer.id,\n email: customer.email ?? '',\n firstName: customer.name?.split(' ')[0],\n lastName: customer.name?.split(' ').slice(1).join(' '),\n };\n }\n\n private async updateCustomer(\n id: string,\n params: UpdateCustomerParams,\n ): Promise<Customer> {\n const customer = await this.stripe.customers.update(id, {\n email: params.email,\n name: params.name,\n metadata: params.metadata,\n });\n\n return {\n id: customer.id,\n email: customer.email ?? '',\n firstName: customer.name?.split(' ')[0],\n lastName: customer.name?.split(' ').slice(1).join(' '),\n };\n }\n}\n"],"mappings":"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","names":["Stripe"]}
package/dist/index.mjs CHANGED
@@ -124,15 +124,23 @@ var FloPay = class {
124
124
  }
125
125
  if (status === 201) {
126
126
  const uuid = body?.data?.uuid;
127
+ const nonce = body?.data?.nonce;
127
128
  if (!uuid) {
128
129
  throw new Error("Checkout session created but no UUID was returned by the billing API");
129
130
  }
131
+ if (!nonce) {
132
+ throw new FloPayError(
133
+ "Checkout session created but no `nonce` was returned by the billing API. Upgrade the billing service to TeamFloPay/backend#640 or later.",
134
+ "api_error",
135
+ { code: "MissingCheckoutSessionToken" }
136
+ );
137
+ }
130
138
  const redirectUrl = new URL(`${checkoutBaseUrl.replace(/\/+$/, "")}/secure`);
131
139
  redirectUrl.searchParams.set("id", uuid);
132
140
  for (const [key, value] of Object.entries(redirectParams)) {
133
141
  redirectUrl.searchParams.set(key, value);
134
142
  }
135
- return { status: 201, redirectUrl: redirectUrl.toString() };
143
+ return { status: 201, redirectUrl: redirectUrl.toString(), nonce };
136
144
  }
137
145
  if (status === 204) {
138
146
  return { status: 204 };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/flopay-node.ts"],"sourcesContent":["import Stripe from 'stripe';\nimport type {\n CheckoutSession,\n LineItem,\n CreateSessionParams,\n CheckoutSessionResult,\n CreateCustomerParams,\n UpdateCustomerParams,\n WebhookEvent,\n Customer,\n} from '@flopay/shared';\nimport {\n FloPayError,\n SDK_VERSION,\n buildProductPayload,\n foldIntoProducts,\n resolveSessionCurrency,\n} from '@flopay/shared';\n\nexport const STRIPE_API_VERSION = '2026-04-22.dahlia';\n\ntype StripeConfig = NonNullable<ConstructorParameters<typeof Stripe>[1]>;\n\n/** Options for the FloPay Node SDK constructor. */\nexport interface FloPayNodeOptions {\n apiVersion?: string;\n /** Stripe secret key, required when using Stripe-specific operations. */\n stripeSecretKey?: string;\n}\n\n/**\n * Server-side FloPay SDK.\n *\n * Supports two modes of operation:\n *\n * 1. **Billing API mode** — Creates checkout sessions via the FloPay/ClickTech\n * billing API (`POST /v1/checkouts/sessions`). This mirrors the\n * `createCheckoutSession` function from `clicktech-core-ui`.\n *\n * 2. **Stripe direct mode** — When a `stripeSecretKey` is provided, also\n * exposes direct Stripe operations (customers, webhooks).\n *\n * @example\n * ```ts\n * import { FloPay } from '@flopay/node';\n *\n * const flopay = new FloPay('sk_test_...');\n *\n * // Create session via billing API\n * const result = await flopay.checkout.sessions.create({\n * billingApiUrl: 'https://billing.example.com',\n * checkoutBaseUrl: 'https://checkout.example.com',\n * clientId: 'client_123',\n * currency: 'USD',\n * items: [{ code: 'prod_abc' }],\n * account: { userId: 'user_1', email: 'user@example.com' },\n * successUrl: '/success',\n * cancelUrl: '/cancel',\n * });\n * ```\n */\nexport class FloPay {\n private readonly stripe: Stripe;\n\n readonly checkout: {\n sessions: {\n /** Create a checkout session via the billing API. */\n create: (params: CreateSessionParams) => Promise<CheckoutSessionResult>;\n /** Retrieve a checkout session from Stripe. */\n retrieve: (id: string) => Promise<CheckoutSession>;\n /** Expire a checkout session on Stripe. */\n expire: (id: string) => Promise<CheckoutSession>;\n /** List line items for a Stripe checkout session. */\n listLineItems: (id: string) => Promise<LineItem[]>;\n };\n };\n\n readonly webhooks: {\n constructEvent: (\n payload: string | Buffer,\n signature: string,\n secret: string,\n ) => WebhookEvent;\n };\n\n readonly customers: {\n create: (params: CreateCustomerParams) => Promise<Customer>;\n retrieve: (id: string) => Promise<Customer>;\n update: (id: string, params: UpdateCustomerParams) => Promise<Customer>;\n };\n\n constructor(secretKey: string, options?: FloPayNodeOptions) {\n if (!secretKey) {\n throw new FloPayError(\n 'A secret key is required to initialize the FloPay server SDK.',\n 'authentication_error',\n );\n }\n\n this.stripe = new Stripe(options?.stripeSecretKey ?? secretKey, {\n apiVersion: (options?.apiVersion ?? STRIPE_API_VERSION) as StripeConfig['apiVersion'],\n appInfo: {\n name: 'flopay-node',\n version: SDK_VERSION,\n },\n });\n\n this.checkout = {\n sessions: {\n create: this.createSession.bind(this),\n retrieve: this.retrieveSession.bind(this),\n expire: this.expireSession.bind(this),\n listLineItems: this.listLineItems.bind(this),\n },\n };\n\n this.webhooks = {\n constructEvent: this.constructWebhookEvent.bind(this),\n };\n\n this.customers = {\n create: this.createCustomer.bind(this),\n retrieve: this.retrieveCustomer.bind(this),\n update: this.updateCustomer.bind(this),\n };\n }\n\n /**\n * Create a checkout session via the billing API.\n *\n * Mirrors `createCheckoutSession` from `clicktech-core-ui/modules`.\n * Posts to `{billingApiUrl}/v1/checkouts/sessions` and returns\n * the session UUID for redirect.\n */\n private async createSession(\n params: CreateSessionParams,\n ): Promise<CheckoutSessionResult> {\n const {\n billingApiUrl,\n checkoutBaseUrl,\n items = [],\n subscriptions = [],\n products,\n account,\n successUrl,\n cancelUrl,\n checkoutMode = 'confirm',\n couponCodes = [],\n tagsData,\n redirectParams = {},\n timeoutMs = 12000,\n clientId,\n currency,\n utmMetadata,\n } = params;\n\n const wireProducts = products ?? foldIntoProducts(items, subscriptions);\n const sessionCurrency = resolveSessionCurrency(currency, items, subscriptions, wireProducts);\n if (!sessionCurrency) {\n throw new FloPayError(\n 'currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.',\n 'validation_error',\n { code: 'CurrencyRequired', param: 'currency' },\n );\n }\n\n const payload: Record<string, unknown> = {\n clientId,\n checkoutVersion: SDK_VERSION,\n successUrl,\n cancelUrl,\n currency: sessionCurrency,\n checkoutMode,\n products: wireProducts.map((product) => buildProductPayload(product, sessionCurrency)),\n accountData: {\n userId: account.userId,\n firstName: account.firstName ?? null,\n lastName: account.lastName ?? null,\n email: account.email,\n country: account.country ?? null,\n gender: account.gender ?? null,\n city: account.city ?? null,\n state: account.state ?? null,\n zip: account.zip ?? null,\n },\n couponCodes,\n };\n\n if (tagsData) {\n payload['tagsData'] = tagsData;\n }\n\n if (utmMetadata?.length) {\n payload['utmMetadata'] = utmMetadata;\n }\n\n const url = `${billingApiUrl.replace(/\\/+$/, '')}/v1/checkouts/sessions`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n let status: number;\n let body: { data?: { uuid?: string } } | undefined;\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n\n status = response.status;\n\n try {\n body = await response.json() as { data?: { uuid?: string } };\n } catch {\n // 204 or empty body\n }\n } finally {\n clearTimeout(timer);\n }\n\n if (status === 201) {\n const uuid = body?.data?.uuid;\n\n if (!uuid) {\n throw new Error('Checkout session created but no UUID was returned by the billing API');\n }\n\n const redirectUrl = new URL(`${checkoutBaseUrl.replace(/\\/+$/, '')}/secure`);\n redirectUrl.searchParams.set('id', uuid);\n\n for (const [key, value] of Object.entries(redirectParams)) {\n redirectUrl.searchParams.set(key, value);\n }\n\n return { status: 201, redirectUrl: redirectUrl.toString() };\n }\n\n if (status === 204) {\n return { status: 204 };\n }\n\n return { status };\n }\n\n private normalizeStripeSession(\n session: Stripe.Checkout.Session,\n ): CheckoutSession {\n const statusMap: Record<string, CheckoutSession['status']> = {\n open: 'open',\n complete: 'complete',\n expired: 'expired',\n };\n\n return {\n id: session.id,\n clientSecret: session.client_secret ?? '',\n mode: session.mode as CheckoutSession['mode'],\n status: statusMap[session.status ?? 'open'] ?? 'open',\n amount: session.amount_total ?? 0,\n currency: session.currency ?? 'usd',\n metadata: (session.metadata as Record<string, string>) ?? {},\n };\n }\n\n private async retrieveSession(id: string): Promise<CheckoutSession> {\n const session = await this.stripe.checkout.sessions.retrieve(id);\n return this.normalizeStripeSession(session);\n }\n\n private async expireSession(id: string): Promise<CheckoutSession> {\n const session = await this.stripe.checkout.sessions.expire(id);\n return this.normalizeStripeSession(session);\n }\n\n private async listLineItems(id: string): Promise<LineItem[]> {\n const items = await this.stripe.checkout.sessions.listLineItems(id);\n return items.data.map((item) => ({\n price: item.price?.id,\n quantity: item.quantity ?? 1,\n }));\n }\n\n private constructWebhookEvent(\n payload: string | Buffer,\n signature: string,\n secret: string,\n ): WebhookEvent {\n const event = this.stripe.webhooks.constructEvent(payload, signature, secret);\n\n return {\n id: event.id,\n type: event.type,\n data: event.data as unknown as Record<string, unknown>,\n created: event.created,\n };\n }\n\n private async createCustomer(params: CreateCustomerParams): Promise<Customer> {\n const customer = await this.stripe.customers.create({\n email: params.email,\n name: params.name,\n metadata: params.metadata,\n });\n\n return {\n id: customer.id,\n email: customer.email ?? params.email,\n firstName: params.name?.split(' ')[0],\n lastName: params.name?.split(' ').slice(1).join(' '),\n };\n }\n\n private async retrieveCustomer(id: string): Promise<Customer> {\n const customer = await this.stripe.customers.retrieve(id);\n\n if (customer.deleted) {\n throw new FloPayError(\n `Customer ${id} has been deleted`,\n 'api_error',\n { code: 'resource_missing' },\n );\n }\n\n return {\n id: customer.id,\n email: customer.email ?? '',\n firstName: customer.name?.split(' ')[0],\n lastName: customer.name?.split(' ').slice(1).join(' '),\n };\n }\n\n private async updateCustomer(\n id: string,\n params: UpdateCustomerParams,\n ): Promise<Customer> {\n const customer = await this.stripe.customers.update(id, {\n email: params.email,\n name: params.name,\n metadata: params.metadata,\n });\n\n return {\n id: customer.id,\n email: customer.email ?? '',\n firstName: customer.name?.split(' ')[0],\n lastName: customer.name?.split(' ').slice(1).join(' '),\n };\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/flopay-node.ts"],"sourcesContent":["import Stripe from 'stripe';\nimport type {\n CheckoutSession,\n LineItem,\n CreateSessionParams,\n CheckoutSessionResult,\n CreateCustomerParams,\n UpdateCustomerParams,\n WebhookEvent,\n Customer,\n} from '@flopay/shared';\nimport {\n FloPayError,\n SDK_VERSION,\n buildProductPayload,\n foldIntoProducts,\n resolveSessionCurrency,\n} from '@flopay/shared';\n\nexport const STRIPE_API_VERSION = '2026-04-22.dahlia';\n\ntype StripeConfig = NonNullable<ConstructorParameters<typeof Stripe>[1]>;\n\n/** Options for the FloPay Node SDK constructor. */\nexport interface FloPayNodeOptions {\n apiVersion?: string;\n /** Stripe secret key, required when using Stripe-specific operations. */\n stripeSecretKey?: string;\n}\n\n/**\n * Server-side FloPay SDK.\n *\n * Supports two modes of operation:\n *\n * 1. **Billing API mode** — Creates checkout sessions via the FloPay/ClickTech\n * billing API (`POST /v1/checkouts/sessions`). This mirrors the\n * `createCheckoutSession` function from `clicktech-core-ui`.\n *\n * 2. **Stripe direct mode** — When a `stripeSecretKey` is provided, also\n * exposes direct Stripe operations (customers, webhooks).\n *\n * @example\n * ```ts\n * import { FloPay } from '@flopay/node';\n *\n * const flopay = new FloPay('sk_test_...');\n *\n * // Create session via billing API\n * const result = await flopay.checkout.sessions.create({\n * billingApiUrl: 'https://billing.example.com',\n * checkoutBaseUrl: 'https://checkout.example.com',\n * clientId: 'client_123',\n * currency: 'USD',\n * items: [{ code: 'prod_abc' }],\n * account: { userId: 'user_1', email: 'user@example.com' },\n * successUrl: '/success',\n * cancelUrl: '/cancel',\n * });\n * ```\n */\nexport class FloPay {\n private readonly stripe: Stripe;\n\n readonly checkout: {\n sessions: {\n /** Create a checkout session via the billing API. */\n create: (params: CreateSessionParams) => Promise<CheckoutSessionResult>;\n /** Retrieve a checkout session from Stripe. */\n retrieve: (id: string) => Promise<CheckoutSession>;\n /** Expire a checkout session on Stripe. */\n expire: (id: string) => Promise<CheckoutSession>;\n /** List line items for a Stripe checkout session. */\n listLineItems: (id: string) => Promise<LineItem[]>;\n };\n };\n\n readonly webhooks: {\n constructEvent: (\n payload: string | Buffer,\n signature: string,\n secret: string,\n ) => WebhookEvent;\n };\n\n readonly customers: {\n create: (params: CreateCustomerParams) => Promise<Customer>;\n retrieve: (id: string) => Promise<Customer>;\n update: (id: string, params: UpdateCustomerParams) => Promise<Customer>;\n };\n\n constructor(secretKey: string, options?: FloPayNodeOptions) {\n if (!secretKey) {\n throw new FloPayError(\n 'A secret key is required to initialize the FloPay server SDK.',\n 'authentication_error',\n );\n }\n\n this.stripe = new Stripe(options?.stripeSecretKey ?? secretKey, {\n apiVersion: (options?.apiVersion ?? STRIPE_API_VERSION) as StripeConfig['apiVersion'],\n appInfo: {\n name: 'flopay-node',\n version: SDK_VERSION,\n },\n });\n\n this.checkout = {\n sessions: {\n create: this.createSession.bind(this),\n retrieve: this.retrieveSession.bind(this),\n expire: this.expireSession.bind(this),\n listLineItems: this.listLineItems.bind(this),\n },\n };\n\n this.webhooks = {\n constructEvent: this.constructWebhookEvent.bind(this),\n };\n\n this.customers = {\n create: this.createCustomer.bind(this),\n retrieve: this.retrieveCustomer.bind(this),\n update: this.updateCustomer.bind(this),\n };\n }\n\n /**\n * Create a checkout session via the billing API.\n *\n * Mirrors `createCheckoutSession` from `clicktech-core-ui/modules`.\n * Posts to `{billingApiUrl}/v1/checkouts/sessions` and returns\n * the session UUID for redirect.\n */\n private async createSession(\n params: CreateSessionParams,\n ): Promise<CheckoutSessionResult> {\n const {\n billingApiUrl,\n checkoutBaseUrl,\n items = [],\n subscriptions = [],\n products,\n account,\n successUrl,\n cancelUrl,\n checkoutMode = 'confirm',\n couponCodes = [],\n tagsData,\n redirectParams = {},\n timeoutMs = 12000,\n clientId,\n currency,\n utmMetadata,\n } = params;\n\n const wireProducts = products ?? foldIntoProducts(items, subscriptions);\n const sessionCurrency = resolveSessionCurrency(currency, items, subscriptions, wireProducts);\n if (!sessionCurrency) {\n throw new FloPayError(\n 'currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.',\n 'validation_error',\n { code: 'CurrencyRequired', param: 'currency' },\n );\n }\n\n const payload: Record<string, unknown> = {\n clientId,\n checkoutVersion: SDK_VERSION,\n successUrl,\n cancelUrl,\n currency: sessionCurrency,\n checkoutMode,\n products: wireProducts.map((product) => buildProductPayload(product, sessionCurrency)),\n accountData: {\n userId: account.userId,\n firstName: account.firstName ?? null,\n lastName: account.lastName ?? null,\n email: account.email,\n country: account.country ?? null,\n gender: account.gender ?? null,\n city: account.city ?? null,\n state: account.state ?? null,\n zip: account.zip ?? null,\n },\n couponCodes,\n };\n\n if (tagsData) {\n payload['tagsData'] = tagsData;\n }\n\n if (utmMetadata?.length) {\n payload['utmMetadata'] = utmMetadata;\n }\n\n const url = `${billingApiUrl.replace(/\\/+$/, '')}/v1/checkouts/sessions`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n let status: number;\n let body: { data?: { uuid?: string; nonce?: string } } | undefined;\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n\n status = response.status;\n\n try {\n body = await response.json() as { data?: { uuid?: string; nonce?: string } };\n } catch {\n // 204 or empty body\n }\n } finally {\n clearTimeout(timer);\n }\n\n if (status === 201) {\n const uuid = body?.data?.uuid;\n const nonce = body?.data?.nonce;\n\n if (!uuid) {\n throw new Error('Checkout session created but no UUID was returned by the billing API');\n }\n\n if (!nonce) {\n throw new FloPayError(\n 'Checkout session created but no `nonce` was returned by the billing API. Upgrade the billing service to TeamFloPay/backend#640 or later.',\n 'api_error',\n { code: 'MissingCheckoutSessionToken' },\n );\n }\n\n const redirectUrl = new URL(`${checkoutBaseUrl.replace(/\\/+$/, '')}/secure`);\n redirectUrl.searchParams.set('id', uuid);\n\n for (const [key, value] of Object.entries(redirectParams)) {\n redirectUrl.searchParams.set(key, value);\n }\n\n return { status: 201, redirectUrl: redirectUrl.toString(), nonce };\n }\n\n if (status === 204) {\n return { status: 204 };\n }\n\n return { status };\n }\n\n private normalizeStripeSession(\n session: Stripe.Checkout.Session,\n ): CheckoutSession {\n const statusMap: Record<string, CheckoutSession['status']> = {\n open: 'open',\n complete: 'complete',\n expired: 'expired',\n };\n\n return {\n id: session.id,\n clientSecret: session.client_secret ?? '',\n mode: session.mode as CheckoutSession['mode'],\n status: statusMap[session.status ?? 'open'] ?? 'open',\n amount: session.amount_total ?? 0,\n currency: session.currency ?? 'usd',\n metadata: (session.metadata as Record<string, string>) ?? {},\n };\n }\n\n private async retrieveSession(id: string): Promise<CheckoutSession> {\n const session = await this.stripe.checkout.sessions.retrieve(id);\n return this.normalizeStripeSession(session);\n }\n\n private async expireSession(id: string): Promise<CheckoutSession> {\n const session = await this.stripe.checkout.sessions.expire(id);\n return this.normalizeStripeSession(session);\n }\n\n private async listLineItems(id: string): Promise<LineItem[]> {\n const items = await this.stripe.checkout.sessions.listLineItems(id);\n return items.data.map((item) => ({\n price: item.price?.id,\n quantity: item.quantity ?? 1,\n }));\n }\n\n private constructWebhookEvent(\n payload: string | Buffer,\n signature: string,\n secret: string,\n ): WebhookEvent {\n const event = this.stripe.webhooks.constructEvent(payload, signature, secret);\n\n return {\n id: event.id,\n type: event.type,\n data: event.data as unknown as Record<string, unknown>,\n created: event.created,\n };\n }\n\n private async createCustomer(params: CreateCustomerParams): Promise<Customer> {\n const customer = await this.stripe.customers.create({\n email: params.email,\n name: params.name,\n metadata: params.metadata,\n });\n\n return {\n id: customer.id,\n email: customer.email ?? params.email,\n firstName: params.name?.split(' ')[0],\n lastName: params.name?.split(' ').slice(1).join(' '),\n };\n }\n\n private async retrieveCustomer(id: string): Promise<Customer> {\n const customer = await this.stripe.customers.retrieve(id);\n\n if (customer.deleted) {\n throw new FloPayError(\n `Customer ${id} has been deleted`,\n 'api_error',\n { code: 'resource_missing' },\n );\n }\n\n return {\n id: customer.id,\n email: customer.email ?? '',\n firstName: customer.name?.split(' ')[0],\n lastName: customer.name?.split(' ').slice(1).join(' '),\n };\n }\n\n private async updateCustomer(\n id: string,\n params: UpdateCustomerParams,\n ): Promise<Customer> {\n const customer = await this.stripe.customers.update(id, {\n email: params.email,\n name: params.name,\n metadata: params.metadata,\n });\n\n return {\n id: customer.id,\n email: customer.email ?? '',\n firstName: customer.name?.split(' ')[0],\n lastName: customer.name?.split(' ').slice(1).join(' '),\n };\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flopay/node",
3
- "version": "1.2.1",
3
+ "version": "1.2.4",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/",
@@ -26,7 +26,7 @@
26
26
  ],
27
27
  "dependencies": {
28
28
  "stripe": "^22.2.0",
29
- "@flopay/shared": "1.2.1"
29
+ "@flopay/shared": "1.2.4"
30
30
  },
31
31
  "devDependencies": {
32
32
  "typescript": "^6.0.3",