@flopay/node 1.9.15 → 1.9.17
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 +151 -140
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @flopay/node
|
|
2
2
|
|
|
3
|
-
Server-side FloPay SDK for Node.js. Creates checkout sessions
|
|
3
|
+
Server-side FloPay SDK for Node.js. Creates checkout sessions through the FloPay billing API, retrieves and expires Stripe checkout sessions, manages Stripe customers, and verifies Stripe webhooks.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,186 +8,197 @@ Server-side FloPay SDK for Node.js. Creates checkout sessions via the billing AP
|
|
|
8
8
|
pnpm add @flopay/node
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
`stripe` and `@flopay/shared` install automatically as dependencies. Use it only from trusted server code: the constructor takes a Stripe secret key, which must never reach a browser.
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Usage
|
|
14
14
|
|
|
15
15
|
### Initialize
|
|
16
16
|
|
|
17
|
+
Create one client with your Stripe secret key. Direct Stripe operations use the API version in `STRIPE_API_VERSION` unless you pass `apiVersion`; pass `stripeSecretKey` to use a different key for them.
|
|
18
|
+
|
|
17
19
|
```ts
|
|
18
20
|
import { FloPay } from '@flopay/node';
|
|
19
21
|
|
|
20
|
-
const flopay = new FloPay('sk_test_...');
|
|
21
|
-
// Direct Stripe operations default to API version 2026-04-22.dahlia.
|
|
22
|
+
const flopay = new FloPay('sk_test_...'); // load the real key from your secret store
|
|
22
23
|
```
|
|
23
24
|
|
|
24
|
-
### Create a
|
|
25
|
+
### Create a checkout session
|
|
26
|
+
|
|
27
|
+
`flopay.checkout.sessions.create()` posts to the billing API's `POST /v1/checkouts/sessions` and resolves with a `CheckoutSessionResult`:
|
|
28
|
+
|
|
29
|
+
- `{ status: 201, redirectUrl, nonce }`: send the buyer to `redirectUrl`, the hosted checkout page (`checkoutBaseUrl` + `/secure`, with the session id and your `redirectParams`). `nonce` is the session-bound checkout token; forward it as the `x-checkout-session-token` header on every continuation call for the same session.
|
|
30
|
+
- `{ status: 204 }`: the buyer already has a payment method on file and nothing needs to be shown.
|
|
31
|
+
|
|
32
|
+
The session currency is required. Pass `currency` on the session, or on the first item, subscription, or product. `checkoutMode` defaults to `'confirm'` on this method, and `timeoutMs` to 12 seconds.
|
|
33
|
+
|
|
34
|
+
`checkoutMetadata` accepts `Record<string, string> | null` and is sent unchanged: omitting it leaves the request body unchanged, an explicit `null` is preserved, and `{}` stays an empty map. The billing API is the only authority on metadata limits.
|
|
35
|
+
|
|
36
|
+
### Retry safely with an idempotency key
|
|
37
|
+
|
|
38
|
+
Pass the same `idempotencyKey` when you retry one logical session creation after an unknown outcome. A non-blank key of at most 255 characters is sent unchanged as the `Idempotency-Key` header. Empty, whitespace-only, overlong, and header-unsafe values are ignored without throwing, and no key is generated when you omit the option. `@flopay/node` has no internal retry loop, so your server owns retry timing and key reuse.
|
|
39
|
+
|
|
40
|
+
### Authorise now, capture later
|
|
41
|
+
|
|
42
|
+
To authorise an eligible one-time card cart without capturing it, pass `captureMethod: 'manual'`. Manual capture cannot be combined with subscriptions, and omitting the option keeps immediate capture. The later capture is deliberately not an `@flopay/node` method: call the merchant-authenticated REST endpoint from trusted server code with a stable `Idempotency-Key`, as described in the [payments and authorisations reference](https://flopay.com/docs/technical-reference/rest/payments).
|
|
43
|
+
|
|
44
|
+
### Saved cards
|
|
45
|
+
|
|
46
|
+
Saved-card management is also not an `@flopay/node` credential surface. From trusted server code, use the authenticated REST API to list display-only payment methods, create a setup session, and delete an eligible method, then pass only the setup session's `sessionId` and `nonce` to `FloPayCardSetup` in `@flopay/react`. Replace a card setup-first, then delete, so a failed or cancelled setup leaves the existing card intact. See the [payment methods reference](https://flopay.com/docs/technical-reference/rest/payment-methods).
|
|
47
|
+
|
|
48
|
+
### Stripe checkout sessions and customers
|
|
49
|
+
|
|
50
|
+
`checkout.sessions.retrieve()`, `expire()`, and `listLineItems()` call Stripe directly and normalize the result. `customers.create()`, `retrieve()`, and `update()` manage Stripe customers and return a normalized `Customer`.
|
|
51
|
+
|
|
52
|
+
### Verify webhooks
|
|
53
|
+
|
|
54
|
+
`webhooks.constructEvent()` verifies the `Stripe-Signature` header against your webhook signing secret and returns a normalized `WebhookEvent` (`id`, `type`, `data`, `created`). Pass the raw, unparsed request body.
|
|
55
|
+
|
|
56
|
+
## Examples
|
|
57
|
+
|
|
58
|
+
Create a checkout session and redirect the buyer:
|
|
25
59
|
|
|
26
60
|
```ts
|
|
61
|
+
import { FloPay } from '@flopay/node';
|
|
62
|
+
|
|
63
|
+
function requiredEnv(name: string): string {
|
|
64
|
+
const value = process.env[name];
|
|
65
|
+
if (!value) throw new Error(`${name} is not set`);
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const flopay = new FloPay(requiredEnv('STRIPE_SECRET_KEY'));
|
|
70
|
+
|
|
27
71
|
const result = await flopay.checkout.sessions.create({
|
|
28
72
|
billingApiUrl: 'https://billing.example.com',
|
|
29
73
|
checkoutBaseUrl: 'https://checkout.example.com',
|
|
30
74
|
clientId: 'client_123',
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
userId: 'user_1',
|
|
46
|
-
email: 'user@example.com',
|
|
47
|
-
firstName: 'John',
|
|
48
|
-
lastName: 'Doe',
|
|
49
|
-
},
|
|
50
|
-
successUrl: 'https://example.com/success',
|
|
51
|
-
cancelUrl: 'https://example.com/cancel',
|
|
52
|
-
checkoutMode: 'confirm', // 'confirm' | 'auto' | 'full'
|
|
75
|
+
currency: 'USD',
|
|
76
|
+
items: [
|
|
77
|
+
{
|
|
78
|
+
providerItemId: 'prod_abc',
|
|
79
|
+
providerItemName: 'Pro Plan',
|
|
80
|
+
totalAmount: 49.99,
|
|
81
|
+
overrideAmount: 24.99,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
subscriptions: [{ providerPlanId: 'plan_xyz', providerPlanName: 'Monthly Plan', totalAmount: 9.99 }],
|
|
85
|
+
account: { userId: 'user_1', email: 'user@example.com', firstName: 'Jane', lastName: 'Doe' },
|
|
86
|
+
successUrl: 'https://shop.example.com/success',
|
|
87
|
+
cancelUrl: 'https://shop.example.com/cancel',
|
|
88
|
+
checkoutMode: 'full',
|
|
53
89
|
checkoutMetadata: { merchantOrderId: 'order-123', campaign: 'summer' },
|
|
54
90
|
couponCodes: ['SAVE10'],
|
|
55
91
|
tagsData: { googleContainerId: 'GTM-XXXX' },
|
|
56
|
-
redirectParams: { email: 'user@example.com'
|
|
57
|
-
timeoutMs:
|
|
58
|
-
idempotencyKey: 'order-123
|
|
92
|
+
redirectParams: { email: 'user@example.com' },
|
|
93
|
+
timeoutMs: 12_000,
|
|
94
|
+
idempotencyKey: 'checkout:order-123',
|
|
59
95
|
});
|
|
60
96
|
|
|
61
|
-
|
|
62
|
-
|
|
97
|
+
if (result.status === 201 && 'redirectUrl' in result) {
|
|
98
|
+
console.log('Redirect the buyer to', result.redirectUrl);
|
|
99
|
+
} else if (result.status === 204) {
|
|
100
|
+
console.log('A payment method is already on file.');
|
|
101
|
+
}
|
|
63
102
|
```
|
|
64
103
|
|
|
65
|
-
|
|
66
|
-
unchanged. Omission leaves the request body unchanged, explicit `null` is
|
|
67
|
-
preserved, and `{}` remains an empty map. The backend is the sole runtime
|
|
68
|
-
authority for metadata limits; a validation rejection is a `FloPayError` with
|
|
69
|
-
the backend code, message, and HTTP `statusCode` intact.
|
|
70
|
-
|
|
71
|
-
Pass the same `idempotencyKey` when retrying one logical session creation after
|
|
72
|
-
an unknown outcome. `@flopay/node` sends a supplied non-whitespace key of at most
|
|
73
|
-
255 characters unchanged as `Idempotency-Key`; it does not generate a key or add
|
|
74
|
-
the header when the option is omitted. Empty, whitespace-only, overlong, and
|
|
75
|
-
Fetch-header-unsafe values are ignored without throwing or emitting a malformed
|
|
76
|
-
header. The Node SDK has no internal retry loop, so the server integration owns
|
|
77
|
-
retry timing and key reuse.
|
|
78
|
-
|
|
79
|
-
To authorise an eligible one-time card cart without capturing it, pass
|
|
80
|
-
`captureMethod: 'manual'` to `checkout.sessions.create`. Do not combine manual
|
|
81
|
-
capture with subscriptions; the SDK rejects that cart before making a request.
|
|
82
|
-
Omitting the option preserves immediate capture and leaves the wire key absent.
|
|
83
|
-
|
|
84
|
-
The later full capture is intentionally not an `@flopay/node` method. Use the
|
|
85
|
-
merchant-authenticated REST endpoint from trusted server code with a stable
|
|
86
|
-
`Idempotency-Key`; never expose merchant credentials to a browser. See the
|
|
87
|
-
repository's [pre-authorisation guide](../../docs/PREAUTHORIZATION.md).
|
|
88
|
-
|
|
89
|
-
Saved-card management is also intentionally not an `@flopay/node` credential
|
|
90
|
-
surface. From trusted merchant server code, use the authenticated REST API to
|
|
91
|
-
list display-only methods, create a setup session, and delete an eligible
|
|
92
|
-
method; pass only the resulting setup `sessionId` and `nonce` to
|
|
93
|
-
`<FloPayCardSetup>` in `@flopay/react`. Replacement is setup-first, then delete,
|
|
94
|
-
so a failed or cancelled setup leaves the existing card intact. The coordinated
|
|
95
|
-
integration guide is tracked in
|
|
96
|
-
[TeamFloPay/docs#38](https://github.com/TeamFloPay/docs/issues/38).
|
|
97
|
-
|
|
98
|
-
### Retrieve / Expire a Checkout Session (Stripe Direct)
|
|
104
|
+
Verify a Stripe webhook in a plain Node.js HTTP server:
|
|
99
105
|
|
|
100
106
|
```ts
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const expired = await flopay.checkout.sessions.expire('cs_test_...');
|
|
107
|
+
import { createServer } from 'node:http';
|
|
108
|
+
import { FloPay } from '@flopay/node';
|
|
105
109
|
|
|
106
|
-
const
|
|
107
|
-
|
|
110
|
+
const flopay = new FloPay(process.env.STRIPE_SECRET_KEY ?? 'sk_test_...');
|
|
111
|
+
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET ?? 'whsec_...';
|
|
112
|
+
const MAX_BODY_BYTES = 64 * 1024; // Pick a limit that fits your largest expected event.
|
|
113
|
+
|
|
114
|
+
createServer((request, response) => {
|
|
115
|
+
const chunks: Buffer[] = [];
|
|
116
|
+
let received = 0;
|
|
117
|
+
request.on('data', (chunk: Buffer) => {
|
|
118
|
+
received += chunk.length;
|
|
119
|
+
if (received > MAX_BODY_BYTES) {
|
|
120
|
+
response.writeHead(413, { Connection: 'close' }).end('Payload too large');
|
|
121
|
+
request.destroy();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
chunks.push(chunk);
|
|
125
|
+
});
|
|
126
|
+
request.on('end', () => {
|
|
127
|
+
try {
|
|
128
|
+
const event = flopay.webhooks.constructEvent(
|
|
129
|
+
Buffer.concat(chunks),
|
|
130
|
+
String(request.headers['stripe-signature']),
|
|
131
|
+
webhookSecret,
|
|
132
|
+
);
|
|
133
|
+
console.log(event.type, event.created);
|
|
134
|
+
response.writeHead(200).end();
|
|
135
|
+
} catch {
|
|
136
|
+
response.writeHead(400).end('Invalid signature');
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}).listen(3000);
|
|
108
140
|
```
|
|
109
141
|
|
|
110
|
-
|
|
142
|
+
Manage Stripe customers:
|
|
111
143
|
|
|
112
144
|
```ts
|
|
113
|
-
|
|
114
|
-
email: 'user@example.com',
|
|
115
|
-
name: 'John Doe',
|
|
116
|
-
metadata: { plan: 'pro' },
|
|
117
|
-
});
|
|
118
|
-
// { id: 'cus_xxx', email: '...', firstName: 'John', lastName: 'Doe' }
|
|
145
|
+
import { FloPay } from '@flopay/node';
|
|
119
146
|
|
|
120
|
-
const
|
|
147
|
+
const flopay = new FloPay(process.env.STRIPE_SECRET_KEY ?? 'sk_test_...');
|
|
121
148
|
|
|
122
|
-
const
|
|
149
|
+
const customer = await flopay.customers.create({
|
|
150
|
+
email: 'user@example.com',
|
|
123
151
|
name: 'Jane Doe',
|
|
124
|
-
metadata: { plan: '
|
|
152
|
+
metadata: { plan: 'pro' },
|
|
125
153
|
});
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### Verify Webhooks
|
|
129
|
-
|
|
130
|
-
```ts
|
|
131
|
-
import { FloPay } from '@flopay/node';
|
|
132
|
-
|
|
133
|
-
const flopay = new FloPay('sk_test_...');
|
|
134
|
-
|
|
135
|
-
// In your webhook handler (e.g. Express, Next.js API route):
|
|
136
|
-
const event = flopay.webhooks.constructEvent(
|
|
137
|
-
requestBody, // raw body string or Buffer
|
|
138
|
-
signatureHeader, // Stripe-Signature header
|
|
139
|
-
'whsec_...', // webhook signing secret
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
console.log(event.type); // e.g. 'checkout.session.completed'
|
|
143
|
-
console.log(event.data); // event payload
|
|
144
|
-
console.log(event.created); // unix timestamp
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
## API Reference
|
|
148
154
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
new FloPay(secretKey: string, options?: FloPayNodeOptions)
|
|
155
|
+
const updated = await flopay.customers.update(customer.id, { metadata: { plan: 'enterprise' } });
|
|
156
|
+
const session = await flopay.checkout.sessions.retrieve('cs_test_...');
|
|
157
|
+
console.log(updated.email, session.status, session.amount);
|
|
153
158
|
```
|
|
154
159
|
|
|
155
|
-
|
|
156
|
-
|-----------|------|-------------|
|
|
157
|
-
| `secretKey` | `string` | Stripe secret key (`sk_test_...` or `sk_live_...`). Required. |
|
|
158
|
-
| `options.apiVersion` | `string?` | Direct Stripe API version override. Defaults to `2026-04-22.dahlia`. |
|
|
159
|
-
| `options.stripeSecretKey` | `string?` | Explicit Stripe key override for direct Stripe operations. |
|
|
160
|
+
## Public API
|
|
160
161
|
|
|
161
|
-
###
|
|
162
|
+
### Exports
|
|
162
163
|
|
|
163
|
-
|
|
|
164
|
-
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `listLineItems(id)` | `Promise<LineItem[]>` | Lists line items for a Stripe checkout session |
|
|
164
|
+
| Export | Description |
|
|
165
|
+
|--------|-------------|
|
|
166
|
+
| `FloPay` | Server-side client: `new FloPay(secretKey, options?)` with a Stripe secret key (`sk_test_...` or `sk_live_...`) |
|
|
167
|
+
| `FloPayNodeOptions` | Constructor options |
|
|
168
|
+
| `STRIPE_API_VERSION` | `'2026-04-22.dahlia'`, the default Stripe API version for direct Stripe operations |
|
|
169
169
|
|
|
170
|
-
###
|
|
170
|
+
### FloPayNodeOptions
|
|
171
171
|
|
|
172
|
-
|
|
|
173
|
-
|
|
174
|
-
| `
|
|
175
|
-
| `
|
|
176
|
-
| `update(id, params)` | `Promise<Customer>` | Updates a Stripe customer |
|
|
172
|
+
| Option | Type | Description |
|
|
173
|
+
|--------|------|-------------|
|
|
174
|
+
| `apiVersion` | `string?` | Stripe API version override for direct Stripe operations |
|
|
175
|
+
| `stripeSecretKey` | `string?` | Stripe secret key for direct Stripe operations, instead of the constructor key |
|
|
177
176
|
|
|
178
|
-
###
|
|
177
|
+
### FloPay methods
|
|
179
178
|
|
|
180
179
|
| Method | Returns | Description |
|
|
181
180
|
|--------|---------|-------------|
|
|
182
|
-
| `
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
|
187
|
-
|
|
188
|
-
| `
|
|
189
|
-
| `
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
181
|
+
| `checkout.sessions.create(params)` | `Promise<CheckoutSessionResult>` | Creates a session through the billing API (`POST /v1/checkouts/sessions`). Accepts `CreateSessionParams`. |
|
|
182
|
+
| `checkout.sessions.retrieve(id)` | `Promise<CheckoutSession>` | Retrieves and normalizes a Stripe checkout session |
|
|
183
|
+
| `checkout.sessions.expire(id)` | `Promise<CheckoutSession>` | Expires a Stripe checkout session |
|
|
184
|
+
| `checkout.sessions.listLineItems(id)` | `Promise<LineItem[]>` | Lists a Stripe checkout session's line items |
|
|
185
|
+
| `customers.create(params)` | `Promise<Customer>` | Creates a Stripe customer |
|
|
186
|
+
| `customers.retrieve(id)` | `Promise<Customer>` | Retrieves a Stripe customer |
|
|
187
|
+
| `customers.update(id, params)` | `Promise<Customer>` | Updates a Stripe customer |
|
|
188
|
+
| `webhooks.constructEvent(payload, signature, secret)` | `WebhookEvent` | Verifies and parses a Stripe webhook event |
|
|
189
|
+
|
|
190
|
+
The parameter and result types (`CreateSessionParams`, `CheckoutSessionResult`, `CheckoutSession`, `Customer`, `LineItem`, `WebhookEvent`) are exported by `@flopay/shared`.
|
|
191
|
+
|
|
192
|
+
## Errors
|
|
193
|
+
|
|
194
|
+
SDK validation and billing API failures are `FloPayError`s from `@flopay/shared`; branch on `type` and `code`.
|
|
195
|
+
|
|
196
|
+
| Code | Type | When |
|
|
197
|
+
|------|------|------|
|
|
198
|
+
| `CurrencyRequired` | `validation_error` | `checkout.sessions.create()` could not resolve a session currency. Thrown before any request, with `param: 'currency'`. |
|
|
199
|
+
| `CaptureMethodUnsupportedForSubscription` | `validation_error` | `captureMethod: 'manual'` was requested for a cart with a subscription. Thrown before any request. |
|
|
200
|
+
| The billing API's `code`, or `http_<status>` | `api_error` | The billing API rejected session creation. The error keeps the billing API's message and code, with the HTTP status in `statusCode`. |
|
|
201
|
+
| `MissingCheckoutSessionToken` | `api_error` | The billing API created a session but returned no `nonce`, which means it is too old for this SDK. |
|
|
202
|
+
| `resource_missing` | `api_error` | `customers.retrieve()` found a deleted customer. |
|
|
203
|
+
|
|
204
|
+
Constructing `FloPay` without a secret key throws an `authentication_error`. A create request that exceeds `timeoutMs` is aborted, and network failures reject with the underlying fetch error rather than a `FloPayError`. Stripe operations, including webhook signature verification, reject with Stripe's own errors. Treat a failed `constructEvent()` as an untrusted request.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flopay/node",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"stripe": "^22.2.1",
|
|
30
|
-
"@flopay/shared": "1.9.
|
|
30
|
+
"@flopay/shared": "1.9.17"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"tsup": "^8.3.0",
|