@volter/twin-stripe 2.0.37 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -207
- package/dist/generated/pack-facts.json +94 -0
- package/dist/spec/SOURCE.md +14 -0
- package/dist/spec/fixtures3.json.gz +0 -0
- package/dist/spec/openapi.json.gz +0 -0
- package/dist/spec/patches.json +34 -0
- package/dist/spec/sources.json +378 -0
- package/dist/src/cli.js +12 -30
- package/dist/src/engine/after-payment.d.ts +9 -0
- package/dist/src/engine/after-payment.js +28 -0
- package/dist/src/engine/apps-secrets.d.ts +1 -0
- package/dist/src/engine/apps-secrets.js +3 -0
- package/dist/src/engine/balance.d.ts +5 -0
- package/dist/src/engine/balance.js +15 -0
- package/dist/src/engine/billing.d.ts +5 -0
- package/dist/src/engine/billing.js +6 -0
- package/dist/src/engine/charges.d.ts +6 -0
- package/dist/src/engine/charges.js +13 -0
- package/dist/src/engine/checkout.d.ts +24 -0
- package/dist/src/engine/checkout.js +30 -0
- package/dist/src/engine/common.d.ts +19 -0
- package/dist/src/engine/common.js +32 -0
- package/dist/src/{screens → engine}/connect-settings.d.ts +1 -7
- package/dist/src/engine/connect-settings.js +26 -0
- package/dist/src/engine/connect.d.ts +66 -0
- package/dist/src/engine/connect.js +189 -0
- package/dist/src/engine/coupons.d.ts +0 -0
- package/dist/src/engine/coupons.js +5 -0
- package/dist/src/engine/credit-notes.d.ts +1 -0
- package/dist/src/engine/credit-notes.js +5 -0
- package/dist/src/engine/customers.d.ts +12 -0
- package/dist/src/engine/customers.js +30 -0
- package/dist/src/engine/display.d.ts +14 -0
- package/dist/src/engine/display.js +16 -0
- package/dist/src/engine/disputes.d.ts +0 -0
- package/dist/src/engine/disputes.js +9 -0
- package/dist/src/engine/entitlements.d.ts +1 -0
- package/dist/src/engine/entitlements.js +4 -0
- package/dist/src/engine/ephemeral-keys.d.ts +0 -0
- package/dist/src/engine/ephemeral-keys.js +4 -0
- package/dist/src/engine/files.d.ts +18 -0
- package/dist/src/engine/files.js +33 -0
- package/dist/src/engine/front.d.ts +5 -0
- package/dist/src/engine/front.js +25 -0
- package/dist/src/engine/invoices.d.ts +24 -0
- package/dist/src/engine/invoices.js +57 -0
- package/dist/src/engine/issuing.d.ts +35 -0
- package/dist/src/engine/issuing.js +45 -0
- package/dist/src/engine/ledger.d.ts +9 -0
- package/dist/src/engine/ledger.js +9 -0
- package/dist/src/engine/oauth.d.ts +4 -0
- package/dist/src/engine/oauth.js +25 -0
- package/dist/src/engine/onboarding.d.ts +13 -0
- package/dist/src/engine/onboarding.js +42 -0
- package/dist/src/{stripe-params.d.ts → engine/params.d.ts} +20 -3
- package/dist/src/{stripe-params.js → engine/params.js} +7 -60
- package/dist/src/engine/payment-intents.d.ts +10 -0
- package/dist/src/engine/payment-intents.js +27 -0
- package/dist/src/engine/payment-links.d.ts +1 -0
- package/dist/src/engine/payment-links.js +5 -0
- package/dist/src/engine/payment-methods.d.ts +16 -0
- package/dist/src/engine/payment-methods.js +22 -0
- package/dist/src/engine/plans.d.ts +6 -0
- package/dist/src/engine/plans.js +26 -0
- package/dist/src/engine/platform.d.ts +39 -0
- package/dist/src/engine/platform.js +34 -0
- package/dist/src/engine/products.d.ts +0 -0
- package/dist/src/engine/products.js +4 -0
- package/dist/src/engine/radar.d.ts +2 -0
- package/dist/src/engine/radar.js +5 -0
- package/dist/src/engine/refunds.d.ts +1 -0
- package/dist/src/engine/refunds.js +28 -0
- package/dist/src/engine/renewals.d.ts +36 -0
- package/dist/src/engine/renewals.js +65 -0
- package/dist/src/engine/setup-intents.d.ts +1 -0
- package/dist/src/engine/setup-intents.js +4 -0
- package/dist/src/{stripe-twin.d.ts → engine/stripe.d.ts} +11 -39
- package/dist/src/{stripe-twin.js → engine/stripe.js} +25 -310
- package/dist/src/engine/subscription-schedules.d.ts +3 -0
- package/dist/src/engine/subscription-schedules.js +1 -0
- package/dist/src/engine/subscriptions.d.ts +12 -0
- package/dist/src/engine/subscriptions.js +31 -0
- package/dist/src/engine/tax.d.ts +7 -0
- package/dist/src/engine/tax.js +28 -0
- package/dist/src/engine/terminal.d.ts +4 -0
- package/dist/src/engine/terminal.js +20 -0
- package/dist/src/engine/test-clocks.d.ts +5 -0
- package/dist/src/engine/test-clocks.js +9 -0
- package/dist/src/engine/tokens.d.ts +7 -0
- package/dist/src/engine/tokens.js +20 -0
- package/dist/src/engine/transfers.d.ts +2 -0
- package/dist/src/engine/transfers.js +1 -0
- package/dist/src/engine/treasury.d.ts +23 -0
- package/dist/src/engine/treasury.js +45 -0
- package/dist/src/{stripe-version.js → engine/version.js} +4 -3
- package/dist/src/engine/webhook-endpoints.d.ts +1 -0
- package/dist/src/engine/webhook-endpoints.js +12 -0
- package/dist/src/engine/webhooks.d.ts +31 -0
- package/dist/src/engine/webhooks.js +68 -0
- package/dist/src/fetch.d.ts +2 -0
- package/dist/src/fetch.js +26 -0
- package/dist/src/generated/surface.gen.json +1 -1
- package/dist/src/generated/ui.gen.json +1 -1
- package/dist/src/index.d.ts +5 -14
- package/dist/src/index.js +8 -75
- package/dist/src/manifest.js +232 -758
- package/dist/src/screens/billing-portal.d.ts +3 -0
- package/dist/src/screens/{portal.js → billing-portal.js} +28 -54
- package/dist/src/screens/checkout.d.ts +2 -31
- package/dist/src/screens/checkout.js +32 -121
- package/dist/src/screens/connect-oauth.d.ts +3 -27
- package/dist/src/screens/connect-oauth.js +51 -132
- package/dist/src/screens/connect-onboarding.d.ts +3 -0
- package/dist/src/screens/{onboarding.js → connect-onboarding.js} +36 -102
- package/dist/src/screens/dashboard.d.ts +3 -0
- package/dist/src/screens/dashboard.js +117 -0
- package/dist/src/screens/shared.d.ts +27 -0
- package/dist/src/screens/shared.js +87 -0
- package/dist/src/screens/stripe-js.d.ts +3 -0
- package/dist/src/screens/stripe-js.js +135 -0
- package/dist/src/semantics/account_links.d.ts +2 -0
- package/dist/src/semantics/account_links.js +22 -0
- package/dist/src/semantics/accounts.d.ts +4 -0
- package/dist/src/semantics/accounts.js +38 -0
- package/dist/src/semantics/apps.d.ts +4 -0
- package/dist/src/semantics/{apps-secrets.js → apps.js} +14 -26
- package/dist/src/semantics/around.d.ts +2 -0
- package/dist/src/semantics/around.js +16 -0
- package/dist/src/semantics/balance.d.ts +2 -11
- package/dist/src/semantics/balance.js +3 -194
- package/dist/src/semantics/balance_transactions.d.ts +2 -0
- package/dist/src/semantics/balance_transactions.js +15 -0
- package/dist/src/semantics/billing_portal.d.ts +5 -0
- package/dist/src/semantics/billing_portal.js +42 -0
- package/dist/src/semantics/charges.d.ts +3 -28
- package/dist/src/semantics/charges.js +5 -202
- package/dist/src/semantics/checkout.d.ts +3 -20
- package/dist/src/semantics/checkout.js +8 -261
- package/dist/src/semantics/clock.d.ts +2 -0
- package/dist/src/semantics/clock.js +21 -0
- package/dist/src/semantics/coupons.d.ts +2 -6
- package/dist/src/semantics/coupons.js +2 -60
- package/dist/src/semantics/customers.d.ts +6 -6
- package/dist/src/semantics/customers.js +27 -387
- package/dist/src/semantics/disputes.d.ts +2 -2
- package/dist/src/semantics/disputes.js +3 -49
- package/dist/src/semantics/doors.d.ts +23 -0
- package/dist/src/semantics/doors.js +73 -0
- package/dist/src/semantics/entitlements.d.ts +2 -2
- package/dist/src/semantics/entitlements.js +5 -50
- package/dist/src/semantics/events.d.ts +12 -0
- package/dist/src/semantics/events.js +36 -0
- package/dist/src/semantics/index.d.ts +2 -0
- package/dist/src/semantics/index.js +107 -0
- package/dist/src/semantics/invoice_payments.d.ts +2 -0
- package/dist/src/semantics/invoice_payments.js +12 -0
- package/dist/src/semantics/invoiceitems.d.ts +3 -0
- package/dist/src/semantics/invoiceitems.js +59 -0
- package/dist/src/semantics/invoices.d.ts +16 -18
- package/dist/src/semantics/invoices.js +118 -444
- package/dist/src/semantics/payment_intents.d.ts +4 -0
- package/dist/src/semantics/payment_intents.js +79 -0
- package/dist/src/semantics/payment_methods.d.ts +5 -0
- package/dist/src/semantics/payment_methods.js +39 -0
- package/dist/src/semantics/payouts.d.ts +2 -0
- package/dist/src/semantics/payouts.js +21 -0
- package/dist/src/semantics/prices.d.ts +4 -0
- package/dist/src/semantics/prices.js +37 -0
- package/dist/src/semantics/products.d.ts +6 -2
- package/dist/src/semantics/products.js +15 -130
- package/dist/src/semantics/promotion_codes.d.ts +3 -0
- package/dist/src/semantics/promotion_codes.js +35 -0
- package/dist/src/semantics/radar.d.ts +2 -2
- package/dist/src/semantics/radar.js +5 -74
- package/dist/src/semantics/refunds.d.ts +3 -9
- package/dist/src/semantics/refunds.js +6 -192
- package/dist/src/semantics/setup_intents.d.ts +3 -0
- package/dist/src/semantics/{setup-intents.js → setup_intents.js} +10 -53
- package/dist/src/semantics/shared.d.ts +822 -47
- package/dist/src/semantics/shared.js +3044 -78
- package/dist/src/semantics/states.d.ts +531 -0
- package/dist/src/semantics/states.js +852 -0
- package/dist/src/semantics/subscription_items.d.ts +5 -0
- package/dist/src/semantics/subscription_items.js +55 -0
- package/dist/src/semantics/subscription_schedules.d.ts +4 -0
- package/dist/src/semantics/subscription_schedules.js +60 -0
- package/dist/src/semantics/subscriptions.d.ts +7 -11
- package/dist/src/semantics/subscriptions.js +44 -415
- package/dist/src/semantics/test_helpers.d.ts +6 -0
- package/dist/src/semantics/{test-clocks.js → test_helpers.js} +14 -40
- package/dist/src/semantics/transfers.d.ts +2 -2
- package/dist/src/semantics/transfers.js +6 -101
- package/dist/src/server.d.ts +7 -0
- package/dist/src/server.js +7 -0
- package/generated/pack-facts.json +94 -0
- package/package.json +10 -20
- package/spec/SOURCE.md +14 -0
- package/spec/fixtures3.json.gz +0 -0
- package/spec/openapi.json.gz +0 -0
- package/spec/patches.json +34 -0
- package/spec/sources.json +378 -0
- package/src/cli.ts +12 -26
- package/src/engine/after-payment.ts +31 -0
- package/src/engine/apps-secrets.ts +4 -0
- package/src/engine/balance.ts +33 -0
- package/src/engine/billing.ts +16 -0
- package/src/engine/charges.ts +19 -0
- package/src/engine/checkout.ts +40 -0
- package/src/engine/common.ts +47 -0
- package/src/engine/connect-settings.ts +23 -0
- package/src/engine/connect.ts +184 -0
- package/src/engine/coupons.ts +5 -0
- package/src/engine/credit-notes.ts +6 -0
- package/src/engine/customers.ts +35 -0
- package/src/engine/display.ts +19 -0
- package/src/engine/disputes.ts +9 -0
- package/src/engine/entitlements.ts +5 -0
- package/src/engine/ephemeral-keys.ts +3 -0
- package/src/engine/files.ts +41 -0
- package/src/engine/front.ts +31 -0
- package/src/engine/invoices.ts +71 -0
- package/src/engine/issuing.ts +61 -0
- package/src/engine/ledger.ts +50 -0
- package/src/engine/oauth.ts +19 -0
- package/src/engine/onboarding.ts +54 -0
- package/src/{stripe-params.ts → engine/params.ts} +8 -56
- package/src/engine/payment-intents.ts +33 -0
- package/src/engine/payment-links.ts +6 -0
- package/src/engine/payment-methods.ts +25 -0
- package/src/engine/plans.ts +42 -0
- package/src/engine/platform.ts +47 -0
- package/src/engine/products.ts +3 -0
- package/src/engine/radar.ts +6 -0
- package/src/engine/refunds.ts +29 -0
- package/src/engine/renewals.ts +73 -0
- package/src/engine/setup-intents.ts +5 -0
- package/src/{stripe-twin.ts → engine/stripe.ts} +28 -323
- package/src/engine/subscription-schedules.ts +10 -0
- package/src/engine/subscriptions.ts +40 -0
- package/src/engine/tax.ts +39 -0
- package/src/engine/terminal.ts +23 -0
- package/src/engine/test-clocks.ts +11 -0
- package/src/engine/tokens.ts +22 -0
- package/src/engine/transfers.ts +23 -0
- package/src/engine/treasury.ts +47 -0
- package/src/{stripe-version.ts → engine/version.ts} +4 -3
- package/src/engine/webhook-endpoints.ts +14 -0
- package/src/engine/webhooks.ts +72 -0
- package/src/fetch.ts +27 -0
- package/src/generated/surface.gen.json +1 -1
- package/src/generated/ui.gen.json +1 -1
- package/src/index.ts +8 -116
- package/src/manifest.ts +234 -786
- package/src/screens/{portal.tsx → billing-portal.tsx} +32 -37
- package/src/screens/checkout.tsx +33 -95
- package/src/screens/connect-oauth.tsx +64 -88
- package/src/screens/{onboarding.tsx → connect-onboarding.tsx} +38 -82
- package/src/screens/dashboard.tsx +198 -0
- package/src/screens/shared.tsx +84 -0
- package/src/screens/stripe-js.ts +170 -0
- package/src/semantics/account_links.ts +21 -0
- package/src/semantics/accounts.ts +43 -0
- package/src/semantics/{apps-secrets.ts → apps.ts} +17 -32
- package/src/semantics/around.ts +20 -0
- package/src/semantics/balance.ts +6 -207
- package/src/semantics/balance_transactions.ts +20 -0
- package/src/semantics/billing_portal.ts +51 -0
- package/src/semantics/charges.ts +12 -211
- package/src/semantics/checkout.ts +13 -268
- package/src/semantics/clock.ts +26 -0
- package/src/semantics/coupons.ts +5 -67
- package/src/semantics/customers.ts +29 -392
- package/src/semantics/disputes.ts +9 -59
- package/src/semantics/doors.ts +80 -0
- package/src/semantics/entitlements.ts +9 -56
- package/src/semantics/events.ts +44 -0
- package/src/semantics/index.ts +111 -0
- package/src/semantics/invoice_payments.ts +18 -0
- package/src/semantics/invoiceitems.ts +59 -0
- package/src/semantics/invoices.ts +109 -443
- package/src/semantics/payment_intents.ts +83 -0
- package/src/semantics/payment_methods.ts +43 -0
- package/src/semantics/payouts.ts +23 -0
- package/src/semantics/prices.ts +42 -0
- package/src/semantics/products.ts +18 -142
- package/src/semantics/promotion_codes.ts +39 -0
- package/src/semantics/radar.ts +8 -77
- package/src/semantics/refunds.ts +9 -212
- package/src/semantics/setup_intents.ts +45 -0
- package/src/semantics/shared.ts +3092 -106
- package/src/semantics/states.ts +883 -0
- package/src/semantics/subscription_items.ts +54 -0
- package/src/semantics/subscription_schedules.ts +66 -0
- package/src/semantics/subscriptions.ts +49 -432
- package/src/semantics/{test-clocks.ts → test_helpers.ts} +23 -47
- package/src/semantics/transfers.ts +11 -131
- package/src/server.ts +8 -0
- package/client/dashboard-api.ts +0 -286
- package/client/stripe-mirror.css +0 -277
- package/client/stripe-mirror.tsx +0 -1559
- package/dist/client/dashboard-api.d.ts +0 -107
- package/dist/client/dashboard-api.js +0 -238
- package/dist/client/dashboard-api.ts +0 -286
- package/dist/client/stripe-mirror.bundle.js +0 -236
- package/dist/client/stripe-mirror.css +0 -277
- package/dist/client/stripe-mirror.d.ts +0 -134
- package/dist/client/stripe-mirror.js +0 -859
- package/dist/client/stripe-mirror.tsx +0 -1559
- package/dist/src/screens/connect-settings.js +0 -103
- package/dist/src/screens/consent-skin.d.ts +0 -4
- package/dist/src/screens/consent-skin.js +0 -18
- package/dist/src/screens/financial-connections.d.ts +0 -5
- package/dist/src/screens/financial-connections.js +0 -90
- package/dist/src/screens/identity.d.ts +0 -5
- package/dist/src/screens/identity.js +0 -86
- package/dist/src/screens/onboarding.d.ts +0 -13
- package/dist/src/screens/portal.d.ts +0 -5
- package/dist/src/screens/public-details.d.ts +0 -5
- package/dist/src/screens/public-details.js +0 -90
- package/dist/src/semantics/after-payment.d.ts +0 -22
- package/dist/src/semantics/after-payment.js +0 -99
- package/dist/src/semantics/apps-secrets.d.ts +0 -2
- package/dist/src/semantics/billing.d.ts +0 -2
- package/dist/src/semantics/billing.js +0 -220
- package/dist/src/semantics/connect.d.ts +0 -5
- package/dist/src/semantics/connect.js +0 -493
- package/dist/src/semantics/credit-notes.d.ts +0 -2
- package/dist/src/semantics/credit-notes.js +0 -172
- package/dist/src/semantics/ephemeral-keys.d.ts +0 -2
- package/dist/src/semantics/ephemeral-keys.js +0 -34
- package/dist/src/semantics/files.d.ts +0 -2
- package/dist/src/semantics/files.js +0 -125
- package/dist/src/semantics/issuing.d.ts +0 -13
- package/dist/src/semantics/issuing.js +0 -575
- package/dist/src/semantics/ledger.d.ts +0 -59
- package/dist/src/semantics/ledger.js +0 -200
- package/dist/src/semantics/payment-intents.d.ts +0 -18
- package/dist/src/semantics/payment-intents.js +0 -452
- package/dist/src/semantics/payment-links.d.ts +0 -2
- package/dist/src/semantics/payment-links.js +0 -133
- package/dist/src/semantics/payment-methods.d.ts +0 -20
- package/dist/src/semantics/payment-methods.js +0 -140
- package/dist/src/semantics/plans.d.ts +0 -5
- package/dist/src/semantics/plans.js +0 -121
- package/dist/src/semantics/platform.d.ts +0 -9
- package/dist/src/semantics/platform.js +0 -206
- package/dist/src/semantics/renewals.d.ts +0 -47
- package/dist/src/semantics/renewals.js +0 -281
- package/dist/src/semantics/setup-intents.d.ts +0 -2
- package/dist/src/semantics/subscription-schedules.d.ts +0 -2
- package/dist/src/semantics/subscription-schedules.js +0 -119
- package/dist/src/semantics/tax.d.ts +0 -2
- package/dist/src/semantics/tax.js +0 -197
- package/dist/src/semantics/terminal.d.ts +0 -5
- package/dist/src/semantics/terminal.js +0 -182
- package/dist/src/semantics/test-clocks.d.ts +0 -6
- package/dist/src/semantics/tokens.d.ts +0 -4
- package/dist/src/semantics/tokens.js +0 -44
- package/dist/src/semantics/treasury.d.ts +0 -2
- package/dist/src/semantics/treasury.js +0 -377
- package/dist/src/semantics/webhook-endpoints.d.ts +0 -3
- package/dist/src/semantics/webhook-endpoints.js +0 -85
- package/dist/src/stripe-budget.d.ts +0 -55
- package/dist/src/stripe-budget.js +0 -155
- package/dist/src/stripe-capabilities.d.ts +0 -3
- package/dist/src/stripe-capabilities.js +0 -5836
- package/dist/src/stripe-conformance.d.ts +0 -43
- package/dist/src/stripe-conformance.js +0 -105
- package/dist/src/stripe-connector.d.ts +0 -161
- package/dist/src/stripe-connector.js +0 -414
- package/dist/src/stripe-emit.d.ts +0 -2
- package/dist/src/stripe-emit.js +0 -145
- package/dist/src/stripe-events.d.ts +0 -93
- package/dist/src/stripe-events.js +0 -392
- package/dist/src/stripe-js.d.ts +0 -4
- package/dist/src/stripe-js.js +0 -70
- package/dist/src/stripe-mirror-ui.d.ts +0 -15
- package/dist/src/stripe-mirror-ui.js +0 -87
- package/dist/src/stripe-perform-harness.d.ts +0 -9
- package/dist/src/stripe-perform-harness.js +0 -26
- package/dist/src/stripe-server.d.ts +0 -33
- package/dist/src/stripe-server.js +0 -429
- package/dist/src/stripe-shared.d.ts +0 -109
- package/dist/src/stripe-shared.js +0 -276
- package/dist/src/stripe-ui-conformance.d.ts +0 -5
- package/dist/src/stripe-ui-conformance.js +0 -79
- package/dist/src/stripe-ui-structure.d.ts +0 -3
- package/dist/src/stripe-ui-structure.js +0 -168
- package/dist/test-fixtures/stripe-known-deviations.json +0 -110
- package/dist/test-fixtures/stripe-openapi-operations.SOURCE.md +0 -14
- package/dist/test-fixtures/stripe-openapi-operations.json +0 -4717
- package/dist/test-fixtures/stripe-schemas.SOURCE.md +0 -35
- package/dist/test-fixtures/stripe-schemas.json +0 -3813
- package/src/screens/connect-settings.tsx +0 -121
- package/src/screens/consent-skin.ts +0 -20
- package/src/screens/financial-connections.tsx +0 -101
- package/src/screens/identity.tsx +0 -96
- package/src/screens/public-details.tsx +0 -105
- package/src/semantics/after-payment.ts +0 -118
- package/src/semantics/billing.ts +0 -216
- package/src/semantics/connect.ts +0 -487
- package/src/semantics/credit-notes.ts +0 -168
- package/src/semantics/ephemeral-keys.ts +0 -34
- package/src/semantics/files.ts +0 -143
- package/src/semantics/issuing.ts +0 -590
- package/src/semantics/ledger.ts +0 -253
- package/src/semantics/payment-intents.ts +0 -463
- package/src/semantics/payment-links.ts +0 -148
- package/src/semantics/payment-methods.ts +0 -145
- package/src/semantics/plans.ts +0 -131
- package/src/semantics/platform.ts +0 -220
- package/src/semantics/renewals.ts +0 -279
- package/src/semantics/setup-intents.ts +0 -87
- package/src/semantics/subscription-schedules.ts +0 -129
- package/src/semantics/tax.ts +0 -220
- package/src/semantics/terminal.ts +0 -195
- package/src/semantics/tokens.ts +0 -52
- package/src/semantics/treasury.ts +0 -383
- package/src/semantics/webhook-endpoints.ts +0 -87
- package/src/stripe-budget.ts +0 -181
- package/src/stripe-capabilities.ts +0 -5651
- package/src/stripe-conformance.ts +0 -124
- package/src/stripe-connector.ts +0 -462
- package/src/stripe-emit.ts +0 -157
- package/src/stripe-events.ts +0 -434
- package/src/stripe-js.ts +0 -70
- package/src/stripe-mirror-ui.ts +0 -90
- package/src/stripe-perform-harness.ts +0 -29
- package/src/stripe-server.ts +0 -393
- package/src/stripe-shared.ts +0 -297
- package/src/stripe-ui-conformance.ts +0 -88
- package/src/stripe-ui-structure.ts +0 -182
- package/test-fixtures/stripe-known-deviations.json +0 -110
- package/test-fixtures/stripe-openapi-operations.json +0 -4717
- package/test-fixtures/stripe-schemas.json +0 -3813
- /package/dist/src/{screens → engine}/industries.d.ts +0 -0
- /package/dist/src/{screens → engine}/industries.js +0 -0
- /package/dist/src/{semantics → engine}/test-cards.d.ts +0 -0
- /package/dist/src/{semantics → engine}/test-cards.js +0 -0
- /package/dist/src/{stripe-version.d.ts → engine/version.d.ts} +0 -0
- /package/src/{screens → engine}/industries.ts +0 -0
- /package/src/{semantics → engine}/test-cards.ts +0 -0
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
// A subscription's invoices over time: the first one Checkout pays, and each renewal. Time's moves are made
|
|
2
|
-
// as the twin's trial end already made them: caught up to the customer's clock (the World's, or its test
|
|
3
|
-
// clock) whenever the twin is asked anything (../stripe-server.ts), each checked against the machines in
|
|
4
|
-
// ../manifest.ts.
|
|
5
|
-
//
|
|
6
|
-
// - Checkout's subscription session pays the first invoice with the card the customer entered, which becomes the
|
|
7
|
-
// subscription's default payment method (docs.stripe.com/payments/checkout/how-checkout-works); nothing is due
|
|
8
|
-
// on a trial's.
|
|
9
|
-
// - At each period's end Stripe drafts a `subscription_cycle` invoice for the next period and advances the
|
|
10
|
-
// period; a trial's end is the first such renewal, and the subscription is active from then
|
|
11
|
-
// (docs.stripe.com/billing/subscriptions/overview#how-payments-work-subscriptions,
|
|
12
|
-
// docs.stripe.com/billing/subscriptions/trials).
|
|
13
|
-
// - About an hour after, Stripe finalizes the draft and charges the subscription's default payment method,
|
|
14
|
-
// else the customer's (docs.stripe.com/invoicing/integration/workflow-transitions#finalized,
|
|
15
|
-
// docs.stripe.com/api/subscriptions/object#subscription_object-default_payment_method).
|
|
16
|
-
// - A declined renewal leaves the invoice open with its attempt counted and the subscription past_due
|
|
17
|
-
// (docs.stripe.com/billing/subscriptions/overview#payment-status); paying the invoice later, through the API
|
|
18
|
-
// (docs.stripe.com/api/invoices/pay) or on the customer portal (docs.stripe.com/customer-management), makes it
|
|
19
|
-
// active again.
|
|
20
|
-
//
|
|
21
|
-
// Where the documentation stops and the twin decides: Stripe retries a failed renewal on its Smart Retries
|
|
22
|
-
// schedule and may then cancel or mark the subscription unpaid; the twin makes no retry (next_payment_attempt
|
|
23
|
-
// is null) and leaves it past_due until the invoice is paid. A paused subscription (pause_collection) is not renewed.
|
|
24
|
-
// - One set to cancel at its period's end is canceled when the period ends, with no renewal
|
|
25
|
-
// (docs.stripe.com/billing/subscriptions/cancel#cancel-at-end-of-cycle).
|
|
26
|
-
import { runAsVendorMove, type SemanticsContext } from '@volter/world-core';
|
|
27
|
-
import { addBillingInterval, applyCouponDiscount, declineFor, mintClientSecret, priceAmount, resolveSubscriptionBillingInterval } from '../stripe-twin.ts';
|
|
28
|
-
import { finalizedFields } from './invoices.ts';
|
|
29
|
-
import { mintCharge } from './payment-intents.ts';
|
|
30
|
-
import { at_, created, finder, type Row } from './shared.ts';
|
|
31
|
-
|
|
32
|
-
const SUB = 'subscription';
|
|
33
|
-
const INV = 'invoice';
|
|
34
|
-
const HOUR = 3600;
|
|
35
|
-
/** The statuses a subscription renews in. */
|
|
36
|
-
const RENEWING = ['trialing', 'active', 'past_due'];
|
|
37
|
-
|
|
38
|
-
/** A customer's now: its test clock's frozen time when it is on one (docs.stripe.com/billing/testing/test-clocks), the
|
|
39
|
-
* World's otherwise. */
|
|
40
|
-
export function clockNow(ctx: SemanticsContext, customer: unknown): number {
|
|
41
|
-
const clock = typeof customer === 'string' ? ctx.get('customer', customer)?.test_clock : undefined;
|
|
42
|
-
const frozen = typeof clock === 'string' ? ctx.get('test_helpers.test_clock', clock)?.frozen_time : undefined;
|
|
43
|
-
return frozen !== undefined ? Number(frozen) : Number(ctx.now());
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** What a subscription's invoices charge: its own default payment method, else its customer's. */
|
|
47
|
-
export function payerOf(ctx: SemanticsContext, sub: Row): string | undefined {
|
|
48
|
-
return typeof sub.default_payment_method === 'string' && sub.default_payment_method ? sub.default_payment_method : customerDefault(ctx, sub);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** The customer's default for invoices, charged when the subscription names none of its own. */
|
|
52
|
-
function customerDefault(ctx: SemanticsContext, sub: Row): string | undefined {
|
|
53
|
-
const customer = typeof sub.customer === 'string' ? ctx.get('customer', sub.customer) : undefined;
|
|
54
|
-
const settings = customer?.invoice_settings as Row | undefined;
|
|
55
|
-
return typeof settings?.default_payment_method === 'string' && settings.default_payment_method ? settings.default_payment_method : undefined;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const itemsOf = (sub: Row): Row[] => ((sub.items as { data?: Row[] } | undefined)?.data) ?? [];
|
|
59
|
-
|
|
60
|
-
/** An item's quantity as it bills: 1 when none is set, and 0 when 0 is (a quantity of 0 bills nothing). */
|
|
61
|
-
export function itemQuantity(it: Row): number {
|
|
62
|
-
return it.quantity === undefined || it.quantity === null ? 1 : Math.max(0, Math.trunc(Number(it.quantity) || 0));
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** The subscription's discounts with a `once` coupon's spent: "When a subscription uses a coupon with `duration=once`, the
|
|
66
|
-
* coupon is considered used after the invoice finalizes and is removed from the subscription's `discounts` array"
|
|
67
|
-
* (docs.stripe.com/billing/subscriptions/coupons#coupon-duration). */
|
|
68
|
-
export function unspentDiscounts(discounts: unknown): Row[] {
|
|
69
|
-
return ((discounts as Row[] | undefined) ?? []).filter((d) => !(d && typeof d === 'object' && (d.coupon as Row | undefined)?.duration === 'once'));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** The coupon ids an invoice's discounts name. */
|
|
73
|
-
const couponsOf = (discounts: unknown): Set<string> => new Set(((discounts as Row[] | undefined) ?? []).map((d) => {
|
|
74
|
-
const c = d && typeof d === 'object' ? d.coupon : undefined;
|
|
75
|
-
return typeof c === 'string' ? c : c && typeof c === 'object' ? String((c as Row).id ?? '') : '';
|
|
76
|
-
}).filter(Boolean));
|
|
77
|
-
|
|
78
|
-
/** A subscription whose invoice has just been finalized: the `once` coupon that invoice took is spent, and a once coupon
|
|
79
|
-
* it did not take (added after it was drafted) stays for the next (unspentDiscounts). */
|
|
80
|
-
export async function spendOnceCoupon(ctx: SemanticsContext, invoice: Row): Promise<void> {
|
|
81
|
-
const subId = typeof invoice.subscription === 'string' ? invoice.subscription : undefined;
|
|
82
|
-
const sub = subId ? ctx.get(SUB, subId) : undefined;
|
|
83
|
-
if (!sub || !subId) return;
|
|
84
|
-
const all = (sub.discounts as Row[] | undefined) ?? [];
|
|
85
|
-
const kept = keptAfter(all, invoice);
|
|
86
|
-
if (kept.length !== all.length) await ctx.write(SUB, subId, { discounts: kept }, 'subscription.discount_spent');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** A subscription's discounts after `invoice` finalizes: less the `once` coupons it took. */
|
|
90
|
-
export function keptAfter(discounts: Row[], invoice: Row): Row[] {
|
|
91
|
-
const taken = couponsOf(invoice.discounts);
|
|
92
|
-
return discounts.filter((d) => !(unspentDiscounts([d]).length === 0 && [...couponsOf([d])].some((c) => taken.has(c))));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** A draft invoice for one period of a subscription, its lines the subscription's items and any one-time lines `once`
|
|
96
|
-
* gives it (a Checkout session's one-time prices, "on the initial invoice only", docs.stripe.com/api/checkout/sessions/
|
|
97
|
-
* create#create_checkout_session-line_items), under the subscription's discount. Answers the invoice id. */
|
|
98
|
-
export async function draftSubscriptionInvoice(ctx: SemanticsContext, sub: Row, o: { start: number; end: number; reason: 'subscription_create' | 'subscription_cycle' | 'subscription_update'; trial?: boolean; once?: Row[]; id?: string }): Promise<string> {
|
|
99
|
-
const id = o.id ?? ctx.mint(INV);
|
|
100
|
-
const currency = String(sub.currency ?? 'usd');
|
|
101
|
-
const lines = itemsOf(sub).map((it, i) => {
|
|
102
|
-
const price = it.price && typeof it.price === 'object' ? (it.price as Row) : ctx.get('price', String(it.price ?? ''));
|
|
103
|
-
const quantity = itemQuantity(it);
|
|
104
|
-
return {
|
|
105
|
-
id: `il_twin_${id}_${i + 1}`, object: 'line_item', type: 'subscription',
|
|
106
|
-
// a trial's first invoice bills nothing for the trial period (docs.stripe.com/billing/subscriptions/trials)
|
|
107
|
-
amount: o.trial ? 0 : priceAmount(price, quantity), currency, quantity, proration: false,
|
|
108
|
-
price: price ?? null, subscription: sub.id, subscription_item: it.id ?? null, invoice_item: null,
|
|
109
|
-
period: { start: o.start, end: o.end }, description: o.trial ? `Trial period for ${String(ctx.get('product', String(price?.product ?? ''))?.name ?? 'the plan')}` : null,
|
|
110
|
-
};
|
|
111
|
-
});
|
|
112
|
-
if (o.once?.length) lines.push(...oneTimeLines(id, sub, o.once, lines.length, o.start, currency));
|
|
113
|
-
const subtotal = lines.reduce((s, l) => s + l.amount, 0);
|
|
114
|
-
// a discount the subscription still carries applies; a `once` coupon is removed once an invoice finalizes
|
|
115
|
-
// (spendOnceCoupon), so it discounts one invoice (docs.stripe.com/billing/subscriptions/coupons#coupon-duration)
|
|
116
|
-
const discount = ((sub.discounts as Row[] | undefined) ?? [])[0];
|
|
117
|
-
const coupon = discount && typeof discount === 'object' ? (discount.coupon as Row | undefined) : undefined;
|
|
118
|
-
const off = coupon ? applyCouponDiscount(subtotal, coupon) : 0;
|
|
119
|
-
const total = Math.max(0, subtotal - off);
|
|
120
|
-
await created(ctx, INV, { id, customer: sub.customer, subscription: sub.id }, {
|
|
121
|
-
created: o.start, status: 'draft', livemode: false, currency, collection_method: 'charge_automatically',
|
|
122
|
-
billing_reason: o.reason, auto_advance: true, attempt_count: 0, attempted: false, next_payment_attempt: null,
|
|
123
|
-
amount_due: total, amount_paid: 0, amount_remaining: total, amount_overpaid: 0, amount_paid_off_stripe: 0, amount_shipping: 0,
|
|
124
|
-
subtotal, total, starting_balance: 0, post_payment_credit_notes_amount: 0, pre_payment_credit_notes_amount: 0,
|
|
125
|
-
// "The invoice that consumed the coupon still shows the applied discount" (docs.stripe.com/billing/subscriptions/coupons)
|
|
126
|
-
period_start: o.start, period_end: o.end, default_tax_rates: [], discounts: coupon && discount ? [discount] : [],
|
|
127
|
-
lines: { object: 'list', data: lines, has_more: false, total_count: lines.length, url: `/v1/invoices/${id}/lines` },
|
|
128
|
-
automatic_tax: { enabled: false, liability: null, status: null },
|
|
129
|
-
status_transitions: { finalized_at: null, marked_uncollectible_at: null, paid_at: null, voided_at: null },
|
|
130
|
-
issuer: { type: 'self' }, payment_settings: { default_mandate: null, payment_method_options: null, payment_method_types: null },
|
|
131
|
-
payment_intent: null, charge: null,
|
|
132
|
-
});
|
|
133
|
-
return id;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/** A Checkout session's one-time line items as lines of its subscription's first invoice, numbered after its `after` lines. */
|
|
137
|
-
function oneTimeLines(invoiceId: string, sub: Row, items: Row[], after: number, at: number, currency: string) {
|
|
138
|
-
return items.map((it, i) => {
|
|
139
|
-
const price = it.price && typeof it.price === 'object' ? (it.price as Row) : null;
|
|
140
|
-
const quantity = itemQuantity(it);
|
|
141
|
-
return {
|
|
142
|
-
id: `il_twin_${invoiceId}_${after + i + 1}`, object: 'line_item', type: 'subscription',
|
|
143
|
-
amount: priceAmount(price ?? undefined, quantity), currency, quantity, proration: false,
|
|
144
|
-
price, subscription: sub.id, subscription_item: null, invoice_item: null,
|
|
145
|
-
period: { start: at, end: at }, description: typeof it.description === 'string' ? it.description : null,
|
|
146
|
-
};
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** Finalize a subscription's draft invoice at `at` and charge what its subscription pays with. The move is time's (a
|
|
151
|
-
* renewal), the customer's (the Checkout page they paid on) or the API's (an update that ends a trial, which names the
|
|
152
|
-
* payer, may ask for no attempt, and moves the subscription itself: semantics/subscriptions.ts applyTrialEnd). Answers
|
|
153
|
-
* whether it was paid. */
|
|
154
|
-
export async function collectSubscriptionInvoice(ctx: SemanticsContext, invoiceId: string, at: number, actor: 'time' | 'external' | 'api', opts: { payer?: string; attempt?: boolean } = {}): Promise<boolean> {
|
|
155
|
-
const inv = ctx.get(INV, invoiceId);
|
|
156
|
-
const sub = inv && typeof inv.subscription === 'string' ? ctx.get(SUB, inv.subscription) : undefined;
|
|
157
|
-
if (!inv || !sub || inv.status !== 'draft') return false;
|
|
158
|
-
const total = Number(inv.total) || 0;
|
|
159
|
-
const numbered = { ...finalizedFields(ctx, inv, at), status_transitions: { ...(inv.status_transitions as Row), finalized_at: at } };
|
|
160
|
-
const op = ctx.call.operation.id;
|
|
161
|
-
// nothing due (a trial's first invoice) is paid as it is finalized; finalizing spends a `once` coupon
|
|
162
|
-
if (total <= 0) {
|
|
163
|
-
if (ctx.legal(INV, 'status', op, 'draft', 'paid', invoiceId, actor)) return false;
|
|
164
|
-
await spendOnceCoupon(ctx, inv);
|
|
165
|
-
await ctx.write(INV, invoiceId, { ...numbered, status: 'paid', paid: true, auto_advance: false, status_transitions: { ...numbered.status_transitions, paid_at: at } }, 'invoice.paid');
|
|
166
|
-
return true;
|
|
167
|
-
}
|
|
168
|
-
const payer = actor === 'api' ? opts.payer : payerOf(ctx, sub);
|
|
169
|
-
// default_incomplete finalizes the invoice without attempting payment (docs.stripe.com/api/subscriptions/update#update_subscription-payment_behavior)
|
|
170
|
-
const attempt = opts.attempt !== false;
|
|
171
|
-
const decline = payer && attempt ? declineFor(finder(ctx), { payment_method: payer }) : undefined;
|
|
172
|
-
const pays = attempt && !!payer && !decline;
|
|
173
|
-
if (ctx.legal(INV, 'status', op, 'draft', pays ? 'paid' : 'open', invoiceId, actor)) return false;
|
|
174
|
-
await spendOnceCoupon(ctx, inv);
|
|
175
|
-
const piId = ctx.mint('payment_intent');
|
|
176
|
-
const currency = String(inv.currency ?? 'usd');
|
|
177
|
-
const chargeId = pays ? await mintCharge(ctx, piId, { currency, customer: inv.customer, payment_method: payer, invoice: invoiceId }, total) : null;
|
|
178
|
-
await created(ctx, 'payment_intent', { id: piId, amount: total, currency, customer: inv.customer, invoice: invoiceId, payment_method: pays ? payer : null }, {
|
|
179
|
-
status: pays ? 'succeeded' : 'requires_payment_method', client_secret: mintClientSecret(piId), livemode: false,
|
|
180
|
-
capture_method: 'automatic', amount_capturable: 0, amount_received: pays ? total : 0, next_action: null, latest_charge: chargeId,
|
|
181
|
-
last_payment_error: decline ? { type: 'card_error', code: decline.code, decline_code: decline.decline_code ?? null, message: decline.message, payment_method: { id: payer } } : null,
|
|
182
|
-
automatic_payment_methods: null, payment_method_types: ['card'], payment_method_options: {},
|
|
183
|
-
});
|
|
184
|
-
await ctx.write(INV, invoiceId, {
|
|
185
|
-
...numbered, status: pays ? 'paid' : 'open', paid: pays, attempt_count: attempt ? 1 : 0, attempted: attempt, payment_intent: piId, charge: chargeId,
|
|
186
|
-
amount_paid: pays ? total : 0, amount_remaining: pays ? 0 : total, auto_advance: attempt && !pays, next_payment_attempt: null,
|
|
187
|
-
status_transitions: { ...numbered.status_transitions, paid_at: pays ? at : null },
|
|
188
|
-
}, pays ? 'invoice.paid' : attempt ? 'invoice.payment_failed' : 'invoice.finalized');
|
|
189
|
-
if (actor !== 'api' && !pays && sub.status !== 'past_due' && !ctx.legal(SUB, 'status', op, sub.status, 'past_due', String(sub.id), 'time')) {
|
|
190
|
-
await ctx.write(SUB, String(sub.id), { status: 'past_due' }, 'customer.subscription.updated');
|
|
191
|
-
}
|
|
192
|
-
return pays;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/** Pay a subscription's open invoice with `paymentMethod` (the customer on the portal, `external`): the charge on the
|
|
196
|
-
* invoice's PaymentIntent, the invoice paid, a past_due subscription active. Answers the decline, if the card is refused. */
|
|
197
|
-
export async function payOpenInvoice(ctx: SemanticsContext, invoiceId: string, paymentMethod: string): Promise<{ code: string; message: string } | undefined> {
|
|
198
|
-
const inv = ctx.get(INV, invoiceId);
|
|
199
|
-
if (!inv || inv.status !== 'open') return { code: 'invoice_not_open', message: 'This invoice is no longer open.' };
|
|
200
|
-
const decline = declineFor(finder(ctx), { payment_method: paymentMethod });
|
|
201
|
-
if (decline) return decline;
|
|
202
|
-
const op = ctx.call.operation.id;
|
|
203
|
-
const refused = ctx.legal(INV, 'status', op, 'open', 'paid', invoiceId, 'external');
|
|
204
|
-
if (refused) return { code: refused.code ?? 'invoice_not_open', message: refused.message };
|
|
205
|
-
const total = Number(inv.amount_remaining ?? inv.total) || 0;
|
|
206
|
-
const now = Number(ctx.now());
|
|
207
|
-
const piId = typeof inv.payment_intent === 'string' ? inv.payment_intent : undefined;
|
|
208
|
-
let chargeId: string | null = null;
|
|
209
|
-
const pi = piId ? ctx.get('payment_intent', piId) : undefined;
|
|
210
|
-
if (piId && pi && !ctx.legal('payment_intent', 'status', op, pi.status, 'succeeded', piId, 'external')) {
|
|
211
|
-
chargeId = await mintCharge(ctx, piId, { ...pi, payment_method: paymentMethod, invoice: invoiceId }, total);
|
|
212
|
-
await ctx.write('payment_intent', piId, { status: 'succeeded', payment_method: paymentMethod, amount_received: total, last_payment_error: null, latest_charge: chargeId }, 'payment_intent.succeeded');
|
|
213
|
-
}
|
|
214
|
-
await ctx.write(INV, invoiceId, {
|
|
215
|
-
status: 'paid', paid: true, amount_paid: total, amount_remaining: 0, charge: chargeId, auto_advance: false,
|
|
216
|
-
attempt_count: (Number(inv.attempt_count) || 0) + 1, attempted: true,
|
|
217
|
-
status_transitions: { ...(inv.status_transitions as Row), paid_at: now },
|
|
218
|
-
}, 'invoice.paid');
|
|
219
|
-
const sub = typeof inv.subscription === 'string' ? ctx.get(SUB, inv.subscription) : undefined;
|
|
220
|
-
if (sub && sub.status === 'past_due' && !ctx.legal(SUB, 'status', op, 'past_due', 'active', String(sub.id), 'external')) {
|
|
221
|
-
await ctx.write(SUB, String(sub.id), { status: 'active' }, 'customer.subscription.updated');
|
|
222
|
-
}
|
|
223
|
-
return undefined;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/** Time's moves on subscriptions, caught up to each customer's clock in the order they fell due, each made at the
|
|
227
|
-
* moment it was due: a period that ended renews (a trial's end among them) or, set to cancel, cancels; a renewal
|
|
228
|
-
* drafted an hour or more ago is finalized and charged. */
|
|
229
|
-
export async function advanceBilling(ctx: SemanticsContext, only?: (sub: Row) => boolean): Promise<void> {
|
|
230
|
-
// the billing clock is the vendor's own move, wherever a handler reaches it: never the request's caller's
|
|
231
|
-
return runAsVendorMove(() => advanceBillingDue(ctx, only));
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async function advanceBillingDue(ctx: SemanticsContext, only?: (sub: Row) => boolean): Promise<void> {
|
|
235
|
-
const at = at_(ctx);
|
|
236
|
-
for (let guard = 0; guard < 10_000 && (await billingStep(ctx, at, only)); guard++);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/** The earliest move that has come due, made at its moment; false when none has. */
|
|
240
|
-
async function billingStep(ctx: SemanticsContext, at: (t: number) => Promise<SemanticsContext>, only?: (sub: Row) => boolean): Promise<boolean> {
|
|
241
|
-
// the earliest move that has come due, of any subscription
|
|
242
|
-
let next: { t: number; kind: 'period' | 'collect'; id: string } | undefined;
|
|
243
|
-
for (const sub of ctx.rows(SUB)) {
|
|
244
|
-
if (only && !only(sub)) continue;
|
|
245
|
-
if (!RENEWING.includes(String(sub.status)) || typeof sub.current_period_end !== 'number' || sub.pause_collection) continue;
|
|
246
|
-
if (sub.current_period_end <= clockNow(ctx, sub.customer) && (!next || sub.current_period_end < next.t)) next = { t: sub.current_period_end, kind: 'period', id: String(sub.id) };
|
|
247
|
-
}
|
|
248
|
-
for (const inv of ctx.rows(INV)) {
|
|
249
|
-
if (inv.status !== 'draft' || inv.billing_reason !== 'subscription_cycle' || inv.auto_advance !== true) continue;
|
|
250
|
-
const sub = typeof inv.subscription === 'string' ? ctx.get(SUB, inv.subscription) : undefined;
|
|
251
|
-
if (!sub || (only && !only(sub))) continue;
|
|
252
|
-
const t = Number(inv.created) + HOUR;
|
|
253
|
-
if (t <= clockNow(ctx, sub.customer) && (!next || t < next.t)) next = { t, kind: 'collect', id: String(inv.id) };
|
|
254
|
-
}
|
|
255
|
-
if (!next) return false;
|
|
256
|
-
const c = await at(next.t);
|
|
257
|
-
if (next.kind === 'collect') {
|
|
258
|
-
await collectSubscriptionInvoice(c, next.id, next.t, 'time');
|
|
259
|
-
return true;
|
|
260
|
-
}
|
|
261
|
-
const id = next.id;
|
|
262
|
-
const sub = c.get(SUB, id)!;
|
|
263
|
-
const op = c.call.operation.id;
|
|
264
|
-
// one set to cancel at its period's end is canceled then, with no renewal
|
|
265
|
-
// (docs.stripe.com/billing/subscriptions/cancel#cancel-at-end-of-cycle)
|
|
266
|
-
if (sub.cancel_at_period_end === true) {
|
|
267
|
-
if (c.legal(SUB, 'status', op, sub.status, 'canceled', id, 'time')) return false;
|
|
268
|
-
await c.write(SUB, id, { status: 'canceled', ended_at: next.t, canceled_at: sub.canceled_at ?? next.t }, 'customer.subscription.deleted');
|
|
269
|
-
return true;
|
|
270
|
-
}
|
|
271
|
-
const start = next.t;
|
|
272
|
-
const { interval, interval_count } = resolveSubscriptionBillingInterval(itemsOf(sub), finder(c));
|
|
273
|
-
const end = addBillingInterval(start, interval, interval_count);
|
|
274
|
-
const trialEnds = sub.status === 'trialing';
|
|
275
|
-
if (trialEnds && c.legal(SUB, 'status', op, 'trialing', 'active', id, 'time')) return false;
|
|
276
|
-
const invoiceId = await draftSubscriptionInvoice(c, sub, { start, end, reason: 'subscription_cycle' });
|
|
277
|
-
await c.write(SUB, id, { current_period_start: start, current_period_end: end, latest_invoice: invoiceId, ...(trialEnds ? { status: 'active' } : {}) }, 'customer.subscription.updated');
|
|
278
|
-
return true;
|
|
279
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
// SetupIntent semantics. The machine in ../manifest.ts says which moves of `status` are legal and
|
|
2
|
-
// what Stripe answers otherwise; these handlers compute what each move does. List, retrieve and
|
|
3
|
-
// update are the derived core's.
|
|
4
|
-
import type { Semantics, SemanticsContext } from '@volter/world-core';
|
|
5
|
-
import { asBool, microdepositsNextAction, mintClientSecret, requiresMicrodeposits, verifyMicrodeposits } from '../stripe-twin.ts';
|
|
6
|
-
import { mintMandate } from './after-payment.ts';
|
|
7
|
-
import { attachable, attachPaymentMethod, methodFromData, microdepositsAsked } from './payment-methods.ts';
|
|
8
|
-
import { at, confirmOnly, created, fail, mergeUpdate, send, type Row } from './shared.ts';
|
|
9
|
-
|
|
10
|
-
const SI = 'setup_intent';
|
|
11
|
-
|
|
12
|
-
/** The intent the path names, and the machine's refusal when this operation may not move it. */
|
|
13
|
-
function load(ctx: SemanticsContext, operationId: string): { si: Row } | { answer: Response } {
|
|
14
|
-
const id = at(ctx, 'intent');
|
|
15
|
-
const si = ctx.get(SI, id);
|
|
16
|
-
if (!si) return { answer: fail(ctx, `No such setup_intent: '${id}'`, 404, 'resource_missing') };
|
|
17
|
-
const refused = ctx.legal(SI, 'status', operationId, si.status, undefined, id);
|
|
18
|
-
return refused ? { answer: ctx.refuse(refused) } : { si };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const create: Semantics = async (ctx) => {
|
|
22
|
-
// mandate_data and return_url each "can only be used with `confirm=true`" (the served spec; shared.ts confirmOnly)
|
|
23
|
-
const unconfirmed = confirmOnly(ctx, ['mandate_data', 'return_url'], asBool(ctx.params.confirm));
|
|
24
|
-
if (unconfirmed) return unconfirmed;
|
|
25
|
-
// the id is minted first so the client secret can carry it: Stripe.js parses the id back out
|
|
26
|
-
const id = typeof ctx.params.id === 'string' && ctx.params.id ? ctx.params.id : ctx.mint(SI);
|
|
27
|
-
// it waits for a payment method until it has one, then for its confirmation (manifest.ts)
|
|
28
|
-
const hasMethod = typeof ctx.params.payment_method === 'string' && ctx.params.payment_method !== '';
|
|
29
|
-
if (hasMethod) ctx.legal(SI, 'status', 'PostSetupIntents', 'requires_payment_method', 'requires_confirmation', id);
|
|
30
|
-
return ctx.reply(
|
|
31
|
-
// allowed_payment_method_types in the served version names the methods (docs.stripe.com/api/setup_intents/create)
|
|
32
|
-
await created(ctx, SI, { ...ctx.params, ...(Array.isArray(ctx.params.allowed_payment_method_types) ? { payment_method_types: ctx.params.allowed_payment_method_types.map(String) } : {}), id }, {
|
|
33
|
-
status: hasMethod ? 'requires_confirmation' : 'requires_payment_method', usage: 'off_session', client_secret: mintClientSecret(id), payment_method_types: ['card'],
|
|
34
|
-
next_action: null, latest_attempt: null, last_setup_error: null, livemode: false,
|
|
35
|
-
}),
|
|
36
|
-
);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// a bank account that needs micro-deposit verification waits in requires_action; anything else succeeds. The confirm's
|
|
40
|
-
// payment_method_data and mandate_data are instructions, not fields of the SetupIntent.
|
|
41
|
-
const confirm: Semantics = async (ctx) => {
|
|
42
|
-
const loaded = load(ctx, 'PostSetupIntentsIntentConfirm');
|
|
43
|
-
if ('answer' in loaded) return loaded.answer;
|
|
44
|
-
const si = loaded.si;
|
|
45
|
-
const id = String(si.id);
|
|
46
|
-
const { payment_method_data: data, mandate_data: _mandate, ...fields } = ctx.params;
|
|
47
|
-
const made = await methodFromData(ctx, data);
|
|
48
|
-
const pm = made ?? (typeof ctx.params.payment_method === 'string' ? ctx.params.payment_method : typeof si.payment_method === 'string' ? si.payment_method : undefined);
|
|
49
|
-
if (si.status !== 'requires_action' && (requiresMicrodeposits(ctx.params, si) || microdepositsAsked(ctx, ctx.params, si, pm))) {
|
|
50
|
-
return ctx.reply(await ctx.write(SI, id, mergeUpdate(ctx, SI, id, { ...fields, ...(pm ? { payment_method: pm } : {}), status: 'requires_action', next_action: microdepositsNextAction() }), 'setup_intent.requires_action'));
|
|
51
|
-
}
|
|
52
|
-
// a setup for a customer saves the method to them (docs.stripe.com/payments/save-and-reuse): its payment method is attached
|
|
53
|
-
const customer = typeof si.customer === 'string' ? si.customer : undefined;
|
|
54
|
-
const saved = pm && customer && attachable(ctx, pm) && ctx.get('payment_method', pm)?.customer !== customer ? String((await attachPaymentMethod(ctx, pm, customer)).id) : pm;
|
|
55
|
-
return ctx.reply(await ctx.write(SI, id, mergeUpdate(ctx, SI, id, { ...fields, ...(saved ? { payment_method: saved } : {}), status: 'succeeded' }), 'setup_intent.confirm'));
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const cancel: Semantics = async (ctx) => {
|
|
59
|
-
const loaded = load(ctx, 'PostSetupIntentsIntentCancel');
|
|
60
|
-
if ('answer' in loaded) return loaded.answer;
|
|
61
|
-
const cancellation_reason = typeof ctx.params.cancellation_reason === 'string' ? ctx.params.cancellation_reason : 'abandoned';
|
|
62
|
-
return ctx.reply(await ctx.write(SI, String(loaded.si.id), { status: 'canceled', cancellation_reason }, 'setup_intent.canceled'));
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const verify: Semantics = async (ctx) => {
|
|
66
|
-
const id = at(ctx, 'intent');
|
|
67
|
-
const si = ctx.get(SI, id);
|
|
68
|
-
if (!si) return fail(ctx, `No such setup_intent: '${id}'`, 404, 'resource_missing');
|
|
69
|
-
// the deposit amounts are checked before the intent's state, as Stripe does
|
|
70
|
-
const wrong = verifyMicrodeposits(ctx.params, si);
|
|
71
|
-
if (wrong) return send(ctx, wrong);
|
|
72
|
-
const refused = ctx.legal(SI, 'status', 'PostSetupIntentsIntentVerifyMicrodeposits', si.status, undefined, id);
|
|
73
|
-
if (refused) return ctx.refuse(refused);
|
|
74
|
-
const mandate = await mintMandate(ctx, si.payment_method, 'multi_use');
|
|
75
|
-
// verified, the bank account is saved to the setup's customer, as a confirmed setup's method is
|
|
76
|
-
const pm = typeof si.payment_method === 'string' ? si.payment_method : undefined;
|
|
77
|
-
const customer = typeof si.customer === 'string' ? si.customer : undefined;
|
|
78
|
-
if (pm && customer && attachable(ctx, pm) && ctx.get('payment_method', pm)?.customer !== customer) await attachPaymentMethod(ctx, pm, customer);
|
|
79
|
-
return ctx.reply(await ctx.write(SI, id, { status: 'succeeded', next_action: null, last_setup_error: null, mandate }, 'setup_intent.succeeded'));
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
export const setupIntents: Record<string, Semantics> = {
|
|
83
|
-
PostSetupIntents: create,
|
|
84
|
-
PostSetupIntentsIntentConfirm: confirm,
|
|
85
|
-
PostSetupIntentsIntentCancel: cancel,
|
|
86
|
-
PostSetupIntentsIntentVerifyMicrodeposits: verify,
|
|
87
|
-
};
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
// Subscription schedule semantics: a schedule drives a subscription through phases (items for a
|
|
2
|
-
// duration). One made for a customer starts its subscription at once on the first phase's items. Cancel and
|
|
3
|
-
// release end it, the machine's moves in ../manifest.ts. List and update are the derived core's.
|
|
4
|
-
//
|
|
5
|
-
// Where the documentation stops and the twin decides: the subscription a schedule starts raises no first invoice.
|
|
6
|
-
import type { Semantics, SemanticsContext } from '@volter/world-core';
|
|
7
|
-
import { addBillingInterval, buildSubscriptionItemsList } from '../stripe-twin.ts';
|
|
8
|
-
import { at, created, fail, finder, type Row } from './shared.ts';
|
|
9
|
-
import { storeItems } from './subscriptions.ts';
|
|
10
|
-
|
|
11
|
-
const SS = 'subscription_schedule';
|
|
12
|
-
const scheduleMissing = (ctx: SemanticsContext, id: string): Response => fail(ctx, `No such subscription_schedule: '${id}'`, 404, 'resource_missing');
|
|
13
|
-
|
|
14
|
-
type Interval = Parameters<typeof addBillingInterval>[1];
|
|
15
|
-
|
|
16
|
-
/** A schedule's phases as Stripe answers them (the served spec's subscription_schedule_phase_configuration): each
|
|
17
|
-
* starts where the last ended (the first at the schedule's start_date, "now" or a timestamp), and ends at its
|
|
18
|
-
* end_date or after its duration ("duration: interval, interval_count", the served spec's phases[] parameter) on the
|
|
19
|
-
* calendar; its currency is its first price's, each item carries its price, quantity and (empty) discounts. Where the
|
|
20
|
-
* documentation stops and the twin decides: a phase with neither lasts one interval of its first price. */
|
|
21
|
-
function phasesOf(ctx: SemanticsContext, given: Row[], start: number): Row[] {
|
|
22
|
-
let cursor = start;
|
|
23
|
-
return given.map((p) => {
|
|
24
|
-
const items = (Array.isArray(p.items) ? (p.items as Row[]) : []).map((it) => ({ price: it.price ?? null, quantity: Math.max(1, Math.trunc(Number(it.quantity) || 1)), discounts: [], metadata: {}, tax_rates: [], billing_thresholds: null }));
|
|
25
|
-
const price = typeof items[0]?.price === 'string' ? ctx.get('price', items[0]!.price as string) : undefined;
|
|
26
|
-
const recurring = (price?.recurring as Row | undefined) ?? {};
|
|
27
|
-
const d = p.duration && typeof p.duration === 'object' ? (p.duration as Row) : undefined;
|
|
28
|
-
const startDate = cursor;
|
|
29
|
-
const end = p.end_date !== undefined && p.end_date !== 'now' ? Number(p.end_date)
|
|
30
|
-
: d ? addBillingInterval(startDate, String(d.interval) as Interval, Math.max(1, Number(d.interval_count) || 1))
|
|
31
|
-
: addBillingInterval(startDate, String(recurring.interval ?? 'month') as Interval, Math.max(1, Number(recurring.interval_count) || 1));
|
|
32
|
-
cursor = end;
|
|
33
|
-
return {
|
|
34
|
-
add_invoice_items: [], application_fee_percent: null, automatic_tax: { enabled: false, liability: null, disabled_reason: null }, billing_cycle_anchor: null,
|
|
35
|
-
billing_thresholds: null, collection_method: null, currency: String(price?.currency ?? 'usd'), default_payment_method: null, default_tax_rates: [],
|
|
36
|
-
description: null, discounts: [], end_date: end, invoice_settings: null, items, metadata: p.metadata && typeof p.metadata === 'object' ? p.metadata : {},
|
|
37
|
-
on_behalf_of: null, proration_behavior: typeof p.proration_behavior === 'string' ? p.proration_behavior : 'create_prorations', start_date: startDate,
|
|
38
|
-
transfer_data: null, trial_end: null,
|
|
39
|
-
};
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// a schedule starts from a customer or from an existing subscription
|
|
44
|
-
const create: Semantics = async (ctx) => {
|
|
45
|
-
const params = ctx.params;
|
|
46
|
-
const customer = typeof params.customer === 'string' ? params.customer : '';
|
|
47
|
-
const fromSub = typeof params.from_subscription === 'string' ? params.from_subscription : '';
|
|
48
|
-
if (!customer && !fromSub) return fail(ctx, 'Missing required param: customer (or from_subscription).', 400, 'parameter_missing');
|
|
49
|
-
if (customer && !ctx.get('customer', customer)) return fail(ctx, `No such customer: '${customer}'`, 404, 'resource_missing');
|
|
50
|
-
if (fromSub && !ctx.get('subscription', fromSub)) return fail(ctx, `No such subscription: '${fromSub}'`, 404, 'resource_missing');
|
|
51
|
-
// start_date is an instruction ("now" or when), not a field of the schedule
|
|
52
|
-
const start = params.start_date !== undefined && params.start_date !== 'now' ? Number(params.start_date) : Number(ctx.now());
|
|
53
|
-
const phases = phasesOf(ctx, Array.isArray(params.phases) ? (params.phases as Row[]) : [], start);
|
|
54
|
-
const { phases: _p, start_date: _s, default_settings: givenDefaults, ...rest } = params;
|
|
55
|
-
const defaults = givenDefaults && typeof givenDefaults === 'object' ? (givenDefaults as Row) : {};
|
|
56
|
-
const schedId = ctx.mint(SS);
|
|
57
|
-
// a schedule for a customer whose first phase starts now starts its subscription at once, on that phase's items
|
|
58
|
-
// (docs.stripe.com/billing/subscriptions/subscription-schedules#managing)
|
|
59
|
-
let subscription = fromSub || null;
|
|
60
|
-
if (!fromSub && phases.length) {
|
|
61
|
-
const now = Number(ctx.now());
|
|
62
|
-
const subId = ctx.mint('subscription');
|
|
63
|
-
const items = buildSubscriptionItemsList(phases[0]!.items as Row[], subId, now, finder(ctx));
|
|
64
|
-
await created(ctx, 'subscription', { customer, id: subId }, {
|
|
65
|
-
status: 'active', livemode: false, currency: 'usd', collection_method: String((params.default_settings as Row | undefined)?.collection_method ?? 'charge_automatically'),
|
|
66
|
-
cancel_at_period_end: false, start_date: now, billing_cycle_anchor: now, metadata: {}, discounts: [], billing_schedules: [],
|
|
67
|
-
trial_start: null, trial_end: null, current_period_start: now, current_period_end: phases[0]!.end_date, items, schedule: schedId,
|
|
68
|
-
automatic_tax: { enabled: false, liability: null }, billing_mode: { type: 'classic' }, invoice_settings: { issuer: { type: 'self' } },
|
|
69
|
-
});
|
|
70
|
-
await storeItems(ctx, subId, (items.data as Row[]) ?? []);
|
|
71
|
-
subscription = subId;
|
|
72
|
-
}
|
|
73
|
-
return ctx.reply(
|
|
74
|
-
await created(ctx, SS, { ...rest, id: schedId }, {
|
|
75
|
-
status: 'active', livemode: false, phases, current_phase: phases.length ? { start_date: phases[0]!.start_date, end_date: phases[0]!.end_date } : null,
|
|
76
|
-
subscription, released_subscription: null, released_at: null,
|
|
77
|
-
canceled_at: null, completed_at: null, end_behavior: (params.end_behavior as string) ?? 'release',
|
|
78
|
-
renewal_interval: null, metadata: {}, billing_mode: { type: 'classic' },
|
|
79
|
-
// "billing_cycle_anchor: Possible values are phase_start or automatic" and invoice_settings are required (the
|
|
80
|
-
// served spec's default settings); the rest answer what the request set, else null
|
|
81
|
-
default_settings: {
|
|
82
|
-
application_fee_percent: defaults.application_fee_percent ?? null, automatic_tax: { enabled: false, liability: null, disabled_reason: null },
|
|
83
|
-
billing_cycle_anchor: typeof defaults.billing_cycle_anchor === 'string' ? defaults.billing_cycle_anchor : 'automatic', billing_thresholds: null,
|
|
84
|
-
collection_method: typeof defaults.collection_method === 'string' ? defaults.collection_method : 'charge_automatically', default_payment_method: defaults.default_payment_method ?? null,
|
|
85
|
-
description: defaults.description ?? null, invoice_settings: { account_tax_ids: null, days_until_due: null, issuer: { type: 'self' } }, on_behalf_of: null, transfer_data: null,
|
|
86
|
-
},
|
|
87
|
-
// a schedule made from a subscription is its customer's
|
|
88
|
-
customer: customer || String(ctx.get('subscription', fromSub)?.customer ?? ''),
|
|
89
|
-
}),
|
|
90
|
-
);
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
function end(kind: 'cancel' | 'release'): Semantics {
|
|
94
|
-
return async (ctx) => {
|
|
95
|
-
const id = at(ctx, 'schedule');
|
|
96
|
-
const sched = ctx.get(SS, id);
|
|
97
|
-
if (!sched) return scheduleMissing(ctx, id);
|
|
98
|
-
const refused = ctx.legal(SS, 'status', kind === 'cancel' ? 'PostSubscriptionSchedulesScheduleCancel' : 'PostSubscriptionSchedulesScheduleRelease', sched.status, undefined, id);
|
|
99
|
-
if (refused) return ctx.refuse(refused);
|
|
100
|
-
const now = ctx.now();
|
|
101
|
-
const subId = typeof sched.subscription === 'string' ? sched.subscription : undefined;
|
|
102
|
-
const sub = subId ? ctx.get('subscription', subId) : undefined;
|
|
103
|
-
// "Cancels a subscription schedule and its associated subscription immediately (if the subscription schedule has an
|
|
104
|
-
// active subscription)" (docs.stripe.com/api/subscription_schedules/cancel); releasing "will remove its subscription
|
|
105
|
-
// property and set the subscription's ID to the released_subscription property", leaving the subscription in place
|
|
106
|
-
// (docs.stripe.com/api/subscription_schedules/release)
|
|
107
|
-
if (kind === 'cancel' && subId && sub && sub.status !== 'canceled') {
|
|
108
|
-
ctx.legal('subscription', 'status', 'PostSubscriptionSchedulesScheduleCancel', String(sub.status), 'canceled', subId);
|
|
109
|
-
await ctx.write('subscription', subId, { status: 'canceled', canceled_at: now, ended_at: now }, 'subscription.cancel');
|
|
110
|
-
}
|
|
111
|
-
if (kind === 'release' && subId && sub) await ctx.write('subscription', subId, { schedule: null }, 'subscription.update');
|
|
112
|
-
const fields = kind === 'cancel'
|
|
113
|
-
? { status: 'canceled', canceled_at: now, current_phase: null }
|
|
114
|
-
: { status: 'released', released_at: now, released_subscription: subId ?? null, subscription: null, current_phase: null };
|
|
115
|
-
return ctx.reply(await ctx.write(SS, id, fields, `subscription_schedule.${kind}`));
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const retrieve: Semantics = async (ctx) => {
|
|
120
|
-
const s = ctx.get(SS, at(ctx, 'schedule'));
|
|
121
|
-
return s ? ctx.reply(s) : scheduleMissing(ctx, at(ctx, 'schedule'));
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
export const subscriptionSchedules: Record<string, Semantics> = {
|
|
125
|
-
PostSubscriptionSchedules: create,
|
|
126
|
-
PostSubscriptionSchedulesScheduleCancel: end('cancel'),
|
|
127
|
-
PostSubscriptionSchedulesScheduleRelease: end('release'),
|
|
128
|
-
GetSubscriptionSchedulesSchedule: retrieve,
|
|
129
|
-
};
|