@owlmeans/server-payment 0.1.18-rc.2 → 0.1.18-rc.21
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 +109 -25
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/server-payment/SKILL.md +475 -52
- package/build/actions/index.d.ts +1 -0
- package/build/actions/index.d.ts.map +1 -1
- package/build/actions/index.js +1 -0
- package/build/actions/index.js.map +1 -1
- package/build/actions/resync-subscriptions.d.ts +3 -0
- package/build/actions/resync-subscriptions.d.ts.map +1 -0
- package/build/actions/resync-subscriptions.js +7 -0
- package/build/actions/resync-subscriptions.js.map +1 -0
- package/build/actions/resync.d.ts +1 -0
- package/build/actions/resync.d.ts.map +1 -1
- package/build/actions/resync.js +7 -3
- package/build/actions/resync.js.map +1 -1
- package/build/actions/webhook.d.ts.map +1 -1
- package/build/actions/webhook.js +6 -3
- package/build/actions/webhook.js.map +1 -1
- package/build/config.d.ts +60 -5
- package/build/config.d.ts.map +1 -1
- package/build/config.js +237 -5
- package/build/config.js.map +1 -1
- package/build/consts.d.ts +75 -3
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +94 -5
- package/build/consts.js.map +1 -1
- package/build/consumer/capture.d.ts +74 -0
- package/build/consumer/capture.d.ts.map +1 -0
- package/build/consumer/capture.js +291 -0
- package/build/consumer/capture.js.map +1 -0
- package/build/consumer/format.d.ts +27 -0
- package/build/consumer/format.d.ts.map +1 -0
- package/build/consumer/format.js +81 -0
- package/build/consumer/format.js.map +1 -0
- package/build/consumer/handlers.d.ts +28 -0
- package/build/consumer/handlers.d.ts.map +1 -0
- package/build/consumer/handlers.js +173 -0
- package/build/consumer/handlers.js.map +1 -0
- package/build/consumer/index.d.ts +7 -0
- package/build/consumer/index.d.ts.map +1 -0
- package/build/consumer/index.js +6 -0
- package/build/consumer/index.js.map +1 -0
- package/build/consumer/mail.d.ts +27 -0
- package/build/consumer/mail.d.ts.map +1 -0
- package/build/consumer/mail.js +314 -0
- package/build/consumer/mail.js.map +1 -0
- package/build/consumer/origin.d.ts +14 -0
- package/build/consumer/origin.d.ts.map +1 -0
- package/build/consumer/origin.js +47 -0
- package/build/consumer/origin.js.map +1 -0
- package/build/consumer/reconcile.d.ts +12 -0
- package/build/consumer/reconcile.d.ts.map +1 -0
- package/build/consumer/reconcile.js +317 -0
- package/build/consumer/reconcile.js.map +1 -0
- package/build/consumer/records.d.ts +78 -0
- package/build/consumer/records.d.ts.map +1 -0
- package/build/consumer/records.js +296 -0
- package/build/consumer/records.js.map +1 -0
- package/build/consumer/service.d.ts +51 -0
- package/build/consumer/service.d.ts.map +1 -0
- package/build/consumer/service.js +760 -0
- package/build/consumer/service.js.map +1 -0
- package/build/consumer/withdrawal.d.ts +57 -0
- package/build/consumer/withdrawal.d.ts.map +1 -0
- package/build/consumer/withdrawal.js +247 -0
- package/build/consumer/withdrawal.js.map +1 -0
- package/build/entitlement.d.ts +10 -0
- package/build/entitlement.d.ts.map +1 -0
- package/build/entitlement.js +69 -0
- package/build/entitlement.js.map +1 -0
- package/build/entrypoints.d.ts +1 -1
- package/build/entrypoints.d.ts.map +1 -1
- package/build/entrypoints.js +2 -1
- package/build/entrypoints.js.map +1 -1
- package/build/gate.d.ts +32 -4
- package/build/gate.d.ts.map +1 -1
- package/build/gate.js +59 -19
- package/build/gate.js.map +1 -1
- package/build/index.d.ts +18 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +15 -3
- package/build/index.js.map +1 -1
- package/build/limit.d.ts +13 -0
- package/build/limit.d.ts.map +1 -0
- package/build/limit.js +47 -0
- package/build/limit.js.map +1 -0
- package/build/model.d.ts +10 -1
- package/build/model.d.ts.map +1 -1
- package/build/model.js +215 -17
- package/build/model.js.map +1 -1
- package/build/observer.d.ts +9 -0
- package/build/observer.d.ts.map +1 -1
- package/build/observer.js +38 -12
- package/build/observer.js.map +1 -1
- package/build/plan.d.ts +22 -0
- package/build/plan.d.ts.map +1 -0
- package/build/plan.js +72 -0
- package/build/plan.js.map +1 -0
- package/build/plugins/checkout-plugins.d.ts +49 -0
- package/build/plugins/checkout-plugins.d.ts.map +1 -0
- package/build/plugins/checkout-plugins.js +124 -0
- package/build/plugins/checkout-plugins.js.map +1 -0
- package/build/plugins/estimate.d.ts +43 -0
- package/build/plugins/estimate.d.ts.map +1 -0
- package/build/plugins/estimate.js +268 -0
- package/build/plugins/estimate.js.map +1 -0
- package/build/plugins/events.d.ts +45 -10
- package/build/plugins/events.d.ts.map +1 -1
- package/build/plugins/events.js +605 -136
- package/build/plugins/events.js.map +1 -1
- package/build/plugins/fx.d.ts +31 -0
- package/build/plugins/fx.d.ts.map +1 -0
- package/build/plugins/fx.js +81 -0
- package/build/plugins/fx.js.map +1 -0
- package/build/plugins/portal.d.ts +37 -0
- package/build/plugins/portal.d.ts.map +1 -0
- package/build/plugins/portal.js +265 -0
- package/build/plugins/portal.js.map +1 -0
- package/build/plugins/refunds.d.ts +33 -0
- package/build/plugins/refunds.d.ts.map +1 -0
- package/build/plugins/refunds.js +80 -0
- package/build/plugins/refunds.js.map +1 -0
- package/build/plugins/stripe.d.ts +36 -4
- package/build/plugins/stripe.d.ts.map +1 -1
- package/build/plugins/stripe.js +492 -97
- package/build/plugins/stripe.js.map +1 -1
- package/build/plugins/webhook-manager.d.ts +46 -0
- package/build/plugins/webhook-manager.d.ts.map +1 -0
- package/build/plugins/webhook-manager.js +231 -0
- package/build/plugins/webhook-manager.js.map +1 -0
- package/build/reconcile.d.ts +15 -0
- package/build/reconcile.d.ts.map +1 -0
- package/build/reconcile.js +88 -0
- package/build/reconcile.js.map +1 -0
- package/build/resource.d.ts +12 -1
- package/build/resource.d.ts.map +1 -1
- package/build/resource.js +97 -6
- package/build/resource.js.map +1 -1
- package/build/service.d.ts +28 -3
- package/build/service.d.ts.map +1 -1
- package/build/service.js +146 -19
- package/build/service.js.map +1 -1
- package/build/subscription.d.ts +48 -0
- package/build/subscription.d.ts.map +1 -0
- package/build/subscription.js +176 -0
- package/build/subscription.js.map +1 -0
- package/build/sync.d.ts +28 -1
- package/build/sync.d.ts.map +1 -1
- package/build/sync.js +208 -31
- package/build/sync.js.map +1 -1
- package/build/types.d.ts +1115 -36
- package/build/types.d.ts.map +1 -1
- package/build/usage.d.ts +63 -0
- package/build/usage.d.ts.map +1 -0
- package/build/usage.js +363 -0
- package/build/usage.js.map +1 -0
- package/build/utils.d.ts +52 -1
- package/build/utils.d.ts.map +1 -1
- package/build/utils.js +69 -7
- package/build/utils.js.map +1 -1
- package/package.json +17 -13
- package/src/actions/index.ts +1 -0
- package/src/actions/resync-subscriptions.ts +10 -0
- package/src/actions/resync.ts +6 -3
- package/src/actions/webhook.ts +5 -3
- package/src/config.ts +264 -8
- package/src/consts.ts +114 -6
- package/src/consumer/capture.ts +362 -0
- package/src/consumer/format.ts +90 -0
- package/src/consumer/handlers.ts +211 -0
- package/src/consumer/index.ts +6 -0
- package/src/consumer/mail.ts +368 -0
- package/src/consumer/origin.ts +63 -0
- package/src/consumer/reconcile.ts +329 -0
- package/src/consumer/records.ts +374 -0
- package/src/consumer/service.ts +868 -0
- package/src/consumer/withdrawal.ts +302 -0
- package/src/entitlement.ts +84 -0
- package/src/entrypoints.ts +2 -1
- package/src/gate.ts +88 -21
- package/src/index.ts +24 -3
- package/src/limit.ts +57 -0
- package/src/model.ts +237 -18
- package/src/observer.ts +44 -11
- package/src/plan.ts +89 -0
- package/src/plugins/checkout-plugins.ts +155 -0
- package/src/plugins/estimate.ts +339 -0
- package/src/plugins/events.ts +677 -121
- package/src/plugins/fx.ts +122 -0
- package/src/plugins/portal.ts +306 -0
- package/src/plugins/refunds.ts +108 -0
- package/src/plugins/stripe.ts +581 -96
- package/src/plugins/webhook-manager.ts +270 -0
- package/src/reconcile.ts +103 -0
- package/src/resource.ts +152 -7
- package/src/service.ts +174 -18
- package/src/subscription.ts +231 -0
- package/src/sync.ts +249 -29
- package/src/types.ts +1227 -32
- package/src/usage.ts +453 -0
- package/src/utils.ts +127 -10
- package/tests/checkout-consumer.spec.ts +348 -0
- package/tests/checkout-plugins.spec.ts +164 -0
- package/tests/checkout.spec.ts +184 -83
- package/tests/consumer-events.spec.ts +218 -0
- package/tests/consumer-fixtures.ts +132 -0
- package/tests/consumer-ops.spec.ts +351 -0
- package/tests/consumer-rights.integration.spec.ts +150 -0
- package/tests/consumer-rights.spec.ts +501 -0
- package/tests/context.ts +109 -0
- package/tests/entitlement.spec.ts +103 -0
- package/tests/estimate.spec.ts +240 -0
- package/tests/events.spec.ts +356 -0
- package/tests/fake-stripe.ts +972 -0
- package/tests/gate.spec.ts +62 -72
- package/tests/limit-gate.spec.ts +68 -0
- package/tests/portal.spec.ts +200 -0
- package/tests/protocol.spec.ts +23 -6
- package/tests/sync.spec.ts +114 -0
- package/tests/usage.integration.spec.ts +101 -0
- package/tests/usage.spec.ts +171 -0
- package/tests/webhook-manager.spec.ts +152 -0
package/README.md
CHANGED
|
@@ -1,42 +1,126 @@
|
|
|
1
1
|
# @owlmeans/server-payment
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Server-side payments for OwlMeans applications: a Stripe gateway embedded in the backend
|
|
4
|
+
(amount and quantity checkout, subscriptions, the customer portal), a subscription store that
|
|
5
|
+
understands the whole Stripe lifecycle, a usage ledger for counted plan limits, the entitlement
|
|
6
|
+
service that resolves what an organization entity may do, and the two gate services that refuse a
|
|
7
|
+
request before its handler runs. The contracts — plans, limits, promos, the entitlement view, the
|
|
8
|
+
refusal errors — live in `@owlmeans/payment`.
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
register completion callbacks on the payment observer. Amount policies are validated at declaration
|
|
9
|
-
time. Stripe Checkout is always authoritative: completion is idempotent by session id and an
|
|
10
|
-
amount checkout is fulfilled only after its paid currency and subtotal match its signed-in metadata.
|
|
10
|
+
## Declare the catalogue
|
|
11
11
|
|
|
12
12
|
```typescript
|
|
13
|
-
import { CheckoutPricingMode, PlanDuration, ProductType } from '@owlmeans/payment'
|
|
13
|
+
import { CheckoutPricingMode, LimitKind, LimitWindow, PlanDuration, ProductType } from '@owlmeans/payment'
|
|
14
14
|
import {
|
|
15
|
-
|
|
15
|
+
declarePaymentPlan, declarePaymentProduct, portalBranding, stripeSecrets,
|
|
16
16
|
} from '@owlmeans/server-payment'
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
stripeSecrets(cfg, { api: '/secrets/stripe-key' }) // `webhook` is an optional override
|
|
19
|
+
portalBranding(cfg, { returnUrl: 'https://app.example.com/billing', headline: 'Example' })
|
|
20
|
+
|
|
21
|
+
declarePaymentProduct(cfg, { sku: 'app-plans', type: ProductType.Service, services: ['app'], name: 'Plans' })
|
|
22
|
+
declarePaymentPlan(cfg, {
|
|
23
|
+
productSku: 'app-plans', sku: 'free', duration: PlanDuration.Monthly, rank: 0, free: true, price: 0,
|
|
24
|
+
capabilities: [{ scope: 'feature', permissions: { basic: true } }],
|
|
25
|
+
limits: { seats: { kind: LimitKind.Occupancy, limit: 1 }, exports: { kind: LimitKind.Window, window: LimitWindow.Month, limit: 3 } },
|
|
21
26
|
})
|
|
22
27
|
declarePaymentPlan(cfg, {
|
|
23
|
-
productSku: 'app-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
presetsMinor: [1_000, 2_000, 5_000, 10_000], fixedMinor: 0, rateBps: 200,
|
|
28
|
-
},
|
|
28
|
+
productSku: 'app-plans', sku: 'pro-monthly', duration: PlanDuration.Monthly, rank: 10, price: 20,
|
|
29
|
+
recurring: { interval: 'month' },
|
|
30
|
+
capabilities: [{ scope: 'feature', permissions: { basic: true, whitelabel: true } }],
|
|
31
|
+
limits: { seats: { kind: LimitKind.Occupancy, limit: 5 }, exports: { kind: LimitKind.Window, window: LimitWindow.Day, limit: 20 } },
|
|
29
32
|
})
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
declarePaymentProduct(cfg, { sku: 'app-credits', type: ProductType.Consumable, services: ['app'], name: 'Credits' })
|
|
35
|
+
declarePaymentPlan(cfg, {
|
|
36
|
+
productSku: 'app-credits', sku: 'app-credit-unit', duration: PlanDuration.Consumable, price: 0.02,
|
|
37
|
+
pricingMode: CheckoutPricingMode.Amount,
|
|
38
|
+
amountPolicy: { currency: 'usd', minimumMinor: 500, maximumMinor: 50_000, defaultMinor: 1_000,
|
|
39
|
+
presetsMinor: [1_000, 5_000], fixedMinor: 0, rateBps: 200 },
|
|
34
40
|
})
|
|
35
41
|
```
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
`declarePaymentPlan` validates each declaration before recording it; `assertPlanDeclarations` checks
|
|
44
|
+
the catalogue as a whole (one free plan per rank, no two paid plans of a product at one rank) and runs
|
|
45
|
+
when the gateway initializes. A limit key may use a different kind on different plans — each kind keeps
|
|
46
|
+
its own counter window, so a lifetime count still sticks to the entity when it moves to a monthly plan.
|
|
47
|
+
|
|
48
|
+
## Wire the services
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import {
|
|
52
|
+
appendPaymentGatewayService, entitlements, gateway, observer, paymentGateEntrypoints,
|
|
53
|
+
} from '@owlmeans/server-payment'
|
|
54
|
+
|
|
55
|
+
appendPaymentGatewayService(context) // the process that owns Stripe
|
|
56
|
+
appendPaymentGatewayService(context, { manage: false }) // a process that only reads entitlements
|
|
57
|
+
export const serverBindings = [...paymentGateEntrypoints]
|
|
58
|
+
|
|
59
|
+
observer(context).onTopUp(async completion => { /* credit, idempotent by completion.externalId */ })
|
|
60
|
+
observer(context).onSubscription(async event => { /* idempotent by event.eventKey */ })
|
|
61
|
+
observer(context).onRefund(async event => { /* claw back, idempotent by event.eventKey */ })
|
|
62
|
+
observer(context).onDispute(async event => { /* … */ })
|
|
63
|
+
observer(context).onPaymentFailed(async event => { /* notify */ })
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
A managed gateway brings Stripe to the declared state at boot — products and prices, one
|
|
67
|
+
customer-portal configuration, one webhook endpoint at this deployment's public URL (its signing
|
|
68
|
+
secret stored in `payment-webhook`) — and each step is fingerprinted, so an unchanged deployment
|
|
69
|
+
makes no Stripe call.
|
|
70
|
+
|
|
71
|
+
Several deployments of one service may share a Stripe account (typically every test-mode
|
|
72
|
+
deployment), each with its own database and URL — and a deployment's identity is its webhook URL,
|
|
73
|
+
even an undeliverable local one.
|
|
74
|
+
|
|
75
|
+
- **Webhook endpoint.** A deployment owns exactly the endpoint at its own URL: it deletes an
|
|
76
|
+
endpoint only when its own `payment-webhook` rows name it — the endpoint of a URL it has moved away
|
|
77
|
+
from — and never one it merely finds at another URL, whatever its metadata says. The endpoint of a
|
|
78
|
+
retired deployment is removed by hand in the Stripe dashboard; one deleted from outside comes back
|
|
79
|
+
on the next forced `resync`.
|
|
80
|
+
- **Portal configuration.** Each deployment has its own, tagged
|
|
81
|
+
`{ owlmeans: 'payment', service, deployment: <webhook URL> }`. It updates the configuration its
|
|
82
|
+
`portal:<service>` fingerprint row names, unless that one is tagged for another deployment, and
|
|
83
|
+
without a row adopts only a configuration carrying exactly its own tag. Stripe cannot delete a
|
|
84
|
+
portal configuration, so one a deployment can no longer identify stays in the account and a new
|
|
85
|
+
one is created.
|
|
86
|
+
|
|
87
|
+
## Use it
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// Checkout and the portal (in-process; resolve the stable entityId at your authenticated boundary)
|
|
91
|
+
const url = await gateway(ctx).createLink(ctx, { productSku: 'app-plans', planSku: 'pro-monthly', entityId, service: 'app', successUrl })
|
|
92
|
+
const portal = await gateway(ctx).portalLink(ctx, entityId, { flow: PortalFlow.Change, planSku: 'pro-monthly', returnUrl })
|
|
93
|
+
await gateway(ctx).grantInternalPlan(ctx, entityId, 'free') // when the organization is created
|
|
94
|
+
|
|
95
|
+
// What the entity may do
|
|
96
|
+
const view = await entitlements(ctx).entitlements(entityId) // the EntitlementView a UI renders
|
|
97
|
+
await entitlements(ctx).hasCapability(entityId, 'feature:whitelabel')
|
|
98
|
+
|
|
99
|
+
// Spend a counted allowance — key it by the record it pays for
|
|
100
|
+
await entitlements(ctx).consume({ entityId, limitKey: 'exports', eventKey: `export:${reportId}`, ref: reportId })
|
|
101
|
+
await entitlements(ctx).release({ entityId, limitKey: 'exports', eventKey: `export:${reportId}` })
|
|
102
|
+
|
|
103
|
+
// Periodic repair
|
|
104
|
+
await reconcileAll(ctx, { freePlanSku: 'free' })
|
|
105
|
+
await entitlements(ctx).reconcileOccupancy(entityId, 'seats', liveSeatCount)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Routes declare what they need; the gates refuse before the handler:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
protocol(route(...), contract(...), { gate: { alias: ENTITLEMENT_GATE, params: ['feature:whitelabel'] } })
|
|
112
|
+
protocol(route(...), contract(...), { gate: { alias: LIMIT_GATE, params: [formatLimitParam('seats')] } })
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`CapabilityRequired` and `LimitExhausted` extend `AuthForbidden`, so an HTTP boundary answers 403.
|
|
116
|
+
The limit gate never consumes; the handler does.
|
|
117
|
+
|
|
118
|
+
## Routes
|
|
119
|
+
|
|
120
|
+
`paymentGateEntrypoints` binds the immutable `paymentGate` tree: the public Stripe webhook
|
|
121
|
+
(`POST /payment-gate/webhook/:paygate`, verified by signature over the raw body), and two
|
|
122
|
+
Ed25519-guarded repairs — `resync` (products, portal, webhook endpoint) and `resyncSubscriptions`
|
|
123
|
+
(re-reads every live Stripe subscription, answering `{ scanned, updated }`).
|
|
40
124
|
|
|
41
125
|
<!-- owlmeans:agent-guidance:start -->
|
|
42
126
|
## Agent guidance
|
|
@@ -46,7 +130,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
46
130
|
your project's skill store (`.agents/skills/`):
|
|
47
131
|
|
|
48
132
|
```sh
|
|
49
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
133
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.38
|
|
50
134
|
```
|
|
51
135
|
|
|
52
136
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/server-payment",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-09-
|
|
4
|
+
"version": "0.1.18-rc.21",
|
|
5
|
+
"generatedAt": "2026-09-23T19:57:40.693Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|