@volter/twin-stripe 2.0.38 → 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,95 +1,12 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import { mintCharge } from './payment-intents.ts';
|
|
11
|
-
import { collectSubscriptionInvoice, draftSubscriptionInvoice } from './renewals.ts';
|
|
12
|
-
import { storeItems } from './subscriptions.ts';
|
|
13
|
-
import { at, created, fail, finder, path, redeemCoupon, type Row } from './shared.ts';
|
|
14
|
-
|
|
15
|
-
const CS = 'checkout.session';
|
|
16
|
-
const sessionMissing = (ctx: SemanticsContext, id: string): Response => fail(ctx, `No such checkout.session: '${id}'`, 404, 'resource_missing');
|
|
17
|
-
|
|
18
|
-
// Stripe normalizes the create input into the Session: line_items become the list envelope, and
|
|
19
|
-
// subscription_data (create-only) is kept aside for completion to copy onto the subscription
|
|
20
|
-
/** The discount a session is created with (discounts[0][promotion_code] or [coupon]), or Stripe's refusal: an unknown
|
|
21
|
-
* code or coupon, and one that has expired or been deactivated, are refused at create
|
|
22
|
-
* (docs.stripe.com/api/checkout/sessions/create#create_checkout_session-discounts, docs.stripe.com/error-codes#coupon-expired).
|
|
23
|
-
* Where the documentation stops and the twin decides: the refusals' wording. */
|
|
24
|
-
function discountOf(ctx: SemanticsContext, params: Row, now: number): { coupon: Row; promotion?: string } | Response | undefined {
|
|
25
|
-
const first = Array.isArray(params.discounts) ? (params.discounts[0] as Row | undefined) : undefined;
|
|
26
|
-
if (!first || typeof first !== 'object') return undefined;
|
|
27
|
-
const expired = (what: string) => ctx.refuse({ status: 400, code: 'coupon_expired', param: 'discounts', message: `This ${what} has expired.` });
|
|
28
|
-
let promotion: string | undefined;
|
|
29
|
-
let couponId = typeof first.coupon === 'string' ? first.coupon : undefined;
|
|
30
|
-
if (typeof first.promotion_code === 'string') {
|
|
31
|
-
const pc = ctx.get('promotion_code', first.promotion_code);
|
|
32
|
-
if (!pc) return ctx.refuse({ status: 400, code: 'resource_missing', param: 'discounts[0][promotion_code]', message: `No such promotion code: '${first.promotion_code}'` });
|
|
33
|
-
if (typeof pc.expires_at === 'number' && pc.expires_at <= now) return expired('promotion code');
|
|
34
|
-
if (pc.active === false) return ctx.refuse({ status: 400, param: 'discounts[0][promotion_code]', message: 'This promotion code is inactive.' });
|
|
35
|
-
promotion = String(pc.id);
|
|
36
|
-
couponId = typeof pc.coupon === 'string' ? pc.coupon : String((pc.coupon as Row | undefined)?.id ?? '');
|
|
37
|
-
}
|
|
38
|
-
const coupon = couponId ? ctx.get('coupon', couponId) : undefined;
|
|
39
|
-
if (!coupon) return ctx.refuse({ status: 400, code: 'resource_missing', param: 'discounts[0][coupon]', message: `No such coupon: '${couponId ?? ''}'` });
|
|
40
|
-
if (coupon.valid === false || (typeof coupon.redeem_by === 'number' && coupon.redeem_by <= now)) return expired('coupon');
|
|
41
|
-
return { coupon, ...(promotion ? { promotion } : {}) };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** A payment or subscription session with nothing to sell (docs.stripe.com/api/checkout/sessions/create#create_checkout_session-line_items). */
|
|
45
|
-
function noLineItems(ctx: SemanticsContext): Response {
|
|
46
|
-
return fail(ctx, 'You must provide at least one recurring price in `subscription` mode when not using `line_items[].price_data` or a Price with `recurring`, and at least one `line_items` for `payment` mode.', 400, 'parameter_missing');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** A destination charge to an account the platform does not have (docs.stripe.com/connect/destination-charges). */
|
|
50
|
-
function noDestination(ctx: SemanticsContext, destination: unknown): Response {
|
|
51
|
-
return ctx.refuse({ status: 400, code: 'resource_missing', param: 'payment_intent_data[transfer_data][destination]', message: `No such destination: '${String(destination)}'` });
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** A session's trial Stripe refuses: `subscription_data.trial_end` "Has to be at least 48 hours in the future", and
|
|
55
|
-
* `subscription_data.trial_period_days` "Has to be at least 1" (docs.stripe.com/api/checkout/sessions/create). Where the
|
|
56
|
-
* documentation stops and the twin decides: the refusals' wording. */
|
|
57
|
-
function sessionTrialRefused(ctx: SemanticsContext, data: unknown, now: number): Response | undefined {
|
|
58
|
-
if (!data || typeof data !== 'object') return undefined;
|
|
59
|
-
const { trial_end: end, trial_period_days: days } = data as Row;
|
|
60
|
-
const endRefused = end !== undefined && !(Number.isInteger(Number(end)) && Number(end) >= now + 48 * 3600);
|
|
61
|
-
const daysRefused = days !== undefined && !(Number.isInteger(Number(days)) && Number(days) >= 1);
|
|
62
|
-
return endRefused || daysRefused ? sessionTrialRefusal(ctx, endRefused ? 'trial_end' : 'trial_period_days') : undefined;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function sessionTrialRefusal(ctx: SemanticsContext, param: 'trial_end' | 'trial_period_days'): Response {
|
|
66
|
-
const message = param === 'trial_end' ? 'The `subscription_data[trial_end]` timestamp has to be at least 48 hours in the future.' : 'The `subscription_data[trial_period_days]` has to be at least 1.';
|
|
67
|
-
return ctx.refuse({ status: 400, param: `subscription_data[${param}]`, message });
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** The payment method types a session takes: the list a caller pinned to an earlier version gives as
|
|
71
|
-
* payment_method_types ("A list of the types of payment methods (e.g., `card`) this Checkout Session can accept", the
|
|
72
|
-
* parameter the served version's allowed_payment_method_types is described against: "Unlike `payment_method_types`,
|
|
73
|
-
* this acts as a filter on the dynamically computed set of eligible payment methods", docs.stripe.com/api/checkout/
|
|
74
|
-
* sessions/create), else the allowed_payment_method_types of the served one, else card. Where the documentation stops
|
|
75
|
-
* and the twin decides: an allowed list is answered whole (the twin does not compute eligibility), and without one the
|
|
76
|
-
* session takes card (the account's enabled methods are not modelled). */
|
|
77
|
-
function sessionMethodTypes(params: Row): string[] {
|
|
78
|
-
const given = Array.isArray(params.payment_method_types) ? params.payment_method_types : params.allowed_payment_method_types;
|
|
79
|
-
return Array.isArray(given) && given.length ? given.map(String) : ['card'];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** Metadata passed on to what a session makes (payment_intent_data[metadata], subscription_data[metadata]) as a
|
|
83
|
-
* create's own is kept: "Individual keys can be unset by posting an empty value to them. All keys can be unset by
|
|
84
|
-
* posting an empty value to `metadata`" (every metadata parameter, docs.stripe.com/api/metadata), so a key posted
|
|
85
|
-
* empty is not set, and metadata posted empty is {} (stripe-server.ts createMetadata, the top-level case). */
|
|
86
|
-
export function nestedMetadata(given: unknown): Row | undefined {
|
|
87
|
-
if (given === '') return {};
|
|
88
|
-
if (!given || typeof given !== 'object' || Array.isArray(given)) return undefined;
|
|
89
|
-
return Object.fromEntries(Object.entries(given as Row).filter(([, v]) => v !== ''));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const create: Semantics = async (ctx) => {
|
|
1
|
+
// Stripe's checkout operations (docs/contributing/architecture.md, "What an author writes"): each handler over the
|
|
2
|
+
// handler contract's context, computing with the engine (../engine/).
|
|
3
|
+
import type { HandlerContext } from '@volter/world-core';
|
|
4
|
+
import { CS, sessionMethodTypes } from '../engine/checkout.ts';
|
|
5
|
+
import type { Row } from '../engine/common.ts';
|
|
6
|
+
import { applyCouponDiscount, lineItemEntries, nowUnix, paginate, resolveLineItem } from '../engine/stripe.ts';
|
|
7
|
+
import { actingAccount, at, checkoutNoDestination, created, discountOf, fail, finder, noLineItems, path, sessionMissing, sessionTrialRefused } from './shared.ts';
|
|
8
|
+
|
|
9
|
+
export async function PostCheckoutSessions(ctx: HandlerContext): Promise<Response> {
|
|
93
10
|
const params = ctx.params;
|
|
94
11
|
const mode = typeof params.mode === 'string' ? params.mode : 'payment';
|
|
95
12
|
if (!['payment', 'subscription', 'setup'].includes(mode)) return fail(ctx, 'Invalid mode: must be one of payment, subscription, or setup.', 400, 'parameter_invalid_string_enum');
|
|
@@ -102,7 +19,7 @@ const create: Semantics = async (ctx) => {
|
|
|
102
19
|
const missing = resolved.find((r) => r.priceMissing);
|
|
103
20
|
if (missing) return fail(ctx, `No such price: '${missing.priceMissing}'`, 400, 'resource_missing');
|
|
104
21
|
const items = resolved.map((r) => r.item);
|
|
105
|
-
const now = Number(ctx.
|
|
22
|
+
const now = Number(nowUnix(ctx.occurredAt));
|
|
106
23
|
const trialRefused = sessionTrialRefused(ctx, params.subscription_data, now);
|
|
107
24
|
if (trialRefused) return trialRefused;
|
|
108
25
|
const discount = discountOf(ctx, params, now);
|
|
@@ -144,190 +61,18 @@ const create: Semantics = async (ctx) => {
|
|
|
144
61
|
const pid = params.payment_intent_data && typeof params.payment_intent_data === 'object' ? (params.payment_intent_data as Row) : undefined;
|
|
145
62
|
if (pid && mode === 'payment') {
|
|
146
63
|
const destination = (pid.transfer_data as Row | undefined)?.destination;
|
|
147
|
-
if (destination !== undefined && (typeof destination !== 'string' || !ctx.get('account', destination))) return
|
|
64
|
+
if (destination !== undefined && (typeof destination !== 'string' || !ctx.get('account', destination))) return checkoutNoDestination(ctx, destination);
|
|
148
65
|
fields._payment_intent_data = pid;
|
|
149
66
|
}
|
|
150
67
|
if (discount) fields._discount = { coupon: String(discount.coupon.id), promotion_code: discount.promotion ?? null };
|
|
151
68
|
return ctx.reply(await created(ctx, CS, { id, ...fields }, {}));
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// the session's items, a page at a time
|
|
155
|
-
const lineItems: Semantics = async (ctx) => {
|
|
156
|
-
const s = ctx.get(CS, at(ctx, 'session'));
|
|
157
|
-
if (!s) return sessionMissing(ctx, at(ctx, 'session'));
|
|
158
|
-
const li = s.line_items as { data?: Row[] } | undefined;
|
|
159
|
-
const { page, hasMore } = paginate(Array.isArray(li?.data) ? li!.data! : [], ctx.params);
|
|
160
|
-
return ctx.reply({ object: 'list', url: path(ctx), has_more: hasMore, data: page });
|
|
161
|
-
};
|
|
69
|
+
}
|
|
162
70
|
|
|
163
|
-
|
|
71
|
+
export async function PostCheckoutSessionsSessionExpire(ctx: HandlerContext): Promise<Response> {
|
|
164
72
|
const id = at(ctx, 'session');
|
|
165
73
|
const s = ctx.get(CS, id);
|
|
166
74
|
if (!s) return sessionMissing(ctx, id);
|
|
167
75
|
const refused = ctx.legal(CS, 'status', 'PostCheckoutSessionsSessionExpire', s.status, undefined, id);
|
|
168
76
|
if (refused) return ctx.refuse(refused);
|
|
169
77
|
return ctx.reply(await ctx.write(CS, id, { status: 'expired', url: null }, 'checkout.session.expire'));
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
/** The card the customer entered on the page. */
|
|
173
|
-
export type EnteredCard = { number: string; exp_month: number; exp_year: number };
|
|
174
|
-
|
|
175
|
-
/** What completing a session makes, by its mode, as the fields that link it: a payment is a PaymentIntent paid with
|
|
176
|
-
* the entered card, its charge in the ledger as any payment's is. */
|
|
177
|
-
async function complete(ctx: SemanticsContext, id: string, existing: Row, card?: EnteredCard): Promise<Row> {
|
|
178
|
-
const link: Row = { status: 'complete' };
|
|
179
|
-
const customer = typeof existing.customer === 'string' ? existing.customer : undefined;
|
|
180
|
-
const currency = typeof existing.currency === 'string' ? existing.currency : 'usd';
|
|
181
|
-
// the entered card becomes a PaymentMethod carrying what a later charge to it answers and what Stripe does after it
|
|
182
|
-
// succeeds (docs.stripe.com/testing#declined-payments, #disputes)
|
|
183
|
-
const made = card ? await created(ctx, 'payment_method', {}, { type: 'card', customer: null, livemode: false, billing_details: { address: null, email: null, name: null, phone: null }, ...paymentMethodSubObject('card', { card }), _declineOutcome: declineFor(() => undefined, { card }) ?? null, ...(afterSuccessOf(ctx, card) ? { _afterSuccess: afterSuccessOf(ctx, card) } : {}) }) : undefined;
|
|
184
|
-
// and Checkout attaches it to the customer when it saves it: a subscription's card ("If your Checkout Session uses
|
|
185
|
-
// subscription mode, Stripe saves the payment method by default", docs.stripe.com/payments/checkout/how-checkout-works),
|
|
186
|
-
// a setup session's (it exists to save one), and a payment's only under payment_intent_data.setup_future_usage ("to have
|
|
187
|
-
// Checkout automatically attach the payment method to the Customer you pass in", docs.stripe.com/api/checkout/sessions/
|
|
188
|
-
// create#create_checkout_session-customer). Attaching is its own write, so payment_method.attached is delivered ("Occurs
|
|
189
|
-
// whenever a new payment method is attached to a customer", docs.stripe.com/api/events/types) before what the card then
|
|
190
|
-
// pays for. Where the documentation stops and the twin decides: the order of the events, which Stripe does not
|
|
191
|
-
// guarantee (docs.stripe.com/webhooks#event-ordering). A gap: the customer's own opt-in to save a payment's card
|
|
192
|
-
// (saved_payment_method_options.payment_method_save) is not modelled; the page offers no such box.
|
|
193
|
-
const saves = existing.mode !== 'payment' || !!((ctx.row(CS, id)?._payment_intent_data ?? {}) as Row).setup_future_usage;
|
|
194
|
-
const pm = made && customer && saves ? await ctx.write('payment_method', String(made.id), { customer }, 'payment_method.attach') : made;
|
|
195
|
-
if (existing.mode === 'payment') {
|
|
196
|
-
const amount = Number(existing.amount_total) || 0;
|
|
197
|
-
const piId = ctx.mint('payment_intent');
|
|
198
|
-
const pid = (ctx.row(CS, id)?._payment_intent_data ?? {}) as Row;
|
|
199
|
-
const connect = {
|
|
200
|
-
...(pid.application_fee_amount !== undefined ? { application_fee_amount: Math.trunc(Number(pid.application_fee_amount) || 0) } : {}),
|
|
201
|
-
...(pid.transfer_data && typeof pid.transfer_data === 'object' ? { transfer_data: { destination: (pid.transfer_data as Row).destination } } : {}),
|
|
202
|
-
...(typeof pid.description === 'string' ? { description: pid.description } : {}),
|
|
203
|
-
// the session's setup_future_usage is its PaymentIntent's (payment_intent_data: "A subset of parameters to be passed to PaymentIntent creation")
|
|
204
|
-
...(typeof pid.setup_future_usage === 'string' ? { setup_future_usage: pid.setup_future_usage } : {}),
|
|
205
|
-
...(nestedMetadata(pid.metadata) ? { metadata: nestedMetadata(pid.metadata) } : {}),
|
|
206
|
-
};
|
|
207
|
-
const fields = { amount, currency, id: piId, ...(customer ? { customer } : {}), ...(pm ? { payment_method: pm.id } : {}), ...connect };
|
|
208
|
-
await created(ctx, 'payment_intent', fields, { status: 'succeeded', amount_received: amount, client_secret: mintClientSecret(piId), livemode: false, payment_method_types: Array.isArray(existing.payment_method_types) ? existing.payment_method_types : ['card'] });
|
|
209
|
-
const charge = await mintCharge(ctx, piId, fields, amount);
|
|
210
|
-
await ctx.write('payment_intent', piId, { latest_charge: charge }, 'payment_intent.succeeded');
|
|
211
|
-
link.payment_intent = piId;
|
|
212
|
-
link.payment_status = 'paid';
|
|
213
|
-
} else if (existing.mode === 'subscription') {
|
|
214
|
-
if (customer) {
|
|
215
|
-
// the subscription bills the session's own (already resolved) line items, and takes the
|
|
216
|
-
// session's subscription_data: metadata, description, and a trial as its first period
|
|
217
|
-
const now = Number(ctx.now());
|
|
218
|
-
// "Line items with one-time Prices will be on the initial invoice only" (docs.stripe.com/api/checkout/sessions/
|
|
219
|
-
// create#create_checkout_session-line_items): the subscription's items are the recurring ones
|
|
220
|
-
const inline = (ctx.row(CS, id)?._price_data as Array<Row | null> | undefined) ?? [];
|
|
221
|
-
const lines = ((existing.line_items as { data?: unknown } | undefined)?.data as Row[] | undefined) ?? [];
|
|
222
|
-
const allLineItems = inline.some(Boolean) ? await sessionPrices(ctx, lines, inline) : lines;
|
|
223
|
-
const sessionLineItems = allLineItems.filter((it) => !isOneTime(it));
|
|
224
|
-
const oneTime = allLineItems.filter((it) => isOneTime(it));
|
|
225
|
-
const { interval, interval_count } = resolveSubscriptionBillingInterval(sessionLineItems, finder(ctx));
|
|
226
|
-
const subId = ctx.mint('subscription');
|
|
227
|
-
// the session's view never carries its create-only subscription_data: read the stored row
|
|
228
|
-
const raw = ctx.row(CS, id)?._subscription_data;
|
|
229
|
-
const subData = (raw && typeof raw === 'object' ? raw : {}) as Row;
|
|
230
|
-
const subMetadata = nestedMetadata(subData.metadata) ?? {};
|
|
231
|
-
// a trial of `trial_period_days` from now, or to `trial_end` (docs.stripe.com/payments/checkout/free-trials)
|
|
232
|
-
const trialEnd = resolveTrial(subData, now)?.end ?? null;
|
|
233
|
-
const sub = await created(ctx, 'subscription', { customer, id: subId }, {
|
|
234
|
-
status: trialEnd === null ? 'active' : 'trialing', livemode: false, currency, collection_method: 'charge_automatically',
|
|
235
|
-
// a trial's end is the billing anchor (docs.stripe.com/api/subscriptions/create#create_subscription-trial_end)
|
|
236
|
-
cancel_at_period_end: false, start_date: now, billing_cycle_anchor: trialEnd ?? now, metadata: subMetadata,
|
|
237
|
-
description: typeof subData.description === 'string' ? subData.description : null,
|
|
238
|
-
// the session's discount is the subscription's (docs.stripe.com/payments/checkout/discounts)
|
|
239
|
-
discounts: sessionDiscount(ctx, id, customer, now), billing_schedules: [],
|
|
240
|
-
trial_start: trialEnd === null ? null : now, trial_end: trialEnd,
|
|
241
|
-
current_period_start: now, current_period_end: trialEnd ?? addBillingInterval(now, interval, interval_count),
|
|
242
|
-
// Checkout saves the card as the subscription's default payment method (docs.stripe.com/payments/checkout/how-checkout-works)
|
|
243
|
-
default_payment_method: pm?.id ?? null,
|
|
244
|
-
items: buildSubscriptionItemsList(sessionLineItems, subId, now, finder(ctx)),
|
|
245
|
-
automatic_tax: { enabled: false, liability: null }, billing_mode: { type: 'classic' },
|
|
246
|
-
invoice_settings: { issuer: { type: 'self' } },
|
|
247
|
-
});
|
|
248
|
-
await storeItems(ctx, subId, ((sub.items as Row | undefined)?.data as Row[] | undefined) ?? []);
|
|
249
|
-
// its first invoice, paid on the page by the card just entered (nothing is due on a trial's)
|
|
250
|
-
// (docs.stripe.com/billing/subscriptions/overview#how-payments-work-subscriptions)
|
|
251
|
-
const invoice = await draftSubscriptionInvoice(ctx, sub, { start: now, end: Number(sub.current_period_end), reason: 'subscription_create', trial: trialEnd !== null, once: oneTime });
|
|
252
|
-
await collectSubscriptionInvoice(ctx, invoice, now, 'external');
|
|
253
|
-
await ctx.write('subscription', subId, { latest_invoice: invoice }, 'subscription.update');
|
|
254
|
-
link.subscription = sub.id;
|
|
255
|
-
link.invoice = invoice;
|
|
256
|
-
}
|
|
257
|
-
link.payment_status = 'paid';
|
|
258
|
-
} else link.setup_intent = await completeSetup(ctx, customer, pm);
|
|
259
|
-
return link;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/** A line item of a one-time Price: a stored Price, or an inline `price_data`, without `recurring` ("Line items with
|
|
263
|
-
* one-time Prices will be on the initial invoice only", docs.stripe.com/api/checkout/sessions/create). */
|
|
264
|
-
export function isOneTime(item: Row, priceData?: Row | null): boolean {
|
|
265
|
-
if (priceData) return !priceData.recurring;
|
|
266
|
-
return !!item.price && typeof item.price === 'object' && !(item.price as Row).recurring;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/** A subscription session's lines at completion, each inline `price_data` made the Price it generates ("Data used to
|
|
270
|
-
* generate a new Price object inline", the same page), under the product it names or one made from its product_data,
|
|
271
|
-
* so the subscription's items bill it. Where the documentation stops and the twin decides: such a Price is archived
|
|
272
|
-
* (active=false), as a subscription item's price_data makes one (docs.stripe.com/products-prices/manage-prices). */
|
|
273
|
-
async function sessionPrices(ctx: SemanticsContext, lines: Row[], inline: Array<Row | null>): Promise<Row[]> {
|
|
274
|
-
const out: Row[] = [];
|
|
275
|
-
for (const [i, line] of lines.entries()) {
|
|
276
|
-
const d = inline[i];
|
|
277
|
-
if (!d) { out.push(line); continue; }
|
|
278
|
-
const pd = d.product_data && typeof d.product_data === 'object' ? (d.product_data as Row) : undefined;
|
|
279
|
-
const product = typeof d.product === 'string' ? d.product : String((await created(ctx, 'product', { name: pd?.name ?? 'Item' }, { active: true, livemode: false, images: [], marketing_features: [], metadata: {}, updated: ctx.now() })).id);
|
|
280
|
-
const r = d.recurring && typeof d.recurring === 'object' ? (d.recurring as Row) : undefined;
|
|
281
|
-
const price = await created(ctx, 'price', {
|
|
282
|
-
product, currency: String(d.currency ?? line.currency ?? 'usd').toLowerCase(),
|
|
283
|
-
unit_amount: d.unit_amount_decimal !== undefined ? null : Math.trunc(Number(d.unit_amount) || 0),
|
|
284
|
-
unit_amount_decimal: d.unit_amount_decimal !== undefined ? String(d.unit_amount_decimal) : String(Math.trunc(Number(d.unit_amount) || 0)),
|
|
285
|
-
...(r ? { recurring: { interval: r.interval, interval_count: Math.max(1, Math.trunc(Number(r.interval_count ?? 1)) || 1), usage_type: 'licensed', trial_period_days: null, meter: null } } : { recurring: null }),
|
|
286
|
-
}, { active: false, livemode: false, billing_scheme: 'per_unit', type: r ? 'recurring' : 'one_time', metadata: {}, lookup_key: null, nickname: null, tax_behavior: 'unspecified' });
|
|
287
|
-
out.push({ ...line, price });
|
|
288
|
-
}
|
|
289
|
-
return out;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/** A setup session saves the card through a succeeded SetupIntent (docs.stripe.com/payments/save-and-reuse?platform=checkout). */
|
|
293
|
-
async function completeSetup(ctx: SemanticsContext, customer: string | undefined, pm: Row | undefined): Promise<unknown> {
|
|
294
|
-
const siId = ctx.mint('setup_intent');
|
|
295
|
-
const si = await created(ctx, 'setup_intent', { id: siId, ...(customer ? { customer } : {}), ...(pm ? { payment_method: pm.id } : {}) }, { status: 'succeeded', usage: 'off_session', client_secret: mintClientSecret(siId), payment_method_types: ['card'], livemode: false });
|
|
296
|
-
return si.id;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/** The discount a completed subscription session gives its subscription. */
|
|
300
|
-
function sessionDiscount(ctx: SemanticsContext, id: string, customer: string, now: number): Row[] {
|
|
301
|
-
const d = ctx.row(CS, id)?._discount as Row | undefined;
|
|
302
|
-
const coupon = d && typeof d.coupon === 'string' ? ctx.get('coupon', d.coupon) : undefined;
|
|
303
|
-
return coupon ? [{ ...buildDiscount(coupon, customer, now), promotion_code: d?.promotion_code ?? null, checkout_session: id }] : [];
|
|
304
78
|
}
|
|
305
|
-
|
|
306
|
-
/** The customer paying on the hosted page (src/screens/checkout.tsx): the external actor's move on the
|
|
307
|
-
* session's machine, then what the payment made, linked, and the session stored complete. */
|
|
308
|
-
export async function completeSession(ctx: SemanticsContext, id: string, existing: Row, details: Row = {}, card?: EnteredCard): Promise<Row | Response> {
|
|
309
|
-
const refused = ctx.legal(CS, 'status', 'PostCheckoutSessionsSession', existing.status, 'complete', id, 'external');
|
|
310
|
-
if (refused) return ctx.refuse(refused);
|
|
311
|
-
const link = await complete(ctx, id, existing, card);
|
|
312
|
-
// the discount the buyer took is redeemed (shared.ts redeemCoupon)
|
|
313
|
-
const d = ctx.row(CS, id)?._discount as Row | undefined;
|
|
314
|
-
if (d) await redeemCoupon(ctx, d.coupon, d.promotion_code);
|
|
315
|
-
// the session's url "is only present when the session is active" (docs.stripe.com/api/checkout/sessions/object)
|
|
316
|
-
return ctx.write(CS, id, { ...details, ...link, url: null }, 'checkout.session.completed');
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const update: Semantics = async (ctx) => {
|
|
320
|
-
const id = at(ctx, 'session');
|
|
321
|
-
const existing = ctx.get(CS, id);
|
|
322
|
-
if (!existing) return sessionMissing(ctx, id);
|
|
323
|
-
// a session completes only when its customer pays on the hosted page (src/screens/checkout.tsx); the API takes no
|
|
324
|
-
// status, which the parameter check refuses as Stripe does (../stripe-params.ts)
|
|
325
|
-
return ctx.reply(await ctx.write(CS, id, ctx.params, 'checkout.session.update'));
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
export const checkout: Record<string, Semantics> = {
|
|
329
|
-
PostCheckoutSessions: create,
|
|
330
|
-
GetCheckoutSessionsSessionLineItems: lineItems,
|
|
331
|
-
PostCheckoutSessionsSessionExpire: expire,
|
|
332
|
-
PostCheckoutSessionsSession: update,
|
|
333
|
-
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Stripe's own moves as time passes (docs/contributing/architecture.md, "What an author writes": clock), caught up to
|
|
2
|
+
// the World's clock before anything is answered, so every door (the API, the hosted pages) reads the account as it
|
|
3
|
+
// stands now. Each move is the engine's.
|
|
4
|
+
import type { HandlerContext } from '@volter/world-core';
|
|
5
|
+
import { advanceBilling, advancePayouts, finishClockAdvances, finishReportRuns, lapseCoupons, lapseRealtimeRequests, settleBankDebits, settleHeldRefunds, settleTopups } from './shared.ts';
|
|
6
|
+
|
|
7
|
+
export async function clock(ctx: HandlerContext): Promise<void> {
|
|
8
|
+
// a test clock advanced by the last request has reached its time
|
|
9
|
+
await finishClockAdvances(ctx);
|
|
10
|
+
// a subscription renews at its period's end, its renewal charged an hour later
|
|
11
|
+
await advanceBilling(ctx);
|
|
12
|
+
// a submitted bank debit settles (in test mode at once)
|
|
13
|
+
await settleBankDebits(ctx);
|
|
14
|
+
// a coupon past its redeem_by is no longer valid
|
|
15
|
+
await lapseCoupons(ctx);
|
|
16
|
+
// a top-up's funds arrive five days after it is made
|
|
17
|
+
await settleTopups(ctx);
|
|
18
|
+
// a report run completes
|
|
19
|
+
await finishReportRuns(ctx);
|
|
20
|
+
// a real-time authorization request no one answered is decided when its window ends
|
|
21
|
+
await lapseRealtimeRequests(ctx);
|
|
22
|
+
// a refund held for want of balance is made when funds cover it, before any payout takes them
|
|
23
|
+
await settleHeldRefunds(ctx);
|
|
24
|
+
// each account's automatic payouts
|
|
25
|
+
await advancePayouts(ctx);
|
|
26
|
+
}
|
package/src/semantics/coupons.ts
CHANGED
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import type { Semantics, SemanticsContext } from '@volter/world-core';
|
|
6
|
-
import { asBool } from '../stripe-twin.ts';
|
|
7
|
-
import { at, at_, created, fail, list, newest, where, type Row } from './shared.ts';
|
|
1
|
+
// Stripe's coupons operations (docs/contributing/architecture.md, "What an author writes"): each handler over the
|
|
2
|
+
// handler contract's context, computing with the engine (../engine/).
|
|
3
|
+
import type { HandlerContext } from '@volter/world-core';
|
|
4
|
+
import { amountOffOf, created, fail } from './shared.ts';
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
function amountOffOf(ctx: SemanticsContext, params: Record<string, unknown>): number | Response {
|
|
11
|
-
const amountOff = Number(params.amount_off);
|
|
12
|
-
if (!Number.isInteger(amountOff) || amountOff <= 0) return fail(ctx, 'Invalid integer: amount_off must be a positive integer.', 400, 'parameter_invalid_integer');
|
|
13
|
-
if (params.currency === undefined || params.currency === '') return fail(ctx, 'Missing required param: currency.', 400, 'parameter_missing');
|
|
14
|
-
return amountOff;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const createCoupon: Semantics = async (ctx) => {
|
|
6
|
+
export async function PostCoupons(ctx: HandlerContext): Promise<Response> {
|
|
18
7
|
const params = ctx.params;
|
|
19
8
|
const hasPct = params.percent_off !== undefined;
|
|
20
9
|
const hasAmt = params.amount_off !== undefined;
|
|
@@ -43,55 +32,4 @@ const createCoupon: Semantics = async (ctx) => {
|
|
|
43
32
|
applies_to: null,
|
|
44
33
|
}),
|
|
45
34
|
);
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// a code the caller does not give is derived from the promotion code's id
|
|
49
|
-
const createPromotionCode: Semantics = async (ctx) => {
|
|
50
|
-
// what it promotes: promotion[type]=coupon and promotion[coupon] (2025-09-30.clover,
|
|
51
|
-
// docs.stripe.com/changelog/clover/2025-09-30/polymorphic-coupon), or the top-level coupon a caller pinned to an
|
|
52
|
-
// earlier version sends; the twin keeps the coupon's id on the code for its rules
|
|
53
|
-
const { promotion, coupon: legacy, ...params } = ctx.params;
|
|
54
|
-
const promo = promotion && typeof promotion === 'object' ? (promotion as Record<string, unknown>) : undefined;
|
|
55
|
-
if (promo && promo.type !== 'coupon') return fail(ctx, 'Invalid promotion[type]: must be coupon.', 400, 'parameter_invalid_string_enum');
|
|
56
|
-
const coupon = typeof promo?.coupon === 'string' ? promo.coupon : typeof legacy === 'string' ? legacy : '';
|
|
57
|
-
if (!coupon) return fail(ctx, promo ? 'Missing required param: promotion[coupon].' : 'Missing required param: promotion.', 400, 'parameter_missing');
|
|
58
|
-
if (!ctx.get('coupon', coupon)) return fail(ctx, `No such coupon: '${coupon}'`, 400, 'resource_missing');
|
|
59
|
-
const id = ctx.mint('promotion_code');
|
|
60
|
-
const code = typeof params.code === 'string' && params.code ? params.code : `TWIN${id.toUpperCase().replace(/[^A-Z0-9]/g, '')}`;
|
|
61
|
-
return ctx.reply(
|
|
62
|
-
await created(ctx, 'promotion_code', { ...params, coupon, promotion: { type: 'coupon', coupon }, id, code }, {
|
|
63
|
-
active: params.active !== undefined ? asBool(params.active) : true,
|
|
64
|
-
customer: typeof params.customer === 'string' ? params.customer : null,
|
|
65
|
-
expires_at: params.expires_at !== undefined ? Number(params.expires_at) : null,
|
|
66
|
-
max_redemptions: params.max_redemptions !== undefined ? Number(params.max_redemptions) : null,
|
|
67
|
-
times_redeemed: 0, livemode: false, metadata: {}, restrictions: { first_time_transaction: false, minimum_amount: null, minimum_amount_currency: null },
|
|
68
|
-
}),
|
|
69
|
-
);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
/** Time's lapsing of coupons, caught up to the World's clock: a valid coupon whose redeem_by has passed is no longer
|
|
73
|
-
* valid from that moment (manifest.ts). Where the documentation stops and the twin decides: the lapse sends no event
|
|
74
|
-
* (the events page names none for it). */
|
|
75
|
-
export async function lapseCoupons(ctx: SemanticsContext): Promise<void> {
|
|
76
|
-
const now = Number(ctx.now());
|
|
77
|
-
for (const c of ctx.rowsRaw('coupon')) {
|
|
78
|
-
if (c.valid !== true || typeof c.redeem_by !== 'number' || c.redeem_by > now) continue;
|
|
79
|
-
const t = await at_(ctx)(c.redeem_by);
|
|
80
|
-
t.legal('coupon', 'valid', ctx.call.operation.id, 'true', 'false', String(c.id), 'time');
|
|
81
|
-
await t.write('coupon', String(c.id), { valid: false }, 'coupon.lapsed');
|
|
82
|
-
}
|
|
83
35
|
}
|
|
84
|
-
|
|
85
|
-
const listPromotionCodes: Semantics = async (ctx) =>
|
|
86
|
-
list(ctx, 'promotion_code', where(ctx, newest(ctx, 'promotion_code'), {
|
|
87
|
-
code: (p, v) => p.code === v,
|
|
88
|
-
active: (p, v) => asBool(p.active) === asBool(v),
|
|
89
|
-
coupon: (p, v) => p.coupon === v,
|
|
90
|
-
customer: (p, v) => p.customer === v,
|
|
91
|
-
}));
|
|
92
|
-
|
|
93
|
-
export const coupons: Record<string, Semantics> = {
|
|
94
|
-
PostCoupons: createCoupon,
|
|
95
|
-
PostPromotionCodes: createPromotionCode,
|
|
96
|
-
GetPromotionCodes: listPromotionCodes,
|
|
97
|
-
};
|