@volter/twin-stripe 2.0.37 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -207
- package/dist/generated/pack-facts.json +94 -0
- package/dist/spec/SOURCE.md +14 -0
- package/dist/spec/fixtures3.json.gz +0 -0
- package/dist/spec/openapi.json.gz +0 -0
- package/dist/spec/patches.json +34 -0
- package/dist/spec/sources.json +378 -0
- package/dist/src/cli.js +12 -30
- package/dist/src/engine/after-payment.d.ts +9 -0
- package/dist/src/engine/after-payment.js +28 -0
- package/dist/src/engine/apps-secrets.d.ts +1 -0
- package/dist/src/engine/apps-secrets.js +3 -0
- package/dist/src/engine/balance.d.ts +5 -0
- package/dist/src/engine/balance.js +15 -0
- package/dist/src/engine/billing.d.ts +5 -0
- package/dist/src/engine/billing.js +6 -0
- package/dist/src/engine/charges.d.ts +6 -0
- package/dist/src/engine/charges.js +13 -0
- package/dist/src/engine/checkout.d.ts +24 -0
- package/dist/src/engine/checkout.js +30 -0
- package/dist/src/engine/common.d.ts +19 -0
- package/dist/src/engine/common.js +32 -0
- package/dist/src/{screens → engine}/connect-settings.d.ts +1 -7
- package/dist/src/engine/connect-settings.js +26 -0
- package/dist/src/engine/connect.d.ts +66 -0
- package/dist/src/engine/connect.js +189 -0
- package/dist/src/engine/coupons.d.ts +0 -0
- package/dist/src/engine/coupons.js +5 -0
- package/dist/src/engine/credit-notes.d.ts +1 -0
- package/dist/src/engine/credit-notes.js +5 -0
- package/dist/src/engine/customers.d.ts +12 -0
- package/dist/src/engine/customers.js +30 -0
- package/dist/src/engine/display.d.ts +14 -0
- package/dist/src/engine/display.js +16 -0
- package/dist/src/engine/disputes.d.ts +0 -0
- package/dist/src/engine/disputes.js +9 -0
- package/dist/src/engine/entitlements.d.ts +1 -0
- package/dist/src/engine/entitlements.js +4 -0
- package/dist/src/engine/ephemeral-keys.d.ts +0 -0
- package/dist/src/engine/ephemeral-keys.js +4 -0
- package/dist/src/engine/files.d.ts +18 -0
- package/dist/src/engine/files.js +33 -0
- package/dist/src/engine/front.d.ts +5 -0
- package/dist/src/engine/front.js +25 -0
- package/dist/src/engine/invoices.d.ts +24 -0
- package/dist/src/engine/invoices.js +57 -0
- package/dist/src/engine/issuing.d.ts +35 -0
- package/dist/src/engine/issuing.js +45 -0
- package/dist/src/engine/ledger.d.ts +9 -0
- package/dist/src/engine/ledger.js +9 -0
- package/dist/src/engine/oauth.d.ts +4 -0
- package/dist/src/engine/oauth.js +25 -0
- package/dist/src/engine/onboarding.d.ts +13 -0
- package/dist/src/engine/onboarding.js +42 -0
- package/dist/src/{stripe-params.d.ts → engine/params.d.ts} +20 -3
- package/dist/src/{stripe-params.js → engine/params.js} +7 -60
- package/dist/src/engine/payment-intents.d.ts +10 -0
- package/dist/src/engine/payment-intents.js +27 -0
- package/dist/src/engine/payment-links.d.ts +1 -0
- package/dist/src/engine/payment-links.js +5 -0
- package/dist/src/engine/payment-methods.d.ts +16 -0
- package/dist/src/engine/payment-methods.js +22 -0
- package/dist/src/engine/plans.d.ts +6 -0
- package/dist/src/engine/plans.js +26 -0
- package/dist/src/engine/platform.d.ts +39 -0
- package/dist/src/engine/platform.js +34 -0
- package/dist/src/engine/products.d.ts +0 -0
- package/dist/src/engine/products.js +4 -0
- package/dist/src/engine/radar.d.ts +2 -0
- package/dist/src/engine/radar.js +5 -0
- package/dist/src/engine/refunds.d.ts +1 -0
- package/dist/src/engine/refunds.js +28 -0
- package/dist/src/engine/renewals.d.ts +36 -0
- package/dist/src/engine/renewals.js +65 -0
- package/dist/src/engine/setup-intents.d.ts +1 -0
- package/dist/src/engine/setup-intents.js +4 -0
- package/dist/src/{stripe-twin.d.ts → engine/stripe.d.ts} +11 -39
- package/dist/src/{stripe-twin.js → engine/stripe.js} +25 -310
- package/dist/src/engine/subscription-schedules.d.ts +3 -0
- package/dist/src/engine/subscription-schedules.js +1 -0
- package/dist/src/engine/subscriptions.d.ts +12 -0
- package/dist/src/engine/subscriptions.js +31 -0
- package/dist/src/engine/tax.d.ts +7 -0
- package/dist/src/engine/tax.js +28 -0
- package/dist/src/engine/terminal.d.ts +4 -0
- package/dist/src/engine/terminal.js +20 -0
- package/dist/src/engine/test-clocks.d.ts +5 -0
- package/dist/src/engine/test-clocks.js +9 -0
- package/dist/src/engine/tokens.d.ts +7 -0
- package/dist/src/engine/tokens.js +20 -0
- package/dist/src/engine/transfers.d.ts +2 -0
- package/dist/src/engine/transfers.js +1 -0
- package/dist/src/engine/treasury.d.ts +23 -0
- package/dist/src/engine/treasury.js +45 -0
- package/dist/src/{stripe-version.js → engine/version.js} +4 -3
- package/dist/src/engine/webhook-endpoints.d.ts +1 -0
- package/dist/src/engine/webhook-endpoints.js +12 -0
- package/dist/src/engine/webhooks.d.ts +31 -0
- package/dist/src/engine/webhooks.js +68 -0
- package/dist/src/fetch.d.ts +2 -0
- package/dist/src/fetch.js +26 -0
- package/dist/src/generated/surface.gen.json +1 -1
- package/dist/src/generated/ui.gen.json +1 -1
- package/dist/src/index.d.ts +5 -14
- package/dist/src/index.js +8 -75
- package/dist/src/manifest.js +232 -758
- package/dist/src/screens/billing-portal.d.ts +3 -0
- package/dist/src/screens/{portal.js → billing-portal.js} +28 -54
- package/dist/src/screens/checkout.d.ts +2 -31
- package/dist/src/screens/checkout.js +32 -121
- package/dist/src/screens/connect-oauth.d.ts +3 -27
- package/dist/src/screens/connect-oauth.js +51 -132
- package/dist/src/screens/connect-onboarding.d.ts +3 -0
- package/dist/src/screens/{onboarding.js → connect-onboarding.js} +36 -102
- package/dist/src/screens/dashboard.d.ts +3 -0
- package/dist/src/screens/dashboard.js +117 -0
- package/dist/src/screens/shared.d.ts +27 -0
- package/dist/src/screens/shared.js +87 -0
- package/dist/src/screens/stripe-js.d.ts +3 -0
- package/dist/src/screens/stripe-js.js +135 -0
- package/dist/src/semantics/account_links.d.ts +2 -0
- package/dist/src/semantics/account_links.js +22 -0
- package/dist/src/semantics/accounts.d.ts +4 -0
- package/dist/src/semantics/accounts.js +38 -0
- package/dist/src/semantics/apps.d.ts +4 -0
- package/dist/src/semantics/{apps-secrets.js → apps.js} +14 -26
- package/dist/src/semantics/around.d.ts +2 -0
- package/dist/src/semantics/around.js +16 -0
- package/dist/src/semantics/balance.d.ts +2 -11
- package/dist/src/semantics/balance.js +3 -194
- package/dist/src/semantics/balance_transactions.d.ts +2 -0
- package/dist/src/semantics/balance_transactions.js +15 -0
- package/dist/src/semantics/billing_portal.d.ts +5 -0
- package/dist/src/semantics/billing_portal.js +42 -0
- package/dist/src/semantics/charges.d.ts +3 -28
- package/dist/src/semantics/charges.js +5 -202
- package/dist/src/semantics/checkout.d.ts +3 -20
- package/dist/src/semantics/checkout.js +8 -261
- package/dist/src/semantics/clock.d.ts +2 -0
- package/dist/src/semantics/clock.js +21 -0
- package/dist/src/semantics/coupons.d.ts +2 -6
- package/dist/src/semantics/coupons.js +2 -60
- package/dist/src/semantics/customers.d.ts +6 -6
- package/dist/src/semantics/customers.js +27 -387
- package/dist/src/semantics/disputes.d.ts +2 -2
- package/dist/src/semantics/disputes.js +3 -49
- package/dist/src/semantics/doors.d.ts +23 -0
- package/dist/src/semantics/doors.js +73 -0
- package/dist/src/semantics/entitlements.d.ts +2 -2
- package/dist/src/semantics/entitlements.js +5 -50
- package/dist/src/semantics/events.d.ts +12 -0
- package/dist/src/semantics/events.js +36 -0
- package/dist/src/semantics/index.d.ts +2 -0
- package/dist/src/semantics/index.js +107 -0
- package/dist/src/semantics/invoice_payments.d.ts +2 -0
- package/dist/src/semantics/invoice_payments.js +12 -0
- package/dist/src/semantics/invoiceitems.d.ts +3 -0
- package/dist/src/semantics/invoiceitems.js +59 -0
- package/dist/src/semantics/invoices.d.ts +16 -18
- package/dist/src/semantics/invoices.js +118 -444
- package/dist/src/semantics/payment_intents.d.ts +4 -0
- package/dist/src/semantics/payment_intents.js +79 -0
- package/dist/src/semantics/payment_methods.d.ts +5 -0
- package/dist/src/semantics/payment_methods.js +39 -0
- package/dist/src/semantics/payouts.d.ts +2 -0
- package/dist/src/semantics/payouts.js +21 -0
- package/dist/src/semantics/prices.d.ts +4 -0
- package/dist/src/semantics/prices.js +37 -0
- package/dist/src/semantics/products.d.ts +6 -2
- package/dist/src/semantics/products.js +15 -130
- package/dist/src/semantics/promotion_codes.d.ts +3 -0
- package/dist/src/semantics/promotion_codes.js +35 -0
- package/dist/src/semantics/radar.d.ts +2 -2
- package/dist/src/semantics/radar.js +5 -74
- package/dist/src/semantics/refunds.d.ts +3 -9
- package/dist/src/semantics/refunds.js +6 -192
- package/dist/src/semantics/setup_intents.d.ts +3 -0
- package/dist/src/semantics/{setup-intents.js → setup_intents.js} +10 -53
- package/dist/src/semantics/shared.d.ts +822 -47
- package/dist/src/semantics/shared.js +3044 -78
- package/dist/src/semantics/states.d.ts +531 -0
- package/dist/src/semantics/states.js +852 -0
- package/dist/src/semantics/subscription_items.d.ts +5 -0
- package/dist/src/semantics/subscription_items.js +55 -0
- package/dist/src/semantics/subscription_schedules.d.ts +4 -0
- package/dist/src/semantics/subscription_schedules.js +60 -0
- package/dist/src/semantics/subscriptions.d.ts +7 -11
- package/dist/src/semantics/subscriptions.js +44 -415
- package/dist/src/semantics/test_helpers.d.ts +6 -0
- package/dist/src/semantics/{test-clocks.js → test_helpers.js} +14 -40
- package/dist/src/semantics/transfers.d.ts +2 -2
- package/dist/src/semantics/transfers.js +6 -101
- package/dist/src/server.d.ts +7 -0
- package/dist/src/server.js +7 -0
- package/generated/pack-facts.json +94 -0
- package/package.json +10 -20
- package/spec/SOURCE.md +14 -0
- package/spec/fixtures3.json.gz +0 -0
- package/spec/openapi.json.gz +0 -0
- package/spec/patches.json +34 -0
- package/spec/sources.json +378 -0
- package/src/cli.ts +12 -26
- package/src/engine/after-payment.ts +31 -0
- package/src/engine/apps-secrets.ts +4 -0
- package/src/engine/balance.ts +33 -0
- package/src/engine/billing.ts +16 -0
- package/src/engine/charges.ts +19 -0
- package/src/engine/checkout.ts +40 -0
- package/src/engine/common.ts +47 -0
- package/src/engine/connect-settings.ts +23 -0
- package/src/engine/connect.ts +184 -0
- package/src/engine/coupons.ts +5 -0
- package/src/engine/credit-notes.ts +6 -0
- package/src/engine/customers.ts +35 -0
- package/src/engine/display.ts +19 -0
- package/src/engine/disputes.ts +9 -0
- package/src/engine/entitlements.ts +5 -0
- package/src/engine/ephemeral-keys.ts +3 -0
- package/src/engine/files.ts +41 -0
- package/src/engine/front.ts +31 -0
- package/src/engine/invoices.ts +71 -0
- package/src/engine/issuing.ts +61 -0
- package/src/engine/ledger.ts +50 -0
- package/src/engine/oauth.ts +19 -0
- package/src/engine/onboarding.ts +54 -0
- package/src/{stripe-params.ts → engine/params.ts} +8 -56
- package/src/engine/payment-intents.ts +33 -0
- package/src/engine/payment-links.ts +6 -0
- package/src/engine/payment-methods.ts +25 -0
- package/src/engine/plans.ts +42 -0
- package/src/engine/platform.ts +47 -0
- package/src/engine/products.ts +3 -0
- package/src/engine/radar.ts +6 -0
- package/src/engine/refunds.ts +29 -0
- package/src/engine/renewals.ts +73 -0
- package/src/engine/setup-intents.ts +5 -0
- package/src/{stripe-twin.ts → engine/stripe.ts} +28 -323
- package/src/engine/subscription-schedules.ts +10 -0
- package/src/engine/subscriptions.ts +40 -0
- package/src/engine/tax.ts +39 -0
- package/src/engine/terminal.ts +23 -0
- package/src/engine/test-clocks.ts +11 -0
- package/src/engine/tokens.ts +22 -0
- package/src/engine/transfers.ts +23 -0
- package/src/engine/treasury.ts +47 -0
- package/src/{stripe-version.ts → engine/version.ts} +4 -3
- package/src/engine/webhook-endpoints.ts +14 -0
- package/src/engine/webhooks.ts +72 -0
- package/src/fetch.ts +27 -0
- package/src/generated/surface.gen.json +1 -1
- package/src/generated/ui.gen.json +1 -1
- package/src/index.ts +8 -116
- package/src/manifest.ts +234 -786
- package/src/screens/{portal.tsx → billing-portal.tsx} +32 -37
- package/src/screens/checkout.tsx +33 -95
- package/src/screens/connect-oauth.tsx +64 -88
- package/src/screens/{onboarding.tsx → connect-onboarding.tsx} +38 -82
- package/src/screens/dashboard.tsx +198 -0
- package/src/screens/shared.tsx +84 -0
- package/src/screens/stripe-js.ts +170 -0
- package/src/semantics/account_links.ts +21 -0
- package/src/semantics/accounts.ts +43 -0
- package/src/semantics/{apps-secrets.ts → apps.ts} +17 -32
- package/src/semantics/around.ts +20 -0
- package/src/semantics/balance.ts +6 -207
- package/src/semantics/balance_transactions.ts +20 -0
- package/src/semantics/billing_portal.ts +51 -0
- package/src/semantics/charges.ts +12 -211
- package/src/semantics/checkout.ts +13 -268
- package/src/semantics/clock.ts +26 -0
- package/src/semantics/coupons.ts +5 -67
- package/src/semantics/customers.ts +29 -392
- package/src/semantics/disputes.ts +9 -59
- package/src/semantics/doors.ts +80 -0
- package/src/semantics/entitlements.ts +9 -56
- package/src/semantics/events.ts +44 -0
- package/src/semantics/index.ts +111 -0
- package/src/semantics/invoice_payments.ts +18 -0
- package/src/semantics/invoiceitems.ts +59 -0
- package/src/semantics/invoices.ts +109 -443
- package/src/semantics/payment_intents.ts +83 -0
- package/src/semantics/payment_methods.ts +43 -0
- package/src/semantics/payouts.ts +23 -0
- package/src/semantics/prices.ts +42 -0
- package/src/semantics/products.ts +18 -142
- package/src/semantics/promotion_codes.ts +39 -0
- package/src/semantics/radar.ts +8 -77
- package/src/semantics/refunds.ts +9 -212
- package/src/semantics/setup_intents.ts +45 -0
- package/src/semantics/shared.ts +3092 -106
- package/src/semantics/states.ts +883 -0
- package/src/semantics/subscription_items.ts +54 -0
- package/src/semantics/subscription_schedules.ts +66 -0
- package/src/semantics/subscriptions.ts +49 -432
- package/src/semantics/{test-clocks.ts → test_helpers.ts} +23 -47
- package/src/semantics/transfers.ts +11 -131
- package/src/server.ts +8 -0
- package/client/dashboard-api.ts +0 -286
- package/client/stripe-mirror.css +0 -277
- package/client/stripe-mirror.tsx +0 -1559
- package/dist/client/dashboard-api.d.ts +0 -107
- package/dist/client/dashboard-api.js +0 -238
- package/dist/client/dashboard-api.ts +0 -286
- package/dist/client/stripe-mirror.bundle.js +0 -236
- package/dist/client/stripe-mirror.css +0 -277
- package/dist/client/stripe-mirror.d.ts +0 -134
- package/dist/client/stripe-mirror.js +0 -859
- package/dist/client/stripe-mirror.tsx +0 -1559
- package/dist/src/screens/connect-settings.js +0 -103
- package/dist/src/screens/consent-skin.d.ts +0 -4
- package/dist/src/screens/consent-skin.js +0 -18
- package/dist/src/screens/financial-connections.d.ts +0 -5
- package/dist/src/screens/financial-connections.js +0 -90
- package/dist/src/screens/identity.d.ts +0 -5
- package/dist/src/screens/identity.js +0 -86
- package/dist/src/screens/onboarding.d.ts +0 -13
- package/dist/src/screens/portal.d.ts +0 -5
- package/dist/src/screens/public-details.d.ts +0 -5
- package/dist/src/screens/public-details.js +0 -90
- package/dist/src/semantics/after-payment.d.ts +0 -22
- package/dist/src/semantics/after-payment.js +0 -99
- package/dist/src/semantics/apps-secrets.d.ts +0 -2
- package/dist/src/semantics/billing.d.ts +0 -2
- package/dist/src/semantics/billing.js +0 -220
- package/dist/src/semantics/connect.d.ts +0 -5
- package/dist/src/semantics/connect.js +0 -493
- package/dist/src/semantics/credit-notes.d.ts +0 -2
- package/dist/src/semantics/credit-notes.js +0 -172
- package/dist/src/semantics/ephemeral-keys.d.ts +0 -2
- package/dist/src/semantics/ephemeral-keys.js +0 -34
- package/dist/src/semantics/files.d.ts +0 -2
- package/dist/src/semantics/files.js +0 -125
- package/dist/src/semantics/issuing.d.ts +0 -13
- package/dist/src/semantics/issuing.js +0 -575
- package/dist/src/semantics/ledger.d.ts +0 -59
- package/dist/src/semantics/ledger.js +0 -200
- package/dist/src/semantics/payment-intents.d.ts +0 -18
- package/dist/src/semantics/payment-intents.js +0 -452
- package/dist/src/semantics/payment-links.d.ts +0 -2
- package/dist/src/semantics/payment-links.js +0 -133
- package/dist/src/semantics/payment-methods.d.ts +0 -20
- package/dist/src/semantics/payment-methods.js +0 -140
- package/dist/src/semantics/plans.d.ts +0 -5
- package/dist/src/semantics/plans.js +0 -121
- package/dist/src/semantics/platform.d.ts +0 -9
- package/dist/src/semantics/platform.js +0 -206
- package/dist/src/semantics/renewals.d.ts +0 -47
- package/dist/src/semantics/renewals.js +0 -281
- package/dist/src/semantics/setup-intents.d.ts +0 -2
- package/dist/src/semantics/subscription-schedules.d.ts +0 -2
- package/dist/src/semantics/subscription-schedules.js +0 -119
- package/dist/src/semantics/tax.d.ts +0 -2
- package/dist/src/semantics/tax.js +0 -197
- package/dist/src/semantics/terminal.d.ts +0 -5
- package/dist/src/semantics/terminal.js +0 -182
- package/dist/src/semantics/test-clocks.d.ts +0 -6
- package/dist/src/semantics/tokens.d.ts +0 -4
- package/dist/src/semantics/tokens.js +0 -44
- package/dist/src/semantics/treasury.d.ts +0 -2
- package/dist/src/semantics/treasury.js +0 -377
- package/dist/src/semantics/webhook-endpoints.d.ts +0 -3
- package/dist/src/semantics/webhook-endpoints.js +0 -85
- package/dist/src/stripe-budget.d.ts +0 -55
- package/dist/src/stripe-budget.js +0 -155
- package/dist/src/stripe-capabilities.d.ts +0 -3
- package/dist/src/stripe-capabilities.js +0 -5836
- package/dist/src/stripe-conformance.d.ts +0 -43
- package/dist/src/stripe-conformance.js +0 -105
- package/dist/src/stripe-connector.d.ts +0 -161
- package/dist/src/stripe-connector.js +0 -414
- package/dist/src/stripe-emit.d.ts +0 -2
- package/dist/src/stripe-emit.js +0 -145
- package/dist/src/stripe-events.d.ts +0 -93
- package/dist/src/stripe-events.js +0 -392
- package/dist/src/stripe-js.d.ts +0 -4
- package/dist/src/stripe-js.js +0 -70
- package/dist/src/stripe-mirror-ui.d.ts +0 -15
- package/dist/src/stripe-mirror-ui.js +0 -87
- package/dist/src/stripe-perform-harness.d.ts +0 -9
- package/dist/src/stripe-perform-harness.js +0 -26
- package/dist/src/stripe-server.d.ts +0 -33
- package/dist/src/stripe-server.js +0 -429
- package/dist/src/stripe-shared.d.ts +0 -109
- package/dist/src/stripe-shared.js +0 -276
- package/dist/src/stripe-ui-conformance.d.ts +0 -5
- package/dist/src/stripe-ui-conformance.js +0 -79
- package/dist/src/stripe-ui-structure.d.ts +0 -3
- package/dist/src/stripe-ui-structure.js +0 -168
- package/dist/test-fixtures/stripe-known-deviations.json +0 -110
- package/dist/test-fixtures/stripe-openapi-operations.SOURCE.md +0 -14
- package/dist/test-fixtures/stripe-openapi-operations.json +0 -4717
- package/dist/test-fixtures/stripe-schemas.SOURCE.md +0 -35
- package/dist/test-fixtures/stripe-schemas.json +0 -3813
- package/src/screens/connect-settings.tsx +0 -121
- package/src/screens/consent-skin.ts +0 -20
- package/src/screens/financial-connections.tsx +0 -101
- package/src/screens/identity.tsx +0 -96
- package/src/screens/public-details.tsx +0 -105
- package/src/semantics/after-payment.ts +0 -118
- package/src/semantics/billing.ts +0 -216
- package/src/semantics/connect.ts +0 -487
- package/src/semantics/credit-notes.ts +0 -168
- package/src/semantics/ephemeral-keys.ts +0 -34
- package/src/semantics/files.ts +0 -143
- package/src/semantics/issuing.ts +0 -590
- package/src/semantics/ledger.ts +0 -253
- package/src/semantics/payment-intents.ts +0 -463
- package/src/semantics/payment-links.ts +0 -148
- package/src/semantics/payment-methods.ts +0 -145
- package/src/semantics/plans.ts +0 -131
- package/src/semantics/platform.ts +0 -220
- package/src/semantics/renewals.ts +0 -279
- package/src/semantics/setup-intents.ts +0 -87
- package/src/semantics/subscription-schedules.ts +0 -129
- package/src/semantics/tax.ts +0 -220
- package/src/semantics/terminal.ts +0 -195
- package/src/semantics/tokens.ts +0 -52
- package/src/semantics/treasury.ts +0 -383
- package/src/semantics/webhook-endpoints.ts +0 -87
- package/src/stripe-budget.ts +0 -181
- package/src/stripe-capabilities.ts +0 -5651
- package/src/stripe-conformance.ts +0 -124
- package/src/stripe-connector.ts +0 -462
- package/src/stripe-emit.ts +0 -157
- package/src/stripe-events.ts +0 -434
- package/src/stripe-js.ts +0 -70
- package/src/stripe-mirror-ui.ts +0 -90
- package/src/stripe-perform-harness.ts +0 -29
- package/src/stripe-server.ts +0 -393
- package/src/stripe-shared.ts +0 -297
- package/src/stripe-ui-conformance.ts +0 -88
- package/src/stripe-ui-structure.ts +0 -182
- package/test-fixtures/stripe-known-deviations.json +0 -110
- package/test-fixtures/stripe-openapi-operations.json +0 -4717
- package/test-fixtures/stripe-schemas.json +0 -3813
- /package/dist/src/{screens → engine}/industries.d.ts +0 -0
- /package/dist/src/{screens → engine}/industries.js +0 -0
- /package/dist/src/{semantics → engine}/test-cards.d.ts +0 -0
- /package/dist/src/{semantics → engine}/test-cards.js +0 -0
- /package/dist/src/{stripe-version.d.ts → engine/version.d.ts} +0 -0
- /package/src/{screens → engine}/industries.ts +0 -0
- /package/src/{semantics → engine}/test-cards.ts +0 -0
|
@@ -1,94 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
// STRIPE CONNECT OAUTH (Standard accounts) — a hosted flow (docs/contributing/architecture.md, "Screens") and the two
|
|
3
|
-
// connect.stripe.com endpoints behind it, as docs.stripe.com/connect/oauth-reference and
|
|
4
|
-
// docs.stripe.com/connect/oauth-standard-accounts describe them. A platform sends a person to
|
|
5
|
-
// GET connect.stripe.com/oauth/authorize with its client_id; the person connects (in test mode Stripe lets them "Force-skip
|
|
6
|
-
// the account form", docs.stripe.com/connect/testing#using-oauth) or denies; Stripe sends them back to the redirect_uri
|
|
7
|
-
// with a `code`, the `scope` granted and the `state` (or `error=access_denied`); the platform's server turns the code into
|
|
8
|
-
// the connection with POST connect.stripe.com/oauth/token, authenticated by its secret key, and learns the account id
|
|
9
|
-
// (`stripe_user_id`) it then acts as with the Stripe-Account header. POST connect.stripe.com/oauth/deauthorize revokes the
|
|
10
|
-
// connection. The page is @volter/world-ui's consent piece under Stripe's skin; nothing of Stripe's page is copied.
|
|
11
|
-
//
|
|
12
|
-
// What the documentation says and the twin keeps:
|
|
13
|
-
// - authorize: `client_id` is the platform's (its Connect OAuth settings, ./connect-settings.tsx, where OAuth is enabled
|
|
14
|
-
// and the redirect URIs are registered); `response_type` "The only option at the moment is code"; `redirect_uri`, "If
|
|
15
|
-
// provided, this must exactly match one of the ... redirect_uri values in your application settings" and "Defaults to
|
|
16
|
-
// the redirect_uri in your application settings" ("the first URI configured"); `scope` read_write or read_only,
|
|
17
|
-
// "Defaults to read_only"; `state` passed back. `stripe_user[...]` prefills the new account, and "Any parameters with
|
|
18
|
-
// invalid values are silently ignored". Errors: "the user's browser won't be redirected except in the case of
|
|
19
|
-
// access_denied. Instead, errors will be returned in a JSON dictionary" of error, error_description and state, with
|
|
20
|
-
// the codes invalid_scope, invalid_redirect_uri, invalid_request (Missing response_type) and unsupported_response_type.
|
|
21
|
-
// Denied: `error=access_denied&error_description=The%20user%20denied%20your%20request`. Success: code, scope, state.
|
|
22
|
-
// - token: the code "can only be used once and expires in 5 minutes"; "Consuming an authorization code more than once
|
|
23
|
-
// revokes the account connection"; grant_type authorization_code or refresh_token; a refresh `scope` of "equal or lesser
|
|
24
|
-
// scope" and "Any existing access token with the same scope and mode ... is revoked"; the response's scope,
|
|
25
|
-
// stripe_user_id, livemode, token_type "bearer", access_token, stripe_publishable_key and refresh_token; errors
|
|
26
|
-
// invalid_request, invalid_grant ("Authorization code does not exist: {AUTHORIZATION_CODE}", verbatim from the guide),
|
|
27
|
-
// unsupported_grant_type and invalid_scope as `{ error, error_description }`.
|
|
28
|
-
// - deauthorize: client_id and stripe_user_id, answered `{ stripe_user_id }`; errors invalid_request and invalid_client
|
|
29
|
-
// ("stripe_user_id doesn't exist or isn't connected to your application"); afterwards "the account can't be accessed by
|
|
30
|
-
// your platform ... through the API" (stripe-server.ts refuses the Stripe-Account header for it, account_invalid).
|
|
31
|
-
// - events: account.application.authorized when the person connects and account.application.deauthorized when the
|
|
32
|
-
// connection is revoked, each the connected account's (top-level `account`, Connect endpoints), carrying the
|
|
33
|
-
// `application` object ({ id: the client_id, object: 'application', name }).
|
|
34
|
-
//
|
|
35
|
-
// Where the documentation stops and the twin decides:
|
|
36
|
-
// - the twin is a test-mode Stripe: its client_id is a test one (./connect-settings.tsx PLATFORM_CLIENT_ID), redirect
|
|
37
|
-
// URIs may be http and localhost ("Your test client_id allows you to ... Set your redirect_uri to a non-HTTPS URL /
|
|
38
|
-
// to localhost"), a code, token and connection are livemode false, and a live secret key (sk_live_) is refused as the
|
|
39
|
-
// documented mode mismatch (invalid_grant at token, invalid_client at deauthorize).
|
|
40
|
-
// - the only way through the page is the test-mode skip: it creates a NEW Standard account from the prefill and connects
|
|
41
|
-
// it; logging in to connect an existing Stripe account is not modelled. A skipped test account can take payments at
|
|
42
|
-
// once (charges and payouts enabled, card_payments and transfers active, nothing due), and controls itself (controller
|
|
43
|
-
// type `account`), since the platform did not create it.
|
|
44
|
-
// - an unknown client_id answers invalid_client "No application matches the supplied client identifier", and a
|
|
45
|
-
// platform that has not enabled OAuth invalid_client naming its settings page; a missing client_id is invalid_request.
|
|
46
|
-
// The JSON errors are HTTP 400; the order of the checks is client_id, OAuth enabled, redirect_uri, response_type,
|
|
47
|
-
// scope. With no redirect URI registered and none given, invalid_redirect_uri. read_only is accepted from any platform
|
|
48
|
-
// (the docs limit it to extensions; the twin does not tell a platform from an extension).
|
|
49
|
-
// - the secret key may come as Bearer, as Basic's user, or as the `client_secret` parameter (the reference's curl uses
|
|
50
|
-
// `-u`, stripe-node a Bearer header); without one the answer is 401 invalid_request, and a key that is not a secret
|
|
51
|
-
// key (sk_…/rk_…) 401 invalid_request too. The token and deauthorize wording other than the one quoted above is the
|
|
52
|
-
// twin's. An expired code is invalid_grant "Authorization code expired: {code}"; a reused one invalid_grant "This
|
|
53
|
-
// authorization code has already been used. All tokens issued with this code have been revoked." and revokes.
|
|
54
|
-
// - tokens are deterministic, not secret: access_token `sk_test_oauth_{account}_{n}`, refresh_token `rt_twin_{n}`,
|
|
55
|
-
// stripe_publishable_key `pk_test_oauth_{account}`; a refresh answers the same refresh_token (`rt_twin_{n}` of the code
|
|
56
|
-
// `ac_twin_{n}` that made the connection). The access_token and publishable key are deprecated by Stripe (use the
|
|
57
|
-
// Stripe-Account header) and act as the account (stripe-server.ts actingAsOAuthKey); a read_only one is not held to
|
|
58
|
-
// reads (manifest todo `stripe.connect.oauth_scope_enforcement`).
|
|
59
|
-
// - concurrency: a code's number is taken with its write in one move; a code redeemed twice at once lets one exchange
|
|
60
|
-
// win, and whichever of the two finishes last revokes the connection, so a double exchange always ends revoked.
|
|
61
|
-
import { semanticsContext } from '@volter/world-core';
|
|
62
2
|
import { Consent, CONSENT_CSS, flowPage } from '@volter/world-ui';
|
|
63
|
-
import
|
|
64
|
-
import {
|
|
65
|
-
import {
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
68
|
-
import {
|
|
69
|
-
import { connectOAuthSettings, PLATFORM_CLIENT_ID, SETTINGS_URL } from "./connect-settings.js";
|
|
70
|
-
const OPERATION = surface.operations.find((o) => o.id === 'PostAccountsAccount');
|
|
3
|
+
import { PLATFORM_CLIENT_ID, SETTINGS_URL } from "../engine/connect-settings.js";
|
|
4
|
+
import { OAUTH_CONNECTIONS, publicBusinessName } from "../engine/display.js";
|
|
5
|
+
import { redactKey } from "../engine/oauth.js";
|
|
6
|
+
import { accountSettings, nowUnix, PLATFORM_ACCOUNT_ID } from "../engine/stripe.js";
|
|
7
|
+
import { formOf, notFound, seeOther, STRIPE_CONSENT_SKIN } from "./shared.js";
|
|
8
|
+
import { connectOAuthSettings, created, decide } from "../semantics/shared.js";
|
|
71
9
|
/** A code "expires in 5 minutes". */
|
|
72
|
-
|
|
10
|
+
const CODE_TTL_SECONDS = 300;
|
|
73
11
|
const CODES = '_oauth_code';
|
|
74
12
|
/** One connection per connected account (the twin's one platform): its scope, tokens, and whether it was revoked. */
|
|
75
|
-
|
|
13
|
+
const CONNECTIONS = OAUTH_CONNECTIONS;
|
|
76
14
|
const SCOPES = ['read_write', 'read_only'];
|
|
77
15
|
const json = (body, status = 200) => Response.json(body, { status, headers: { 'request-id': 'req_twin', 'cache-control': 'no-store' } });
|
|
78
16
|
const oauthError = (error, description, status = 400, extra = {}) => json({ error, error_description: description, ...extra }, status);
|
|
79
|
-
/** Whether a connected account's OAuth connection to the platform was revoked (deauthorized, or its code reused). */
|
|
80
|
-
export function connectionRevoked(ctx, account) {
|
|
81
|
-
return ctx.rowsRaw(CONNECTIONS).some((c) => c.id === account && c.revoked === true);
|
|
82
|
-
}
|
|
83
|
-
/** The connected account an OAuth key acts as — the connection's current access_token (sk_test_oauth_…) or its
|
|
84
|
-
* stripe_publishable_key (pk_test_oauth_…) — with the scope it was issued; undefined for any other key, and null for an
|
|
85
|
-
* OAuth-shaped key no live connection holds (revoked, replaced by a refresh, or never issued). */
|
|
86
|
-
export function oauthKeyAccount(ctx, key) {
|
|
87
|
-
if (!/^(sk|pk)_test_oauth_/.test(key))
|
|
88
|
-
return undefined;
|
|
89
|
-
const held = ctx.rowsRaw(CONNECTIONS).find((c) => c.revoked !== true && (c.access_token === key || c.publishable_key === key));
|
|
90
|
-
return held ? { account: String(held.id), scope: String(key.startsWith('pk_') ? held.scope : held.access_scope ?? held.scope) } : null;
|
|
91
|
-
}
|
|
92
17
|
// ── authorize ──
|
|
93
18
|
/** A query parameter, or undefined when absent (an empty value is absent too, as a browser form sends one). */
|
|
94
19
|
const param = (q, k) => { const v = q.get(k); return v === null || v === '' ? undefined : v; };
|
|
@@ -135,7 +60,7 @@ function withParams(uri, params) {
|
|
|
135
60
|
/** The prefill the request carries, invalid values silently dropped (docs.stripe.com/connect/oauth-reference). Only
|
|
136
61
|
* what the new account's object shows the platform, or the page shows the person, is read: the person's birth date and
|
|
137
62
|
* the business address are the account application's, which the twin does not model (the test-mode skip). */
|
|
138
|
-
|
|
63
|
+
function prefill(q) {
|
|
139
64
|
const v = (k) => param(q, `stripe_user[${k}]`)?.trim() || undefined;
|
|
140
65
|
const out = {};
|
|
141
66
|
const keep = (k, ok = () => true) => { const s = v(k); if (s !== undefined && ok(s))
|
|
@@ -179,13 +104,11 @@ async function createAccount(ctx, p) {
|
|
|
179
104
|
function application(ctx) {
|
|
180
105
|
return { id: PLATFORM_CLIENT_ID, object: 'application', name: publicBusinessName(ctx.get('account', PLATFORM_ACCOUNT_ID)) };
|
|
181
106
|
}
|
|
182
|
-
/** Send one of the account.application.* events as the connected account's
|
|
107
|
+
/** Send one of the account.application.* events as the connected account's: the notice it is sent for (the kernel
|
|
108
|
+
* sends it, ../semantics/events.ts renders it). */
|
|
183
109
|
async function applicationEvent(ctx, type, account) {
|
|
184
|
-
await
|
|
110
|
+
await ctx.write('_application_notice', `${account}:${type}:${ctx.occurredAt}`, { account, application: application(ctx) }, type);
|
|
185
111
|
}
|
|
186
|
-
/** A code's number: `ac_twin_{n}`, the refresh token its exchange mints being `rt_twin_{n}`, so the one follows the
|
|
187
|
-
* other and neither is counted separately. */
|
|
188
|
-
const codeNumber = (code) => code.replace(/^ac_twin_/, '');
|
|
189
112
|
function page(ctx, action, a, p) {
|
|
190
113
|
const name = publicBusinessName(ctx.get('account', PLATFORM_ACCOUNT_ID));
|
|
191
114
|
const who = [p.email, [p.first_name, p.last_name].filter(Boolean).join(' '), p.business_name].filter((s) => s).join(' · ');
|
|
@@ -203,19 +126,17 @@ function page(ctx, action, a, p) {
|
|
|
203
126
|
/** The person connects: a new Standard account, its connection's code, the authorized event, and the redirect. */
|
|
204
127
|
async function connect(ctx, a, p) {
|
|
205
128
|
const account = await createAccount(ctx, p);
|
|
206
|
-
// the code's
|
|
207
|
-
const
|
|
208
|
-
const code = await ctx.
|
|
209
|
-
|
|
210
|
-
return { value: id, write: { resource: CODES, id, fields, operation: 'oauth_code.issue' } };
|
|
211
|
-
});
|
|
129
|
+
// the code's row id is minted and its value drawn from the World's secret; the row keeps the value's SHA-256 alone
|
|
130
|
+
const id = ctx.mint(CODES);
|
|
131
|
+
const code = `ac_${(await ctx.secret(`stripe-oauth-code:${id}`)).replace(/[^A-Za-z0-9]/g, '').slice(0, 24)}`;
|
|
132
|
+
await ctx.record(CODES, { client_id: PLATFORM_CLIENT_ID, account, scope: a.scope, redirect_uri: a.redirect, expires_at: nowUnix(ctx.occurredAt) + CODE_TTL_SECONDS, used: false, livemode: false, sha256: ctx.crypto.sha256(code) }, id);
|
|
212
133
|
await applicationEvent(ctx, 'account.application.authorized', account);
|
|
213
134
|
return seeOther(withParams(a.redirect, [['scope', a.scope], ['code', code], ['state', a.state]]));
|
|
214
135
|
}
|
|
215
|
-
async function authorize(
|
|
136
|
+
async function authorize(ctx) {
|
|
137
|
+
const request = ctx.call.request;
|
|
216
138
|
const url = new URL(request.url);
|
|
217
139
|
const form = request.method === 'POST' ? await formOf(request.clone()) : {};
|
|
218
|
-
const ctx = await semanticsContext(manifest, request, OPERATION, scope);
|
|
219
140
|
const a = checked(ctx, url.searchParams);
|
|
220
141
|
if (a instanceof Response)
|
|
221
142
|
return a;
|
|
@@ -259,12 +180,6 @@ function secretKey(request, params) {
|
|
|
259
180
|
}
|
|
260
181
|
return params.client_secret || undefined;
|
|
261
182
|
}
|
|
262
|
-
/** A key shown the way Stripe's errors show one: its prefix and its last four. */
|
|
263
|
-
export const redactKey = (key) => {
|
|
264
|
-
const prefix = /^(sk|rk|pk)_(test|live)_/.exec(key)?.[0] ?? '';
|
|
265
|
-
const rest = key.slice(prefix.length);
|
|
266
|
-
return rest.length > 4 ? `${prefix}${'*'.repeat(rest.length - 4)}${rest.slice(-4)}` : `${prefix}${'*'.repeat(rest.length)}`;
|
|
267
|
-
};
|
|
268
183
|
/** The refusal of a request without a usable secret key, or undefined when it has one (answered with whether it is live). */
|
|
269
184
|
function keyRefused(key) {
|
|
270
185
|
if (!key)
|
|
@@ -279,27 +194,30 @@ function keyRefused(key) {
|
|
|
279
194
|
}
|
|
280
195
|
const live = (key) => /^(sk|rk)_live_/.test(key);
|
|
281
196
|
/** A new access token for a connection: its generation counted from the ones it had. */
|
|
282
|
-
|
|
197
|
+
/** A connection's access token of one generation: its account, then a digest of the connection's secret and the
|
|
198
|
+
* generation (the secret read before the atomic move that numbers the generation). */
|
|
199
|
+
// p3: keyed by the connection's secret, drawn from ctx.secret
|
|
200
|
+
const accessToken = (ctx, secret, account, n) => `sk_test_oauth_${account}_${ctx.crypto.digest('sha256', `${secret}:${n}`, 'base64url').replace(/[-_]/g, '').slice(0, 24)}`;
|
|
283
201
|
/** Revoke an account's connection, once: the check and the write are one move, and only the move that revoked it sends
|
|
284
202
|
* account.application.deauthorized. Answers whether this call revoked it. */
|
|
285
203
|
async function revoke(ctx, account) {
|
|
286
|
-
const revoked = await ctx
|
|
204
|
+
const revoked = await decide(ctx, (rows) => {
|
|
287
205
|
const held = rows(CONNECTIONS).find((c) => c.id === account);
|
|
288
206
|
if (!held || held.revoked === true)
|
|
289
207
|
return { value: false };
|
|
290
|
-
return { value: true, write: { resource: CONNECTIONS, id: account, fields: { revoked: true, access_token: null, revoked_at:
|
|
208
|
+
return { value: true, write: { resource: CONNECTIONS, id: account, fields: { revoked: true, access_token: null, revoked_at: nowUnix(ctx.occurredAt) }, operation: 'oauth_connection.revoke' } };
|
|
291
209
|
});
|
|
292
210
|
if (revoked)
|
|
293
211
|
await applicationEvent(ctx, 'account.application.deauthorized', account);
|
|
294
212
|
return revoked;
|
|
295
213
|
}
|
|
296
|
-
async function token(
|
|
214
|
+
async function token(ctx) {
|
|
215
|
+
const request = ctx.call.request;
|
|
297
216
|
const params = await bodyOf(request);
|
|
298
217
|
const key = secretKey(request, params);
|
|
299
218
|
const refused = keyRefused(key);
|
|
300
219
|
if (refused)
|
|
301
220
|
return refused;
|
|
302
|
-
const ctx = await semanticsContext(manifest, request, OPERATION, scope);
|
|
303
221
|
const grant = params.grant_type;
|
|
304
222
|
if (!grant)
|
|
305
223
|
return oauthError('invalid_request', 'No grant type specified');
|
|
@@ -311,16 +229,16 @@ async function token(request, scope) {
|
|
|
311
229
|
return oauthError('invalid_request', 'No authorization code provided');
|
|
312
230
|
if (live(key))
|
|
313
231
|
return oauthError('invalid_grant', `Authorization code provided does not belong to the livemode of the API key: ${code} is a test mode code, but a live mode key was used.`);
|
|
314
|
-
const found = await ctx
|
|
315
|
-
const row = rows(CODES).find((r) => r.
|
|
232
|
+
const found = await decide(ctx, (rows) => {
|
|
233
|
+
const row = rows(CODES).find((r) => r.sha256 === ctx.crypto.sha256(code));
|
|
316
234
|
if (!row)
|
|
317
235
|
return { value: { outcome: 'missing' } };
|
|
318
236
|
// a second use is marked in the same move, so the exchange that spent it sees it after writing its connection
|
|
319
237
|
if (row.used === true)
|
|
320
|
-
return { value: { outcome: 'used', row }, write: { resource: CODES, id:
|
|
321
|
-
if (
|
|
238
|
+
return { value: { outcome: 'used', row }, write: { resource: CODES, id: String(row.id), fields: { reused: true }, operation: 'oauth_code.reuse' } };
|
|
239
|
+
if (nowUnix(ctx.occurredAt) >= Number(row.expires_at))
|
|
322
240
|
return { value: { outcome: 'expired', row } };
|
|
323
|
-
return { value: { outcome: 'ok', row }, write: { resource: CODES, id:
|
|
241
|
+
return { value: { outcome: 'ok', row }, write: { resource: CODES, id: String(row.id), fields: { used: true, used_at: nowUnix(ctx.occurredAt) }, operation: 'oauth_code.redeem' } };
|
|
324
242
|
});
|
|
325
243
|
if (found.outcome === 'missing')
|
|
326
244
|
return oauthError('invalid_grant', `Authorization code does not exist: ${code}`);
|
|
@@ -333,11 +251,12 @@ async function token(request, scope) {
|
|
|
333
251
|
if (found.outcome === 'expired')
|
|
334
252
|
return oauthError('invalid_grant', `Authorization code expired: ${code}`);
|
|
335
253
|
const granted = String(found.row.scope);
|
|
336
|
-
const refresh = `
|
|
337
|
-
const
|
|
254
|
+
const refresh = `rt_${(await ctx.secret(`stripe-oauth-refresh:${String(found.row.id)}`)).replace(/[-_]/g, '').slice(0, 32)}`;
|
|
255
|
+
const secret = await ctx.secret(`stripe-oauth-access:${account}`);
|
|
256
|
+
const connection = { client_id: PLATFORM_CLIENT_ID, account, scope: granted, code: found.row.id, refresh_token: refresh, access_token: accessToken(ctx, secret, account, 1), access_scope: granted, generation: 1, publishable_key: `pk_test_oauth_${account}`, revoked: false, livemode: false };
|
|
338
257
|
await ctx.record(CONNECTIONS, connection, account);
|
|
339
258
|
// the code used again while this exchange was writing: that use found no connection to revoke, so this one does
|
|
340
|
-
if (ctx.rowsRaw(CODES).some((r) => r.id ===
|
|
259
|
+
if (ctx.rowsRaw(CODES).some((r) => r.id === found.row.id && r.reused === true)) {
|
|
341
260
|
await revoke(ctx, account);
|
|
342
261
|
return oauthError('invalid_grant', 'This authorization code has already been used. All tokens issued with this code have been revoked.');
|
|
343
262
|
}
|
|
@@ -360,12 +279,13 @@ async function token(request, scope) {
|
|
|
360
279
|
// "Any existing access token with the same scope and mode ... is revoked": the connection holds the one now issued; the
|
|
361
280
|
// generation is read and written in one move, so two refreshes at once issue two tokens, and a refresh never lands
|
|
362
281
|
// on a connection revoked meanwhile
|
|
363
|
-
const
|
|
282
|
+
const secret = await ctx.secret(`stripe-oauth-access:${account}`);
|
|
283
|
+
const renewed = await decide(ctx, (rows) => {
|
|
364
284
|
const now = rows(CONNECTIONS).find((c) => c.id === account && c.revoked !== true);
|
|
365
285
|
if (!now)
|
|
366
286
|
return { value: undefined };
|
|
367
287
|
const generation = Number(now.generation ?? 1) + 1;
|
|
368
|
-
const fields = { access_token: accessToken(account, generation), access_scope: asked, generation };
|
|
288
|
+
const fields = { access_token: accessToken(ctx, secret, account, generation), access_scope: asked, generation };
|
|
369
289
|
return { value: { ...now, ...fields }, write: { resource: CONNECTIONS, id: account, fields, operation: 'oauth_connection.refresh' } };
|
|
370
290
|
});
|
|
371
291
|
if (!renewed)
|
|
@@ -376,13 +296,13 @@ async function token(request, scope) {
|
|
|
376
296
|
function tokenBody(c, scope) {
|
|
377
297
|
return { access_token: c.access_token, livemode: false, refresh_token: c.refresh_token, scope, stripe_publishable_key: c.publishable_key, stripe_user_id: c.account ?? c.id, token_type: 'bearer' };
|
|
378
298
|
}
|
|
379
|
-
async function deauthorize(
|
|
299
|
+
async function deauthorize(ctx) {
|
|
300
|
+
const request = ctx.call.request;
|
|
380
301
|
const params = await bodyOf(request);
|
|
381
302
|
const key = secretKey(request, params);
|
|
382
303
|
const refused = keyRefused(key);
|
|
383
304
|
if (refused)
|
|
384
305
|
return refused;
|
|
385
|
-
const ctx = await semanticsContext(manifest, request, OPERATION, scope);
|
|
386
306
|
const client = params.client_id;
|
|
387
307
|
const account = params.stripe_user_id;
|
|
388
308
|
if (!client)
|
|
@@ -399,16 +319,15 @@ async function deauthorize(request, scope) {
|
|
|
399
319
|
return oauthError('invalid_client', `This application is not connected to stripe account ${account}, or that account does not exist.`);
|
|
400
320
|
return json({ stripe_user_id: account });
|
|
401
321
|
}
|
|
402
|
-
/** connect.stripe.com's OAuth endpoints
|
|
403
|
-
export function
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
};
|
|
322
|
+
/** connect.stripe.com's OAuth endpoints; Stripe's 404 for anything else there. */
|
|
323
|
+
export async function screen(ctx) {
|
|
324
|
+
const request = ctx.call.request;
|
|
325
|
+
const path = new URL(request.url).pathname.replace(/\/+$/, '');
|
|
326
|
+
if (path === '/oauth/authorize' && (request.method === 'GET' || request.method === 'POST'))
|
|
327
|
+
return authorize(ctx);
|
|
328
|
+
if (path === '/oauth/token' && request.method === 'POST')
|
|
329
|
+
return token(ctx);
|
|
330
|
+
if (path === '/oauth/deauthorize' && request.method === 'POST')
|
|
331
|
+
return deauthorize(ctx);
|
|
332
|
+
return notFound();
|
|
414
333
|
}
|
|
@@ -1,44 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
// STRIPE CONNECT HOSTED ONBOARDING — a hosted flow (docs/contributing/architecture.md, "Screens"): a platform
|
|
3
|
-
// creates an Account Link for a connected account and sends the account's owner to its `url`. The link is a
|
|
4
|
-
// single-use entry point: visiting it takes the owner to their onboarding session, and visiting it again, or after it
|
|
5
|
-
// expires, sends them to refresh_url, where the platform makes a new one (docs.stripe.com/connect/hosted-onboarding).
|
|
6
|
-
// At the session's own address they give Stripe what it requires, and Stripe sends them to return_url. Submitting is
|
|
7
|
-
// the owner's move, not an API call. Authored from @volter/world-ui's payment piece; nothing of Stripe's page is copied.
|
|
8
|
-
//
|
|
9
|
-
// The page asks for exactly what Stripe requires before a US Express account with card_payments and transfers can
|
|
10
|
-
// take card payments (docs.stripe.com/connect/required-verification-information; the set is the one Stripe's
|
|
11
|
-
// requirements endpoint answers, docs.stripe.com/_endpoint/get-requirements-for-setups with apiVersion=v1,
|
|
12
|
-
// platformCountry=US, accountCountry=US, dashboardType=express, tosType=full, capabilities card_payments and transfers):
|
|
13
|
-
// thirty requirements for a company (the business profile, the statement descriptor, the company with its tax id,
|
|
14
|
-
// phone and address, its owners, its representative with their title and address, the agreement and the payout bank
|
|
15
|
-
// account) and eighteen for an individual. A submit that leaves any out is refused with what is missing. The account's
|
|
16
|
-
// requirements.currently_due lists what is still missing, and charges and payouts are enabled, and its requested
|
|
17
|
-
// capabilities become active (Stripe's move on the capability machine), only once none is. The agreement is the
|
|
18
|
-
// page's own checkbox, recorded as tos_acceptance with the request's address and the time.
|
|
19
|
-
//
|
|
20
|
-
// Where the documentation stops and the twin decides: only these two US sets are modelled; an account elsewhere, or
|
|
21
|
-
// of another business type, is told its requirements are not modelled. Test mode verifies at once whatever is
|
|
22
|
-
// submitted (docs.stripe.com/connect/testing lists the values that pass: date of birth 1901-01-01, SSN last four
|
|
23
|
-
// 0000, tax id 000000000, routing 110000000, account 000123456789); the industries offered are Stripe's supported
|
|
24
|
-
// merchant category codes as its list gives them (./industries.ts), and an industry the page does not offer leaves
|
|
25
|
-
// business_profile.mcc due; the states are the fifty and the District of Columbia; a request with no forwarded address
|
|
26
|
-
// records 127.0.0.1. An update link (account_update) shows the same page.
|
|
27
|
-
import { semanticsContext } from '@volter/world-core';
|
|
28
2
|
import { CONSENT_CSS, flowPage, Payment, PAYMENT_CSS } from '@volter/world-ui';
|
|
29
|
-
import
|
|
30
|
-
import {
|
|
3
|
+
import { nowUnix } from "../engine/stripe.js";
|
|
4
|
+
import { formOf, notFound, seeOther, STRIPE_CONSENT_SKIN } from "./shared.js";
|
|
5
|
+
import { INDUSTRIES as MCCS } from "../engine/industries.js";
|
|
6
|
+
import { currentlyDue, kindOf, OFFERED } from "../engine/onboarding.js";
|
|
31
7
|
import { created, syncExternals } from "../semantics/shared.js";
|
|
32
|
-
import { formOf, seeOther, STRIPE_CONSENT_SKIN } from "./consent-skin.js";
|
|
33
|
-
import { INDUSTRIES as MCCS } from "./industries.js";
|
|
34
8
|
const LINK = 'account_link';
|
|
35
|
-
const OPERATION =
|
|
9
|
+
const OPERATION = { id: 'PostAccountsAccount' };
|
|
36
10
|
const gone = () => flowPage({ title: 'Stripe', status: 404, css: [CONSENT_CSS, STRIPE_CONSENT_SKIN], body: _jsx("main", { className: "consent", children: _jsx("h1", { className: "consent-heading", children: "This link is invalid." }) }) });
|
|
37
11
|
/** A link that can still be used: not used and not past its expiry. */
|
|
38
|
-
const usable = (ctx, link) => link.used !== true && Number(link.expires_at) >
|
|
12
|
+
const usable = (ctx, link) => link.used !== true && Number(link.expires_at) > nowUnix(ctx.occurredAt);
|
|
39
13
|
// the field asks, with no industry chosen until the person chooses one (an unanswered field leaves the MCC due)
|
|
40
14
|
const INDUSTRIES = [{ value: '', label: 'Select your industry' }, ...MCCS.map(([value, label]) => ({ value, label }))];
|
|
41
|
-
const OFFERED = new Set(MCCS.map(([mcc]) => mcc));
|
|
42
15
|
// the fifty states and the District of Columbia, by their USPS codes (the address's `state`, ISO 3166-2 without its
|
|
43
16
|
// country, docs.stripe.com/api/accounts/object)
|
|
44
17
|
const STATES = [
|
|
@@ -51,43 +24,6 @@ const STATES = [
|
|
|
51
24
|
['SC', 'South Carolina'], ['SD', 'South Dakota'], ['TN', 'Tennessee'], ['TX', 'Texas'], ['UT', 'Utah'], ['VT', 'Vermont'], ['VA', 'Virginia'],
|
|
52
25
|
['WA', 'Washington'], ['WV', 'West Virginia'], ['WI', 'Wisconsin'], ['WY', 'Wyoming'],
|
|
53
26
|
].map(([value, label]) => ({ value, label }));
|
|
54
|
-
const has = (v, k) => (v[k] ?? '').trim() !== '';
|
|
55
|
-
const dobPart = (v, i) => /^\d{4}-\d{2}-\d{2}$/.test(v.dob ?? '') && Number((v.dob ?? '').split('-')[i]) > 0;
|
|
56
|
-
/** Each requirement Stripe names, the form's answer that meets it, and which of the two sets it belongs to. */
|
|
57
|
-
const REQUIREMENTS = [
|
|
58
|
-
['business_profile.mcc', (v) => OFFERED.has((v.mcc ?? '').trim()), ['company', 'individual']],
|
|
59
|
-
['business_profile.url', (v) => has(v, 'url'), ['company', 'individual']],
|
|
60
|
-
['settings.payments.statement_descriptor', (v) => has(v, 'statement_descriptor'), ['company', 'individual']],
|
|
61
|
-
['company.name', (v) => has(v, 'company_name'), ['company']],
|
|
62
|
-
['company.tax_id', (v) => has(v, 'company_tax_id'), ['company']],
|
|
63
|
-
['company.phone', (v) => has(v, 'company_phone'), ['company']],
|
|
64
|
-
['company.address.line1', (v) => has(v, 'company_line1'), ['company']],
|
|
65
|
-
['company.address.city', (v) => has(v, 'company_city'), ['company']],
|
|
66
|
-
['company.address.state', (v) => has(v, 'company_state'), ['company']],
|
|
67
|
-
['company.address.postal_code', (v) => has(v, 'company_postal_code'), ['company']],
|
|
68
|
-
['company.owners_provided', (v) => v.owners_provided === 'provided', ['company']],
|
|
69
|
-
['owners.first_name', (v) => has(v, 'owner_first_name'), ['company']],
|
|
70
|
-
['owners.last_name', (v) => has(v, 'owner_last_name'), ['company']],
|
|
71
|
-
['owners.email', (v) => has(v, 'owner_email'), ['company']],
|
|
72
|
-
...['first_name', 'last_name', 'email', 'phone'].flatMap((f) => [[`representative.${f}`, (v) => has(v, f), ['company']], [`individual.${f}`, (v) => has(v, f), ['individual']]]),
|
|
73
|
-
...['day', 'month', 'year'].flatMap((f, i) => [[`representative.dob.${f}`, (v) => dobPart(v, 2 - i), ['company']], [`individual.dob.${f}`, (v) => dobPart(v, 2 - i), ['individual']]]),
|
|
74
|
-
...['line1', 'city', 'state', 'postal_code'].flatMap((f) => [[`representative.address.${f}`, (v) => has(v, f), ['company']], [`individual.address.${f}`, (v) => has(v, f), ['individual']]]),
|
|
75
|
-
['representative.relationship.title', (v) => has(v, 'title'), ['company']],
|
|
76
|
-
['representative.ssn_last_4', (v) => /^\d{4}$/.test(v.ssn_last_4 ?? ''), ['company']],
|
|
77
|
-
['individual.ssn_last_4', (v) => /^\d{4}$/.test(v.ssn_last_4 ?? ''), ['individual']],
|
|
78
|
-
['tos_acceptance.date', (v) => v.tos === 'accepted', ['company', 'individual']],
|
|
79
|
-
['tos_acceptance.ip', (v) => v.tos === 'accepted', ['company', 'individual']],
|
|
80
|
-
['external_account', (v) => has(v, 'routing_number') && has(v, 'account_number'), ['company', 'individual']],
|
|
81
|
-
];
|
|
82
|
-
/** The set an account onboards against: the two US sets the twin models, or none. */
|
|
83
|
-
export function kindOf(account) {
|
|
84
|
-
const type = account.business_type ?? 'individual';
|
|
85
|
-
return (account.country ?? 'US') === 'US' && (type === 'company' || type === 'individual') ? type : undefined;
|
|
86
|
-
}
|
|
87
|
-
/** What an account of this kind still owes, given the form's answers (none: all of it). */
|
|
88
|
-
export function currentlyDue(kind, v = {}) {
|
|
89
|
-
return REQUIREMENTS.filter(([, met, kinds]) => kinds.includes(kind) && !met(v)).map(([req]) => req);
|
|
90
|
-
}
|
|
91
27
|
const field = (id, label, v, extra = {}) => ({ id, label, value: v[id] ?? '', ...extra });
|
|
92
28
|
const addressFields = (prefix, v) => [
|
|
93
29
|
field(`${prefix}line1`, 'Address', v, { autoComplete: 'address-line1' }), field(`${prefix}city`, 'City', v),
|
|
@@ -163,7 +99,7 @@ async function submit(ctx, link, kind, v, ip) {
|
|
|
163
99
|
...(company
|
|
164
100
|
? { company: { name: v.company_name, phone: v.company_phone, address: addressOf('company_'), tax_id_provided: true, owners_provided: true, directors_provided: false, executives_provided: false } }
|
|
165
101
|
: { individual: { ...person, id: `person_${id}` } }),
|
|
166
|
-
tos_acceptance: { date:
|
|
102
|
+
tos_acceptance: { date: nowUnix(ctx.occurredAt), ip, user_agent: null },
|
|
167
103
|
}, 'account.updated');
|
|
168
104
|
if (company) {
|
|
169
105
|
await created(ctx, 'person', { account: id }, { ...person, relationship: { representative: true, executive: true, director: false, owner: false, percent_ownership: null, title: v.title }, metadata: {} });
|
|
@@ -191,35 +127,33 @@ async function visit(ctx, id) {
|
|
|
191
127
|
return seeOther(`${SESSIONS}${session}`);
|
|
192
128
|
}
|
|
193
129
|
/** connect.stripe.com's onboarding pages, or undefined for any other request. */
|
|
194
|
-
export function
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
return seeOther(String(link.return_url));
|
|
224
|
-
};
|
|
130
|
+
export async function screen(ctx) {
|
|
131
|
+
const request = ctx.call.request;
|
|
132
|
+
const path = new URL(request.url).pathname;
|
|
133
|
+
const entry = /^\/setup\/(acctlink_[A-Za-z0-9_]+)\/?$/.exec(path);
|
|
134
|
+
const at = /^\/setup\/s\/(onbs_[A-Za-z0-9_]+)\/?$/.exec(path);
|
|
135
|
+
if (entry && request.method === 'GET')
|
|
136
|
+
return visit(ctx, entry[1]);
|
|
137
|
+
if (!at || (request.method !== 'GET' && request.method !== 'POST'))
|
|
138
|
+
return notFound();
|
|
139
|
+
const sid = at[1];
|
|
140
|
+
const form = request.method === 'POST' ? await formOf(request.clone()) : {};
|
|
141
|
+
// the session the visited link opened, until it is submitted
|
|
142
|
+
const stored = ctx.rowsRaw(LINK).find((l) => l._session === sid && l._submitted !== true);
|
|
143
|
+
const link = stored ? ctx.get(LINK, String(stored.id)) : undefined;
|
|
144
|
+
if (!stored || !link)
|
|
145
|
+
return gone();
|
|
146
|
+
const sessionUrl = `${SESSIONS}${sid}`;
|
|
147
|
+
const account = ctx.get('account', String(link.account)) ?? {};
|
|
148
|
+
const kind = kindOf(account);
|
|
149
|
+
if (!kind)
|
|
150
|
+
return unmodelled(account);
|
|
151
|
+
if (request.method === 'GET')
|
|
152
|
+
return page(ctx, sessionUrl, link, kind);
|
|
153
|
+
const missing = currentlyDue(kind, form);
|
|
154
|
+
if (missing.length)
|
|
155
|
+
return incomplete(ctx, sessionUrl, link, kind, form, missing);
|
|
156
|
+
await ctx.write(LINK, String(stored.id), { _submitted: true }, 'account_link.submitted');
|
|
157
|
+
await submit(ctx, link, kind, form, request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || '127.0.0.1');
|
|
158
|
+
return seeOther(String(link.return_url));
|
|
225
159
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { flowPage } from '@volter/world-ui';
|
|
3
|
+
import { platformAccountDefault } from "../engine/connect.js";
|
|
4
|
+
import { CONNECT_SETTINGS_PATH, PLATFORM_CLIENT_ID, redirectUris, SETTINGS } from "../engine/connect-settings.js";
|
|
5
|
+
import { publicBusinessName } from "../engine/display.js";
|
|
6
|
+
import { PLATFORM_ACCOUNT_ID } from "../engine/stripe.js";
|
|
7
|
+
import { formOf, notFound } from "./shared.js";
|
|
8
|
+
import { connectOAuthSettings, created } from "../semantics/shared.js";
|
|
9
|
+
const PUBLIC_DETAILS_PATH = '/settings/public';
|
|
10
|
+
const MAX_NAME = 5000;
|
|
11
|
+
const SETTINGS_CSS = `
|
|
12
|
+
* { box-sizing: border-box; }
|
|
13
|
+
body { margin: 0; background: #f6f8fa; color: #1a1f36; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Ubuntu, sans-serif; font-size: 14px; }
|
|
14
|
+
.co { max-width: 680px; margin: 48px auto; padding: 0 16px; }
|
|
15
|
+
.co h1 { font-size: 28px; margin: 16px 0 4px; }
|
|
16
|
+
.co-lead { color: #697386; margin: 0 0 24px; }
|
|
17
|
+
.co-card { background: #ffffff; border: 1px solid #e3e8ee; border-radius: 8px; padding: 24px; margin-bottom: 16px; }
|
|
18
|
+
.co-card h2 { font-size: 16px; margin: 0 0 12px; }
|
|
19
|
+
.co-card label { display: block; font-weight: 600; margin-bottom: 6px; }
|
|
20
|
+
.co-card small { display: block; color: #697386; margin-bottom: 8px; }
|
|
21
|
+
.co-card textarea { width: 100%; min-height: 96px; padding: 8px 10px; border: 1px solid #c1c9d2; border-radius: 6px; font: 13px ui-monospace, Menlo, monospace; }
|
|
22
|
+
.co-id { font: 14px ui-monospace, Menlo, monospace; background: #f6f8fa; border: 1px solid #e3e8ee; border-radius: 6px; padding: 6px 10px; display: inline-block; }
|
|
23
|
+
.co-toggle { display: flex; gap: 8px; align-items: center; font-weight: 600; }
|
|
24
|
+
.co-actions { display: flex; justify-content: flex-end; }
|
|
25
|
+
.co-actions button { background: #635bff; color: #ffffff; border: 0; border-radius: 6px; padding: 8px 16px; font: inherit; font-weight: 600; cursor: pointer; }
|
|
26
|
+
.co-notice { padding: 10px 12px; border-radius: 6px; margin-bottom: 16px; }
|
|
27
|
+
.co-error { background: #fff0f3; color: #df1b41; }
|
|
28
|
+
.co-saved { background: #e7f8ef; color: #0e6245; }
|
|
29
|
+
.co-uris { margin: 0; padding-left: 18px; }
|
|
30
|
+
`;
|
|
31
|
+
function settingsPage(s, opts = {}) {
|
|
32
|
+
const enabled = opts.enabled ?? s.oauth_enabled;
|
|
33
|
+
return flowPage({
|
|
34
|
+
title: 'OAuth – Connect settings – Stripe',
|
|
35
|
+
css: [SETTINGS_CSS],
|
|
36
|
+
...(opts.error ? { status: 400 } : {}),
|
|
37
|
+
body: (_jsxs("main", { className: "co", children: [_jsx("h1", { children: "OAuth" }), _jsx("p", { className: "co-lead", children: "Onboard Standard accounts by sending them to Stripe's OAuth flow. Test mode." }), opts.error ? _jsx("p", { className: "co-notice co-error", role: "alert", children: opts.error }) : null, opts.saved ? _jsx("p", { className: "co-notice co-saved", role: "status", children: "Your OAuth settings were saved." }) : null, _jsxs("section", { className: "co-card", children: [_jsx("h2", { children: "Test mode client ID" }), _jsx("small", { children: "The client_id your platform sends to connect.stripe.com/oauth/authorize." }), _jsx("code", { className: "co-id", "data-testid": "connect-client-id", children: PLATFORM_CLIENT_ID }), _jsx("p", { "data-testid": "connect-oauth-status", children: s.oauth_enabled ? 'OAuth is enabled.' : 'OAuth is disabled.' }), s.redirect_uris.length ? _jsx("ul", { className: "co-uris", children: s.redirect_uris.map((u) => _jsx("li", { "data-testid": "connect-redirect-uri", children: u }, u)) }) : _jsx("p", { children: "No redirect URIs." })] }), _jsxs("form", { className: "co-card", method: "post", children: [_jsxs("label", { className: "co-toggle", htmlFor: "oauth_enabled", children: [_jsx("input", { type: "checkbox", id: "oauth_enabled", name: "oauth_enabled", value: "on", defaultChecked: enabled }), " Enable OAuth for Standard accounts"] }), _jsx("label", { htmlFor: "redirect_uris", children: "Redirect URIs" }), _jsx("small", { children: "One per line. The first is used when a request names none. http and localhost are allowed in test mode." }), _jsx("textarea", { id: "redirect_uris", name: "redirect_uris", defaultValue: opts.value ?? s.redirect_uris.join('\n') }), _jsx("div", { className: "co-actions", children: _jsx("button", { type: "submit", children: "Save" }) })] })] })),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const DETAILS_CSS = `
|
|
41
|
+
* { box-sizing: border-box; }
|
|
42
|
+
body { margin: 0; background: #f6f8fa; color: #1a1f36; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Ubuntu, sans-serif; font-size: 14px; }
|
|
43
|
+
.pd { max-width: 640px; margin: 48px auto; padding: 0 16px; }
|
|
44
|
+
.pd h1 { font-size: 28px; margin: 16px 0 4px; }
|
|
45
|
+
.pd-lead { color: #697386; margin: 0 0 24px; }
|
|
46
|
+
.pd-card { background: #ffffff; border: 1px solid #e3e8ee; border-radius: 8px; padding: 24px; }
|
|
47
|
+
.pd-card label { display: block; font-weight: 600; margin-bottom: 6px; }
|
|
48
|
+
.pd-card small { display: block; color: #697386; margin-bottom: 8px; }
|
|
49
|
+
.pd-card input { width: 100%; padding: 8px 10px; border: 1px solid #c1c9d2; border-radius: 6px; font: inherit; }
|
|
50
|
+
.pd-actions { margin-top: 20px; display: flex; justify-content: flex-end; }
|
|
51
|
+
.pd-actions button { background: #635bff; color: #ffffff; border: 0; border-radius: 6px; padding: 8px 16px; font: inherit; font-weight: 600; cursor: pointer; }
|
|
52
|
+
.pd-notice { padding: 10px 12px; border-radius: 6px; margin-bottom: 16px; }
|
|
53
|
+
.pd-error { background: #fff0f3; color: #df1b41; }
|
|
54
|
+
.pd-saved { background: #e7f8ef; color: #0e6245; }
|
|
55
|
+
`;
|
|
56
|
+
function detailsPage(account, opts = {}) {
|
|
57
|
+
const named = account?.business_profile?.name;
|
|
58
|
+
const value = opts.value ?? (typeof named === 'string' ? named : '');
|
|
59
|
+
return flowPage({
|
|
60
|
+
title: 'Public details – Stripe',
|
|
61
|
+
css: [DETAILS_CSS],
|
|
62
|
+
...(opts.error ? { status: 400 } : {}),
|
|
63
|
+
body: (_jsxs("main", { className: "pd", children: [_jsx("h1", { children: "Public details" }), _jsxs("p", { className: "pd-lead", children: ["What your customers see on Checkout, the customer portal, receipts and invoices. Currently shown: ", _jsx("strong", { "data-testid": "public-business-name", children: publicBusinessName(account) })] }), opts.error ? _jsx("p", { className: "pd-notice pd-error", role: "alert", children: opts.error }) : null, opts.saved ? _jsx("p", { className: "pd-notice pd-saved", role: "status", children: "Your public details were saved." }) : null, _jsxs("form", { className: "pd-card", method: "post", children: [_jsx("label", { htmlFor: "business_name", children: "Business name" }), _jsx("small", { children: "The name customers see when they pay you." }), _jsx("input", { id: "business_name", name: "business_name", autoComplete: "organization", defaultValue: value, required: true, maxLength: MAX_NAME }), _jsx("div", { className: "pd-actions", children: _jsx("button", { type: "submit", children: "Save" }) })] })] })),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** Save the business name on the platform's own account: the stored record, made from Stripe's default the first
|
|
67
|
+
* time anything is saved, and otherwise changed in place (its other public details kept). */
|
|
68
|
+
async function save(ctx, name) {
|
|
69
|
+
const held = ctx.get('account', PLATFORM_ACCOUNT_ID);
|
|
70
|
+
const profile = { business_profile: { ...((held ?? platformAccountDefault()).business_profile ?? {}), name } };
|
|
71
|
+
await (held ? ctx.write('account', PLATFORM_ACCOUNT_ID, profile, 'account.update') : created(ctx, 'account', { ...platformAccountDefault(), ...profile }, {}, { operation: 'account.update' }));
|
|
72
|
+
}
|
|
73
|
+
/** What the page says of a name no browser sends (the field is required and bounded), for a client that sends it. */
|
|
74
|
+
function nameRefused(name) {
|
|
75
|
+
return !name ? 'Enter your business name.' : name.length > MAX_NAME ? `Your business name must be at most ${MAX_NAME} characters.` : undefined;
|
|
76
|
+
}
|
|
77
|
+
async function settings(ctx) {
|
|
78
|
+
const request = ctx.call.request;
|
|
79
|
+
const url = new URL(request.url);
|
|
80
|
+
if (request.method !== 'GET' && request.method !== 'POST')
|
|
81
|
+
return notFound();
|
|
82
|
+
const form = request.method === 'POST' ? await formOf(request.clone()) : {};
|
|
83
|
+
const held = connectOAuthSettings(ctx);
|
|
84
|
+
if (request.method === 'GET')
|
|
85
|
+
return settingsPage(held, { saved: url.searchParams.has('saved') });
|
|
86
|
+
const enabled = form.oauth_enabled === 'on' || form.oauth_enabled === 'true';
|
|
87
|
+
const parsed = redirectUris(form.redirect_uris ?? '');
|
|
88
|
+
if ('refused' in parsed)
|
|
89
|
+
return settingsPage(held, { value: form.redirect_uris ?? '', enabled, error: parsed.refused });
|
|
90
|
+
await ctx.record(SETTINGS, { oauth_enabled: enabled, redirect_uris: parsed.uris }, PLATFORM_CLIENT_ID);
|
|
91
|
+
return new Response(null, { status: 303, headers: { location: '?saved=1' } });
|
|
92
|
+
}
|
|
93
|
+
async function publicDetails(ctx) {
|
|
94
|
+
const request = ctx.call.request;
|
|
95
|
+
const url = new URL(request.url);
|
|
96
|
+
if (request.method !== 'GET' && request.method !== 'POST')
|
|
97
|
+
return notFound();
|
|
98
|
+
const form = request.method === 'POST' ? await formOf(request.clone()) : {};
|
|
99
|
+
if (request.method === 'GET')
|
|
100
|
+
return detailsPage(ctx.get('account', PLATFORM_ACCOUNT_ID), { saved: url.searchParams.has('saved') });
|
|
101
|
+
const name = (form.business_name ?? '').trim();
|
|
102
|
+
const refused = nameRefused(name);
|
|
103
|
+
if (refused)
|
|
104
|
+
return detailsPage(ctx.get('account', PLATFORM_ACCOUNT_ID), { value: form.business_name ?? '', error: refused });
|
|
105
|
+
await save(ctx, name);
|
|
106
|
+
// a query-only reference: the page's own path, wherever it is mounted and with or without its trailing slash
|
|
107
|
+
return new Response(null, { status: 303, headers: { location: '?saved=1' } });
|
|
108
|
+
}
|
|
109
|
+
/** dashboard.stripe.com's pages: Public details and the Connect OAuth settings; Stripe's 404 for any other. */
|
|
110
|
+
export async function screen(ctx) {
|
|
111
|
+
const path = new URL(ctx.call.request.url).pathname.replace(/\/+$/, '');
|
|
112
|
+
if (path === PUBLIC_DETAILS_PATH)
|
|
113
|
+
return publicDetails(ctx);
|
|
114
|
+
if (CONNECT_SETTINGS_PATH.test(path))
|
|
115
|
+
return settings(ctx);
|
|
116
|
+
return notFound();
|
|
117
|
+
}
|