@plumbus/payments-stripe 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +60 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/catalog.d.ts +14 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +286 -0
- package/dist/catalog.js.map +1 -0
- package/dist/charges.d.ts +64 -0
- package/dist/charges.d.ts.map +1 -0
- package/dist/charges.js +339 -0
- package/dist/charges.js.map +1 -0
- package/dist/config-rules.d.ts +7 -0
- package/dist/config-rules.d.ts.map +1 -0
- package/dist/config-rules.js +87 -0
- package/dist/config-rules.js.map +1 -0
- package/dist/diagnose.d.ts +17 -0
- package/dist/diagnose.d.ts.map +1 -0
- package/dist/diagnose.js +237 -0
- package/dist/diagnose.js.map +1 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +45 -0
- package/dist/errors.js.map +1 -0
- package/dist/events.d.ts +38 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +167 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/mapping.d.ts +328 -0
- package/dist/mapping.d.ts.map +1 -0
- package/dist/mapping.js +437 -0
- package/dist/mapping.js.map +1 -0
- package/dist/provider.d.ts +33 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +634 -0
- package/dist/provider.js.map +1 -0
- package/dist/resolve.d.ts +10 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +171 -0
- package/dist/resolve.js.map +1 -0
- package/dist/secrets.d.ts +7 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +33 -0
- package/dist/secrets.js.map +1 -0
- package/dist/testing/index.d.ts +54 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +119 -0
- package/dist/testing/index.js.map +1 -0
- package/instructions/README.md +14 -0
- package/instructions/configure-stripe.md +69 -0
- package/instructions/framework.md +62 -0
- package/instructions/testing.md +41 -0
- package/instructions/webhooks.md +44 -0
- package/package.json +66 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @plumbus/payments-stripe — Framework
|
|
2
|
+
|
|
3
|
+
`stripeProvider()` implements the `@plumbus/payments` provider contract with Stripe Connect and Stripe Billing. The app never imports `stripe` for money movement; it calls the payments capabilities and helpers.
|
|
4
|
+
|
|
5
|
+
**Peers:** `"@plumbus/core": "0.7.x"`, `"@plumbus/payments": "0.2.x"` — copy literally. The official `stripe` SDK (22.x) is a dependency of this package; do not add it to the app.
|
|
6
|
+
|
|
7
|
+
## Stripe APIs used (newest only)
|
|
8
|
+
|
|
9
|
+
| Need | Stripe API |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Seller accounts | Accounts **v2** (`/v2/core/accounts`): `merchant` configuration (card payments, direct charges) and/or `recipient` configuration (`stripe_transfers`, destination charges and transfers), `dashboard`, `defaults.responsibilities` |
|
|
12
|
+
| Hosted onboarding | Account Links **v2** (`/v2/core/account_links`, `account_onboarding`, every applied configuration) |
|
|
13
|
+
| Embedded seller components | Account Sessions (`/v1/account_sessions`) |
|
|
14
|
+
| Express dashboard link | Login Links (`/v1/accounts/:id/login_links`) |
|
|
15
|
+
| Payment pages | Checkout Sessions (`mode: payment`, `ui_mode: hosted_page \| embedded_page`): direct charges on the seller account (`Stripe-Account`) with `application_fee_amount`; destination charges on the platform with `transfer_data.destination` (+ `on_behalf_of`); platform charges with `transfer_group` |
|
|
16
|
+
| Custom amounts | Prices with `custom_unit_amount` |
|
|
17
|
+
| Holds and saved cards | `capture_method: manual`, `setup_future_usage: off_session`, off-session PaymentIntents, capture and cancel |
|
|
18
|
+
| Card-saving pages | Checkout Sessions `mode: setup`; Payment Methods list/detach |
|
|
19
|
+
| Invoices | Invoices (`send_invoice`) + Invoice Items, finalize, send, void; Invoice Payments |
|
|
20
|
+
| Payment links | Payment Links |
|
|
21
|
+
| Subscriptions | Checkout Sessions `mode: subscription`, Subscriptions update/cancel |
|
|
22
|
+
| Client and billing portals | Billing Portal sessions (a configuration is made when an account has none) |
|
|
23
|
+
| Transfers | Transfers (`source_transaction`), transfer reversals |
|
|
24
|
+
| Payouts | Balance Settings (schedule, delay), Balance (`instant_available`), Payouts |
|
|
25
|
+
| Disputes | Disputes update (evidence, submit) and close |
|
|
26
|
+
| Your plans | Products (stable ids), Prices by lookup key, Entitlement Features, Product Features, Active Entitlements, Billing Meters and meter events |
|
|
27
|
+
| Clients and billing customers | Customers (on the seller account for direct charges, on the platform otherwise) |
|
|
28
|
+
| Refunds | Refunds (`refund_application_fee`, `reverse_transfer` for destination charges) |
|
|
29
|
+
| Account webhooks | **Thin** v2 events `v2.core.account…` via an event destination (`@self`) |
|
|
30
|
+
| Everything else | **Snapshot** events from the platform (`@self`) and sellers' accounts (`@accounts`) via one event destination |
|
|
31
|
+
| Destinations | Event Destinations **v2** (`plumbus payments webhooks setup`) |
|
|
32
|
+
|
|
33
|
+
Pinned API version: `STRIPE_API_VERSION` (`2026-08-26.dahlia`). Snapshot destinations must render events in this version; `doctor` checks it. Not supported, by design: OAuth "connect an existing Stripe account" (v1-only), legacy Standard/Express/Custom account types, private-preview features.
|
|
34
|
+
|
|
35
|
+
## Public exports
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import {
|
|
39
|
+
stripeProvider,
|
|
40
|
+
STRIPE_API_VERSION,
|
|
41
|
+
STRIPE_SNAPSHOT_EVENTS,
|
|
42
|
+
STRIPE_THIN_EVENTS,
|
|
43
|
+
STRIPE_DESTINATION_NAMES,
|
|
44
|
+
} from '@plumbus/payments-stripe';
|
|
45
|
+
import {
|
|
46
|
+
signStripeWebhook,
|
|
47
|
+
stripeSnapshotEvent,
|
|
48
|
+
stripeThinAccountEvent,
|
|
49
|
+
createStripeHttpStub,
|
|
50
|
+
} from '@plumbus/payments-stripe/testing';
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`(await provider.client())` returns the configured `Stripe` instance for Stripe-only reads the neutral API does not cover. Do not use it to create charges, refunds, transfers, subscriptions, or accounts.
|
|
54
|
+
|
|
55
|
+
## Critical rules
|
|
56
|
+
|
|
57
|
+
1. **Stripe rules are enforced at startup:** Express needs `fees: 'platform'` and `losses: 'platform'`; `losses: 'platform'` always needs `fees: 'platform'`. Stripe's recommendations (destination charges for Express and no-dashboard sellers, direct for full) are warnings.
|
|
58
|
+
2. **Keys are read on first use.** Pass `secretKey` / `webhookSecrets` as functions reading env or a secret manager; the mode (live/test) comes from the key prefix, and events from the other mode are ignored.
|
|
59
|
+
3. **Both destinations sign with their own secret.** Pass every secret in `webhookSecrets` (plus the old ones during a rotation).
|
|
60
|
+
4. **Stripe errors keep Stripe's message.** Invalid requests surface as `validation` errors with `metadata.stripeCode`; outages as retryable `internal` errors.
|
|
61
|
+
5. **Only objects created through this package are tracked.** Payments a seller makes in their own dashboard are ignored (their refunds/disputes too, unless the charge is ours), and so are platform objects without this package's metadata.
|
|
62
|
+
6. **Run `plumbus payments catalog sync` after changing `billing`.** Subscriptions to plans find their prices by lookup key; a missing one fails with `stripe_price_missing`.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Testing with @plumbus/payments-stripe
|
|
2
|
+
|
|
3
|
+
Unit tests never call Stripe. For app logic, prefer the neutral fake (`@plumbus/payments/testing`). Use this package's helpers when you need real Stripe signatures or want to assert the requests Stripe would receive.
|
|
4
|
+
|
|
5
|
+
## Signed events
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { signStripeWebhook, stripeSnapshotEvent, stripeThinAccountEvent } from '@plumbus/payments-stripe/testing';
|
|
9
|
+
|
|
10
|
+
const delivery = signStripeWebhook({
|
|
11
|
+
payload: stripeSnapshotEvent({
|
|
12
|
+
type: 'checkout.session.completed',
|
|
13
|
+
account: 'acct_seller',
|
|
14
|
+
object: { id: 'cs_test_1', object: 'checkout.session' },
|
|
15
|
+
}),
|
|
16
|
+
secret: 'whsec_test',
|
|
17
|
+
});
|
|
18
|
+
// deliverTestWebhook(payments, ctx, delivery) or app.inject({ method: 'POST', url, headers: delivery.headers, payload: delivery.rawBody })
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`stripeThinAccountEvent({ type: 'v2.core.account.updated', accountId })` builds a thin notification. `signStripeWebhook({ timestamp })` produces stale signatures for negative tests.
|
|
22
|
+
|
|
23
|
+
## Stubbing Stripe's API
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { createStripeHttpStub } from '@plumbus/payments-stripe/testing';
|
|
27
|
+
|
|
28
|
+
const stub = createStripeHttpStub()
|
|
29
|
+
.on('POST /v2/core/accounts', () => ({ id: 'acct_seller', object: 'v2.core.account', livemode: false /* … */ }))
|
|
30
|
+
.on('GET /v1/checkout/sessions/*', () => ({ id: 'cs_test_1', status: 'complete', payment_status: 'paid' /* … */ }))
|
|
31
|
+
.on('POST /v1/refunds', () => ({ status: 400, body: { error: { type: 'invalid_request_error', message: '…' } } }));
|
|
32
|
+
|
|
33
|
+
const provider = stripeProvider({ secretKey: 'sk_test_x', webhookSecrets: ['whsec_test'], httpClient: stub.httpClient, maxNetworkRetries: 0 });
|
|
34
|
+
// stub.requests: method, path, query, headers (stripe-account, idempotency-key, stripe-version), body
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Unmatched routes answer 404, and the first route that matches answers (register one handler per route and keep changing state in variables). v1 bodies are form fields (`line_items[0][price_data][unit_amount]`); v2 bodies are JSON. Platform requests have no `stripe-account` header; seller requests (direct charges, payouts) do.
|
|
38
|
+
|
|
39
|
+
## Live test mode (optional, manual)
|
|
40
|
+
|
|
41
|
+
With a **test** key and the Stripe CLI forwarding (see [webhooks.md](./webhooks.md)): onboard a seller with Stripe's test onboarding data, create a charge, pay with card `4242 4242 4242 4242`, and watch `payments.charge.paid`. Never run this against live keys. In the Plumbus repo, `examples/payments-connect-app` runs this on the real runtime (`node scripts/dev.mjs --stripe`), and its `scripts/e2e.mjs` runs 28 scenarios — every payments feature, including destination charges, transfers, payouts, and platform billing with `catalog sync` — against a local Stripe simulator without keys.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Stripe webhooks for @plumbus/payments
|
|
2
|
+
|
|
3
|
+
One route (`POST /payments/webhooks/stripe`) receives two Stripe **event destinations**:
|
|
4
|
+
|
|
5
|
+
| Destination | Payload | `events_from` | Events |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| `plumbus-payments-sellers` | snapshot, version `2026-08-26.dahlia` | `@self` (your platform) and `@accounts` (connected accounts) | `STRIPE_SNAPSHOT_EVENTS`: checkout.session.*, payment_intent.*, refund.*, charge.refunded, charge.refund.updated, charge.dispute.*, payment_method.*, customer.subscription.*, invoice.*, transfer.*, payout.*, entitlements.active_entitlement_summary.updated |
|
|
8
|
+
| `plumbus-payments-accounts` | thin | `@self` | `STRIPE_THIN_EVENTS`: v2.core.account.created/updated/closed and `[requirements]`, `[configuration.merchant]`, `[configuration.recipient]`, `[defaults]`, `[identity]` updates |
|
|
9
|
+
|
|
10
|
+
Events from your platform's own account (destination and platform charges, transfers, your plans) carry no seller account. The adapter reads routing hints from the event's object — the `plumbus_tenant_id` metadata this package stamps on every Stripe object, the customer, payment, or subscription — so the route can find the tenant.
|
|
11
|
+
|
|
12
|
+
## Production / staging
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
plumbus payments webhooks setup --url https://api.example.com/payments/webhooks/stripe
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- Creates both destinations (skips ones that already exist for that URL) and prints each **signing secret once**.
|
|
19
|
+
- **After upgrading this package, run it again.** It adds the events a newer release needs to existing destinations. A snapshot destination from an earlier release takes events only from `@accounts`, and Stripe cannot change a destination's sources: setup creates a new one (add its secret) and `doctor --live` names the old one to delete.
|
|
20
|
+
- Put both secrets in `STRIPE_WEBHOOK_SECRETS` (comma-separated). During a rotation, list old and new secrets until Stripe stops signing with the old one.
|
|
21
|
+
- Verify: `plumbus payments doctor --live --webhook-url https://api.example.com/payments/webhooks/stripe`.
|
|
22
|
+
|
|
23
|
+
Do not create these destinations by hand with different names; `doctor` looks for the names above.
|
|
24
|
+
|
|
25
|
+
## Local development with the Stripe CLI
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
stripe listen --latest \
|
|
29
|
+
--forward-to localhost:3000/payments/webhooks/stripe \
|
|
30
|
+
--forward-connect-to localhost:3000/payments/webhooks/stripe \
|
|
31
|
+
--thin-events 'v2.core.account.created,v2.core.account.updated,v2.core.account.closed,v2.core.account[requirements].updated,v2.core.account[configuration.merchant].updated,v2.core.account[configuration.merchant].capability_status_updated,v2.core.account[configuration.recipient].updated,v2.core.account[configuration.recipient].capability_status_updated' \
|
|
32
|
+
--forward-thin-to localhost:3000/payments/webhooks/stripe
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Put the printed `whsec_…` secret in `STRIPE_WEBHOOK_SECRETS`. `--forward-to` carries your platform's own events (destination charges, plans, transfers); platform objects the app did not create are recorded as ignored — that is expected.
|
|
36
|
+
|
|
37
|
+
## How the route behaves
|
|
38
|
+
|
|
39
|
+
- Invalid signature / missing header → `400`, nothing recorded.
|
|
40
|
+
- Event from the other mode (test vs live), unrelated type, unknown seller, or a platform object without a tenant → `200`, recorded as `ignored` with a reason (`livemode_mismatch`, `unhandled_type`, `unknown_seller_account`, `unknown_platform_object`).
|
|
41
|
+
- Relevant event → `200`, recorded and queued; the worker re-reads Stripe and applies it. The same event delivered again → `200`, `duplicate` — unless its processing had failed; then it is queued again (resend it from the Dashboard to retry).
|
|
42
|
+
- Recording fails (database down) → `500`, so Stripe retries.
|
|
43
|
+
|
|
44
|
+
Inspect deliveries in the `PaymentProviderEvent` table (`status`, `ignoredReason`, `error`).
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@plumbus/payments-stripe",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Stripe provider for @plumbus/payments — Connect with Accounts v2 sellers (direct and destination charges, transfers, payouts), Checkout, invoices, subscriptions, Stripe Billing plans and meters, and snapshot + thin webhook verification.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./testing": {
|
|
14
|
+
"types": "./dist/testing/index.d.ts",
|
|
15
|
+
"default": "./dist/testing/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"instructions",
|
|
21
|
+
"README.md",
|
|
22
|
+
"CHANGELOG.md"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"plumbus",
|
|
26
|
+
"payments",
|
|
27
|
+
"stripe",
|
|
28
|
+
"stripe-connect",
|
|
29
|
+
"stripe-billing"
|
|
30
|
+
],
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/plumbus-framework/plumbus.git",
|
|
34
|
+
"directory": "packages/payments-stripe"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20.6.0"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"stripe": "^22.6.2"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@plumbus/core": "0.7.x",
|
|
47
|
+
"@plumbus/payments": "0.2.x"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^25.4.0",
|
|
51
|
+
"typescript": "^5.7.0",
|
|
52
|
+
"vitest": "^3.2.4",
|
|
53
|
+
"@plumbus/payments": "0.2.0",
|
|
54
|
+
"@plumbus/core": "0.7.7"
|
|
55
|
+
},
|
|
56
|
+
"license": "MIT",
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsc -b",
|
|
59
|
+
"dev": "tsc -b --watch",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"lint": "biome lint src",
|
|
62
|
+
"format": "biome format --write src",
|
|
63
|
+
"format:check": "biome format src",
|
|
64
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json"
|
|
65
|
+
}
|
|
66
|
+
}
|