@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
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// THE DASHBOARD — dashboard.stripe.com's pages an operator sets what Stripe's API cannot (docs/contributing/architecture.md,
|
|
2
|
+
// "Screens": a workspace): Public details (/settings/public) and the Connect OAuth settings
|
|
3
|
+
// (/settings/connect/onboarding-options/oauth). Stripe's other Dashboard pages answer Stripe's 404 here.
|
|
4
|
+
//
|
|
5
|
+
// THE DASHBOARD'S PUBLIC DETAILS — a workspace page (docs/contributing/architecture.md, "Screens") of the Dashboard
|
|
6
|
+
// (manifest screen `dashboard`), at dashboard.stripe.com/settings/public: Settings → Business → Public details, where
|
|
7
|
+
// the operator sets what their customers see ("Public Details → Business Settings",
|
|
8
|
+
// support.stripe.com/questions/update-account-name-shown-in-the-dashboard). Its business name is the account's
|
|
9
|
+
// `business_profile.name`, "The customer-facing business name" (docs.stripe.com/api/accounts/object), and it is the
|
|
10
|
+
// name Checkout shows: "You can change a Checkout page's name by modifying the Business name field"
|
|
11
|
+
// (docs.stripe.com/payments/checkout/customization/appearance). Stripe's API has no call that changes the platform's
|
|
12
|
+
// own account (POST /v1/accounts/{account} updates a connected one), so this page is the only way to the act, and
|
|
13
|
+
// saving it writes the platform account's stored record (../engine/connect.ts), which GET /v1/account,
|
|
14
|
+
// Checkout and the customer portal then read. The Dashboard's account name (`settings.dashboard.display_name`,
|
|
15
|
+
// Settings → Account) is a separate, internal setting, "for internal use only" (the same support article), and this
|
|
16
|
+
// page does not touch it. Authored from plain markup under Stripe's type; nothing of Stripe's page is copied.
|
|
17
|
+
//
|
|
18
|
+
// Where the documentation stops and the twin decides: the page holds the business name only (the real page's
|
|
19
|
+
// support details, website and statement descriptor are not modelled here); the name is required and at most 5000
|
|
20
|
+
// characters (the spec's bound on business_profile.name), which the browser holds the field to and the page checks
|
|
21
|
+
// again for a client that is not a browser; no sign-in guards the page. The form posts to the page's own address
|
|
22
|
+
// and the save redirects to its own path. Saving is an update of an account that exists (the first save included, though
|
|
23
|
+
// the twin stores the platform's record only then), so every save is sent as account.updated.
|
|
24
|
+
//
|
|
25
|
+
// THE DASHBOARD'S CONNECT OAUTH SETTINGS — a workspace page (docs/contributing/architecture.md, "Screens") of the
|
|
26
|
+
// Dashboard (manifest screen `dashboard`), at dashboard.stripe.com/settings/connect/onboarding-options/oauth, where a
|
|
27
|
+
// platform starts an OAuth integration: "Enable onboarding accounts with OAuth", "Copy your client_id, a unique
|
|
28
|
+
// identifier for your platform that's generated by Stripe", and "Set your redirect_uri ... You must specify all redirect
|
|
29
|
+
// URLs in your platform settings. If you don't include the redirect_uri parameter in your request, Stripe defaults to
|
|
30
|
+
// using the first address you've configured" (docs.stripe.com/connect/oauth-standard-accounts). The test client_id
|
|
31
|
+
// "allows you to: Set your redirect_uri to a non-HTTPS URL; Set your redirect_uri to localhost"
|
|
32
|
+
// (docs.stripe.com/connect/testing#using-oauth). Stripe's API has no call that reads or changes these settings (GET
|
|
33
|
+
// /v1/account does not carry the client_id), so this page is the only way to them, and connect.stripe.com/oauth/authorize
|
|
34
|
+
// (./connect-oauth.tsx) reads what it saves. Authored from plain markup under Stripe's type; nothing of Stripe's page is
|
|
35
|
+
// copied.
|
|
36
|
+
//
|
|
37
|
+
// Where the documentation stops and the twin decides: the World has one platform, and its test client_id is
|
|
38
|
+
// `ca_twin_self` (PLATFORM_CLIENT_ID; the platform account is acct_twin_self), the same in every World, shown on the
|
|
39
|
+
// page in an element a runner reads (data-testid="connect-client-id"). OAuth starts disabled. The page is served at
|
|
40
|
+
// the docs' address, at /test/… (the docs' test-mode link) and at /settings/connect (the address Cal.com's setup
|
|
41
|
+
// guide gives). The redirect URIs are one form field, one per line (or comma-separated, as the reference describes
|
|
42
|
+
// them), saved whole: each an absolute http or https URL without a fragment; http and localhost are allowed, as the
|
|
43
|
+
// test client_id allows them. No sign-in guards the page. The form posts to the
|
|
44
|
+
// page's own address and the save redirects to it.
|
|
45
|
+
import type { HandlerContext } from '@volter/world-core';
|
|
46
|
+
import { flowPage } from '@volter/world-ui';
|
|
47
|
+
import type { Row } from '../engine/common.ts';
|
|
48
|
+
import { platformAccountDefault } from '../engine/connect.ts';
|
|
49
|
+
import { CONNECT_SETTINGS_PATH, type ConnectOAuthSettings, PLATFORM_CLIENT_ID, redirectUris, SETTINGS } from '../engine/connect-settings.ts';
|
|
50
|
+
import { publicBusinessName } from '../engine/display.ts';
|
|
51
|
+
import { PLATFORM_ACCOUNT_ID } from '../engine/stripe.ts';
|
|
52
|
+
import { formOf, notFound } from './shared.tsx';
|
|
53
|
+
import { connectOAuthSettings, created } from '../semantics/shared.ts';
|
|
54
|
+
|
|
55
|
+
const PUBLIC_DETAILS_PATH = '/settings/public';
|
|
56
|
+
const MAX_NAME = 5000;
|
|
57
|
+
|
|
58
|
+
const SETTINGS_CSS = `
|
|
59
|
+
* { box-sizing: border-box; }
|
|
60
|
+
body { margin: 0; background: #f6f8fa; color: #1a1f36; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Ubuntu, sans-serif; font-size: 14px; }
|
|
61
|
+
.co { max-width: 680px; margin: 48px auto; padding: 0 16px; }
|
|
62
|
+
.co h1 { font-size: 28px; margin: 16px 0 4px; }
|
|
63
|
+
.co-lead { color: #697386; margin: 0 0 24px; }
|
|
64
|
+
.co-card { background: #ffffff; border: 1px solid #e3e8ee; border-radius: 8px; padding: 24px; margin-bottom: 16px; }
|
|
65
|
+
.co-card h2 { font-size: 16px; margin: 0 0 12px; }
|
|
66
|
+
.co-card label { display: block; font-weight: 600; margin-bottom: 6px; }
|
|
67
|
+
.co-card small { display: block; color: #697386; margin-bottom: 8px; }
|
|
68
|
+
.co-card textarea { width: 100%; min-height: 96px; padding: 8px 10px; border: 1px solid #c1c9d2; border-radius: 6px; font: 13px ui-monospace, Menlo, monospace; }
|
|
69
|
+
.co-id { font: 14px ui-monospace, Menlo, monospace; background: #f6f8fa; border: 1px solid #e3e8ee; border-radius: 6px; padding: 6px 10px; display: inline-block; }
|
|
70
|
+
.co-toggle { display: flex; gap: 8px; align-items: center; font-weight: 600; }
|
|
71
|
+
.co-actions { display: flex; justify-content: flex-end; }
|
|
72
|
+
.co-actions button { background: #635bff; color: #ffffff; border: 0; border-radius: 6px; padding: 8px 16px; font: inherit; font-weight: 600; cursor: pointer; }
|
|
73
|
+
.co-notice { padding: 10px 12px; border-radius: 6px; margin-bottom: 16px; }
|
|
74
|
+
.co-error { background: #fff0f3; color: #df1b41; }
|
|
75
|
+
.co-saved { background: #e7f8ef; color: #0e6245; }
|
|
76
|
+
.co-uris { margin: 0; padding-left: 18px; }
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
function settingsPage(s: ConnectOAuthSettings, opts: { value?: string; enabled?: boolean; error?: string; saved?: boolean } = {}): Response {
|
|
80
|
+
const enabled = opts.enabled ?? s.oauth_enabled;
|
|
81
|
+
return flowPage({
|
|
82
|
+
title: 'OAuth – Connect settings – Stripe',
|
|
83
|
+
css: [SETTINGS_CSS],
|
|
84
|
+
...(opts.error ? { status: 400 } : {}),
|
|
85
|
+
body: (
|
|
86
|
+
<main className="co">
|
|
87
|
+
<h1>OAuth</h1>
|
|
88
|
+
<p className="co-lead">Onboard Standard accounts by sending them to Stripe's OAuth flow. Test mode.</p>
|
|
89
|
+
{opts.error ? <p className="co-notice co-error" role="alert">{opts.error}</p> : null}
|
|
90
|
+
{opts.saved ? <p className="co-notice co-saved" role="status">Your OAuth settings were saved.</p> : null}
|
|
91
|
+
<section className="co-card">
|
|
92
|
+
<h2>Test mode client ID</h2>
|
|
93
|
+
<small>The client_id your platform sends to connect.stripe.com/oauth/authorize.</small>
|
|
94
|
+
<code className="co-id" data-testid="connect-client-id">{PLATFORM_CLIENT_ID}</code>
|
|
95
|
+
<p data-testid="connect-oauth-status">{s.oauth_enabled ? 'OAuth is enabled.' : 'OAuth is disabled.'}</p>
|
|
96
|
+
{s.redirect_uris.length ? <ul className="co-uris">{s.redirect_uris.map((u) => <li key={u} data-testid="connect-redirect-uri">{u}</li>)}</ul> : <p>No redirect URIs.</p>}
|
|
97
|
+
</section>
|
|
98
|
+
<form className="co-card" method="post">
|
|
99
|
+
<label className="co-toggle" htmlFor="oauth_enabled"><input type="checkbox" id="oauth_enabled" name="oauth_enabled" value="on" defaultChecked={enabled} /> Enable OAuth for Standard accounts</label>
|
|
100
|
+
<label htmlFor="redirect_uris">Redirect URIs</label>
|
|
101
|
+
<small>One per line. The first is used when a request names none. http and localhost are allowed in test mode.</small>
|
|
102
|
+
<textarea id="redirect_uris" name="redirect_uris" defaultValue={opts.value ?? s.redirect_uris.join('\n')} />
|
|
103
|
+
<div className="co-actions"><button type="submit">Save</button></div>
|
|
104
|
+
</form>
|
|
105
|
+
</main>
|
|
106
|
+
),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const DETAILS_CSS = `
|
|
111
|
+
* { box-sizing: border-box; }
|
|
112
|
+
body { margin: 0; background: #f6f8fa; color: #1a1f36; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Ubuntu, sans-serif; font-size: 14px; }
|
|
113
|
+
.pd { max-width: 640px; margin: 48px auto; padding: 0 16px; }
|
|
114
|
+
.pd h1 { font-size: 28px; margin: 16px 0 4px; }
|
|
115
|
+
.pd-lead { color: #697386; margin: 0 0 24px; }
|
|
116
|
+
.pd-card { background: #ffffff; border: 1px solid #e3e8ee; border-radius: 8px; padding: 24px; }
|
|
117
|
+
.pd-card label { display: block; font-weight: 600; margin-bottom: 6px; }
|
|
118
|
+
.pd-card small { display: block; color: #697386; margin-bottom: 8px; }
|
|
119
|
+
.pd-card input { width: 100%; padding: 8px 10px; border: 1px solid #c1c9d2; border-radius: 6px; font: inherit; }
|
|
120
|
+
.pd-actions { margin-top: 20px; display: flex; justify-content: flex-end; }
|
|
121
|
+
.pd-actions button { background: #635bff; color: #ffffff; border: 0; border-radius: 6px; padding: 8px 16px; font: inherit; font-weight: 600; cursor: pointer; }
|
|
122
|
+
.pd-notice { padding: 10px 12px; border-radius: 6px; margin-bottom: 16px; }
|
|
123
|
+
.pd-error { background: #fff0f3; color: #df1b41; }
|
|
124
|
+
.pd-saved { background: #e7f8ef; color: #0e6245; }
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
function detailsPage(account: Row | undefined, opts: { value?: string; error?: string; saved?: boolean } = {}): Response {
|
|
128
|
+
const named = (account?.business_profile as Row | undefined)?.name;
|
|
129
|
+
const value = opts.value ?? (typeof named === 'string' ? named : '');
|
|
130
|
+
return flowPage({
|
|
131
|
+
title: 'Public details – Stripe',
|
|
132
|
+
css: [DETAILS_CSS],
|
|
133
|
+
...(opts.error ? { status: 400 } : {}),
|
|
134
|
+
body: (
|
|
135
|
+
<main className="pd">
|
|
136
|
+
<h1>Public details</h1>
|
|
137
|
+
<p className="pd-lead">What your customers see on Checkout, the customer portal, receipts and invoices. Currently shown: <strong data-testid="public-business-name">{publicBusinessName(account)}</strong></p>
|
|
138
|
+
{opts.error ? <p className="pd-notice pd-error" role="alert">{opts.error}</p> : null}
|
|
139
|
+
{opts.saved ? <p className="pd-notice pd-saved" role="status">Your public details were saved.</p> : null}
|
|
140
|
+
<form className="pd-card" method="post">
|
|
141
|
+
<label htmlFor="business_name">Business name</label>
|
|
142
|
+
<small>The name customers see when they pay you.</small>
|
|
143
|
+
<input id="business_name" name="business_name" autoComplete="organization" defaultValue={value} required maxLength={MAX_NAME} />
|
|
144
|
+
<div className="pd-actions"><button type="submit">Save</button></div>
|
|
145
|
+
</form>
|
|
146
|
+
</main>
|
|
147
|
+
),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Save the business name on the platform's own account: the stored record, made from Stripe's default the first
|
|
152
|
+
* time anything is saved, and otherwise changed in place (its other public details kept). */
|
|
153
|
+
async function save(ctx: HandlerContext, name: string): Promise<void> {
|
|
154
|
+
const held = ctx.get('account', PLATFORM_ACCOUNT_ID);
|
|
155
|
+
const profile = { business_profile: { ...(((held ?? platformAccountDefault()).business_profile as Row | undefined) ?? {}), name } };
|
|
156
|
+
await (held ? ctx.write('account', PLATFORM_ACCOUNT_ID, profile, 'account.update') : created(ctx, 'account', { ...platformAccountDefault(), ...profile }, {}, { operation: 'account.update' }));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** What the page says of a name no browser sends (the field is required and bounded), for a client that sends it. */
|
|
160
|
+
function nameRefused(name: string): string | undefined {
|
|
161
|
+
return !name ? 'Enter your business name.' : name.length > MAX_NAME ? `Your business name must be at most ${MAX_NAME} characters.` : undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function settings(ctx: HandlerContext): Promise<Response> {
|
|
165
|
+
const request = ctx.call.request;
|
|
166
|
+
const url = new URL(request.url);
|
|
167
|
+
if (request.method !== 'GET' && request.method !== 'POST') return notFound();
|
|
168
|
+
const form = request.method === 'POST' ? await formOf(request.clone()) : {};
|
|
169
|
+
const held = connectOAuthSettings(ctx);
|
|
170
|
+
if (request.method === 'GET') return settingsPage(held, { saved: url.searchParams.has('saved') });
|
|
171
|
+
const enabled = form.oauth_enabled === 'on' || form.oauth_enabled === 'true';
|
|
172
|
+
const parsed = redirectUris(form.redirect_uris ?? '');
|
|
173
|
+
if ('refused' in parsed) return settingsPage(held, { value: form.redirect_uris ?? '', enabled, error: parsed.refused });
|
|
174
|
+
await ctx.record(SETTINGS, { oauth_enabled: enabled, redirect_uris: parsed.uris }, PLATFORM_CLIENT_ID);
|
|
175
|
+
return new Response(null, { status: 303, headers: { location: '?saved=1' } });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function publicDetails(ctx: HandlerContext): Promise<Response> {
|
|
179
|
+
const request = ctx.call.request;
|
|
180
|
+
const url = new URL(request.url);
|
|
181
|
+
if (request.method !== 'GET' && request.method !== 'POST') return notFound();
|
|
182
|
+
const form = request.method === 'POST' ? await formOf(request.clone()) : {};
|
|
183
|
+
if (request.method === 'GET') return detailsPage(ctx.get('account', PLATFORM_ACCOUNT_ID), { saved: url.searchParams.has('saved') });
|
|
184
|
+
const name = (form.business_name ?? '').trim();
|
|
185
|
+
const refused = nameRefused(name);
|
|
186
|
+
if (refused) return detailsPage(ctx.get('account', PLATFORM_ACCOUNT_ID), { value: form.business_name ?? '', error: refused });
|
|
187
|
+
await save(ctx, name);
|
|
188
|
+
// a query-only reference: the page's own path, wherever it is mounted and with or without its trailing slash
|
|
189
|
+
return new Response(null, { status: 303, headers: { location: '?saved=1' } });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** dashboard.stripe.com's pages: Public details and the Connect OAuth settings; Stripe's 404 for any other. */
|
|
193
|
+
export async function screen(ctx: HandlerContext): Promise<Response> {
|
|
194
|
+
const path = new URL(ctx.call.request.url).pathname.replace(/\/+$/, '');
|
|
195
|
+
if (path === PUBLIC_DETAILS_PATH) return publicDetails(ctx);
|
|
196
|
+
if (CONNECT_SETTINGS_PATH.test(path)) return settings(ctx);
|
|
197
|
+
return notFound();
|
|
198
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// What Stripe's screens share (docs/contributing/architecture.md, "Pack layout": screens/shared.tsx): the skin over
|
|
2
|
+
// @volter/world-ui's consent piece for the flows where a person agrees to something (connected-account onboarding,
|
|
3
|
+
// identity verification, linking a bank), a page's form, and the card a customer types on Checkout and the portal.
|
|
4
|
+
export const STRIPE_CONSENT_SKIN = `
|
|
5
|
+
body { background: #f6f9fc; color: #1a1f36; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Ubuntu, sans-serif; }
|
|
6
|
+
.consent-badge { background: #ffffff; border: 1px solid #e3e8ee; color: #1a1f36; }
|
|
7
|
+
.consent-vendor { background: #635bff; border-color: #635bff; }
|
|
8
|
+
.consent-link { border-color: #c1c9d2; }
|
|
9
|
+
.consent-box { background: #ffffff; border-color: #e3e8ee; }
|
|
10
|
+
.consent-who small, .consent-permission summary small, .consent-note { color: #697386; }
|
|
11
|
+
.consent-actions { border-color: #e3e8ee; }
|
|
12
|
+
.consent-deny { background: #ffffff; border-color: #c1c9d2; color: #1a1f36; }
|
|
13
|
+
.consent-allow { background: #635bff; border-color: #635bff; color: #ffffff; }
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
/** The form fields a page's POST carries, as a browser posts them. */
|
|
17
|
+
export async function formOf(request: Request): Promise<Record<string, string>> {
|
|
18
|
+
return Object.fromEntries(new URLSearchParams(await request.text()));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const seeOther = (location: string): Response => new Response(null, { status: 302, headers: { location } });
|
|
22
|
+
|
|
23
|
+
export const DECLINES: Record<string, string> = {
|
|
24
|
+
'4000000000000002': 'Your card was declined.',
|
|
25
|
+
'4000000000009995': 'Your card has insufficient funds.',
|
|
26
|
+
'4000000000000069': 'Your card has expired.',
|
|
27
|
+
'4000000000000127': "Your card's security code is incorrect.",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const luhn = (digits: string): boolean => {
|
|
31
|
+
let sum = 0;
|
|
32
|
+
for (let i = 0; i < digits.length; i++) {
|
|
33
|
+
let d = Number(digits[digits.length - 1 - i]);
|
|
34
|
+
if (i % 2 === 1) { d *= 2; if (d > 9) d -= 9; }
|
|
35
|
+
sum += d;
|
|
36
|
+
}
|
|
37
|
+
return digits.length >= 12 && sum % 10 === 0;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const money = (minor: unknown, currency: unknown): string => {
|
|
41
|
+
const c = typeof currency === 'string' ? currency.toUpperCase() : 'USD';
|
|
42
|
+
const zeroDecimal = ['JPY', 'KRW', 'VND', 'CLP', 'ISK', 'UGX', 'XAF', 'XOF'].includes(c);
|
|
43
|
+
return new Intl.NumberFormat('en-US', { style: 'currency', currency: c }).format((Number(minor) || 0) / (zeroDecimal ? 1 : 100));
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const COUNTRIES = [['US', 'United States'], ['GB', 'United Kingdom'], ['CA', 'Canada'], ['DE', 'Germany'], ['FR', 'France'], ['AU', 'Australia'], ['JP', 'Japan']].map(([value, label]) => ({ value: value!, label: label! }));
|
|
47
|
+
|
|
48
|
+
/** Cards that are saved but decline when charged. */
|
|
49
|
+
export const DECLINES_WHEN_CHARGED: Record<string, string> = { '4000000000000341': 'Your card was declined.' };
|
|
50
|
+
|
|
51
|
+
/** The expiry as the customer typed it. Stripe's field formats itself as it is typed: the digits `1230` read `12 / 30`,
|
|
52
|
+
* and a first digit above 1 is a month on its own (`5` reads `05 / `), so a person types the digits straight through
|
|
53
|
+
* (Checkout's own field, seen on checkout.stripe.com; docs.stripe.com/payments/checkout names no format to type). The
|
|
54
|
+
* page's script (@volter/world-ui Payment, `format: 'card-expiry'`) shows it formatted; this reads what is posted either
|
|
55
|
+
* way: `MM / YY`, `MM/YYYY`, or the bare digits `MMYY`, `MYY` (`530`) and `MMYYYY`; a month past 12 is read, and refused
|
|
56
|
+
* as invalid by cardAnswer. */
|
|
57
|
+
export function parseExpiry(raw: string): { month: number; year: number } | undefined {
|
|
58
|
+
const split = /^\s*(\d{1,2})\s*\/\s*(\d{2}|\d{4})\s*$/.exec(raw);
|
|
59
|
+
const digits = raw.replace(/\s/g, '');
|
|
60
|
+
const bare = split ? undefined : /^(\d{2})(\d{2}|\d{4})$/.exec(digits) ?? /^([2-9])(\d{2})$/.exec(digits);
|
|
61
|
+
const m = split ?? bare;
|
|
62
|
+
if (!m) return undefined;
|
|
63
|
+
return { month: Number(m[1]), year: m[2]!.length === 2 ? 2000 + Number(m[2]) : Number(m[2]) };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The card's answer: undefined when it pays, else what Checkout says under the button. */
|
|
67
|
+
export function cardAnswer(v: Record<string, string>, nowSeconds: number, chargesNow: boolean): string | undefined {
|
|
68
|
+
const digits = (v.cardNumber ?? '').replace(/\D/g, '');
|
|
69
|
+
if (!digits) return 'Your card number is incomplete.';
|
|
70
|
+
if (DECLINES[digits]) return DECLINES[digits];
|
|
71
|
+
if (chargesNow && DECLINES_WHEN_CHARGED[digits]) return DECLINES_WHEN_CHARGED[digits];
|
|
72
|
+
if (!luhn(digits)) return 'Your card number is invalid.';
|
|
73
|
+
const exp = parseExpiry(v.cardExpiry ?? '');
|
|
74
|
+
if (!exp) return "Your card's expiration date is incomplete.";
|
|
75
|
+
const { month, year } = exp;
|
|
76
|
+
if (month < 1 || month > 12) return "Your card's expiration date is invalid.";
|
|
77
|
+
const now = new Date(nowSeconds * 1000);
|
|
78
|
+
if (year < now.getUTCFullYear() || (year === now.getUTCFullYear() && month < now.getUTCMonth() + 1)) return "Your card's expiration year is in the past.";
|
|
79
|
+
if (!/^\d{3,4}$/.test(v.cardCvc ?? '')) return "Your card's security code is incomplete.";
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Stripe's answer for a page of one of its hosts the twin does not serve. */
|
|
84
|
+
export const notFound = (): Response => new Response('Not Found', { status: 404, headers: { 'content-type': 'text/plain; charset=utf-8' } });
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// STRIPE.JS — the client library an application's page loads from js.stripe.com, served as a content screen
|
|
2
|
+
// (docs/contributing/architecture.md, "Screens": a widget the vendor ships into the application's own page "is not a screen: it is a client library,
|
|
3
|
+
// twinned from its published types as an SDK surface is"). Written from @stripe/stripe-js's published loader and types
|
|
4
|
+
// (7.x: dist/index.mjs, dist/stripe-js/stripe.d.ts, dist/stripe-js/hosted-checkout.d.ts), nothing of Stripe's script
|
|
5
|
+
// copied:
|
|
6
|
+
// - the loader injects https://js.stripe.com/<release train>/stripe.js (or /v3/), and accepts a script already on the
|
|
7
|
+
// page whose src matches `/^https:\/\/js\.stripe\.com\/(v3|[a-z]+)\/stripe\.js(\?.*)?$/` (so /v3/stripe.js too);
|
|
8
|
+
// it resolves `window.Stripe`, reads `Stripe.version` (3 is the v3 URL, otherwise the train's name) and calls the
|
|
9
|
+
// instance's `_registerWrapper` when it has one;
|
|
10
|
+
// - `stripe.redirectToCheckout({ sessionId })` sends the customer to that Checkout Session's page (hosted-checkout.d.ts,
|
|
11
|
+
// RedirectToCheckoutServerOptions), which this twin serves at checkout.stripe.com/c/pay/{id} (screens/checkout.tsx),
|
|
12
|
+
// and its promise settles only on failure (`Promise<never | {error}>`);
|
|
13
|
+
// - `stripe.collectFinancialConnectionsAccounts({ clientSecret })` loads the Financial Connections authentication
|
|
14
|
+
// flow for the session whose client_secret it is (stripe.d.ts; docs.stripe.com/js/financial_connections/
|
|
15
|
+
// collect_financial_connections_accounts: "it will load the Authentication Flow, an on-page modal UI"). Where the
|
|
16
|
+
// twin decides: it navigates to the flow's page, js.stripe.com/v3/financial-connections/{client_secret}
|
|
17
|
+
// (screens/financial-connections.tsx), instead of a modal, so its promise does not settle; the flow returns the
|
|
18
|
+
// holder to the session's return_url, where the application reads the session's accounts.
|
|
19
|
+
// - `stripe.elements({ clientSecret?, ... })` groups elements; `elements.create('payment' | 'card')` makes one, which
|
|
20
|
+
// `mount`s into the page and emits `ready` and `change` ({ complete, empty }); `elements.submit()` checks the fields
|
|
21
|
+
// (stripe.d.ts: StripeElements, StripePaymentElement). Where the twin decides: an element is the page's own card
|
|
22
|
+
// fields (number, expiry, CVC), not Stripe's iframe;
|
|
23
|
+
// - `stripe.confirmPayment({ elements, clientSecret?, confirmParams: { return_url }, redirect? })` and
|
|
24
|
+
// `stripe.confirmSetup(...)` confirm the intent whose client secret it is with the card the element holds
|
|
25
|
+
// (`POST /v1/payment_intents/{id}/confirm`, `/v1/setup_intents/{id}/confirm`, with the publishable key), then send
|
|
26
|
+
// the customer to `return_url` with `payment_intent` (or `setup_intent`), its client secret and `redirect_status`,
|
|
27
|
+
// or, with `redirect: 'if_required'`, resolve `{ paymentIntent }` (`{ setupIntent }`); a refusal resolves
|
|
28
|
+
// `{ error }` (docs.stripe.com/js/payment_intents/confirm_payment).
|
|
29
|
+
// Demand: Dub's upgrade button (apps/web/ui/workspaces/upgrade-plan-button.tsx) and cal.com's sign-up call
|
|
30
|
+
// redirectToCheckout; cal.com's booking payment form (packages/platform/atoms/event-types/payments/StripePaymentForm.tsx)
|
|
31
|
+
// and Twenty's billing forms (AddPaymentMethodForm.tsx, useSubmitSubscriptionPayment.ts) confirm intents through
|
|
32
|
+
// Elements. Every other member of the library throws, naming itself, when called: an application reaching one finds the
|
|
33
|
+
// gap instead of a silent no-op.
|
|
34
|
+
import type { HandlerContext } from '@volter/world-core';
|
|
35
|
+
import { notFound } from './shared.tsx';
|
|
36
|
+
|
|
37
|
+
/** The release train a script path names: `/v3`, `/v3/` or `/v3/stripe.js` → 3, `/<train>/stripe.js` → the train. */
|
|
38
|
+
function stripeJsTrain(pathname: string): 3 | string | undefined {
|
|
39
|
+
if (/^\/v3(\/|\/stripe\.js)?$/.test(pathname)) return 3;
|
|
40
|
+
return /^\/([a-z]+)\/stripe\.js$/.exec(pathname)?.[1];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function script(version: 3 | string): string {
|
|
44
|
+
return `(function () {
|
|
45
|
+
var version = ${JSON.stringify(version)};
|
|
46
|
+
function gap(name) {
|
|
47
|
+
return function () { throw new Error("Stripe.js (Volter twin): stripe." + name + " is not modelled by this twin yet"); };
|
|
48
|
+
}
|
|
49
|
+
function Stripe(publishableKey) {
|
|
50
|
+
if (typeof publishableKey !== "string" || !publishableKey) throw new Error("Stripe.js (Volter twin): Stripe() needs a publishable key");
|
|
51
|
+
var api = "https://api.stripe.com/v1";
|
|
52
|
+
// the card fields every mounted element holds, the last mounted first
|
|
53
|
+
var mounted = [];
|
|
54
|
+
function cardOf(elements) {
|
|
55
|
+
var el = (elements && elements._mounted && elements._mounted[elements._mounted.length - 1]) || mounted[mounted.length - 1];
|
|
56
|
+
if (!el) return null;
|
|
57
|
+
var exp = String(el.expiry.value || "").split("/");
|
|
58
|
+
return { number: String(el.number.value || "").replace(/\s+/g, ""), exp_month: exp[0] ? String(Number(exp[0])) : "", exp_year: exp[1] ? String(2000 + Number(exp[1]) % 100) : "", cvc: String(el.cvc.value || "") };
|
|
59
|
+
}
|
|
60
|
+
function form(fields) {
|
|
61
|
+
return Object.keys(fields).filter(function (k) { return fields[k] !== undefined && fields[k] !== ""; }).map(function (k) { return encodeURIComponent(k) + "=" + encodeURIComponent(fields[k]); }).join("&");
|
|
62
|
+
}
|
|
63
|
+
function confirm(kind, options) {
|
|
64
|
+
options = options || {};
|
|
65
|
+
var elements = options.elements;
|
|
66
|
+
var secret = options.clientSecret || (elements && elements._options && elements._options.clientSecret);
|
|
67
|
+
if (typeof secret !== "string" || secret.indexOf("_secret_") < 0) return Promise.resolve({ error: { type: "invalid_request_error", message: "A clientSecret is required." } });
|
|
68
|
+
var id = secret.split("_secret_")[0];
|
|
69
|
+
var card = cardOf(elements);
|
|
70
|
+
var params = options.confirmParams || {};
|
|
71
|
+
var fields = { client_secret: secret };
|
|
72
|
+
if (card) { fields["payment_method_data[type]"] = "card"; fields["payment_method_data[card][number]"] = card.number; fields["payment_method_data[card][exp_month]"] = card.exp_month; fields["payment_method_data[card][exp_year]"] = card.exp_year; fields["payment_method_data[card][cvc]"] = card.cvc; }
|
|
73
|
+
if (params.return_url) fields.return_url = params.return_url;
|
|
74
|
+
var path = kind === "payment" ? "/payment_intents/" : "/setup_intents/";
|
|
75
|
+
return fetch(api + path + encodeURIComponent(id) + "/confirm", { method: "POST", headers: { authorization: "Bearer " + publishableKey, "content-type": "application/x-www-form-urlencoded" }, body: form(fields) })
|
|
76
|
+
.then(function (res) { return res.json().then(function (body) { return { ok: res.ok, body: body }; }); })
|
|
77
|
+
.then(function (r) {
|
|
78
|
+
if (!r.ok) return { error: r.body.error || { type: "api_error", message: "The confirmation failed." } };
|
|
79
|
+
var key = kind === "payment" ? "paymentIntent" : "setupIntent";
|
|
80
|
+
var out = {}; out[key] = r.body;
|
|
81
|
+
if (options.redirect === "if_required" || !params.return_url) return out;
|
|
82
|
+
var q = kind === "payment" ? "payment_intent" : "setup_intent";
|
|
83
|
+
var target = new URL(params.return_url);
|
|
84
|
+
target.searchParams.set(q, r.body.id);
|
|
85
|
+
target.searchParams.set(q + "_client_secret", secret);
|
|
86
|
+
target.searchParams.set("redirect_status", r.body.status === "succeeded" ? "succeeded" : r.body.status === "processing" ? "processing" : "failed");
|
|
87
|
+
window.location.assign(target.toString());
|
|
88
|
+
return new Promise(function () {});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function element(type, group) {
|
|
92
|
+
var handlers = {};
|
|
93
|
+
function emit(name, event) { (handlers[name] || []).forEach(function (h) { h(event); }); }
|
|
94
|
+
var el = {
|
|
95
|
+
_type: type,
|
|
96
|
+
mount: function (target) {
|
|
97
|
+
var node = typeof target === "string" ? document.querySelector(target) : target;
|
|
98
|
+
if (!node) throw new Error("Stripe.js (Volter twin): mount() found no element " + String(target));
|
|
99
|
+
node.innerHTML = "";
|
|
100
|
+
var wrap = document.createElement("div");
|
|
101
|
+
wrap.setAttribute("data-volter-twin-element", type);
|
|
102
|
+
function input(name, placeholder) { var i = document.createElement("input"); i.name = name; i.placeholder = placeholder; i.autocomplete = "off"; i.addEventListener("input", change); wrap.appendChild(i); return i; }
|
|
103
|
+
el.number = input("cardnumber", "Card number");
|
|
104
|
+
el.expiry = input("exp-date", "MM / YY");
|
|
105
|
+
el.cvc = input("cvc", "CVC");
|
|
106
|
+
node.appendChild(wrap);
|
|
107
|
+
mounted.push(el); group._mounted.push(el);
|
|
108
|
+
setTimeout(function () { emit("ready", { elementType: type }); }, 0);
|
|
109
|
+
},
|
|
110
|
+
on: function (name, handler) { (handlers[name] = handlers[name] || []).push(handler); return el; },
|
|
111
|
+
off: function (name, handler) { handlers[name] = (handlers[name] || []).filter(function (h) { return h !== handler; }); return el; },
|
|
112
|
+
update: function () {}, focus: function () { if (el.number) el.number.focus(); }, blur: function () {},
|
|
113
|
+
clear: function () { [el.number, el.expiry, el.cvc].forEach(function (i) { if (i) i.value = ""; }); change(); },
|
|
114
|
+
unmount: function () {}, destroy: function () { mounted = mounted.filter(function (m) { return m !== el; }); }
|
|
115
|
+
};
|
|
116
|
+
function change() {
|
|
117
|
+
var c = { number: el.number && el.number.value, expiry: el.expiry && el.expiry.value, cvc: el.cvc && el.cvc.value };
|
|
118
|
+
emit("change", { elementType: type, empty: !c.number && !c.expiry && !c.cvc, complete: !!(c.number && c.expiry && c.cvc), value: { type: "card" } });
|
|
119
|
+
}
|
|
120
|
+
return el;
|
|
121
|
+
}
|
|
122
|
+
var known = {
|
|
123
|
+
_registerWrapper: function () {},
|
|
124
|
+
elements: function (options) {
|
|
125
|
+
var group = { _options: options || {}, _mounted: [], _created: {} };
|
|
126
|
+
group.create = function (type) { var e = element(type, group); group._created[type] = e; return e; };
|
|
127
|
+
group.getElement = function (type) { return group._created[typeof type === "string" ? type : "card"] || null; };
|
|
128
|
+
group.update = function (next) { group._options = Object.assign({}, group._options, next || {}); };
|
|
129
|
+
group.fetchUpdates = function () { return Promise.resolve({}); };
|
|
130
|
+
group.submit = function () {
|
|
131
|
+
var card = cardOf(group);
|
|
132
|
+
if (!card || !card.number || !card.exp_month || !card.cvc) return Promise.resolve({ error: { type: "validation_error", message: "Your card details are incomplete." } });
|
|
133
|
+
return Promise.resolve({});
|
|
134
|
+
};
|
|
135
|
+
return group;
|
|
136
|
+
},
|
|
137
|
+
confirmPayment: function (options) { return confirm("payment", options); },
|
|
138
|
+
confirmSetup: function (options) { return confirm("setup", options); },
|
|
139
|
+
redirectToCheckout: function (options) {
|
|
140
|
+
if (!options || typeof options.sessionId !== "string" || !options.sessionId) return gap("redirectToCheckout without a sessionId")();
|
|
141
|
+
window.location.assign("https://checkout.stripe.com/c/pay/" + encodeURIComponent(options.sessionId));
|
|
142
|
+
return new Promise(function () {});
|
|
143
|
+
},
|
|
144
|
+
collectFinancialConnectionsAccounts: function (options) {
|
|
145
|
+
if (!options || typeof options.clientSecret !== "string" || !options.clientSecret) return gap("collectFinancialConnectionsAccounts without a clientSecret")();
|
|
146
|
+
window.location.assign("https://js.stripe.com/v3/financial-connections/" + encodeURIComponent(options.clientSecret));
|
|
147
|
+
return new Promise(function () {});
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
return new Proxy(known, {
|
|
151
|
+
get: function (target, name) {
|
|
152
|
+
if (name in target) return target[name];
|
|
153
|
+
if (typeof name === "symbol" || name === "then") return undefined;
|
|
154
|
+
return gap(String(name));
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
Stripe.version = version;
|
|
159
|
+
window.Stripe = Stripe;
|
|
160
|
+
})();
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** js.stripe.com's script for a GET of its path; Stripe's 404 for anything else there. */
|
|
165
|
+
export async function screen(ctx: HandlerContext): Promise<Response> {
|
|
166
|
+
const request = ctx.call.request;
|
|
167
|
+
const train = request.method === 'GET' || request.method === 'HEAD' ? stripeJsTrain(new URL(request.url).pathname) : undefined;
|
|
168
|
+
if (train === undefined) return notFound();
|
|
169
|
+
return new Response(script(train), { headers: { 'content-type': 'application/javascript; charset=utf-8', 'access-control-allow-origin': '*' } });
|
|
170
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Stripe's account_links 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 { nowUnix } from '../engine/stripe.ts';
|
|
5
|
+
import { accountMissing, fail } from './shared.ts';
|
|
6
|
+
|
|
7
|
+
// ── hosted onboarding links and embedded-component sessions ──
|
|
8
|
+
export async function PostAccountLinks(ctx: HandlerContext): Promise<Response> {
|
|
9
|
+
const account = typeof ctx.params.account === 'string' ? ctx.params.account : '';
|
|
10
|
+
if (!account) return fail(ctx, 'Missing required param: account.', 400, 'parameter_missing');
|
|
11
|
+
if (!ctx.get('account', account)) return accountMissing(ctx, account, 400);
|
|
12
|
+
const linkType = typeof ctx.params.type === 'string' ? ctx.params.type : '';
|
|
13
|
+
if (linkType !== 'account_onboarding' && linkType !== 'account_update') return fail(ctx, 'Invalid account link type: must be account_onboarding or account_update.', 400, 'parameter_invalid_string_enum');
|
|
14
|
+
// the owner is sent back to return_url when done, and to refresh_url for a new link once this one is used or expired
|
|
15
|
+
for (const k of ['refresh_url', 'return_url']) if (typeof ctx.params[k] !== 'string' || !ctx.params[k]) return fail(ctx, `Missing required param: ${k}.`, 400, 'parameter_missing');
|
|
16
|
+
const now = Number(nowUnix(ctx.occurredAt));
|
|
17
|
+
const id = ctx.mint('account_link');
|
|
18
|
+
const link = { object: 'account_link', created: now, expires_at: now + 300, url: `https://connect.stripe.com/setup/${id}` };
|
|
19
|
+
await ctx.write('account_link', id, { ...link, account, type: linkType, refresh_url: ctx.params.refresh_url, return_url: ctx.params.return_url, used: false }, 'account_link.create');
|
|
20
|
+
return ctx.reply(link);
|
|
21
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Stripe's accounts 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 { externalList, type Row } from '../engine/common.ts';
|
|
5
|
+
import { capabilityBody, controllerOf, expressRequirements, keptKyc, keptPerson, RELATIONSHIP } from '../engine/connect.ts';
|
|
6
|
+
import { OAUTH_CONNECTIONS } from '../engine/display.ts';
|
|
7
|
+
import { ACCOUNT_TYPES, accountCapabilities, accountRequirements, accountSettings, asBool, nowUnix, PLATFORM_ACCOUNT_ID } from '../engine/stripe.ts';
|
|
8
|
+
import { accountExternal, accountMissing, accountPerson, accountRow, at, connectReview, created, externalMissing, fail, list, newest, path, personMissing, refuseExternalWrite, syncExternals } from './shared.ts';
|
|
9
|
+
|
|
10
|
+
export async function GetAccountsAccountExternalAccounts(ctx: HandlerContext): Promise<Response> {
|
|
11
|
+
const account = at(ctx, 'account');
|
|
12
|
+
if (!accountRow(ctx, account)) return accountMissing(ctx, account);
|
|
13
|
+
return list(ctx, 'external_account', newest(ctx, 'external_account').filter((e) => e.account === account));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// the requested capabilities become Stripe's status map, and settings its canonical shape
|
|
17
|
+
export async function PostAccounts(ctx: HandlerContext): Promise<Response> {
|
|
18
|
+
const params = ctx.params;
|
|
19
|
+
const type = typeof params.type === 'string' ? params.type : 'standard';
|
|
20
|
+
if (!ACCOUNT_TYPES.has(type)) return fail(ctx, 'Invalid account type: must be one of express, standard, or custom.', 400, 'parameter_invalid_string_enum');
|
|
21
|
+
const { capabilities: _caps, settings: _settings, ...accountParams } = keptKyc(params);
|
|
22
|
+
const id = ctx.mint('account');
|
|
23
|
+
const made = await created(ctx, 'account', { ...accountParams, id, type }, {
|
|
24
|
+
business_type: null, charges_enabled: false, payouts_enabled: false, details_submitted: false,
|
|
25
|
+
capabilities: accountCapabilities(params), requirements: type === 'express' ? expressRequirements(params) : accountRequirements(),
|
|
26
|
+
country: typeof params.country === 'string' ? params.country : 'US',
|
|
27
|
+
default_currency: 'usd', email: params.email ?? null, metadata: {}, livemode: false,
|
|
28
|
+
settings: accountSettings(params.settings), business_profile: {},
|
|
29
|
+
controller: controllerOf(type), external_accounts: externalList(id, []),
|
|
30
|
+
future_requirements: { alternatives: [], current_deadline: null, currently_due: [], disabled_reason: null, errors: [], eventually_due: [], past_due: [], pending_verification: [] },
|
|
31
|
+
tos_acceptance: { date: null, ip: null, user_agent: null },
|
|
32
|
+
});
|
|
33
|
+
await connectReview(ctx, String(made.id));
|
|
34
|
+
return ctx.reply(ctx.get('account', String(made.id))!);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// a single-use Express dashboard link, not stored
|
|
38
|
+
export async function PostAccountsAccountLoginLinks(ctx: HandlerContext): Promise<Response> {
|
|
39
|
+
const id = at(ctx, 'account');
|
|
40
|
+
if (!ctx.get('account', id)) return accountMissing(ctx, id);
|
|
41
|
+
return ctx.reply({ object: 'login_link', created: nowUnix(ctx.occurredAt), url: `https://connect.twin.local/express/${id}` });
|
|
42
|
+
}
|
|
43
|
+
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
import type {
|
|
4
|
-
import {
|
|
1
|
+
// Stripe's apps 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 { SECRET } from '../engine/apps-secrets.ts';
|
|
5
|
+
import type { Row } from '../engine/common.ts';
|
|
6
|
+
import { nowUnix } from '../engine/stripe.ts';
|
|
7
|
+
import { created, fail, kept, list, named, newest, scopeKeyOf } from './shared.ts';
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
return scopeType === 'user' ? `user:${String(scope!.user)}` : 'account';
|
|
9
|
+
export async function GetAppsSecretsFind(ctx: HandlerContext): Promise<Response> {
|
|
10
|
+
const name = typeof ctx.params.name === 'string' ? ctx.params.name : '';
|
|
11
|
+
if (!name) return fail(ctx, 'Missing required param: name.', 400, 'parameter_missing');
|
|
12
|
+
const s = named(ctx, name);
|
|
13
|
+
return s ? ctx.reply(s) : fail(ctx, `No such secret: '${name}'`, 404, 'resource_missing');
|
|
12
14
|
}
|
|
13
|
-
const named = (ctx: SemanticsContext, name: string): Row | undefined => ctx.rows(SECRET).find((s) => s.name === name && kept(ctx, SECRET, s, '_scope_key') === scopeKeyOf(ctx));
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
export async function PostAppsSecrets(ctx: HandlerContext): Promise<Response> {
|
|
16
17
|
const params = ctx.params;
|
|
17
18
|
const name = typeof params.name === 'string' ? params.name : '';
|
|
18
19
|
if (!name) return fail(ctx, 'Missing required param: name.', 400, 'parameter_missing');
|
|
@@ -23,7 +24,7 @@ const set: Semantics = async (ctx) => {
|
|
|
23
24
|
if (params.payload === undefined) return fail(ctx, 'Missing required param: payload.', 400, 'parameter_missing');
|
|
24
25
|
const expiresAt = params.expires_at !== undefined ? Math.trunc(Number(params.expires_at)) : null;
|
|
25
26
|
const existing = named(ctx, name);
|
|
26
|
-
if (existing) return ctx.reply(await ctx.write(SECRET, String(existing.id), { payload: params.payload, expires_at: expiresAt, _updated: ctx.
|
|
27
|
+
if (existing) return ctx.reply(await ctx.write(SECRET, String(existing.id), { payload: params.payload, expires_at: expiresAt, _updated: nowUnix(ctx.occurredAt) }, 'apps_secret.update'));
|
|
27
28
|
return ctx.reply(
|
|
28
29
|
await created(ctx, SECRET, { name }, {
|
|
29
30
|
livemode: false, deleted: false, expires_at: expiresAt, payload: params.payload,
|
|
@@ -31,28 +32,12 @@ const set: Semantics = async (ctx) => {
|
|
|
31
32
|
_scope_key: scopeKeyOf(ctx),
|
|
32
33
|
}),
|
|
33
34
|
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const find: Semantics = async (ctx) => {
|
|
37
|
-
const name = typeof ctx.params.name === 'string' ? ctx.params.name : '';
|
|
38
|
-
if (!name) return fail(ctx, 'Missing required param: name.', 400, 'parameter_missing');
|
|
39
|
-
const s = named(ctx, name);
|
|
40
|
-
return s ? ctx.reply(s) : fail(ctx, `No such secret: '${name}'`, 404, 'resource_missing');
|
|
41
|
-
};
|
|
35
|
+
}
|
|
42
36
|
|
|
43
|
-
|
|
37
|
+
export async function PostAppsSecretsDelete(ctx: HandlerContext): Promise<Response> {
|
|
44
38
|
const name = typeof ctx.params.name === 'string' ? ctx.params.name : '';
|
|
45
39
|
if (!name) return fail(ctx, 'Missing required param: name.', 400, 'parameter_missing');
|
|
46
40
|
const s = named(ctx, name);
|
|
47
41
|
if (!s) return fail(ctx, `No such secret: '${name}'`, 404, 'resource_missing');
|
|
48
42
|
return ctx.reply(await ctx.write(SECRET, String(s.id), { deleted: true }, 'apps_secret.delete'));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const listSecrets: Semantics = async (ctx) => list(ctx, SECRET, newest(ctx, SECRET).filter((s) => kept(ctx, SECRET, s, '_scope_key') === scopeKeyOf(ctx)));
|
|
52
|
-
|
|
53
|
-
export const appsSecrets: Record<string, Semantics> = {
|
|
54
|
-
PostAppsSecrets: set,
|
|
55
|
-
GetAppsSecretsFind: find,
|
|
56
|
-
PostAppsSecretsDelete: remove,
|
|
57
|
-
GetAppsSecrets: listSecrets,
|
|
58
|
-
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// What Stripe does around every API request (docs/contributing/architecture.md, "What an author writes": around): a
|
|
2
|
+
// request's key is one Stripe issued (the platform's, or a connected account's OAuth key); an OAuth key acts as its
|
|
3
|
+
// connected account; a publishable key is held to the client-side calls, a revoked account is
|
|
4
|
+
// refused and the parameters are checked against the operation's; a metadata hash merges; and every answer is rendered
|
|
5
|
+
// in the caller's API version (../engine/front.ts).
|
|
6
|
+
import type { HandlerContext } from '@volter/world-core';
|
|
7
|
+
import { actingAsOAuthKey, platformKeyRefused, publishableKeyRefused, refuseParameters, rendered, revokedAccountRefused, withMetadata } from './shared.ts';
|
|
8
|
+
|
|
9
|
+
export async function around(ctx: HandlerContext, next: (request?: Request) => Promise<Response>): Promise<Response> {
|
|
10
|
+
const arrived = ctx.call.request;
|
|
11
|
+
const unkeyed = platformKeyRefused(ctx, arrived);
|
|
12
|
+
if (unkeyed) return unkeyed;
|
|
13
|
+
const acting = await actingAsOAuthKey(ctx, arrived.clone());
|
|
14
|
+
if (acting instanceof Response) return acting;
|
|
15
|
+
if (ctx.call.operation.id !== 'unmatched') {
|
|
16
|
+
const refused = publishableKeyRefused(ctx) ?? revokedAccountRefused(ctx) ?? refuseParameters(ctx);
|
|
17
|
+
if (refused) return refused;
|
|
18
|
+
}
|
|
19
|
+
return rendered(arrived, await next(withMetadata(ctx, acting)));
|
|
20
|
+
}
|