@owlmeans/server-payment 0.1.18-rc.3 → 0.1.18-rc.31
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 +157 -25
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/server-payment/SKILL.md +593 -53
- package/build/access/types.d.ts +46 -0
- package/build/access/types.d.ts.map +1 -0
- package/build/access/types.js +2 -0
- package/build/access/types.js.map +1 -0
- package/build/access.d.ts +6 -0
- package/build/access.d.ts.map +1 -0
- package/build/access.js +45 -0
- package/build/access.js.map +1 -0
- 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 +6 -1
- package/build/actions/resync.d.ts.map +1 -1
- package/build/actions/resync.js +15 -5
- package/build/actions/resync.js.map +1 -1
- package/build/actions/webhook.d.ts +2 -1
- package/build/actions/webhook.d.ts.map +1 -1
- package/build/actions/webhook.js +10 -4
- package/build/actions/webhook.js.map +1 -1
- package/build/bootstrap/types.d.ts +16 -0
- package/build/bootstrap/types.d.ts.map +1 -0
- package/build/bootstrap/types.js +2 -0
- package/build/bootstrap/types.js.map +1 -0
- package/build/bootstrap.d.ts +6 -0
- package/build/bootstrap.d.ts.map +1 -0
- package/build/bootstrap.js +26 -0
- package/build/bootstrap.js.map +1 -0
- package/build/catalogue/types.d.ts +38 -0
- package/build/catalogue/types.d.ts.map +1 -0
- package/build/catalogue/types.js +2 -0
- package/build/catalogue/types.js.map +1 -0
- package/build/catalogue.d.ts +6 -0
- package/build/catalogue.d.ts.map +1 -0
- package/build/catalogue.js +110 -0
- package/build/catalogue.js.map +1 -0
- package/build/commit/types.d.ts +22 -0
- package/build/commit/types.d.ts.map +1 -0
- package/build/commit/types.js +2 -0
- package/build/commit/types.js.map +1 -0
- package/build/commit.d.ts +6 -0
- package/build/commit.d.ts.map +1 -0
- package/build/commit.js +125 -0
- package/build/commit.js.map +1 -0
- package/build/config.d.ts +44 -5
- package/build/config.d.ts.map +1 -1
- package/build/config.js +216 -10
- package/build/config.js.map +1 -1
- package/build/consts.d.ts +74 -16
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +86 -23
- package/build/consts.js.map +1 -1
- package/build/consts.local.d.ts +94 -0
- package/build/consts.local.d.ts.map +1 -0
- package/build/consts.local.js +44 -0
- package/build/consts.local.js.map +1 -0
- package/build/consumer/cancellation/types.d.ts +11 -0
- package/build/consumer/cancellation/types.d.ts.map +1 -0
- package/build/consumer/cancellation/types.js +2 -0
- package/build/consumer/cancellation/types.js.map +1 -0
- package/build/consumer/cancellation.d.ts +6 -0
- package/build/consumer/cancellation.d.ts.map +1 -0
- package/build/consumer/cancellation.js +33 -0
- package/build/consumer/cancellation.js.map +1 -0
- package/build/consumer/capture/types.d.ts +50 -0
- package/build/consumer/capture/types.d.ts.map +1 -0
- package/build/consumer/capture/types.js +2 -0
- package/build/consumer/capture/types.js.map +1 -0
- package/build/consumer/capture.d.ts +6 -0
- package/build/consumer/capture.d.ts.map +1 -0
- package/build/consumer/capture.js +284 -0
- package/build/consumer/capture.js.map +1 -0
- package/build/consumer/consts.local.d.ts +10 -0
- package/build/consumer/consts.local.d.ts.map +1 -0
- package/build/consumer/consts.local.js +11 -0
- package/build/consumer/consts.local.js.map +1 -0
- package/build/consumer/format/types.d.ts +30 -0
- package/build/consumer/format/types.d.ts.map +1 -0
- package/build/consumer/format/types.js +2 -0
- package/build/consumer/format/types.js.map +1 -0
- package/build/consumer/format.d.ts +4 -0
- package/build/consumer/format.d.ts.map +1 -0
- package/build/consumer/format.js +73 -0
- package/build/consumer/format.js.map +1 -0
- package/build/consumer/handlers.d.ts +21 -0
- package/build/consumer/handlers.d.ts.map +1 -0
- package/build/consumer/handlers.js +174 -0
- package/build/consumer/handlers.js.map +1 -0
- package/build/consumer/index.d.ts +10 -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/types.d.ts +29 -0
- package/build/consumer/mail/types.d.ts.map +1 -0
- package/build/consumer/mail/types.js +2 -0
- package/build/consumer/mail/types.js.map +1 -0
- package/build/consumer/mail.d.ts +6 -0
- package/build/consumer/mail.d.ts.map +1 -0
- package/build/consumer/mail.js +313 -0
- package/build/consumer/mail.js.map +1 -0
- package/build/consumer/observers/types.d.ts +15 -0
- package/build/consumer/observers/types.d.ts.map +1 -0
- package/build/consumer/observers/types.js +2 -0
- package/build/consumer/observers/types.js.map +1 -0
- package/build/consumer/observers.d.ts +6 -0
- package/build/consumer/observers.d.ts.map +1 -0
- package/build/consumer/observers.js +74 -0
- package/build/consumer/observers.js.map +1 -0
- package/build/consumer/origin/types.d.ts +17 -0
- package/build/consumer/origin/types.d.ts.map +1 -0
- package/build/consumer/origin/types.js +2 -0
- package/build/consumer/origin/types.js.map +1 -0
- package/build/consumer/origin.d.ts +8 -0
- package/build/consumer/origin.d.ts.map +1 -0
- package/build/consumer/origin.js +43 -0
- package/build/consumer/origin.js.map +1 -0
- package/build/consumer/reconcile/types.d.ts +13 -0
- package/build/consumer/reconcile/types.d.ts.map +1 -0
- package/build/consumer/reconcile/types.js +2 -0
- package/build/consumer/reconcile/types.js.map +1 -0
- package/build/consumer/reconcile.d.ts +5 -0
- package/build/consumer/reconcile.d.ts.map +1 -0
- package/build/consumer/reconcile.js +320 -0
- package/build/consumer/reconcile.js.map +1 -0
- package/build/consumer/records/types.d.ts +66 -0
- package/build/consumer/records/types.d.ts.map +1 -0
- package/build/consumer/records/types.js +2 -0
- package/build/consumer/records/types.js.map +1 -0
- package/build/consumer/records.d.ts +6 -0
- package/build/consumer/records.d.ts.map +1 -0
- package/build/consumer/records.js +243 -0
- package/build/consumer/records.js.map +1 -0
- package/build/consumer/service.d.ts +28 -0
- package/build/consumer/service.d.ts.map +1 -0
- package/build/consumer/service.js +706 -0
- package/build/consumer/service.js.map +1 -0
- package/build/consumer/types.d.ts +84 -0
- package/build/consumer/types.d.ts.map +1 -0
- package/build/consumer/types.js +2 -0
- package/build/consumer/types.js.map +1 -0
- package/build/consumer/types.local.d.ts +26 -0
- package/build/consumer/types.local.d.ts.map +1 -0
- package/build/consumer/types.local.js +2 -0
- package/build/consumer/types.local.js.map +1 -0
- package/build/consumer/withdrawal/types.d.ts +36 -0
- package/build/consumer/withdrawal/types.d.ts.map +1 -0
- package/build/consumer/withdrawal/types.js +2 -0
- package/build/consumer/withdrawal/types.js.map +1 -0
- package/build/consumer/withdrawal.d.ts +6 -0
- package/build/consumer/withdrawal.d.ts.map +1 -0
- package/build/consumer/withdrawal.js +237 -0
- package/build/consumer/withdrawal.js.map +1 -0
- package/build/entitlement.d.ts +4 -0
- package/build/entitlement.d.ts.map +1 -0
- package/build/entitlement.js +72 -0
- package/build/entitlement.js.map +1 -0
- package/build/entrypoints.d.ts +19 -2
- package/build/entrypoints.d.ts.map +1 -1
- package/build/entrypoints.js +16 -4
- package/build/entrypoints.js.map +1 -1
- package/build/gate/request.d.ts +4 -0
- package/build/gate/request.d.ts.map +1 -0
- package/build/gate/request.js +17 -0
- package/build/gate/request.js.map +1 -0
- package/build/gate/types.d.ts +15 -0
- package/build/gate/types.d.ts.map +1 -0
- package/build/gate/types.js +2 -0
- package/build/gate/types.js.map +1 -0
- package/build/gate.d.ts +13 -9
- package/build/gate.d.ts.map +1 -1
- package/build/gate.js +36 -21
- package/build/gate.js.map +1 -1
- package/build/index.d.ts +50 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +29 -3
- package/build/index.js.map +1 -1
- package/build/limit.d.ts +11 -0
- package/build/limit.d.ts.map +1 -0
- package/build/limit.js +48 -0
- package/build/limit.js.map +1 -0
- package/build/log.d.ts +3 -0
- package/build/log.d.ts.map +1 -0
- package/build/log.js +4 -0
- package/build/log.js.map +1 -0
- package/build/models/declarations/types.d.ts +22 -0
- package/build/models/declarations/types.d.ts.map +1 -0
- package/build/models/declarations/types.js +2 -0
- package/build/models/declarations/types.js.map +1 -0
- package/build/models/declarations.d.ts +4 -0
- package/build/models/declarations.d.ts.map +1 -0
- package/build/models/declarations.js +26 -0
- package/build/models/declarations.js.map +1 -0
- package/build/models/purchase/types.d.ts +13 -0
- package/build/models/purchase/types.d.ts.map +1 -0
- package/build/models/purchase/types.js +2 -0
- package/build/models/purchase/types.js.map +1 -0
- package/build/models/purchase.d.ts +4 -0
- package/build/models/purchase.d.ts.map +1 -0
- package/build/models/purchase.js +37 -0
- package/build/models/purchase.js.map +1 -0
- 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/types.d.ts +21 -0
- package/build/plan/types.d.ts.map +1 -0
- package/build/plan/types.js +2 -0
- package/build/plan/types.js.map +1 -0
- package/build/plan.d.ts +7 -0
- package/build/plan.d.ts.map +1 -0
- package/build/plan.js +29 -0
- package/build/plan.js.map +1 -0
- package/build/plugins/checkout-plugins/types.d.ts +25 -0
- package/build/plugins/checkout-plugins/types.d.ts.map +1 -0
- package/build/plugins/checkout-plugins/types.js +2 -0
- package/build/plugins/checkout-plugins/types.js.map +1 -0
- package/build/plugins/checkout-plugins.d.ts +12 -0
- package/build/plugins/checkout-plugins.d.ts.map +1 -0
- package/build/plugins/checkout-plugins.js +72 -0
- package/build/plugins/checkout-plugins.js.map +1 -0
- package/build/plugins/checkout-policy/types.d.ts +18 -0
- package/build/plugins/checkout-policy/types.d.ts.map +1 -0
- package/build/plugins/checkout-policy/types.js +2 -0
- package/build/plugins/checkout-policy/types.js.map +1 -0
- package/build/plugins/checkout-policy.d.ts +4 -0
- package/build/plugins/checkout-policy.d.ts.map +1 -0
- package/build/plugins/checkout-policy.js +26 -0
- package/build/plugins/checkout-policy.js.map +1 -0
- package/build/plugins/checkout-registry/types.d.ts +7 -0
- package/build/plugins/checkout-registry/types.d.ts.map +1 -0
- package/build/plugins/checkout-registry/types.js +2 -0
- package/build/plugins/checkout-registry/types.js.map +1 -0
- package/build/plugins/checkout-registry.d.ts +8 -0
- package/build/plugins/checkout-registry.d.ts.map +1 -0
- package/build/plugins/checkout-registry.js +21 -0
- package/build/plugins/checkout-registry.js.map +1 -0
- package/build/plugins/consts.local.d.ts +13 -0
- package/build/plugins/consts.local.d.ts.map +1 -0
- package/build/plugins/consts.local.js +22 -0
- package/build/plugins/consts.local.js.map +1 -0
- package/build/plugins/customer-lock/types.d.ts +6 -0
- package/build/plugins/customer-lock/types.d.ts.map +1 -0
- package/build/plugins/customer-lock/types.js +2 -0
- package/build/plugins/customer-lock/types.js.map +1 -0
- package/build/plugins/customer-lock.d.ts +5 -0
- package/build/plugins/customer-lock.d.ts.map +1 -0
- package/build/plugins/customer-lock.js +22 -0
- package/build/plugins/customer-lock.js.map +1 -0
- package/build/plugins/estimate/cache.d.ts +4 -0
- package/build/plugins/estimate/cache.d.ts.map +1 -0
- package/build/plugins/estimate/cache.js +7 -0
- package/build/plugins/estimate/cache.js.map +1 -0
- package/build/plugins/estimate/types.d.ts +27 -0
- package/build/plugins/estimate/types.d.ts.map +1 -0
- package/build/plugins/estimate/types.js +2 -0
- package/build/plugins/estimate/types.js.map +1 -0
- package/build/plugins/estimate.d.ts +6 -0
- package/build/plugins/estimate.d.ts.map +1 -0
- package/build/plugins/estimate.js +264 -0
- package/build/plugins/estimate.js.map +1 -0
- package/build/plugins/events/types.d.ts +12 -0
- package/build/plugins/events/types.d.ts.map +1 -0
- package/build/plugins/events/types.js +2 -0
- package/build/plugins/events/types.js.map +1 -0
- package/build/plugins/events.d.ts +6 -13
- package/build/plugins/events.d.ts.map +1 -1
- package/build/plugins/events.js +441 -144
- package/build/plugins/events.js.map +1 -1
- package/build/plugins/fx/types.d.ts +31 -0
- package/build/plugins/fx/types.d.ts.map +1 -0
- package/build/plugins/fx/types.js +2 -0
- package/build/plugins/fx/types.js.map +1 -0
- package/build/plugins/fx.d.ts +6 -0
- package/build/plugins/fx.d.ts.map +1 -0
- package/build/plugins/fx.js +82 -0
- package/build/plugins/fx.js.map +1 -0
- package/build/plugins/portal/types.d.ts +44 -0
- package/build/plugins/portal/types.d.ts.map +1 -0
- package/build/plugins/portal/types.js +2 -0
- package/build/plugins/portal/types.js.map +1 -0
- package/build/plugins/portal.d.ts +6 -0
- package/build/plugins/portal.d.ts.map +1 -0
- package/build/plugins/portal.js +255 -0
- package/build/plugins/portal.js.map +1 -0
- package/build/plugins/refunds/types.d.ts +35 -0
- package/build/plugins/refunds/types.d.ts.map +1 -0
- package/build/plugins/refunds/types.js +2 -0
- package/build/plugins/refunds/types.js.map +1 -0
- package/build/plugins/refunds.d.ts +6 -0
- package/build/plugins/refunds.d.ts.map +1 -0
- package/build/plugins/refunds.js +81 -0
- package/build/plugins/refunds.js.map +1 -0
- package/build/plugins/session/types.d.ts +32 -0
- package/build/plugins/session/types.d.ts.map +1 -0
- package/build/plugins/session/types.js +2 -0
- package/build/plugins/session/types.js.map +1 -0
- package/build/plugins/session.d.ts +4 -0
- package/build/plugins/session.d.ts.map +1 -0
- package/build/plugins/session.js +58 -0
- package/build/plugins/session.js.map +1 -0
- package/build/plugins/status.d.ts +8 -0
- package/build/plugins/status.d.ts.map +1 -0
- package/build/plugins/status.js +21 -0
- package/build/plugins/status.js.map +1 -0
- package/build/plugins/stripe/types.d.ts +20 -0
- package/build/plugins/stripe/types.d.ts.map +1 -0
- package/build/plugins/stripe/types.js +2 -0
- package/build/plugins/stripe/types.js.map +1 -0
- package/build/plugins/stripe.d.ts +5 -13
- package/build/plugins/stripe.d.ts.map +1 -1
- package/build/plugins/stripe.js +481 -129
- package/build/plugins/stripe.js.map +1 -1
- package/build/plugins/subscriptions/types.d.ts +41 -0
- package/build/plugins/subscriptions/types.d.ts.map +1 -0
- package/build/plugins/subscriptions/types.js +2 -0
- package/build/plugins/subscriptions/types.js.map +1 -0
- package/build/plugins/subscriptions.d.ts +6 -0
- package/build/plugins/subscriptions.d.ts.map +1 -0
- package/build/plugins/subscriptions.js +169 -0
- package/build/plugins/subscriptions.js.map +1 -0
- package/build/plugins/types.d.ts +7 -0
- package/build/plugins/types.d.ts.map +1 -0
- package/build/plugins/types.js +2 -0
- package/build/plugins/types.js.map +1 -0
- package/build/plugins/types.local.d.ts +75 -0
- package/build/plugins/types.local.d.ts.map +1 -0
- package/build/plugins/types.local.js +2 -0
- package/build/plugins/types.local.js.map +1 -0
- package/build/plugins/webhook-manager/types.d.ts +62 -0
- package/build/plugins/webhook-manager/types.d.ts.map +1 -0
- package/build/plugins/webhook-manager/types.js +2 -0
- package/build/plugins/webhook-manager/types.js.map +1 -0
- package/build/plugins/webhook-manager.d.ts +8 -0
- package/build/plugins/webhook-manager.d.ts.map +1 -0
- package/build/plugins/webhook-manager.js +245 -0
- package/build/plugins/webhook-manager.js.map +1 -0
- package/build/protocol.d.ts +13 -0
- package/build/protocol.d.ts.map +1 -0
- package/build/protocol.js +17 -0
- package/build/protocol.js.map +1 -0
- package/build/reconcile/types.d.ts +17 -0
- package/build/reconcile/types.d.ts.map +1 -0
- package/build/reconcile/types.js +2 -0
- package/build/reconcile/types.js.map +1 -0
- package/build/reconcile.d.ts +12 -0
- package/build/reconcile.d.ts.map +1 -0
- package/build/reconcile.js +91 -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/schemas.d.ts +19 -0
- package/build/schemas.d.ts.map +1 -0
- package/build/schemas.js +244 -0
- package/build/schemas.js.map +1 -0
- package/build/service.d.ts +28 -3
- package/build/service.d.ts.map +1 -1
- package/build/service.js +140 -19
- package/build/service.js.map +1 -1
- package/build/subscription/types.d.ts +21 -0
- package/build/subscription/types.d.ts.map +1 -0
- package/build/subscription/types.js +2 -0
- package/build/subscription/types.js.map +1 -0
- package/build/subscription.d.ts +4 -0
- package/build/subscription.d.ts.map +1 -0
- package/build/subscription.js +84 -0
- package/build/subscription.js.map +1 -0
- package/build/sync/types.d.ts +25 -0
- package/build/sync/types.d.ts.map +1 -0
- package/build/sync/types.js +2 -0
- package/build/sync/types.js.map +1 -0
- package/build/sync.d.ts +4 -3
- package/build/sync.d.ts.map +1 -1
- package/build/sync.js +231 -79
- package/build/sync.js.map +1 -1
- package/build/types.d.ts +1258 -36
- package/build/types.d.ts.map +1 -1
- package/build/types.local.d.ts +22 -0
- package/build/types.local.d.ts.map +1 -0
- package/build/types.local.js +2 -0
- package/build/types.local.js.map +1 -0
- package/build/usage/types.d.ts +56 -0
- package/build/usage/types.d.ts.map +1 -0
- package/build/usage/types.js +2 -0
- package/build/usage/types.js.map +1 -0
- package/build/usage.d.ts +6 -0
- package/build/usage.d.ts.map +1 -0
- package/build/usage.js +357 -0
- package/build/usage.js.map +1 -0
- package/build/utils/types.d.ts +31 -0
- package/build/utils/types.d.ts.map +1 -0
- package/build/utils/types.js +2 -0
- package/build/utils/types.js.map +1 -0
- package/build/utils.d.ts +21 -7
- package/build/utils.d.ts.map +1 -1
- package/build/utils.js +56 -17
- package/build/utils.js.map +1 -1
- package/package.json +21 -16
- package/src/access/types.ts +52 -0
- package/src/access.ts +86 -0
- package/src/actions/index.ts +1 -0
- package/src/actions/resync-subscriptions.ts +10 -0
- package/src/actions/resync.ts +14 -5
- package/src/actions/webhook.ts +9 -4
- package/src/bootstrap/types.ts +16 -0
- package/src/bootstrap.ts +31 -0
- package/src/catalogue/types.ts +32 -0
- package/src/catalogue.ts +135 -0
- package/src/commit/types.ts +27 -0
- package/src/commit.ts +152 -0
- package/src/config.ts +234 -14
- package/src/consts.local.ts +62 -0
- package/src/consts.ts +100 -36
- package/src/consumer/cancellation/types.ts +13 -0
- package/src/consumer/cancellation.ts +43 -0
- package/src/consumer/capture/types.ts +53 -0
- package/src/consumer/capture.ts +321 -0
- package/src/consumer/consts.local.ts +18 -0
- package/src/consumer/format/types.ts +29 -0
- package/src/consumer/format.ts +85 -0
- package/src/consumer/handlers.ts +204 -0
- package/src/consumer/index.ts +9 -0
- package/src/consumer/mail/types.ts +31 -0
- package/src/consumer/mail.ts +353 -0
- package/src/consumer/observers/types.ts +22 -0
- package/src/consumer/observers.ts +97 -0
- package/src/consumer/origin/types.ts +17 -0
- package/src/consumer/origin.ts +61 -0
- package/src/consumer/reconcile/types.ts +13 -0
- package/src/consumer/reconcile.ts +330 -0
- package/src/consumer/records/types.ts +64 -0
- package/src/consumer/records.ts +288 -0
- package/src/consumer/service.ts +768 -0
- package/src/consumer/types.local.ts +22 -0
- package/src/consumer/types.ts +96 -0
- package/src/consumer/withdrawal/types.ts +37 -0
- package/src/consumer/withdrawal.ts +268 -0
- package/src/entitlement.ts +85 -0
- package/src/entrypoints.ts +17 -4
- package/src/gate/request.ts +24 -0
- package/src/gate/types.ts +13 -0
- package/src/gate.ts +47 -30
- package/src/index.ts +55 -3
- package/src/limit.ts +54 -0
- package/src/log.ts +4 -0
- package/src/models/declarations/types.ts +22 -0
- package/src/models/declarations.ts +30 -0
- package/src/models/purchase/types.ts +13 -0
- package/src/models/purchase.ts +43 -0
- package/src/observer.ts +44 -11
- package/src/plan/types.ts +21 -0
- package/src/plan.ts +46 -0
- package/src/plugins/checkout-plugins/types.ts +25 -0
- package/src/plugins/checkout-plugins.ts +90 -0
- package/src/plugins/checkout-policy/types.ts +18 -0
- package/src/plugins/checkout-policy.ts +34 -0
- package/src/plugins/checkout-registry/types.ts +7 -0
- package/src/plugins/checkout-registry.ts +23 -0
- package/src/plugins/consts.local.ts +30 -0
- package/src/plugins/customer-lock/types.ts +5 -0
- package/src/plugins/customer-lock.ts +25 -0
- package/src/plugins/estimate/cache.ts +12 -0
- package/src/plugins/estimate/types.ts +28 -0
- package/src/plugins/estimate.ts +298 -0
- package/src/plugins/events/types.ts +11 -0
- package/src/plugins/events.ts +466 -132
- package/src/plugins/fx/types.ts +38 -0
- package/src/plugins/fx.ts +100 -0
- package/src/plugins/portal/types.ts +45 -0
- package/src/plugins/portal.ts +284 -0
- package/src/plugins/refunds/types.ts +31 -0
- package/src/plugins/refunds.ts +95 -0
- package/src/plugins/session/types.ts +28 -0
- package/src/plugins/session.ts +73 -0
- package/src/plugins/status.ts +22 -0
- package/src/plugins/stripe/types.ts +20 -0
- package/src/plugins/stripe.ts +523 -135
- package/src/plugins/subscriptions/types.ts +39 -0
- package/src/plugins/subscriptions.ts +195 -0
- package/src/plugins/types.local.ts +61 -0
- package/src/plugins/types.ts +7 -0
- package/src/plugins/webhook-manager/types.ts +63 -0
- package/src/plugins/webhook-manager.ts +288 -0
- package/src/protocol.ts +29 -0
- package/src/reconcile/types.ts +17 -0
- package/src/reconcile.ts +110 -0
- package/src/resource.ts +158 -11
- package/src/schemas.ts +271 -0
- package/src/service.ts +161 -19
- package/src/subscription/types.ts +23 -0
- package/src/subscription.ts +104 -0
- package/src/sync/types.ts +25 -0
- package/src/sync.ts +255 -72
- package/src/types.local.ts +18 -0
- package/src/types.ts +1365 -32
- package/src/usage/types.ts +51 -0
- package/src/usage.ts +424 -0
- package/src/utils/types.ts +30 -0
- package/src/utils.ts +94 -29
- package/tests/checkout-consumer.spec.ts +366 -0
- package/tests/checkout-locks.spec.ts +161 -0
- package/tests/checkout-plugins.spec.ts +164 -0
- package/tests/checkout.spec.ts +209 -93
- package/tests/consumer-events.spec.ts +215 -0
- package/tests/consumer-fixtures.ts +134 -0
- package/tests/consumer-ops.spec.ts +350 -0
- package/tests/consumer-rights.integration.spec.ts +147 -0
- package/tests/consumer-rights.spec.ts +572 -0
- package/tests/context.ts +109 -0
- package/tests/entitlement.spec.ts +139 -0
- package/tests/estimate.spec.ts +275 -0
- package/tests/events.spec.ts +357 -0
- package/tests/fake-stripe.ts +981 -0
- package/tests/gate-handlers.spec.ts +120 -0
- package/tests/gate.spec.ts +63 -72
- package/tests/gateway-options.spec.ts +180 -0
- package/tests/limit-gate.spec.ts +66 -0
- package/tests/portal.spec.ts +202 -0
- package/tests/protocol.spec.ts +17 -3
- package/tests/sync.spec.ts +114 -0
- package/tests/usage.integration.spec.ts +99 -0
- package/tests/usage.spec.ts +169 -0
- package/tests/webhook-manager.spec.ts +149 -0
- package/build/model.d.ts +0 -6
- package/build/model.d.ts.map +0 -1
- package/build/model.js +0 -33
- package/build/model.js.map +0 -1
- package/src/model.ts +0 -37
|
@@ -1,81 +1,621 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: server-payment
|
|
3
|
-
description: Public in-process Stripe gateway for OwlMeans backends — amount and quantity checkout, subscriptions, protocol-bound webhook routes, product sync, fulfillment observers and
|
|
3
|
+
description: Public in-process Stripe gateway for OwlMeans backends — amount and quantity checkout, subscriptions, the checkout plugin seam (per-entity narrowing, admission, holds), protocol-bound webhook routes, product sync with per-currency prices, fulfillment observers, entitlement gates, and the EU consumer-rights service (country lock, purchases and withdrawal windows, spend consent, subscription start requests, the withdrawal and cancellation functions with automatic refunds and credit notes, durable-medium mails, reconcile). Use when wiring @owlmeans/server-payment or changing Stripe payment behavior.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
7
7
|
|
|
8
8
|
# @owlmeans/server-payment
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
**Install:** `bun add @owlmeans/server-payment@^0.1.18-rc.31`
|
|
11
|
+
|
|
12
|
+
Public MIT package. It embeds Stripe into an application backend and owns everything between
|
|
13
|
+
Stripe and an entity's entitlements: the subscription store, one-time fulfillments, the usage
|
|
14
|
+
ledger of counted limits, plan resolution, the two gate services, Stripe's own configuration
|
|
15
|
+
(products, prices, the portal, the webhook endpoint) and the EU consumer-rights records and
|
|
16
|
+
functions. The application owns its catalogue, what a purchase is worth to it (credits,
|
|
17
|
+
provisioning), its usage meter and its side effects. Contracts — plans, limits, promos, the
|
|
18
|
+
entitlement view, the consumer-rights views, calculators, copy and refusals — are
|
|
19
|
+
`@owlmeans/payment`; the model across packages is the `entitlements` skill. Field lists, the
|
|
20
|
+
service contract and the step-by-step algorithms are in `reference.md` in this skill folder.
|
|
13
21
|
|
|
14
22
|
## Wiring
|
|
15
23
|
|
|
16
24
|
```typescript
|
|
17
|
-
stripeSecrets(cfg, { api: '/secrets/stripe-key'
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
stripeSecrets(cfg, { api: '/secrets/stripe-key' }) // webhook secret: optional override
|
|
26
|
+
portalBranding(cfg, { returnUrl: 'https://app.example.com/billing', headline: 'Example' })
|
|
27
|
+
declarePaymentPricing(cfg, { // absent entirely: today's fixed behaviour, unchanged
|
|
28
|
+
tax: { automatic: true, behavior: TaxBehavior.Exclusive, collectTaxId: true, estimate: true },
|
|
29
|
+
currency: { adaptive: true, estimate: true },
|
|
30
|
+
stripe: { settlementCurrency: 'eur', subscriptionPaymentMethodTypes: ['card', 'link', 'klarna'],
|
|
31
|
+
subscriptionPaymentMethodTypesByCurrency: { usd: ['card', 'link'] },
|
|
32
|
+
lockCustomerEmail: true, lockCustomerCountry: true }, // optional: see "Customer locks"
|
|
33
|
+
})
|
|
34
|
+
declareConsumerRights(cfg, { // absent: no consumer-rights behaviour at all
|
|
35
|
+
textVersion: 'terms-2026-09', links: { en: { billingTerms, withdrawalInformation, withdrawalFunction, cancellation } },
|
|
36
|
+
currencies: { eu: 'eur', other: 'usd' },
|
|
37
|
+
mechanisms: { countryLock: true, checkoutTerms: true, performanceConsent: true, subscriptionStart: true,
|
|
38
|
+
withdrawal: true, automaticRefunds: true, cancellation: true, purchaseConfirmation: true },
|
|
39
|
+
trader: { name: 'Example', legalName: 'Example Ltd', address: '…', email: 'support@example.com' },
|
|
40
|
+
mail: { from: 'billing@example.com', bcc: ['archive@example.com'] }, // alias: default MAILER_SERVICE
|
|
41
|
+
consentContext: 'included', // optional: the `_included` consent copy variant
|
|
42
|
+
})
|
|
43
|
+
declarePaymentProduct(cfg, { sku: 'app-plans', type: ProductType.Service, services: ['app'], name: 'Plans' })
|
|
44
|
+
declarePaymentPlan(cfg, { productSku: 'app-plans', sku: 'free', rank: 0, free: true, price: 0, … })
|
|
45
|
+
declarePaymentPlan(cfg, { productSku: 'app-plans', sku: 'pro-monthly', rank: 10, price: 20,
|
|
46
|
+
recurring: { interval: 'month' }, currencyPrices: { usd: 20 },
|
|
47
|
+
withdrawal: { components: [{ key: 'services', basis: 'time', shareMinor: 1000 },
|
|
48
|
+
{ key: 'credits', basis: 'units', shareMinor: 1000 }] }, capabilities: […] })
|
|
49
|
+
|
|
50
|
+
appendPaymentGatewayService(context) // the process that talks to Stripe
|
|
51
|
+
appendPaymentGatewayService(context, { manage: false }) // a worker that only reads entitlements and asserts consent
|
|
52
|
+
// One deployment, several managed processes on one database: the same owner and webhookService everywhere
|
|
53
|
+
appendPaymentGatewayService(context, { owner: 'app-api', webhookService: 'app-hooks' }) // receives the webhook, bootstraps at boot
|
|
54
|
+
appendPaymentGatewayService(context, { owner: 'app-api', webhookService: 'app-hooks', bootstrap: false }) // checkout and portal only
|
|
55
|
+
appendConsumerRights(context, { manage, usage: myUsageMeter }) // before or after the gateway; idempotent
|
|
56
|
+
paymentAccessOf(context).consumerRights().useMailRenderer(myRenderer) // lazy service: works while wiring
|
|
57
|
+
paymentAccessOf(context).gateway().use(myCheckoutPlugin) // a tier / cap / hold plugin — once the context is initialized
|
|
58
|
+
export const serverBindings = [
|
|
59
|
+
...paymentGateEntrypoints, // the library's own, unpinned `paymentGate`
|
|
60
|
+
...consumerRightsEntrypoints(consumerProtocols, { guardMoney, throttle, subjectOf, planNameOf }),
|
|
61
|
+
...checkoutReadEntrypoints(checkoutProtocols),
|
|
62
|
+
]
|
|
63
|
+
// …or the gate re-declared by the application (its aliases, pinned to its service, the library's paths):
|
|
64
|
+
export const hookBindings = [
|
|
65
|
+
bind(own.base),
|
|
66
|
+
bind(own.webhook, paymentGateHandlers.webhook),
|
|
67
|
+
bind(own.resync, paymentGateHandlers.resync),
|
|
68
|
+
bind(own.resyncSubscriptions, paymentGateHandlers.resyncSubscriptions),
|
|
69
|
+
]
|
|
70
|
+
paymentAccessOf(context).observer().onSubscription(async event => { /* keyed by event.eventKey */ })
|
|
27
71
|
```
|
|
28
72
|
|
|
29
|
-
`
|
|
30
|
-
`
|
|
31
|
-
|
|
32
|
-
|
|
73
|
+
- `appendPaymentGatewayService` registers twelve resources, the catalogue service
|
|
74
|
+
(`PAYMENT_SERVICE`), the completion observer, the gateway (`GATEWAY_SERVICE`), the capability
|
|
75
|
+
gate (`ENTITLEMENT_GATE`), the limit gate (`LIMIT_GATE`), the entitlement service
|
|
76
|
+
(`ENTITLEMENT_SERVICE`) and the consumer-rights service (`CONSUMER_RIGHTS_SERVICE`), each only
|
|
77
|
+
when not registered already. `appendConsumerRights` does the consumer-rights part alone.
|
|
78
|
+
- **Registration order is free.** Whichever of `appendConsumerRights` and the gateway comes first,
|
|
79
|
+
`usage` and `stripe` are installed on the one service, and `manage` resolves as the application's
|
|
80
|
+
explicit value, else the gateway's, else managed. The consumer-rights resources keep the aliases
|
|
81
|
+
of the first registration.
|
|
82
|
+
- **The consumer-rights service is lazy** (like the completion observer): `paymentAccessOf(ctx).consumerRights()`,
|
|
83
|
+
`useMeter` and `useMailRenderer` work while the application is wired; the gateway's initialization
|
|
84
|
+
initializes it, so its boot checks still run at boot. The gateway is NOT lazy: `paymentAccessOf(ctx).gateway()` (and
|
|
85
|
+
`.use(plugin)`) needs the initialized context.
|
|
86
|
+
- **`manage: false`** registers the same surface with no Stripe client: no bootstrap at init, and
|
|
87
|
+
`createLink`, `portalLink`, `resyncSubscription`, `resyncAll`, the webhook route, `withdraw` and
|
|
88
|
+
`cancel` throw `PaygateError('unmanaged')`. `grantInternalPlan`, the entitlement service,
|
|
89
|
+
`amountPolicy`, `planPrices` and the consumer-rights reads, `recordConsent`,
|
|
90
|
+
`recordStartRequest` and `assertConsent` work, because they are Mongo only.
|
|
91
|
+
- **Three keys of a deployment's Stripe state**, each a `PaymentGatewayOptions` field, each read
|
|
92
|
+
back on the gateway (`paymentAccessOf(ctx).gateway().webhookService / owner / bootstrap`):
|
|
93
|
+
- `webhookService` (default `cfg.service`) — the `cfg.services` alias whose host and base form
|
|
94
|
+
`webhookOf(ctx).webhookUrlOf`, the portal's deployment key too. Name the process that RECEIVES the webhook
|
|
95
|
+
(the one serving `paymentGate`) in every process, so every bootstrap computes one URL.
|
|
96
|
+
- `owner` (default `cfg.service`) — the `payment-webhook` rows' `service`, the signing-secret
|
|
97
|
+
lookup, the endpoint's `owlmeans:<owner>` description and `service` metadata, the
|
|
98
|
+
`portal:<owner>` fingerprint and the portal's `service` metadata. One value in every process of
|
|
99
|
+
the database, so each finds and replaces the others' rows.
|
|
100
|
+
- `bootstrap` (default: `manage`) — run `stripeBootstrapOf(ctx).bootstrapStripe` at boot. Exactly one kind of process
|
|
101
|
+
keeps it (the receiver). A managed process with `bootstrap: false` still serves checkout, the
|
|
102
|
+
portal, estimates, the resyncs and the webhook route, and a FORCED bootstrap (`resync`, an
|
|
103
|
+
application's maintenance `stripeBootstrapOf(ctx).bootstrapStripe(stripe, { force: true })`) still runs there —
|
|
104
|
+
same URL, same rows, fingerprinted, so idempotent from any process.
|
|
105
|
+
- Refused at construction: `bootstrap: true` with `manage: false`, an empty `owner` or
|
|
106
|
+
`webhookService` (`PaygateError`). A managed, bootstrapping gateway fails its initialization when
|
|
107
|
+
`webhookService` is not declared in `cfg.services` (`WebhookSetupError('service:<alias>')`).
|
|
108
|
+
- `stripe` (a `StripeFactory`, default `paymentAccessOf(ctx).stripeClient`) is the gateway's own Stripe client —
|
|
109
|
+
`paymentAccessOf(ctx).gateway().stripe(ctx)`, used by its methods, its boot bootstrap and the `paymentGate` handlers.
|
|
110
|
+
The consumer-rights service takes its own (`appendConsumerRights({ stripe })`).
|
|
111
|
+
- Every process that registers the gateway runs the collection validators (`collMod`) of all
|
|
112
|
+
twelve records at init: a process of an older version narrows the validators again, so roll
|
|
113
|
+
the processes of one deployment out together.
|
|
114
|
+
- Gateway methods take the stable `entityId`. A public handler resolves it from its request
|
|
115
|
+
entity first; protocol bodies carry `entitySlug`.
|
|
116
|
+
- A value in `stripeSecrets` / `portalBranding` that starts with `/` is read from that file at
|
|
117
|
+
boot, and a missing file fails the boot — so leave `webhook` out unless the file exists.
|
|
118
|
+
|
|
119
|
+
## Declaring plans
|
|
120
|
+
|
|
121
|
+
- **`rank`** orders a product's plans; a higher rank is an upgrade. A safe integer `>= 0`; absent
|
|
122
|
+
reads as `0`.
|
|
123
|
+
- **The free plan is a plan**: `free: true`, `price: 0`, no `gateways`. It is never synchronized to
|
|
124
|
+
Stripe and never checked out; an entity without an entitling subscription resolves to it.
|
|
125
|
+
- `gateways` names the paygates a plan is sold through; absent inherits the product's.
|
|
126
|
+
- **`currencyPrices`** (`{ usd: 20 }`, major units, lowercase codes) are exact prices in further
|
|
127
|
+
currencies, synced as the reusable Price's `currency_options`; a declared price in the Price's
|
|
128
|
+
default currency replaces its converted amount. Only for recurring and quantity plans.
|
|
129
|
+
- **`withdrawal.components`** state the separately priced parts of a subscription for a withdrawal
|
|
130
|
+
(CJEU C-641/19): `{ key, basis: 'time' | 'units', shareMinor }`, the shares summing to
|
|
131
|
+
`round(price × 100)`. Absent: the whole price is one `time` component. They also pick the start
|
|
132
|
+
statement: a `units` part → the `_units` variant, otherwise the base (time-only) texts
|
|
133
|
+
(`consumerCopyHelper.startContextOf`, `@owlmeans/payment`).
|
|
134
|
+
- `declarePaymentPlan` refuses before recording: a bad rank or a priced/gatewayed free plan
|
|
135
|
+
(`PlanRankConflict`), a capability set under the reserved `limit` scope, a malformed limit
|
|
136
|
+
(`LimitMisdeclared('<key>:<reason>')`), a malformed or amount-mode `currencyPrices` or components
|
|
137
|
+
that do not add up (`ProductError('currency-prices:…' | 'withdrawal:<sku>:…')`).
|
|
138
|
+
- `makePlanDeclarationsModel(cfg).assertPlans()` runs when the gateway initializes and fails the boot on two free plans
|
|
139
|
+
at one rank, or two paid non-consumable plans of one product at one rank.
|
|
140
|
+
- A limit key may use a different kind on different plans; each kind counts separately and a
|
|
141
|
+
lifetime count stays with the entity through upgrades, downgrades and cancellations.
|
|
142
|
+
|
|
143
|
+
## Records
|
|
144
|
+
|
|
145
|
+
None declares an ObjectId reference: `entityId` is an organization key and every other id is Stripe's.
|
|
146
|
+
|
|
147
|
+
| Collection | One row per | Indexes |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| `payment-paygate-customer` | Stripe customer (`country`, `currency` from its webhooks; `deletedAt`) | `{paygate, externalId}` unique · `{paygate, entityId}` · `{paygate, profileId}` |
|
|
150
|
+
| `payment-subscription` | subscription: `sub_…`, `free:<entityId>`, `internal:<planSku>:<entityId>` (+ `currency` and the checkout evidence) | `{paygate, externalId}` unique · `{entityId, status, rank:-1}` · `{entityId, planSku}` · `{paygate, customerId}` · `{paygate, itemId}` sparse · `{paygate, status, updatedAt}` |
|
|
151
|
+
| `payment-fulfillment` | one-time checkout session (+ evidence: country, e-mail, totals, terms, `purchaseId`) | `{paygate, externalId}` unique · `{paygate, paymentIntentId}` sparse · `{paygate, chargeId}` sparse · `{entityId, createdAt:-1}` |
|
|
152
|
+
| `payment-webhook` | managed webhook endpoint of an owner (`service` = the gateway's `owner`; `secret` is `secure: true`) | `{paygate, service, url}` unique |
|
|
153
|
+
| `payment-usage` | usage event — the ledger | `{entityId, limitKey, eventKey}` unique · `{entityId, limitKey, window}` · `{entityId, limitKey, ref}` sparse · `{entityId, createdAt:-1}` |
|
|
154
|
+
| `payment-usage-counter` | (entity, limit, window) projection | `{entityId, limitKey, window}` unique |
|
|
155
|
+
| `payment-fingerprint` | synchronized product (`<productSku>`, with `prices[]`) or portal (`portal:<owner>`) | `{sku}` unique |
|
|
156
|
+
| `payment-billing-profile` | organization: the billing country fixed at the first purchase | `{entityId}` unique · `{paygate, customerId}` sparse |
|
|
157
|
+
| `payment-purchase` | purchase = contract + withdrawal window (a paid one-time checkout, a subscription's first invoice) | `{purchaseId}` unique · `{contractRef}` unique · `{entityId, deadline:-1}` · `{entityId, purchasedAt:-1}` · `{sessionId}` unique sparse · `{paygate, subscriptionId}` · `{invoiceId}` · `{invoiceNumber}` · `{paygate, paymentIntentId}` |
|
|
158
|
+
| `payment-consumer-consent` | append-only: a performance consent or a subscription start request | `{entityId, decidedAt:-1}` · `{kind, entityId, planSku, decidedAt:-1}` |
|
|
159
|
+
| `payment-consumer-declaration` | append-only: a withdrawal or cancellation declaration | `{kind, receivedAt:-1}` · `{entityId, receivedAt:-1}` sparse · `{purchaseId}` sparse |
|
|
160
|
+
| `payment-consumer-event` | append-only: one execution or audit step (mail, refund, lock …) | `{recordId, at}` · `{entityId, at:-1}` sparse · `{action, ok, at}` |
|
|
161
|
+
|
|
162
|
+
- Every stored property is declared in the record schema (`additionalProperties: false`): the
|
|
163
|
+
resource writes a property its schema does not know as a string, and the collection validator
|
|
164
|
+
rejects it. New fields on existing records are optional, so old rows stay valid.
|
|
165
|
+
- **A map is stored as an array** (`prices[].options: [{ currency, unitAmount }]`): the resource
|
|
166
|
+
coerces a currency-keyed map's values to strings.
|
|
167
|
+
- A compound sparse index still indexes a row that lacks only some of its keys — a unique index
|
|
168
|
+
that must skip absent values is single-field (`{sessionId}`).
|
|
169
|
+
- Conditional writes (`consentedAt`, `withdrawnAt`) are raw `$set`s guarded by the old value
|
|
170
|
+
(`paymentUtils.conditionalSet`), so a concurrent declaration of the same purchase loses; declarations,
|
|
171
|
+
consents and events are only ever created.
|
|
172
|
+
|
|
173
|
+
## Checkout
|
|
174
|
+
|
|
175
|
+
- `Amount`: one inline `price_data` item for the synchronized product, quantity 1, its
|
|
176
|
+
`tax_behavior` the declared `PricingPolicy.tax.behavior`, no promotion codes. `Quantity`: the
|
|
177
|
+
reusable price under the plan lookup key, adjustable quantity. Subscription: `planSku` (else the
|
|
178
|
+
product's first recurring plan), quantity 1. One-time Sessions enable `invoice_creation`.
|
|
179
|
+
- `CreateLinkParams.locale` is a caller-validated Stripe locale (Session `locale`, the Customer's
|
|
180
|
+
`preferred_locales`). `submitText` is trusted application copy — a string, or a function of
|
|
181
|
+
`CheckoutTextContext {language, currency, unitAmountMinor, interval, region, country}` — on
|
|
182
|
+
every mode; never caller-provided text.
|
|
183
|
+
- A plan the paygate does not sell is refused (`ProductError`). `checkoutOptions` puts automatic
|
|
184
|
+
tax, billing address collection, tax-id collection and Adaptive Pricing on the session exactly
|
|
185
|
+
as `PricingPolicy` declares them.
|
|
186
|
+
- Subscription methods come from `stripe.subscriptionPaymentMethodTypesByCurrency[chargeCurrency]`,
|
|
187
|
+
falling back to `subscriptionPaymentMethodTypes`; with neither, Stripe selects dynamically.
|
|
188
|
+
Currency keys and method names normalize to lowercase. Currency keys must have three letters;
|
|
189
|
+
method lists must be nonempty and unique. Choose account-supported methods for each currency:
|
|
190
|
+
a method the account cannot accept in that currency rejects the whole session.
|
|
191
|
+
- **Without a consumer-rights policy every session is what it always was**: the charge currency
|
|
192
|
+
is the settlement currency (FX from the catalogue), Adaptive Pricing as declared.
|
|
193
|
+
|
|
194
|
+
### Under a consumer-rights policy
|
|
195
|
+
|
|
196
|
+
- **The country.** A locked billing profile overrides `params.country`; another declared country is
|
|
197
|
+
`BillingCountryLocked` (409), and so is a saved customer address that left the locked country
|
|
198
|
+
(an operator relocks). An organization that already paid before its country was locked is
|
|
199
|
+
locked lazily from its Stripe customer's address (`source: 'customer'`). Before any lock, the
|
|
200
|
+
declared country is prefilled on a customer without an address.
|
|
201
|
+
- **The currency.** Charge currency = the profile's currency, else `consumerRegionHelper.chargeCurrencyOf(region)`
|
|
202
|
+
(`policy.currencies`). An amount checkout whose policy currency IS the charge currency is
|
|
203
|
+
charged exactly — no FX call; any other goes through the FX reference rate (rounded up). A
|
|
204
|
+
subscription or quantity session is forced to the charge currency (`currency`) only when its
|
|
205
|
+
synced Price carries it (default or option) — otherwise it is left to Stripe and warned.
|
|
206
|
+
`adaptive_pricing.enabled` is explicitly true only when the policy enables it and the charge
|
|
207
|
+
currency is the settlement currency; every other session sends false. Omitting the flag lets
|
|
208
|
+
Stripe inherit the Dashboard setting and can enable localization for an exact USD checkout.
|
|
209
|
+
- **The lock at Stripe.** A locked profile whose customer carries the address:
|
|
210
|
+
`customer_update.address: 'never'` and `billing_address_collection: 'auto'` (tax follows the
|
|
211
|
+
saved address, Checkout cannot move it); `name: 'auto'` stays. A locked customer without a
|
|
212
|
+
tax-locatable address (`stripeSessionHelper.isTaxLocatable`) keeps `'auto'`/`'required'`, or Stripe refuses the
|
|
213
|
+
session ("Automatic tax calculation in Checkout requires a valid address on the Customer").
|
|
214
|
+
- **Terms.** `mechanisms.checkoutTerms` puts `consent_collection.terms_of_service: 'required'` and
|
|
215
|
+
`custom_text.terms_of_service_acceptance` (the `checkout.terms-acceptance.in-scope | other` copy
|
|
216
|
+
with the billing language's links, ≤ 1200 characters) on every session.
|
|
217
|
+
- **A missing Dashboard terms URL never breaks a payment.** Stripe refuses the checkbox then
|
|
218
|
+
(`invalid_request_error`, param `consent_collection[terms_of_service]`, "You cannot collect consent
|
|
219
|
+
to your terms of service unless a URL is set in the Stripe Dashboard", matched by
|
|
220
|
+
`stripeSessionHelper.isMissingTermsUrl`): the session is created ONCE more without `consent_collection` and without the
|
|
221
|
+
terms text, under the same plugin admissions, metadata `termsCollected: 'false'`; a
|
|
222
|
+
`checkout-terms-fallback` event (`recordKind: 'checkout'`, `recordId` = the entity, `externalId`
|
|
223
|
+
= the session, `ok: false`, the Stripe message in `detail`) is appended per fallback and one
|
|
224
|
+
`warn` log per context tells the operator what to set. Any other refusal is not retried; a
|
|
225
|
+
failing retry releases the admissions and propagates its own error.
|
|
226
|
+
- **Texts.** An in-scope top-up without `submitText` says what it buys (`checkout.top-up`, with the
|
|
227
|
+
country's name); a subscription without `submitText` shows the renewal price in the charge
|
|
228
|
+
currency (`checkout.renewal.<interval>`), and `after_submit` links the cancellation page while
|
|
229
|
+
that mechanism is on. Every text is asserted ≤ 1200 characters.
|
|
230
|
+
- **Start requests.** A subscription needs a fresh start request bound to the organization, the
|
|
231
|
+
plan and the current text version (`assertStartRequest` → `SubscriptionStartRequired`, 428),
|
|
232
|
+
unless the organization is already locked outside the territories — a country picked before
|
|
233
|
+
checkout may differ from the address typed at Stripe.
|
|
234
|
+
- **Metadata** (session and `subscription_data`): `region`, `country`, `language`, `termsVersion`,
|
|
235
|
+
`copyVersion`, `termsCollected` (`'true'` when the checkbox is on the session, `'false'` when the
|
|
236
|
+
policy has it off or after the fallback), `ipCountry` (the `cf-ipcountry` the app passes),
|
|
237
|
+
`startRequestId`, `profileId`, `countryPinned` (`'true'` only under `lockCustomerCountry` when the
|
|
238
|
+
session kept the pinned address). The purchase's `termsAccepted` comes only from the completed
|
|
239
|
+
session's `consent.terms_of_service` — absent when nothing was collected.
|
|
240
|
+
|
|
241
|
+
### Customer locks (`declarePaymentPricing` → `stripe`)
|
|
242
|
+
|
|
243
|
+
Both are opt-in, backend-only, read per call from the Stripe pricing plugin config, and work with
|
|
244
|
+
or without a consumer-rights policy.
|
|
245
|
+
|
|
246
|
+
- **`lockCustomerEmail`** — every `createLink` must pass `CreateLinkParams.email` (the application's
|
|
247
|
+
verified e-mail of the buyer), else `PaygateError('customer-email')` before any Stripe call. It is
|
|
248
|
+
written to the Stripe customer (created with it, or updated — case-insensitively different — in
|
|
249
|
+
the same call as `preferred_locales`), never as `customer_email`: Checkout shows a customer's
|
|
250
|
+
valid e-mail read-only, and only while the customer has none asks for one and saves it. The
|
|
251
|
+
customer carries the LAST payer's e-mail (invoices and renewals go there). Without the lock
|
|
252
|
+
`email` is ignored.
|
|
253
|
+
- **`lockCustomerCountry`** — the known country (the locked profile's, else `params.country`) is
|
|
254
|
+
written to the customer as a country-only address (lines and postal code cleared) when the saved
|
|
255
|
+
one carries no or another country — over another country only before any lock (a locked
|
|
256
|
+
customer's other country is still `BillingCountryLocked`). When the saved address is
|
|
257
|
+
tax-locatable the session keeps it (`customer_update.address: 'never'`, collection `'auto'`):
|
|
258
|
+
no address form, tax on the pinned country; Stripe still shows the card form's "Country or
|
|
259
|
+
region" picker (a payment-method detail no parameter hides), which then moves neither tax nor the
|
|
260
|
+
saved address. Metadata `countryPinned: 'true'` makes the first completed purchase lock THAT
|
|
261
|
+
country (`lockFromSession`: `metadata.country` before `customer_details.address.country`).
|
|
262
|
+
- **Tax-locatable** (`stripeSessionHelper.isTaxLocatable`, from Stripe's customer-locations table): a country alone
|
|
263
|
+
everywhere except US (needs `postal_code`) and CA / IN (`postal_code` or `state`). A pinned US,
|
|
264
|
+
CA or IN customer without one is not kept: Checkout collects the address — country editable —
|
|
265
|
+
and the purchase locks what was typed, as without the option.
|
|
266
|
+
- **The portal** never offers `email` under `lockCustomerEmail`, nor `address` under
|
|
267
|
+
`lockCustomerCountry` (or `countryLock`); both flags are in its fingerprint.
|
|
268
|
+
|
|
269
|
+
## Checkout plugins
|
|
270
|
+
|
|
271
|
+
`paymentAccessOf(ctx).gateway().use(plugin)` seats a `CheckoutPlugin` per gateway instance (the `ExecutionService.use`
|
|
272
|
+
registry: a plugin whose `alias` is registered already replaces it). All hooks are optional:
|
|
273
|
+
|
|
274
|
+
| Hook | Called | Contract |
|
|
275
|
+
|---|---|---|
|
|
276
|
+
| `narrow(ctx, {entityId, productSku, planSku?, base, at})` | every amount checkout and every `gateway.amountPolicy` | answers an `AmountNarrowing` or `null`; a throw fails the checkout closed |
|
|
277
|
+
| `admit(ctx, CheckoutAttempt)` | before the session, every mode | throw (`CheckoutLimitExceeded`) to veto; may return `{ reservationId }` |
|
|
278
|
+
| `created(ctx, CheckoutCreated)` | after the session | gets `sessionId`, `url`, `expiresAt`, amounts, its own `reservationId`; a throw expires the session, releases every hold and propagates |
|
|
279
|
+
| `settled(ctx, CheckoutSettled)` | webhook: `paid`, `expired`, `failed`; and `failed` for a checkout that never became usable | errors are logged, never raised |
|
|
280
|
+
| `sessionTtlSeconds` | — | the smallest declared, clamped to 30 min – 24 h, becomes `expires_at`; none declared: Stripe's default |
|
|
281
|
+
|
|
282
|
+
- **One narrowing path.** `createLink` and `gateway.amountPolicy(ctx, entityId, productSku,
|
|
283
|
+
planSku?)` both call `checkoutPluginsOf(ctx).narrowAmountFor` → `amountNarrowingHelper.narrowAmountPolicy` (`@owlmeans/payment`); an amount
|
|
284
|
+
above the narrowed maximum, or any amount while `blocked`, is `CheckoutLimitExceeded` (409). A
|
|
285
|
+
control and a refusal cannot disagree. An amount above the plan's own maximum stays the base
|
|
286
|
+
policy's error.
|
|
287
|
+
- A veto, a Stripe refusal or a throwing `created` releases what the earlier plugins admitted
|
|
288
|
+
(`settled` with `outcome: 'failed'` and their `reservationId`).
|
|
289
|
+
- `gateway.planPrices(ctx, productSku)` answers `PlanPriceView[]` (a default entry per Price plus
|
|
290
|
+
one per currency option) from the synced fingerprint rows — no Stripe call.
|
|
33
291
|
|
|
34
|
-
##
|
|
292
|
+
## Price sync and the tax estimate
|
|
35
293
|
|
|
36
|
-
- `
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
|
|
294
|
+
- `productSyncOf(ctx).syncStripeProducts` gives a matching, still-`unspecified` price the declared `tax.behavior` IN
|
|
295
|
+
PLACE and a fresh one on creation; a price carrying the OPPOSITE behavior is replaced. Before an
|
|
296
|
+
in-place update it checks the account's tax-settings default and skips — logging why — when that
|
|
297
|
+
would change existing renewals, unless `stripe.migrateUnspecifiedPrices` opts in.
|
|
298
|
+
- A recurring or quantity plan whose catalogue currency differs from `stripe.settlementCurrency` is
|
|
299
|
+
converted with an unlocked FX Quote (`reference_rate`, rounded up). Its **currency options**:
|
|
300
|
+
the declared `currencyPrices`, plus the catalogue currency (exact `round(price × 100)`) when it is
|
|
301
|
+
one of the policy's region currencies; each option carries the declared `tax_behavior`. Active
|
|
302
|
+
prices are listed with `expand: ['data.currency_options']`; a changed option replaces the Price
|
|
303
|
+
(deactivate + create with `transfer_lookup_key`) — options are never edited in place, so a
|
|
304
|
+
subscriber keeps the Price they accepted.
|
|
305
|
+
- The resolved amounts, currencies and options are fingerprinted; an unchanged fingerprint makes no
|
|
306
|
+
product/price call. Each sync stores the product's `prices[]` (`SyncedPrice`: price id, lookup
|
|
307
|
+
key, default currency and amount, options, tax behavior, interval, source amount) on the
|
|
308
|
+
fingerprint row — what `planPrices`, checkout currency forcing and the estimate read. A row from
|
|
309
|
+
before `prices[]` existed syncs once more.
|
|
310
|
+
- `estimatePrice` (`plugins/estimate.ts`) is a Stripe Tax calculation for one product/plan's
|
|
311
|
+
reference amount at a country — **$0.05 per distinct** (currency, country, amount, tax code,
|
|
312
|
+
behavior, matching tax ids) combination, cached per gateway-service instance for 24h; failures
|
|
313
|
+
are never cached. **A locked profile overrides the requested country** (`source: 'profile'`,
|
|
314
|
+
`locked: true`); the estimate carries `region`. Under a policy with region currencies a
|
|
315
|
+
recurring plan is estimated in the charge currency at its synced unit amount (default or
|
|
316
|
+
option); the `local` line (FX Quotes, a PREVIEW endpoint) only when the session's CHARGE currency
|
|
317
|
+
(the region's, or the locked profile's) is the settlement currency — the same test the session's
|
|
318
|
+
`adaptive_pricing` takes. An amount plan stays estimated in its policy currency but is charged in
|
|
319
|
+
the region's currency, so an EU top-up gets its local line through the policy → settlement →
|
|
320
|
+
local chain, and an exact-USD one none.
|
|
43
321
|
|
|
44
|
-
|
|
45
|
-
pricing mode, net amount, adjusted pre-tax subtotal, currency, product, plan, service and owner.
|
|
322
|
+
## The subscription store
|
|
46
323
|
|
|
47
|
-
|
|
324
|
+
- **The effective plan** is the highest-ranked row in `ENTITLING_STATUSES` (catalogue rank; the
|
|
325
|
+
newest on a tie), else the declared free plan, else `PlanRequired`.
|
|
326
|
+
- **A subscription row may override its plan.** `PaymentSubscriptionRecord.overrides`
|
|
327
|
+
(`SubscriptionOverrides`: `limits: { <key>: { limit } }`) is applied by `planHelper.overriddenPlan(plan, row)`
|
|
328
|
+
inside `resolveEffectivePlan` and `snapshotOf`, so ceilings, the entitlement view, both gates and
|
|
329
|
+
observers read one answer and nothing else needs to know. An override sets the ceiling of a key
|
|
330
|
+
the plan declares and drops that key's promo; kind and window stay the plan's (counters are keyed
|
|
331
|
+
by them); an undeclared key or a ceiling that is not a safe integer `>= 0` is ignored. An operator
|
|
332
|
+
writes it, no paygate does: every sync and re-grant spreads the previous row, so it survives. It
|
|
333
|
+
belongs to the row — it follows the subscription through a plan change, ends with it, and the
|
|
334
|
+
free-plan fallback (no row) has none. A new overridable parameter is a new optional key of
|
|
335
|
+
`SubscriptionOverrides`, declared in `PaymentSubscriptionSchema` and merged in `planHelper.overriddenPlan`.
|
|
336
|
+
- `mapStatus`: `active`→Active, `trialing`→Trial, `past_due`→PastDue, `unpaid`/`paused`→Suspended,
|
|
337
|
+
`incomplete`→Created, `incomplete_expired`→Ended, `canceled`→Canceled; paused collection is
|
|
338
|
+
Suspended with `pausedAt`.
|
|
339
|
+
- **State is written before observers, and classified against what observers were last told**
|
|
340
|
+
(`propagated`, stamped with `lastEventId` only after every observer succeeded).
|
|
341
|
+
`CommitOptions.beforePropagate(record, change)` runs between the write and the observers — the
|
|
342
|
+
Stripe path writes a subscription's purchase there on `created`, so the window exists before an
|
|
343
|
+
observer grants the bundle.
|
|
344
|
+
- Classification, first match: `created` · `canceled` · `paused` · `resumed` · `upgraded` /
|
|
345
|
+
`downgraded` · `cancel-scheduled` · `cancel-undone` · `renewed` (once per invoice) · `past-due` ·
|
|
346
|
+
`suspended` · `trial-ending`.
|
|
347
|
+
- A repeated delivery (`lastEventId`) is ignored; an older payload than the stored state is not
|
|
348
|
+
applied. `grantInternalPlan(ctx, entityId, planSku, { force?, periodEnd? })` upserts an internal
|
|
349
|
+
row; `resyncSubscription` / `resyncAll` re-read and apply as a webhook would.
|
|
48
350
|
|
|
49
|
-
|
|
50
|
-
nothing until `payment_status === 'paid'`. Amount fulfillment compares Stripe currency and actual
|
|
51
|
-
`amount_subtotal` with the server-created metadata. Completion is discriminated:
|
|
351
|
+
## The usage ledger
|
|
52
352
|
|
|
53
|
-
-
|
|
54
|
-
|
|
353
|
+
`payment-usage` events are the source of truth; `payment-usage-counter` is the projection that
|
|
354
|
+
admission reads. `consume` increments the counter FIRST by one conditional upsert (`used <= limit
|
|
355
|
+
- amount`), then appends the event — **the counter may over-count, never over-admit**. An event
|
|
356
|
+
key is idempotent; `release` appends `release:<eventKey>` first. `reconcileCounters`,
|
|
357
|
+
`reconcileOccupancy`, `reconcileOf(ctx).reconcileEntity` and `.reconcileAll` repair it (details: the `entitlements`
|
|
358
|
+
skill).
|
|
55
359
|
|
|
56
|
-
|
|
57
|
-
and gets `fulfilledAt` only after they succeed; observer failures escape so Stripe retries. Consumer
|
|
58
|
-
callbacks must still use `externalId` as their own append-only event key, covering a crash after the
|
|
59
|
-
side effect and before `fulfilledAt`. A session without pricing-mode metadata is a legacy quantity
|
|
60
|
-
session and remains fulfillable.
|
|
360
|
+
## Entitlements and gates
|
|
61
361
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
362
|
+
- `paymentAccessOf(ctx).entitlements()` → `effectivePlan`, `entitlements` (the `EntitlementView`), `hasCapability`,
|
|
363
|
+
`limitState`, the ledger operations. Never calls Stripe.
|
|
364
|
+
- **Capability gate** (`ENTITLEMENT_GATE`) passes when the view grants ANY parameter; **limit gate**
|
|
365
|
+
(`LIMIT_GATE`) when any `limit:<key>[>=n]` has room — it never consumes. Both refuse with
|
|
366
|
+
`AuthForbidden` refusals (403) and fail closed on an unreadable store.
|
|
367
|
+
- The consumer-rights refusals (428/409) and `CheckoutLimitExceeded` are not entitlement refusals.
|
|
65
368
|
|
|
66
|
-
##
|
|
369
|
+
## Consumer rights
|
|
67
370
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
Never put an application auth guard on the Stripe webhook.
|
|
371
|
+
The EU right of withdrawal (with the Art. 11a withdrawal function), spend consent for prepaid
|
|
372
|
+
credits, subscription start requests, the cancellation function and a billing country fixed at the
|
|
373
|
+
first purchase. The service is `paymentAccessOf(ctx).consumerRights()` (`.consumerRightsOf()` is null-safe); its
|
|
374
|
+
full contract is in `reference.md`.
|
|
73
375
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
376
|
+
- **A purchase** is a paid one-time checkout, or a subscription's FIRST invoice — renewals,
|
|
377
|
+
internal grants and manual credits never are. Its row (`purchaseId` `stripe:<cs_…>` /
|
|
378
|
+
`stripe:<sub_…>`, `contractRef` `CR-YYMMDD-XXXXXX` over an unambiguous alphabet, retried on a
|
|
379
|
+
collision) is written **before anything is granted**: in `checkout.session.completed` before
|
|
380
|
+
`onTopUp`, in the first subscription commit before the `created` observers. A completed
|
|
381
|
+
subscription checkout then refines it with the buyer's own country, e-mail, totals and terms
|
|
382
|
+
acceptance. Window = in scope, before `deadline` (`withdrawalDeadlineHelper.withdrawalDeadlineOf`, policy margin),
|
|
383
|
+
neither withdrawn nor refunded; a full refund closes it (`refundedAt`).
|
|
384
|
+
- **In scope** when the buyer's own country or the organization's locked country is in the policy's
|
|
385
|
+
territories; an unknown country is protected by default. A business tax id does not exempt.
|
|
386
|
+
- **The lock**: the first completed purchase's `customer_details.address.country` (else the
|
|
387
|
+
declared one) locks the profile — first write wins through the unique index; a later different
|
|
388
|
+
country is a `lock-mismatch` event, never a relock. The request's `cf-ipcountry` is stored as
|
|
389
|
+
`ipCountry` beside it, and the `lock` event flags `ipMismatch`. The profile's currency is an
|
|
390
|
+
entitling Stripe subscription's currency when one exists (two subscriptions of one customer can
|
|
391
|
+
not differ), else the region's. Only `lock(entityId, country, 'manual', { force: true, by,
|
|
392
|
+
reason })` replaces a lock, audited as `relock`.
|
|
393
|
+
- **Unlock** (an operator, Mongo only — unmanaged works): `unlock(entityId, { by, reason })` deletes
|
|
394
|
+
the profile (only while it still holds the country read) and appends an `unlock` event carrying the
|
|
395
|
+
whole row; it answers the profile as it was, or `null`. After an unlock no lock is taken from the
|
|
396
|
+
paygate customer's saved address — neither at checkout nor by `reconcile` — so the next COMPLETED
|
|
397
|
+
purchase locks the country again from its own address.
|
|
398
|
+
- **Performance consent** (top-ups only — a start request covers a subscription's own invoice):
|
|
399
|
+
required while an open in-scope top-up window has none. `assertConsent` is one indexed query and
|
|
400
|
+
throws `PerformanceConsentRequired` (428, `pending`, latest `deadline`); an application calls it
|
|
401
|
+
only where credits will actually be spent. `consentView` carries the policy's `consentContext`
|
|
402
|
+
as `context`. `recordConsent` renders the statement itself (`consumerCopyHelper.consentStatementOf` with the
|
|
403
|
+
trader's `name` and the policy's `consentContext`, so the stored verbatim text and the consent
|
|
404
|
+
mail are the `_<context>` variant the dialog showed; without a context the base statement),
|
|
405
|
+
stores that `context` on the record, refuses a stale `textVersion` with a fresh 428, covers only
|
|
406
|
+
the open windows the body lists, stamps `consentedAt` conditionally, mails the confirmation, then
|
|
407
|
+
tells `onConsent`.
|
|
408
|
+
- **Start requests follow the plan's withdrawal arithmetic.** `startView(entityId, planSku)` names
|
|
409
|
+
`context = consumerCopyHelper.startContextOf(plan)` (`'units'` for a plan with a `units` component, absent for a
|
|
410
|
+
time-only one); `recordStartRequest(subject, body, origin, { plan })` renders the statement with
|
|
411
|
+
the SAME `consumerCopyHelper.startContextOf(plan)` and the plan's short name the application passes (default: its
|
|
412
|
+
localized title), stores it verbatim with its `context`, and the start mail repeats it with
|
|
413
|
+
`email.start.rule` of that variant (the record's `context`). Usable `startRequestTtlSeconds`
|
|
414
|
+
(3600); the purchase takes it as `servicesStartedAt`/`consentedAt`.
|
|
415
|
+
- **Withdrawal** (`withdraw(subject | null, body, origin)`, managed only): in-app by `purchaseId`,
|
|
416
|
+
public by contract reference or invoice number plus an e-mail of the purchase, its profile or
|
|
417
|
+
its paygate customer. The declaration and the conditional `withdrawnAt` are written BEFORE
|
|
418
|
+
Stripe; the receipt is mailed at once; then the paygate steps under
|
|
419
|
+
`withdrawal:<id>:<step>` keys; then `onWithdrawal`. The refund comes from the application's
|
|
420
|
+
`UsageMeter` through the `@owlmeans/payment` calculators (deduction `usedAfter + settled +
|
|
421
|
+
clawed`); no meter, or `automaticRefunds` off, is `review` (no paygate call). A late
|
|
422
|
+
declaration is `expired` — recorded and acknowledged, nothing executed. A repeated one answers
|
|
423
|
+
the original receipt. The public answer is always the bare `DeclarationReceipt`.
|
|
424
|
+
- **Cancellation** (`cancel(subject | null, body, origin)`): in-app the organization's entitling
|
|
425
|
+
Stripe subscription; public a contract reference plus e-mail, else the one organization whose
|
|
426
|
+
paygate customer has that e-mail. Ordinary → `cancel_at_period_end`, or `cancel_at` a later
|
|
427
|
+
boundary (`cancellationEffectiveAt`, no proration); already scheduled → `already-scheduled`;
|
|
428
|
+
**extraordinary → `review`, the paygate untouched** (an operator decides, the receipt says so).
|
|
429
|
+
- **Observers `onConsent` / `onWithdrawal` / `onCancellation` run AFTER the records and the paygate
|
|
430
|
+
steps; a throw is recorded (`observers` event) and retried by `reconcile()` — unlike the paygate
|
|
431
|
+
callbacks, whose throw makes Stripe redeliver.** A `WithdrawalEvent` with `status: 'refunded'`
|
|
432
|
+
carries `units.returned` — take exactly those back; `review` means an operator refunds later,
|
|
433
|
+
and that refund arrives as an ordinary `RefundEvent`.
|
|
434
|
+
- **`RefundEvent.withdrawalId`**: a refund this package made for a withdrawal carries
|
|
435
|
+
`metadata.withdrawalId`; an `onRefund` observer MUST skip its own claw-back for it, or the units
|
|
436
|
+
are taken back twice.
|
|
437
|
+
|
|
438
|
+
## Durable-medium mails
|
|
439
|
+
|
|
440
|
+
Sent through the optional mailer (`mail.alias`, default `MAILER_SERVICE`), text + HTML, in the
|
|
441
|
+
language the consumer was shown, from the `payment-consumer-rights` copy (`email.*`): purchase
|
|
442
|
+
confirmation (in-scope purchases with an e-mail; withdrawal information with the function's
|
|
443
|
+
address and the model form), consent confirmation, start confirmation, withdrawal receipt,
|
|
444
|
+
cancellation receipt. User values are HTML-escaped; a deadline is shown as its last included day.
|
|
445
|
+
The mails and the withdrawal information name the trader's `legalName, address, email`; the
|
|
446
|
+
statements its `name`. Every send, skip or failure is a `mail` event (step = kind); addresses on
|
|
447
|
+
`.test`/`.example`/`.invalid`/`.localhost` and every subdomain of them (`consumerFormatHelper.isReservedAddress`; case, a
|
|
448
|
+
display-name form and a trailing dot read through) are never sent (recorded as skipped); each `bcc`
|
|
449
|
+
address gets its own copy. **The purchase confirmation goes out once per purchase**: a delivery
|
|
450
|
+
sends it only after winning the conditional `confirmationMailAt` claim on the purchase row (a mail
|
|
451
|
+
event of it from before the claim counts too) — concurrent deliveries in several processes, a
|
|
452
|
+
redelivery and a subscription checkout refining its purchase mail nothing twice; a failed send is
|
|
453
|
+
retried by `reconcile` only. `useMailRenderer((kind, data, rendered) => message | null |
|
|
454
|
+
undefined)` replaces (`message`), suppresses (`null`, recorded as skipped) or keeps a mail. The
|
|
455
|
+
boot warns once when a mailing mechanism is on and the trader has no address or e-mail, or no
|
|
456
|
+
mailer is registered.
|
|
457
|
+
|
|
458
|
+
## Reconcile
|
|
459
|
+
|
|
460
|
+
`paymentAccessOf(ctx).consumerRights().reconcile({ since?, limit? })` — the application's nightly job: retries the
|
|
461
|
+
paygate steps of withdrawals (refund, credit note, subscription cancel) and scheduled
|
|
462
|
+
cancellations, failed mails and failed observers; backfills purchases (records only, no mail) from
|
|
463
|
+
completed sessions of the last 16 days without a row; locks organizations that paid before the
|
|
464
|
+
lock from their paygate customer (never one an operator unlocked). A retry uses a fresh idempotency key (`…:<attempt>`; Stripe
|
|
465
|
+
replays a stored failure for a day) and first adopts a refund or credit note an earlier attempt
|
|
466
|
+
made (found by `metadata.withdrawalId`). Five failures of a step leave it to an operator. At most
|
|
467
|
+
`limit` (50) items per step.
|
|
468
|
+
|
|
469
|
+
## Protocols, handlers and security
|
|
470
|
+
|
|
471
|
+
- `paymentGate` (bound by `paymentGateEntrypoints`): `webhook` is public because Stripe signs the
|
|
472
|
+
untouched raw body — never put an application guard on it; `resync` and `resyncSubscriptions`
|
|
473
|
+
carry `GUARD_ED25519`.
|
|
474
|
+
- **An application that declares the gate itself** — its own aliases, every route pinned to the
|
|
475
|
+
service that receives the webhook — binds its declarations to `paymentGateHandlers` (`webhook`,
|
|
476
|
+
`resync`, `resyncSubscriptions`) with `bind(own.webhook, paymentGateHandlers.webhook)` from
|
|
477
|
+
`@owlmeans/server-entrypoint` / `@owlmeans/server-app`, and never registers `paymentGate` or
|
|
478
|
+
`paymentGateEntrypoints`. A handler reads its context from the entrypoint it is bound to, so the
|
|
479
|
+
application's declaration is the one mounted and run. Its declarations keep the library's paths
|
|
480
|
+
(`webhookOf(ctx).webhookUrlOf` forms the Stripe URL from `paymentGate`'s), contracts (`PaygateParams`,
|
|
481
|
+
`ResyncResult`, `ResyncSubscriptionsResult` — `bind` refuses another shape at compile time) and
|
|
482
|
+
guards. Two ways to get it wrong: `bindAll(declarations, [paymentGateHandlers.webhook])` pairs by
|
|
483
|
+
object identity and leaves the application's declaration without a handler, and
|
|
484
|
+
`handlers().params(own.webhook, paymentGateHandlers.webhook)` answers every request with
|
|
485
|
+
`HandlerMisconfiguredError` (a handler bound to another protocol).
|
|
486
|
+
- `consumerRightsEntrypoints(protocols, { resolveEntity?, subjectOf?, guardMoney?, throttle?,
|
|
487
|
+
metaOf?, publicMinMs?, planNameOf?, serviceAlias? })` binds `makeConsumerRightsProtocols`' tree.
|
|
488
|
+
**Every hook gets the request's context as its LAST argument** — `resolveEntity(req, ctx)`,
|
|
489
|
+
`subjectOf(req, ctx)`, `guardMoney(req, action, ctx)`, `throttle(req, key, ctx)`,
|
|
490
|
+
`metaOf(req, ctx)`, `planNameOf(planSku, language, req, ctx)` — so an application reaches its
|
|
491
|
+
services (a throttle store) through it and keeps no module state. Account routes act for
|
|
492
|
+
`resolveEntity` (default `req.entity.id`, else `AuthForbidden`); consent, start, withdraw and
|
|
493
|
+
cancel pass `guardMoney` first (refuse API keys there). **A tree with a `public` subtree needs
|
|
494
|
+
`throttle` — a wiring error otherwise.** Public declarations are throttled with `{action, email,
|
|
495
|
+
ip}`, a filled `honeypot` gets a decoy receipt and nothing is recorded, and every answer takes at
|
|
496
|
+
least `publicMinMs` (1000 ms) so a match is not visible in the timing either.
|
|
497
|
+
- `checkoutReadEntrypoints(protocols, { resolveEntity?, gatewayAlias? })` binds
|
|
498
|
+
`makeCheckoutReadProtocols`: `amountPolicy` and `planPrices`; its `resolveEntity(req, ctx)` too.
|
|
499
|
+
- **`originHelper.requestOriginOf(req)`** is the evidence of every consumer act (the default `metaOf`): `ip` =
|
|
500
|
+
`cf-connecting-ip` → the LAST `x-forwarded-for` entry → `x-real-ip` → the socket; the raw
|
|
501
|
+
`x-forwarded-for`, `user-agent` (≤ 512), `cf-ipcountry`, `accept-language`.
|
|
502
|
+
|
|
503
|
+
## Stripe self-management
|
|
504
|
+
|
|
505
|
+
Runs in `initialize()` of a managed gateway with `bootstrap` (default), after the context is
|
|
506
|
+
ready, and on a forced `stripeBootstrapOf(ctx).bootstrapStripe` from any managed process; each step independent: products
|
|
507
|
+
and prices (above), the portal configuration, the webhook endpoint. A deployment's identity is its
|
|
508
|
+
webhook URL (`webhookOf(ctx).webhookUrlOf()`: the `webhookService` alias's host and base); its rows carry the
|
|
509
|
+
`owner`.
|
|
510
|
+
|
|
511
|
+
- **The portal configuration**: customer update (tax id; email unless `stripe.lockCustomerEmail`;
|
|
512
|
+
address unless `mechanisms.countryLock` or `stripe.lockCustomerCountry`), invoice history,
|
|
513
|
+
payment method update, cancellation at period end without proration, price switching between the
|
|
514
|
+
active recurring prices. Its fingerprint covers the catalogue (incl. `currencyPrices`), the lock
|
|
515
|
+
flags, the region currencies, the branding and the deployment key. Each deployment owns its own configuration, tagged `{ owlmeans: 'payment',
|
|
516
|
+
service: <owner>, deployment: webhookOf(ctx).webhookUrlOf() }`; one tagged for another deployment (a former
|
|
517
|
+
webhook URL included) is never touched — a moved URL creates a new configuration.
|
|
518
|
+
- `portalLink(ctx, entityId, { flow, planSku?, returnUrl })`: a customer is required
|
|
519
|
+
(`PortalUnavailable`, 409); `Cancel`/`Update`/`Change` need an entitling Stripe subscription.
|
|
520
|
+
- **The webhook endpoint** at `webhookOf(ctx).webhookUrlOf()`, subscribed to `WEBHOOK_EVENTS`, on the API version
|
|
521
|
+
read back from the client; only an https public host. A deployment deletes only the endpoints its
|
|
522
|
+
own rows name. A new URL (a moved host, a new `webhookService`) creates the endpoint there, then
|
|
523
|
+
deletes the endpoints of the owner's rows at other URLs and those rows — no migration step. The
|
|
524
|
+
secret (create-only) is stored field-encrypted where the database has a key; the receiver reads
|
|
525
|
+
the newest row of its `owner`.
|
|
526
|
+
- **Do not bump the Stripe SDK** (17.x, API `2025-02-24.acacia`): `current_period_*`,
|
|
527
|
+
`invoice.subscription`, `invoice.payment_intent`, `charge.invoice` are top-level there and move
|
|
528
|
+
later; credit notes link a refund by `refund`; `presentment_details` is untyped. Read them through
|
|
529
|
+
narrow accessors.
|
|
530
|
+
- **Dashboard prerequisites** (test and live): a terms-of-service URL in Settings → Public details
|
|
531
|
+
before `checkoutTerms` is on (without it every checkout falls back to no checkbox — payments go on,
|
|
532
|
+
but the acceptance is not collected: watch for `checkout-terms-fallback` events); Checkout's return/refund
|
|
533
|
+
policy off or pointing at the Billing Terms, never "no refunds"; business name, address and
|
|
534
|
+
support e-mail in Public details; the "successful payments" and "refunds" customer e-mails.
|
|
535
|
+
|
|
536
|
+
## Event dispatch
|
|
537
|
+
|
|
538
|
+
| Event | Persisted | Observer · event key |
|
|
539
|
+
|---|---|---|
|
|
540
|
+
| `customer.created` / `.updated` | customer upsert (`country`, `currency`) | — |
|
|
541
|
+
| `customer.deleted` | customer `deletedAt` | — |
|
|
542
|
+
| `checkout.session.completed` / `.async_payment_succeeded`, `payment` mode, paid | lock + purchase + confirmation mail, fulfillment (+ evidence), `fulfilledAt` after observers | `onTopUp` · session id; plugins `settled('paid')` |
|
|
543
|
+
| same, `subscription` mode | subscription applied if no webhook did (purchase in its first commit), purchase refined, lock, subscription evidence, confirmation mail | plugins `settled('paid')` |
|
|
544
|
+
| `checkout.session.async_payment_failed` | fulfillment `failedAt` | `onPaymentFailed {kind:'checkout'}` · `payment-failed:<session>:0`; `settled('failed')` |
|
|
545
|
+
| `checkout.session.expired` | unfulfilled fulfillment purged | plugins `settled('expired')` |
|
|
546
|
+
| `customer.subscription.created` / `.updated` / `.pending_update_*` / `.paused` / `.resumed` | subscription applied (+ `currency`) | `onSubscription` · classified |
|
|
547
|
+
| `customer.subscription.deleted` | applied as Canceled + `endedAt` | `canceled` |
|
|
548
|
+
| `customer.subscription.trial_will_end` | applied | `trial-ending` |
|
|
549
|
+
| `invoice.paid` | cycle invoice ⇒ re-read, applied as a renewal; otherwise `latestInvoiceId` | `renewed` |
|
|
550
|
+
| `invoice.payment_failed` / `.payment_action_required` | re-read and applied | classified, then `onPaymentFailed {kind:'invoice'}` · `payment-failed:<invoice>:<attempt>` |
|
|
551
|
+
| `invoice.upcoming` | nothing | — |
|
|
552
|
+
| `invoice.marked_uncollectible` / `invoice.voided` | re-read and applied / `latestInvoiceId` | classified / — |
|
|
553
|
+
| `charge.refunded`, `refund.created` / `.updated` (succeeded) | fulfillment `refundedMinor`/`refundedAt`; purchase `refundedMinor`, `refundedAt` on a whole refund | `onRefund` · `refund:<refund>` (`metadata`, `withdrawalId`) |
|
|
554
|
+
| `refund.failed` | — | — |
|
|
555
|
+
| `charge.dispute.*` | target `disputedAt`, `disputeStatus` | `onDispute {phase}` · `dispute:<dispute>:<phase>` |
|
|
556
|
+
|
|
557
|
+
A refund or dispute resolves to its target by payment intent (fulfillment), by charge, by invoice
|
|
558
|
+
(the subscription whose latest invoice it is, else the invoice's subscription); a subscription
|
|
559
|
+
refund touches the purchase only when it is of the purchase's own (first) invoice.
|
|
560
|
+
|
|
561
|
+
## Observer API and idempotency keys
|
|
562
|
+
|
|
563
|
+
| Callback | Payload | Key | A throw |
|
|
564
|
+
|---|---|---|---|
|
|
565
|
+
| `onTopUp` | `TopUpCompletion` | session id | Stripe redelivers |
|
|
566
|
+
| `onSubscription` | `SubscriptionEvent` | `subscription:<id>:<change>:…` | Stripe redelivers |
|
|
567
|
+
| `onRefund` | `RefundEvent` (+ `metadata`, `withdrawalId`) | `refund:<refund>` | Stripe redelivers |
|
|
568
|
+
| `onDispute` | `DisputeEvent` | `dispute:<dispute>:<phase>` | Stripe redelivers |
|
|
569
|
+
| `onPaymentFailed` | `PaymentFailedEvent` | `payment-failed:<session\|invoice>:<attempt>` | Stripe redelivers |
|
|
570
|
+
| `onConsent` | `ConsentEvent` (`kind`, `consentId`, `purchaseIds`, `planSku`) | `consent:<id>` | recorded; `reconcile` retries |
|
|
571
|
+
| `onWithdrawal` | `WithdrawalEvent` (`status`, `purchase: PurchaseRef`, `refund`, `units`, `subscriptionCanceled`) | `withdrawal:<id>` | recorded; `reconcile` retries |
|
|
572
|
+
| `onCancellation` | `CancellationEvent` (`matched`, `kind`, `status`, `effectiveAt`) | `cancellation:<id>` | recorded; `reconcile` retries |
|
|
573
|
+
|
|
574
|
+
Every callback must be idempotent by its key. A proportional claw-back of an ordinary top-up
|
|
575
|
+
refund uses `netAmountMinor * refundedTotalMinor / paidMinor`.
|
|
576
|
+
|
|
577
|
+
## Testing
|
|
578
|
+
|
|
579
|
+
Unit specs run a real server context — real catalogue, services and gates — over in-memory
|
|
580
|
+
resources (unique and sparse-unique indexes, raw conditional updates) and a fake Stripe that records
|
|
581
|
+
every SDK call and its request options (idempotency keys) and can fail a method's next calls
|
|
582
|
+
(`state.failures`: a message, a real SDK error such as `new Stripe.errors.StripeInvalidRequestError(…)`,
|
|
583
|
+
or a list of them, one per call), so "no Stripe call" is an assertion. `makeFakeContext` wires the
|
|
584
|
+
consumer-rights call before the gateway by default (`gatewayFirst`, `rights` and a pre-init `wire`
|
|
585
|
+
hook test the other orders); `gateway` passes the gateway's options (default `{ manage: false }`,
|
|
586
|
+
the fake always its Stripe client, so a managed one boot-bootstraps against it), `service` /
|
|
587
|
+
`host` / `base` / `services` shape `cfg.service` and `cfg.services`, and `seed` pre-fills stores
|
|
588
|
+
(a database shared between two processes). `gate-handlers.spec.ts` binds `paymentGateHandlers`
|
|
589
|
+
onto a re-declared, pinned gate and runs it in that process's context. The consumer-rights service there is
|
|
590
|
+
managed through the fake (`appendConsumerRights({ manage: true, stripe })`) with a console mailer
|
|
591
|
+
(`fake.mails`). The Mongo-gated specs prove admission under concurrency, the collection validators
|
|
592
|
+
of every record, the unique indexes, the concurrent first lock and the single winner of
|
|
593
|
+
concurrent withdrawals.
|
|
77
594
|
|
|
78
595
|
## External docs
|
|
79
596
|
|
|
80
|
-
- https://docs.stripe.com/api/checkout/sessions/create — Checkout
|
|
81
|
-
- https://docs.stripe.com/
|
|
597
|
+
- https://docs.stripe.com/api/checkout/sessions/create — `customer`: "If the Customer already has a valid email set, the email will be prefilled and not editable in Checkout" (else Checkout saves the typed one); `customer_email` only prefills a NEW customer and cannot be combined with `customer`. Inline `price_data`; `consent_collection.terms_of_service` needs a terms URL in the Dashboard (else `invalid_request_error` on param `consent_collection[terms_of_service]`); `custom_text.{submit, after_submit, terms_of_service_acceptance}` ≤ 1200 characters each; `currency` forces a Price's currency option; `expires_at` 30 min – 24 h.
|
|
598
|
+
- https://docs.stripe.com/tax/customer-locations — minimal tax location: country alone except US (postal code), CA and IN (postal code or province); https://docs.stripe.com/tax/checkout/page — an existing customer with `customer_update.address: 'never'` is taxed on its saved address. Verified against test mode (2026-10): a pinned DE customer keeps its VAT when the card country is switched to US; a country-only US/CA customer is refused at session creation.
|
|
599
|
+
- https://docs.stripe.com/payments/checkout/localize-prices/manual-currency-prices — `currency_options` on a Price, one reusable Price for several currencies; manual options override Adaptive Pricing for that currency.
|
|
600
|
+
- https://docs.stripe.com/payments/currencies/localize-prices/adaptive-pricing — Adaptive Pricing requires the price currency to be a settlement currency; webhook amounts stay in the integration currency.
|
|
601
|
+
- https://docs.stripe.com/api/checkout/sessions/create?query=adaptive_pricing — `adaptive_pricing.enabled` defaults to the Dashboard setting when omitted; send an explicit boolean for every session.
|
|
602
|
+
- https://docs.stripe.com/payments/klarna — Klarna eligibility depends on currency, customer location and purchase use case; verify the account's supported currencies before explicitly requesting it.
|
|
603
|
+
- https://docs.stripe.com/invoicing/multi-currency-customers — a customer's subscriptions share one currency; one-time payments may differ.
|
|
604
|
+
- https://docs.stripe.com/invoicing/integration/programmatic-credit-notes — preview a credit note on an invoice line; link an existing refund with `refund`; custom lines are not allowed with automatic tax.
|
|
605
|
+
- https://docs.stripe.com/tax/reports — a refund or a credit note lowers reported tax; only the credit note is the corrective document of an issued invoice.
|
|
606
|
+
- https://docs.stripe.com/api/refunds/create — `payment_intent`, `amount`, `reason: requested_by_customer`, `metadata`; an idempotency key replays the stored answer (failures too) for 24 h.
|
|
607
|
+
- https://docs.stripe.com/api/subscriptions/cancel and /update — `cancel(prorate, invoice_now, cancellation_details)`; `update(cancel_at_period_end | cancel_at, proration_behavior)`.
|
|
608
|
+
- https://docs.stripe.com/payments/checkout/receipts — one-time Checkout needs `invoice_creation.enabled` for a post-payment invoice; a Customer's `preferred_locales` localizes Stripe mails.
|
|
609
|
+
- https://docs.stripe.com/checkout/fulfillment — fulfillment must be idempotent and support delayed-payment success events.
|
|
610
|
+
- https://docs.stripe.com/api/webhook_endpoints/create — the signing `secret` is returned only by create; `api_version` is create-only.
|
|
611
|
+
- https://docs.stripe.com/api/customer_portal/configurations/create — `features.customer_update.allowed_updates`, subscription cancel/update features; configurations are never deletable.
|
|
612
|
+
- https://docs.stripe.com/api/tax/calculations/create — `percentage_decimal` is a STRING; parse it exactly.
|
|
613
|
+
- https://docs.stripe.com/api/fx_quotes/create — a PREVIEW endpoint (`stripe.rawRequest`); `reference_rate` for settlement conversion.
|
|
614
|
+
|
|
615
|
+
## Related
|
|
616
|
+
|
|
617
|
+
- [[entitlements]] — the model across packages
|
|
618
|
+
- [[payment]] — the contracts: grammars, views, consumer-rights calculators, copy, refusals, factories
|
|
619
|
+
- [[web-payment]] — hooks and pieces, the consumer-rights dialogs and functions
|
|
620
|
+
- [[mailer]] — the mail transport the consumer-rights mails go through
|
|
621
|
+
- [[mongo-resource]] — raw collection access, duplicate-key detection, field locking
|