@volter/twin-stripe 2.0.38 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -207
- package/dist/generated/pack-facts.json +94 -0
- package/dist/spec/SOURCE.md +14 -0
- package/dist/spec/fixtures3.json.gz +0 -0
- package/dist/spec/openapi.json.gz +0 -0
- package/dist/spec/patches.json +34 -0
- package/dist/spec/sources.json +378 -0
- package/dist/src/cli.js +12 -30
- package/dist/src/engine/after-payment.d.ts +9 -0
- package/dist/src/engine/after-payment.js +28 -0
- package/dist/src/engine/apps-secrets.d.ts +1 -0
- package/dist/src/engine/apps-secrets.js +3 -0
- package/dist/src/engine/balance.d.ts +5 -0
- package/dist/src/engine/balance.js +15 -0
- package/dist/src/engine/billing.d.ts +5 -0
- package/dist/src/engine/billing.js +6 -0
- package/dist/src/engine/charges.d.ts +6 -0
- package/dist/src/engine/charges.js +13 -0
- package/dist/src/engine/checkout.d.ts +24 -0
- package/dist/src/engine/checkout.js +30 -0
- package/dist/src/engine/common.d.ts +19 -0
- package/dist/src/engine/common.js +32 -0
- package/dist/src/{screens → engine}/connect-settings.d.ts +1 -7
- package/dist/src/engine/connect-settings.js +26 -0
- package/dist/src/engine/connect.d.ts +66 -0
- package/dist/src/engine/connect.js +189 -0
- package/dist/src/engine/coupons.d.ts +0 -0
- package/dist/src/engine/coupons.js +5 -0
- package/dist/src/engine/credit-notes.d.ts +1 -0
- package/dist/src/engine/credit-notes.js +5 -0
- package/dist/src/engine/customers.d.ts +12 -0
- package/dist/src/engine/customers.js +30 -0
- package/dist/src/engine/display.d.ts +14 -0
- package/dist/src/engine/display.js +16 -0
- package/dist/src/engine/disputes.d.ts +0 -0
- package/dist/src/engine/disputes.js +9 -0
- package/dist/src/engine/entitlements.d.ts +1 -0
- package/dist/src/engine/entitlements.js +4 -0
- package/dist/src/engine/ephemeral-keys.d.ts +0 -0
- package/dist/src/engine/ephemeral-keys.js +4 -0
- package/dist/src/engine/files.d.ts +18 -0
- package/dist/src/engine/files.js +33 -0
- package/dist/src/engine/front.d.ts +5 -0
- package/dist/src/engine/front.js +25 -0
- package/dist/src/engine/invoices.d.ts +24 -0
- package/dist/src/engine/invoices.js +57 -0
- package/dist/src/engine/issuing.d.ts +35 -0
- package/dist/src/engine/issuing.js +45 -0
- package/dist/src/engine/ledger.d.ts +9 -0
- package/dist/src/engine/ledger.js +9 -0
- package/dist/src/engine/oauth.d.ts +4 -0
- package/dist/src/engine/oauth.js +25 -0
- package/dist/src/engine/onboarding.d.ts +13 -0
- package/dist/src/engine/onboarding.js +42 -0
- package/dist/src/{stripe-params.d.ts → engine/params.d.ts} +20 -3
- package/dist/src/{stripe-params.js → engine/params.js} +7 -60
- package/dist/src/engine/payment-intents.d.ts +10 -0
- package/dist/src/engine/payment-intents.js +27 -0
- package/dist/src/engine/payment-links.d.ts +1 -0
- package/dist/src/engine/payment-links.js +5 -0
- package/dist/src/engine/payment-methods.d.ts +16 -0
- package/dist/src/engine/payment-methods.js +22 -0
- package/dist/src/engine/plans.d.ts +6 -0
- package/dist/src/engine/plans.js +26 -0
- package/dist/src/engine/platform.d.ts +39 -0
- package/dist/src/engine/platform.js +34 -0
- package/dist/src/engine/products.d.ts +0 -0
- package/dist/src/engine/products.js +4 -0
- package/dist/src/engine/radar.d.ts +2 -0
- package/dist/src/engine/radar.js +5 -0
- package/dist/src/engine/refunds.d.ts +1 -0
- package/dist/src/engine/refunds.js +28 -0
- package/dist/src/engine/renewals.d.ts +36 -0
- package/dist/src/engine/renewals.js +65 -0
- package/dist/src/engine/setup-intents.d.ts +1 -0
- package/dist/src/engine/setup-intents.js +4 -0
- package/dist/src/{stripe-twin.d.ts → engine/stripe.d.ts} +11 -39
- package/dist/src/{stripe-twin.js → engine/stripe.js} +25 -310
- package/dist/src/engine/subscription-schedules.d.ts +3 -0
- package/dist/src/engine/subscription-schedules.js +1 -0
- package/dist/src/engine/subscriptions.d.ts +12 -0
- package/dist/src/engine/subscriptions.js +31 -0
- package/dist/src/engine/tax.d.ts +7 -0
- package/dist/src/engine/tax.js +28 -0
- package/dist/src/engine/terminal.d.ts +4 -0
- package/dist/src/engine/terminal.js +20 -0
- package/dist/src/engine/test-clocks.d.ts +5 -0
- package/dist/src/engine/test-clocks.js +9 -0
- package/dist/src/engine/tokens.d.ts +7 -0
- package/dist/src/engine/tokens.js +20 -0
- package/dist/src/engine/transfers.d.ts +2 -0
- package/dist/src/engine/transfers.js +1 -0
- package/dist/src/engine/treasury.d.ts +23 -0
- package/dist/src/engine/treasury.js +45 -0
- package/dist/src/{stripe-version.js → engine/version.js} +4 -3
- package/dist/src/engine/webhook-endpoints.d.ts +1 -0
- package/dist/src/engine/webhook-endpoints.js +12 -0
- package/dist/src/engine/webhooks.d.ts +31 -0
- package/dist/src/engine/webhooks.js +68 -0
- package/dist/src/fetch.d.ts +2 -0
- package/dist/src/fetch.js +26 -0
- package/dist/src/generated/surface.gen.json +1 -1
- package/dist/src/generated/ui.gen.json +1 -1
- package/dist/src/index.d.ts +5 -14
- package/dist/src/index.js +8 -75
- package/dist/src/manifest.js +232 -758
- package/dist/src/screens/billing-portal.d.ts +3 -0
- package/dist/src/screens/{portal.js → billing-portal.js} +28 -54
- package/dist/src/screens/checkout.d.ts +2 -31
- package/dist/src/screens/checkout.js +32 -121
- package/dist/src/screens/connect-oauth.d.ts +3 -27
- package/dist/src/screens/connect-oauth.js +51 -132
- package/dist/src/screens/connect-onboarding.d.ts +3 -0
- package/dist/src/screens/{onboarding.js → connect-onboarding.js} +36 -102
- package/dist/src/screens/dashboard.d.ts +3 -0
- package/dist/src/screens/dashboard.js +117 -0
- package/dist/src/screens/shared.d.ts +27 -0
- package/dist/src/screens/shared.js +87 -0
- package/dist/src/screens/stripe-js.d.ts +3 -0
- package/dist/src/screens/stripe-js.js +135 -0
- package/dist/src/semantics/account_links.d.ts +2 -0
- package/dist/src/semantics/account_links.js +22 -0
- package/dist/src/semantics/accounts.d.ts +4 -0
- package/dist/src/semantics/accounts.js +38 -0
- package/dist/src/semantics/apps.d.ts +4 -0
- package/dist/src/semantics/{apps-secrets.js → apps.js} +14 -26
- package/dist/src/semantics/around.d.ts +2 -0
- package/dist/src/semantics/around.js +16 -0
- package/dist/src/semantics/balance.d.ts +2 -11
- package/dist/src/semantics/balance.js +3 -194
- package/dist/src/semantics/balance_transactions.d.ts +2 -0
- package/dist/src/semantics/balance_transactions.js +15 -0
- package/dist/src/semantics/billing_portal.d.ts +5 -0
- package/dist/src/semantics/billing_portal.js +42 -0
- package/dist/src/semantics/charges.d.ts +3 -28
- package/dist/src/semantics/charges.js +5 -202
- package/dist/src/semantics/checkout.d.ts +3 -20
- package/dist/src/semantics/checkout.js +8 -261
- package/dist/src/semantics/clock.d.ts +2 -0
- package/dist/src/semantics/clock.js +21 -0
- package/dist/src/semantics/coupons.d.ts +2 -6
- package/dist/src/semantics/coupons.js +2 -60
- package/dist/src/semantics/customers.d.ts +6 -6
- package/dist/src/semantics/customers.js +27 -387
- package/dist/src/semantics/disputes.d.ts +2 -2
- package/dist/src/semantics/disputes.js +3 -49
- package/dist/src/semantics/doors.d.ts +23 -0
- package/dist/src/semantics/doors.js +73 -0
- package/dist/src/semantics/entitlements.d.ts +2 -2
- package/dist/src/semantics/entitlements.js +5 -50
- package/dist/src/semantics/events.d.ts +12 -0
- package/dist/src/semantics/events.js +36 -0
- package/dist/src/semantics/index.d.ts +2 -0
- package/dist/src/semantics/index.js +107 -0
- package/dist/src/semantics/invoice_payments.d.ts +2 -0
- package/dist/src/semantics/invoice_payments.js +12 -0
- package/dist/src/semantics/invoiceitems.d.ts +3 -0
- package/dist/src/semantics/invoiceitems.js +59 -0
- package/dist/src/semantics/invoices.d.ts +16 -18
- package/dist/src/semantics/invoices.js +118 -444
- package/dist/src/semantics/payment_intents.d.ts +4 -0
- package/dist/src/semantics/payment_intents.js +79 -0
- package/dist/src/semantics/payment_methods.d.ts +5 -0
- package/dist/src/semantics/payment_methods.js +39 -0
- package/dist/src/semantics/payouts.d.ts +2 -0
- package/dist/src/semantics/payouts.js +21 -0
- package/dist/src/semantics/prices.d.ts +4 -0
- package/dist/src/semantics/prices.js +37 -0
- package/dist/src/semantics/products.d.ts +6 -2
- package/dist/src/semantics/products.js +15 -130
- package/dist/src/semantics/promotion_codes.d.ts +3 -0
- package/dist/src/semantics/promotion_codes.js +35 -0
- package/dist/src/semantics/radar.d.ts +2 -2
- package/dist/src/semantics/radar.js +5 -74
- package/dist/src/semantics/refunds.d.ts +3 -9
- package/dist/src/semantics/refunds.js +6 -192
- package/dist/src/semantics/setup_intents.d.ts +3 -0
- package/dist/src/semantics/{setup-intents.js → setup_intents.js} +10 -53
- package/dist/src/semantics/shared.d.ts +822 -47
- package/dist/src/semantics/shared.js +3044 -78
- package/dist/src/semantics/states.d.ts +531 -0
- package/dist/src/semantics/states.js +852 -0
- package/dist/src/semantics/subscription_items.d.ts +5 -0
- package/dist/src/semantics/subscription_items.js +55 -0
- package/dist/src/semantics/subscription_schedules.d.ts +4 -0
- package/dist/src/semantics/subscription_schedules.js +60 -0
- package/dist/src/semantics/subscriptions.d.ts +7 -11
- package/dist/src/semantics/subscriptions.js +44 -415
- package/dist/src/semantics/test_helpers.d.ts +6 -0
- package/dist/src/semantics/{test-clocks.js → test_helpers.js} +14 -40
- package/dist/src/semantics/transfers.d.ts +2 -2
- package/dist/src/semantics/transfers.js +6 -101
- package/dist/src/server.d.ts +7 -0
- package/dist/src/server.js +7 -0
- package/generated/pack-facts.json +94 -0
- package/package.json +10 -20
- package/spec/SOURCE.md +14 -0
- package/spec/fixtures3.json.gz +0 -0
- package/spec/openapi.json.gz +0 -0
- package/spec/patches.json +34 -0
- package/spec/sources.json +378 -0
- package/src/cli.ts +12 -26
- package/src/engine/after-payment.ts +31 -0
- package/src/engine/apps-secrets.ts +4 -0
- package/src/engine/balance.ts +33 -0
- package/src/engine/billing.ts +16 -0
- package/src/engine/charges.ts +19 -0
- package/src/engine/checkout.ts +40 -0
- package/src/engine/common.ts +47 -0
- package/src/engine/connect-settings.ts +23 -0
- package/src/engine/connect.ts +184 -0
- package/src/engine/coupons.ts +5 -0
- package/src/engine/credit-notes.ts +6 -0
- package/src/engine/customers.ts +35 -0
- package/src/engine/display.ts +19 -0
- package/src/engine/disputes.ts +9 -0
- package/src/engine/entitlements.ts +5 -0
- package/src/engine/ephemeral-keys.ts +3 -0
- package/src/engine/files.ts +41 -0
- package/src/engine/front.ts +31 -0
- package/src/engine/invoices.ts +71 -0
- package/src/engine/issuing.ts +61 -0
- package/src/engine/ledger.ts +50 -0
- package/src/engine/oauth.ts +19 -0
- package/src/engine/onboarding.ts +54 -0
- package/src/{stripe-params.ts → engine/params.ts} +8 -56
- package/src/engine/payment-intents.ts +33 -0
- package/src/engine/payment-links.ts +6 -0
- package/src/engine/payment-methods.ts +25 -0
- package/src/engine/plans.ts +42 -0
- package/src/engine/platform.ts +47 -0
- package/src/engine/products.ts +3 -0
- package/src/engine/radar.ts +6 -0
- package/src/engine/refunds.ts +29 -0
- package/src/engine/renewals.ts +73 -0
- package/src/engine/setup-intents.ts +5 -0
- package/src/{stripe-twin.ts → engine/stripe.ts} +28 -323
- package/src/engine/subscription-schedules.ts +10 -0
- package/src/engine/subscriptions.ts +40 -0
- package/src/engine/tax.ts +39 -0
- package/src/engine/terminal.ts +23 -0
- package/src/engine/test-clocks.ts +11 -0
- package/src/engine/tokens.ts +22 -0
- package/src/engine/transfers.ts +23 -0
- package/src/engine/treasury.ts +47 -0
- package/src/{stripe-version.ts → engine/version.ts} +4 -3
- package/src/engine/webhook-endpoints.ts +14 -0
- package/src/engine/webhooks.ts +72 -0
- package/src/fetch.ts +27 -0
- package/src/generated/surface.gen.json +1 -1
- package/src/generated/ui.gen.json +1 -1
- package/src/index.ts +8 -116
- package/src/manifest.ts +234 -786
- package/src/screens/{portal.tsx → billing-portal.tsx} +32 -37
- package/src/screens/checkout.tsx +33 -95
- package/src/screens/connect-oauth.tsx +64 -88
- package/src/screens/{onboarding.tsx → connect-onboarding.tsx} +38 -82
- package/src/screens/dashboard.tsx +198 -0
- package/src/screens/shared.tsx +84 -0
- package/src/screens/stripe-js.ts +170 -0
- package/src/semantics/account_links.ts +21 -0
- package/src/semantics/accounts.ts +43 -0
- package/src/semantics/{apps-secrets.ts → apps.ts} +17 -32
- package/src/semantics/around.ts +20 -0
- package/src/semantics/balance.ts +6 -207
- package/src/semantics/balance_transactions.ts +20 -0
- package/src/semantics/billing_portal.ts +51 -0
- package/src/semantics/charges.ts +12 -211
- package/src/semantics/checkout.ts +13 -268
- package/src/semantics/clock.ts +26 -0
- package/src/semantics/coupons.ts +5 -67
- package/src/semantics/customers.ts +29 -392
- package/src/semantics/disputes.ts +9 -59
- package/src/semantics/doors.ts +80 -0
- package/src/semantics/entitlements.ts +9 -56
- package/src/semantics/events.ts +44 -0
- package/src/semantics/index.ts +111 -0
- package/src/semantics/invoice_payments.ts +18 -0
- package/src/semantics/invoiceitems.ts +59 -0
- package/src/semantics/invoices.ts +109 -443
- package/src/semantics/payment_intents.ts +83 -0
- package/src/semantics/payment_methods.ts +43 -0
- package/src/semantics/payouts.ts +23 -0
- package/src/semantics/prices.ts +42 -0
- package/src/semantics/products.ts +18 -142
- package/src/semantics/promotion_codes.ts +39 -0
- package/src/semantics/radar.ts +8 -77
- package/src/semantics/refunds.ts +9 -212
- package/src/semantics/setup_intents.ts +45 -0
- package/src/semantics/shared.ts +3092 -106
- package/src/semantics/states.ts +883 -0
- package/src/semantics/subscription_items.ts +54 -0
- package/src/semantics/subscription_schedules.ts +66 -0
- package/src/semantics/subscriptions.ts +49 -432
- package/src/semantics/{test-clocks.ts → test_helpers.ts} +23 -47
- package/src/semantics/transfers.ts +11 -131
- package/src/server.ts +8 -0
- package/client/dashboard-api.ts +0 -286
- package/client/stripe-mirror.css +0 -277
- package/client/stripe-mirror.tsx +0 -1559
- package/dist/client/dashboard-api.d.ts +0 -107
- package/dist/client/dashboard-api.js +0 -238
- package/dist/client/dashboard-api.ts +0 -286
- package/dist/client/stripe-mirror.bundle.js +0 -236
- package/dist/client/stripe-mirror.css +0 -277
- package/dist/client/stripe-mirror.d.ts +0 -134
- package/dist/client/stripe-mirror.js +0 -859
- package/dist/client/stripe-mirror.tsx +0 -1559
- package/dist/src/screens/connect-settings.js +0 -103
- package/dist/src/screens/consent-skin.d.ts +0 -4
- package/dist/src/screens/consent-skin.js +0 -18
- package/dist/src/screens/financial-connections.d.ts +0 -5
- package/dist/src/screens/financial-connections.js +0 -90
- package/dist/src/screens/identity.d.ts +0 -5
- package/dist/src/screens/identity.js +0 -86
- package/dist/src/screens/onboarding.d.ts +0 -13
- package/dist/src/screens/portal.d.ts +0 -5
- package/dist/src/screens/public-details.d.ts +0 -5
- package/dist/src/screens/public-details.js +0 -90
- package/dist/src/semantics/after-payment.d.ts +0 -22
- package/dist/src/semantics/after-payment.js +0 -99
- package/dist/src/semantics/apps-secrets.d.ts +0 -2
- package/dist/src/semantics/billing.d.ts +0 -2
- package/dist/src/semantics/billing.js +0 -220
- package/dist/src/semantics/connect.d.ts +0 -5
- package/dist/src/semantics/connect.js +0 -493
- package/dist/src/semantics/credit-notes.d.ts +0 -2
- package/dist/src/semantics/credit-notes.js +0 -172
- package/dist/src/semantics/ephemeral-keys.d.ts +0 -2
- package/dist/src/semantics/ephemeral-keys.js +0 -34
- package/dist/src/semantics/files.d.ts +0 -2
- package/dist/src/semantics/files.js +0 -125
- package/dist/src/semantics/issuing.d.ts +0 -13
- package/dist/src/semantics/issuing.js +0 -575
- package/dist/src/semantics/ledger.d.ts +0 -59
- package/dist/src/semantics/ledger.js +0 -200
- package/dist/src/semantics/payment-intents.d.ts +0 -18
- package/dist/src/semantics/payment-intents.js +0 -452
- package/dist/src/semantics/payment-links.d.ts +0 -2
- package/dist/src/semantics/payment-links.js +0 -133
- package/dist/src/semantics/payment-methods.d.ts +0 -20
- package/dist/src/semantics/payment-methods.js +0 -140
- package/dist/src/semantics/plans.d.ts +0 -5
- package/dist/src/semantics/plans.js +0 -121
- package/dist/src/semantics/platform.d.ts +0 -9
- package/dist/src/semantics/platform.js +0 -206
- package/dist/src/semantics/renewals.d.ts +0 -47
- package/dist/src/semantics/renewals.js +0 -281
- package/dist/src/semantics/setup-intents.d.ts +0 -2
- package/dist/src/semantics/subscription-schedules.d.ts +0 -2
- package/dist/src/semantics/subscription-schedules.js +0 -119
- package/dist/src/semantics/tax.d.ts +0 -2
- package/dist/src/semantics/tax.js +0 -197
- package/dist/src/semantics/terminal.d.ts +0 -5
- package/dist/src/semantics/terminal.js +0 -182
- package/dist/src/semantics/test-clocks.d.ts +0 -6
- package/dist/src/semantics/tokens.d.ts +0 -4
- package/dist/src/semantics/tokens.js +0 -44
- package/dist/src/semantics/treasury.d.ts +0 -2
- package/dist/src/semantics/treasury.js +0 -377
- package/dist/src/semantics/webhook-endpoints.d.ts +0 -3
- package/dist/src/semantics/webhook-endpoints.js +0 -85
- package/dist/src/stripe-budget.d.ts +0 -55
- package/dist/src/stripe-budget.js +0 -155
- package/dist/src/stripe-capabilities.d.ts +0 -3
- package/dist/src/stripe-capabilities.js +0 -5836
- package/dist/src/stripe-conformance.d.ts +0 -43
- package/dist/src/stripe-conformance.js +0 -105
- package/dist/src/stripe-connector.d.ts +0 -161
- package/dist/src/stripe-connector.js +0 -414
- package/dist/src/stripe-emit.d.ts +0 -2
- package/dist/src/stripe-emit.js +0 -145
- package/dist/src/stripe-events.d.ts +0 -93
- package/dist/src/stripe-events.js +0 -392
- package/dist/src/stripe-js.d.ts +0 -4
- package/dist/src/stripe-js.js +0 -70
- package/dist/src/stripe-mirror-ui.d.ts +0 -15
- package/dist/src/stripe-mirror-ui.js +0 -87
- package/dist/src/stripe-perform-harness.d.ts +0 -9
- package/dist/src/stripe-perform-harness.js +0 -26
- package/dist/src/stripe-server.d.ts +0 -33
- package/dist/src/stripe-server.js +0 -429
- package/dist/src/stripe-shared.d.ts +0 -109
- package/dist/src/stripe-shared.js +0 -276
- package/dist/src/stripe-ui-conformance.d.ts +0 -5
- package/dist/src/stripe-ui-conformance.js +0 -79
- package/dist/src/stripe-ui-structure.d.ts +0 -3
- package/dist/src/stripe-ui-structure.js +0 -168
- package/dist/test-fixtures/stripe-known-deviations.json +0 -110
- package/dist/test-fixtures/stripe-openapi-operations.SOURCE.md +0 -14
- package/dist/test-fixtures/stripe-openapi-operations.json +0 -4717
- package/dist/test-fixtures/stripe-schemas.SOURCE.md +0 -35
- package/dist/test-fixtures/stripe-schemas.json +0 -3813
- package/src/screens/connect-settings.tsx +0 -121
- package/src/screens/consent-skin.ts +0 -20
- package/src/screens/financial-connections.tsx +0 -101
- package/src/screens/identity.tsx +0 -96
- package/src/screens/public-details.tsx +0 -105
- package/src/semantics/after-payment.ts +0 -118
- package/src/semantics/billing.ts +0 -216
- package/src/semantics/connect.ts +0 -487
- package/src/semantics/credit-notes.ts +0 -168
- package/src/semantics/ephemeral-keys.ts +0 -34
- package/src/semantics/files.ts +0 -143
- package/src/semantics/issuing.ts +0 -590
- package/src/semantics/ledger.ts +0 -253
- package/src/semantics/payment-intents.ts +0 -463
- package/src/semantics/payment-links.ts +0 -148
- package/src/semantics/payment-methods.ts +0 -145
- package/src/semantics/plans.ts +0 -131
- package/src/semantics/platform.ts +0 -220
- package/src/semantics/renewals.ts +0 -279
- package/src/semantics/setup-intents.ts +0 -87
- package/src/semantics/subscription-schedules.ts +0 -129
- package/src/semantics/tax.ts +0 -220
- package/src/semantics/terminal.ts +0 -195
- package/src/semantics/tokens.ts +0 -52
- package/src/semantics/treasury.ts +0 -383
- package/src/semantics/webhook-endpoints.ts +0 -87
- package/src/stripe-budget.ts +0 -181
- package/src/stripe-capabilities.ts +0 -5651
- package/src/stripe-conformance.ts +0 -124
- package/src/stripe-connector.ts +0 -462
- package/src/stripe-emit.ts +0 -157
- package/src/stripe-events.ts +0 -434
- package/src/stripe-js.ts +0 -70
- package/src/stripe-mirror-ui.ts +0 -90
- package/src/stripe-perform-harness.ts +0 -29
- package/src/stripe-server.ts +0 -393
- package/src/stripe-shared.ts +0 -297
- package/src/stripe-ui-conformance.ts +0 -88
- package/src/stripe-ui-structure.ts +0 -182
- package/test-fixtures/stripe-known-deviations.json +0 -110
- package/test-fixtures/stripe-openapi-operations.json +0 -4717
- package/test-fixtures/stripe-schemas.json +0 -3813
- /package/dist/src/{screens → engine}/industries.d.ts +0 -0
- /package/dist/src/{screens → engine}/industries.js +0 -0
- /package/dist/src/{semantics → engine}/test-cards.d.ts +0 -0
- /package/dist/src/{semantics → engine}/test-cards.js +0 -0
- /package/dist/src/{stripe-version.d.ts → engine/version.d.ts} +0 -0
- /package/src/{screens → engine}/industries.ts +0 -0
- /package/src/{semantics → engine}/test-cards.ts +0 -0
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import { validateMoney } from "../stripe-twin.js";
|
|
2
|
-
import { settleTopup } from "./ledger.js";
|
|
3
|
-
import { authorizeCharge, mintCharge } from "./payment-intents.js";
|
|
4
|
-
import { at, at_, created, fail, send } from "./shared.js";
|
|
5
|
-
const LOC = 'terminal.location';
|
|
6
|
-
const READER = 'terminal.reader';
|
|
7
|
-
const CONF = 'terminal.configuration';
|
|
8
|
-
/** Update and delete by id, finding tombstones as the hand-written routes did, with Stripe's own operation names. */
|
|
9
|
-
function patch(resource, param, missing, op) {
|
|
10
|
-
return (ctx) => patchRow(ctx, resource, param, missing, op);
|
|
11
|
-
}
|
|
12
|
-
async function patchRow(ctx, resource, param, missing, op) {
|
|
13
|
-
const id = at(ctx, param);
|
|
14
|
-
if (!ctx.row(resource, id, { withDeleted: true }))
|
|
15
|
-
return fail(ctx, `No such ${missing}: '${id}'`, 404, 'resource_missing');
|
|
16
|
-
return ctx.reply(await ctx.write(resource, id, ctx.params, op));
|
|
17
|
-
}
|
|
18
|
-
// A reader's update takes its label and metadata only; it moves to another location by registering it
|
|
19
|
-
// there again (https://docs.stripe.com/api/terminal/readers/update, docs.stripe.com/terminal/fleet/locations-and-zones)
|
|
20
|
-
// (the parameter check refuses anything else, as Stripe does: ../stripe-params.ts)
|
|
21
|
-
const updateReader = patch(READER, 'reader', 'reader', 'terminal_reader.updated');
|
|
22
|
-
function remove(resource, param, missing, op, object) {
|
|
23
|
-
return async (ctx) => {
|
|
24
|
-
const id = at(ctx, param);
|
|
25
|
-
if (!ctx.row(resource, id, { withDeleted: true }))
|
|
26
|
-
return fail(ctx, `No such ${missing}: '${id}'`, 404, 'resource_missing');
|
|
27
|
-
const row = ctx.get(resource, id);
|
|
28
|
-
await ctx.write(resource, id, { deleted: true }, op);
|
|
29
|
-
// a deleted reader still says what device it was
|
|
30
|
-
return ctx.reply({ id, object, deleted: true, ...(object === 'terminal.reader' && row ? { device_type: row.device_type, serial_number: row.serial_number } : {}) });
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
const createLocation = async (ctx) => {
|
|
34
|
-
if (ctx.params.display_name === undefined || ctx.params.display_name === '')
|
|
35
|
-
return fail(ctx, 'Missing required param: display_name.', 400, 'parameter_missing');
|
|
36
|
-
const addr = ctx.params.address && typeof ctx.params.address === 'object' ? ctx.params.address : undefined;
|
|
37
|
-
if (!addr || addr.country === undefined || addr.country === '')
|
|
38
|
-
return fail(ctx, 'Missing required param: address[country].', 400, 'parameter_missing');
|
|
39
|
-
return ctx.reply(await created(ctx, LOC, ctx.params, { livemode: false, metadata: {} }));
|
|
40
|
-
};
|
|
41
|
-
// a test registration code (simulated-wpe) registers a simulated reader, online
|
|
42
|
-
const createReader = async (ctx) => {
|
|
43
|
-
if (ctx.params.registration_code === undefined || ctx.params.registration_code === '')
|
|
44
|
-
return fail(ctx, 'Missing required param: registration_code.', 400, 'parameter_missing');
|
|
45
|
-
return ctx.reply(await created(ctx, READER, ctx.params, {
|
|
46
|
-
status: 'online', device_type: 'simulated_wisepos_e', device_sw_version: null, ip_address: '0.0.0.0',
|
|
47
|
-
serial_number: `STR-${ctx.rowsRaw(READER, { withDeleted: true }).length + 1}`, action: null,
|
|
48
|
-
livemode: false, metadata: {}, label: ctx.params.label ?? 'Twin Reader',
|
|
49
|
-
}));
|
|
50
|
-
};
|
|
51
|
-
const readerMissing = (ctx, id) => fail(ctx, `No such reader: '${id}'`, 404, 'resource_missing');
|
|
52
|
-
// handing the reader a PaymentIntent puts it in an in-progress process_payment_intent action
|
|
53
|
-
const processPaymentIntent = async (ctx) => {
|
|
54
|
-
const id = at(ctx, 'reader');
|
|
55
|
-
if (!ctx.get(READER, id))
|
|
56
|
-
return readerMissing(ctx, id);
|
|
57
|
-
const pi = typeof ctx.params.payment_intent === 'string' ? ctx.params.payment_intent : '';
|
|
58
|
-
if (!pi)
|
|
59
|
-
return fail(ctx, 'Missing required param: payment_intent.', 400, 'parameter_missing');
|
|
60
|
-
if (!ctx.get('payment_intent', pi))
|
|
61
|
-
return fail(ctx, `No such payment_intent: '${pi}'`, 400, 'resource_missing');
|
|
62
|
-
const action = { type: 'process_payment_intent', status: 'in_progress', failure_code: null, failure_message: null, process_payment_intent: { payment_intent: pi } };
|
|
63
|
-
return ctx.reply(await ctx.write(READER, id, { action }, 'terminal_reader.updated'));
|
|
64
|
-
};
|
|
65
|
-
/** A card presented to a reader with no payment in progress. */
|
|
66
|
-
function nothingToPresent(ctx) {
|
|
67
|
-
return fail(ctx, 'This reader has no in-progress payment to present a payment method to.', 400, 'terminal_reader_invalid_state');
|
|
68
|
-
}
|
|
69
|
-
/** Under manual capture the presented card only holds the amount (docs.stripe.com/terminal/payments/collect-card-payment#capture-payment):
|
|
70
|
-
* the hold is an uncaptured charge, as an online authorization's is (authorizeCharge), which the intent's capture takes. */
|
|
71
|
-
async function holdForCapture(ctx, piId, pi, pm, amount) {
|
|
72
|
-
const charge = await authorizeCharge(ctx, piId, { ...pi, payment_method: pm }, amount);
|
|
73
|
-
await ctx.write('payment_intent', piId, { status: 'requires_capture', payment_method: pm, amount_capturable: amount, latest_charge: charge }, 'payment_intent.amount_capturable_updated');
|
|
74
|
-
}
|
|
75
|
-
const presentPaymentMethod = async (ctx) => {
|
|
76
|
-
const id = at(ctx, 'reader');
|
|
77
|
-
const reader = ctx.get(READER, id);
|
|
78
|
-
if (!reader)
|
|
79
|
-
return readerMissing(ctx, id);
|
|
80
|
-
const action = reader.action;
|
|
81
|
-
const piId = String((action?.process_payment_intent?.payment_intent) ?? '');
|
|
82
|
-
if (!action || action.type !== 'process_payment_intent' || action.status !== 'in_progress' || !piId)
|
|
83
|
-
return nothingToPresent(ctx);
|
|
84
|
-
const pi = ctx.get('payment_intent', piId);
|
|
85
|
-
if (!pi)
|
|
86
|
-
return fail(ctx, `No such payment_intent: '${piId}'`, 400, 'resource_missing');
|
|
87
|
-
const manual = pi.capture_method === 'manual' || pi.capture_method === 'manual_preferred';
|
|
88
|
-
const to = manual ? 'requires_capture' : 'succeeded';
|
|
89
|
-
const refused = ctx.legal('payment_intent', 'status', 'PostTestHelpersTerminalReadersReaderPresentPaymentMethod', pi.status, to, piId);
|
|
90
|
-
if (refused)
|
|
91
|
-
return ctx.refuse(refused);
|
|
92
|
-
const pm = await created(ctx, 'payment_method', {}, {
|
|
93
|
-
type: 'card_present', customer: null, livemode: false, billing_details: { address: null, email: null, name: null, phone: null },
|
|
94
|
-
card_present: { brand: 'visa', last4: '4242', exp_month: 12, exp_year: 2030, funding: 'credit', country: 'US', read_method: 'contactless_emv', networks: { available: ['visa'], preferred: null } },
|
|
95
|
-
});
|
|
96
|
-
const amount = Number(pi.amount) || 0;
|
|
97
|
-
if (manual)
|
|
98
|
-
await holdForCapture(ctx, piId, pi, String(pm.id), amount);
|
|
99
|
-
else {
|
|
100
|
-
const charge = await mintCharge(ctx, piId, { ...pi, payment_method: pm.id }, amount);
|
|
101
|
-
await ctx.write('payment_intent', piId, { status: 'succeeded', payment_method: pm.id, amount_received: amount, latest_charge: charge }, 'payment_intent.succeeded');
|
|
102
|
-
}
|
|
103
|
-
return ctx.reply(await ctx.write(READER, id, { action: { ...action, status: 'succeeded' } }, 'terminal_reader.action_succeeded'));
|
|
104
|
-
};
|
|
105
|
-
const cancelAction = async (ctx) => {
|
|
106
|
-
const id = at(ctx, 'reader');
|
|
107
|
-
if (!ctx.get(READER, id))
|
|
108
|
-
return readerMissing(ctx, id);
|
|
109
|
-
return ctx.reply(await ctx.write(READER, id, { action: null }, 'terminal_reader.updated'));
|
|
110
|
-
};
|
|
111
|
-
// a short-lived secret for the reader SDK, answered and not kept
|
|
112
|
-
const connectionToken = async (ctx) => {
|
|
113
|
-
const loc = typeof ctx.params.location === 'string' ? ctx.params.location : undefined;
|
|
114
|
-
return ctx.reply({ object: 'terminal.connection_token', secret: `pst_test_twin_${ctx.rowsRaw(READER, { withDeleted: true }).length + 1}`, ...(loc ? { location: loc } : {}) });
|
|
115
|
-
};
|
|
116
|
-
const createConfiguration = async (ctx) => ctx.reply(await created(ctx, CONF, ctx.params, { is_account_default: false, livemode: false, tipping: null, name: ctx.params.name ?? null }));
|
|
117
|
-
// ── top-ups ──
|
|
118
|
-
// a top-up adds money to the balance it names, through the ledger, once its funds arrive: "USA (USD) ACH Debit Transfer
|
|
119
|
-
// 5 days" (docs.stripe.com/connect/top-ups), and to the Issuing balance alike: "Top-ups debit your bank account. Your
|
|
120
|
-
// top-ups can take up to 5 business days to become available. While they're pending, they won't be added to your
|
|
121
|
-
// Issuing balance" (docs.stripe.com/issuing/funding/balance). It is pending until its expected_availability_date, then
|
|
122
|
-
// settled by settleTopups. Where the documentation stops and the twin decides: every top-up takes exactly five days,
|
|
123
|
-
// whatever its currency and balance, and succeeds.
|
|
124
|
-
const TOPUP_DAYS = 5;
|
|
125
|
-
const createTopup = async (ctx) => {
|
|
126
|
-
const bad = validateMoney(ctx.params);
|
|
127
|
-
if (bad)
|
|
128
|
-
return send(ctx, bad);
|
|
129
|
-
const now = Number(ctx.now());
|
|
130
|
-
return ctx.reply(await created(ctx, 'topup', ctx.params, {
|
|
131
|
-
status: 'pending', expected_availability_date: now + TOPUP_DAYS * 86_400, livemode: false,
|
|
132
|
-
metadata: {}, source: null, statement_descriptor: ctx.params.statement_descriptor ?? null,
|
|
133
|
-
balance_transaction: null, failure_code: null, failure_message: null,
|
|
134
|
-
}));
|
|
135
|
-
};
|
|
136
|
-
/** Time's settling of top-ups, caught up to the World's clock: a pending top-up succeeds on its expected_availability_date,
|
|
137
|
-
* its funds entered then in the balance it names. */
|
|
138
|
-
export async function settleTopups(ctx) {
|
|
139
|
-
const now = Number(ctx.now());
|
|
140
|
-
for (const t of ctx.rowsRaw('topup')) {
|
|
141
|
-
if (t.status !== 'pending' || Number(t.expected_availability_date) > now)
|
|
142
|
-
continue;
|
|
143
|
-
const c = await at_(ctx)(Number(t.expected_availability_date));
|
|
144
|
-
c.legal('topup', 'status', ctx.call.operation.id, 'pending', 'succeeded', String(t.id), 'vendor');
|
|
145
|
-
const bt = await settleTopup(c, String(t.id), Number(t.amount), String(t.currency), t.destination_balance === 'issuing' ? 'issuing' : 'payments');
|
|
146
|
-
await c.write('topup', String(t.id), { status: 'succeeded', balance_transaction: bt }, 'topup.succeeded');
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
const cancelTopup = async (ctx) => {
|
|
150
|
-
const id = at(ctx, 'topup');
|
|
151
|
-
const t = ctx.get('topup', id);
|
|
152
|
-
if (!t)
|
|
153
|
-
return fail(ctx, `No such topup: '${id}'`, 404, 'resource_missing');
|
|
154
|
-
const refused = ctx.legal('topup', 'status', 'PostTopupsTopupCancel', t.status, undefined, id);
|
|
155
|
-
if (refused)
|
|
156
|
-
return ctx.refuse(refused);
|
|
157
|
-
return ctx.reply(await ctx.write('topup', id, { status: 'canceled' }, 'topup.canceled'));
|
|
158
|
-
};
|
|
159
|
-
const updateTopup = async (ctx) => {
|
|
160
|
-
const id = at(ctx, 'topup');
|
|
161
|
-
if (!ctx.get('topup', id))
|
|
162
|
-
return fail(ctx, `No such topup: '${id}'`, 404, 'resource_missing');
|
|
163
|
-
return ctx.reply(await ctx.write('topup', id, ctx.params, 'topup.updated'));
|
|
164
|
-
};
|
|
165
|
-
export const terminal = {
|
|
166
|
-
PostTerminalLocations: createLocation,
|
|
167
|
-
PostTerminalLocationsLocation: patch(LOC, 'location', 'location', 'terminal_location.updated'),
|
|
168
|
-
DeleteTerminalLocationsLocation: remove(LOC, 'location', 'location', 'terminal_location.deleted', 'terminal.location'),
|
|
169
|
-
PostTerminalReaders: createReader,
|
|
170
|
-
PostTerminalReadersReader: updateReader,
|
|
171
|
-
DeleteTerminalReadersReader: remove(READER, 'reader', 'reader', 'terminal_reader.deleted', 'terminal.reader'),
|
|
172
|
-
PostTerminalReadersReaderProcessPaymentIntent: processPaymentIntent,
|
|
173
|
-
PostTerminalReadersReaderCancelAction: cancelAction,
|
|
174
|
-
PostTestHelpersTerminalReadersReaderPresentPaymentMethod: presentPaymentMethod,
|
|
175
|
-
PostTerminalConnectionTokens: connectionToken,
|
|
176
|
-
PostTerminalConfigurations: createConfiguration,
|
|
177
|
-
PostTerminalConfigurationsConfiguration: patch(CONF, 'configuration', 'configuration', 'terminal_configuration.updated'),
|
|
178
|
-
DeleteTerminalConfigurationsConfiguration: remove(CONF, 'configuration', 'configuration', 'terminal_configuration.deleted', 'terminal.configuration'),
|
|
179
|
-
PostTopups: createTopup,
|
|
180
|
-
PostTopupsTopupCancel: cancelTopup,
|
|
181
|
-
PostTopupsTopup: updateTopup,
|
|
182
|
-
};
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { Semantics, SemanticsContext } from '@volter/world-core';
|
|
2
|
-
/** Each advancing clock whose advance has taken its time reaches its target, caught up before anything is answered:
|
|
3
|
-
* its frozen_time moves, it is ready, and its customers' trials and periods that ended by then end
|
|
4
|
-
* (./renewals.ts advanceBilling). The advance's own write is when it began (the kernel's updatedAt). */
|
|
5
|
-
export declare function finishClockAdvances(ctx: SemanticsContext): Promise<void>;
|
|
6
|
-
export declare const testClocks: Record<string, Semantics>;
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { paymentMethodSubObject } from "../stripe-twin.js";
|
|
2
|
-
import { created, fail } from "./shared.js";
|
|
3
|
-
/** The Card object a card token carries, as the served spec's `card` describes it: its brand as a name ("Card brand.
|
|
4
|
-
* Can be `American Express`, ... `MasterCard`, `UnionPay`, `Visa`, or `Unknown`"), the holder's name and address as
|
|
5
|
-
* given ("Cardholder name"), and the CVC's check when one was given ("If a CVC was provided, results of the check:
|
|
6
|
-
* pass, fail, unavailable, or unchecked"). Where the documentation stops and the twin decides: a test card's CVC passes. */
|
|
7
|
-
const BRAND_NAMES = { amex: 'American Express', cartes_bancaires: 'Cartes Bancaires', diners: 'Diners Club', discover: 'Discover', eftpos_au: 'Eftpos Australia', girocard: 'Girocard', jcb: 'JCB', mastercard: 'MasterCard', unionpay: 'UnionPay', visa: 'Visa' };
|
|
8
|
-
export function legacyCard(id, card) {
|
|
9
|
-
const pm = paymentMethodSubObject('card', { card }).card;
|
|
10
|
-
const given = (k) => (typeof card[k] === 'string' && card[k] !== '' ? card[k] : null);
|
|
11
|
-
return {
|
|
12
|
-
id, object: 'card', brand: BRAND_NAMES[String(pm.brand)] ?? 'Unknown', last4: pm.last4, exp_month: pm.exp_month, exp_year: pm.exp_year,
|
|
13
|
-
funding: pm.funding ?? 'credit', country: pm.country ?? null, fingerprint: pm.fingerprint ?? null, name: given('name'),
|
|
14
|
-
address_line1: given('address_line1'), address_line2: given('address_line2'), address_city: given('address_city'), address_state: given('address_state'),
|
|
15
|
-
address_zip: given('address_zip'), address_country: given('address_country'), address_line1_check: null, address_zip_check: null,
|
|
16
|
-
cvc_check: card.cvc !== undefined && card.cvc !== '' ? 'pass' : null, dynamic_last4: null, tokenization_method: null, metadata: {}, customer: null,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
/** A token asked for none of what a token holds. */
|
|
20
|
-
function noTokenSource(ctx) {
|
|
21
|
-
return fail(ctx, 'You must provide one of the following parameters: card, bank_account, pii.', 400, 'parameter_missing');
|
|
22
|
-
}
|
|
23
|
-
/** A bank account token's account, as the legacy BankAccount object holds it. */
|
|
24
|
-
function bankAccountOf(ctx, ba) {
|
|
25
|
-
const acct = typeof ba.account_number === 'string' ? ba.account_number.replace(/\D/g, '') : '';
|
|
26
|
-
return {
|
|
27
|
-
id: ctx.mint('external_account'), object: 'bank_account', account_holder_name: ba.account_holder_name ?? null,
|
|
28
|
-
account_holder_type: ba.account_holder_type ?? null, bank_name: 'STRIPE TEST BANK',
|
|
29
|
-
country: ba.country ?? 'US', currency: ba.currency ?? 'usd', fingerprint: 'twin_ba_fp',
|
|
30
|
-
last4: acct ? acct.slice(-4) : '6789', routing_number: ba.routing_number ?? '110000000', status: 'new',
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
const create = async (ctx) => {
|
|
34
|
-
const params = ctx.params;
|
|
35
|
-
const isObject = (v) => !!v && typeof v === 'object';
|
|
36
|
-
const type = isObject(params.card) ? 'card' : isObject(params.bank_account) ? 'bank_account' : isObject(params.pii) ? 'pii' : undefined;
|
|
37
|
-
if (!type)
|
|
38
|
-
return noTokenSource(ctx);
|
|
39
|
-
const sub = type === 'card'
|
|
40
|
-
? { card: legacyCard(ctx.mint('payment_method').replace('pm_', 'card_'), params.card) }
|
|
41
|
-
: type === 'bank_account' ? { bank_account: bankAccountOf(ctx, params.bank_account) } : {};
|
|
42
|
-
return ctx.reply(await created(ctx, 'token', { type }, { ...sub, client_ip: null, used: false, livemode: false }));
|
|
43
|
-
};
|
|
44
|
-
export const tokens = { PostTokens: create };
|
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
import { PLATFORM_ACCOUNT_ID, validateMoney } from "../stripe-twin.js";
|
|
2
|
-
import { at, created, fail, list, newest, send, where } from "./shared.js";
|
|
3
|
-
const FA = 'treasury.financial_account';
|
|
4
|
-
const TX = 'treasury.transaction';
|
|
5
|
-
const ENTRY = 'treasury.transaction_entry';
|
|
6
|
-
const ENTRY_TYPES = new Set(['credit_reversal', 'debit_reversal', 'inbound_transfer', 'inbound_transfer_return', 'issuing_authorization_hold', 'issuing_authorization_release', 'outbound_payment', 'outbound_transfer', 'received_credit', 'received_debit']);
|
|
7
|
-
const faMissing = (ctx, id, status = 404) => fail(ctx, `No such financial account: '${id}'`, status, 'resource_missing');
|
|
8
|
-
const metadataOf = (ctx) => (ctx.params.metadata && typeof ctx.params.metadata === 'object' ? ctx.params.metadata : {});
|
|
9
|
-
/** A financial account's features as the served spec's treasury.financial_account_features gives them: a toggle
|
|
10
|
-
* (card_issuing, deposit_insurance, intra_stripe_flows) or a feature per network (financial_addresses.aba,
|
|
11
|
-
* inbound_transfers.ach, outbound_payments.ach and .us_domestic_wire, outbound_transfers likewise), each
|
|
12
|
-
* { requested, status, status_details }. What a request asks for is laid over what the account had. Where the
|
|
13
|
-
* documentation stops and the twin decides: a requested feature is active at once (test mode), and one never requested
|
|
14
|
-
* is left out (each is optional in the served spec). Answers the features and the active features' paths. */
|
|
15
|
-
const NETWORKS = { financial_addresses: ['aba'], inbound_transfers: ['ach'], outbound_payments: ['ach', 'us_domestic_wire'], outbound_transfers: ['ach', 'us_domestic_wire'] };
|
|
16
|
-
const TOGGLES = ['card_issuing', 'deposit_insurance', 'intra_stripe_flows'];
|
|
17
|
-
function featuresOf(requested, had = {}) {
|
|
18
|
-
const on = (v) => v === true || v === 'true';
|
|
19
|
-
const setting = (asked) => ({ requested: asked, status: asked ? 'active' : 'restricted', status_details: [] });
|
|
20
|
-
const features = { ...had };
|
|
21
|
-
for (const t of TOGGLES) {
|
|
22
|
-
const r = requested[t];
|
|
23
|
-
if (r && typeof r === 'object' && r.requested !== undefined)
|
|
24
|
-
features[t] = setting(on(r.requested));
|
|
25
|
-
}
|
|
26
|
-
for (const [f, nets] of Object.entries(NETWORKS)) {
|
|
27
|
-
const r = requested[f];
|
|
28
|
-
if (!r || typeof r !== 'object')
|
|
29
|
-
continue;
|
|
30
|
-
const current = features[f] ?? {};
|
|
31
|
-
const next = { ...current };
|
|
32
|
-
for (const n of nets) {
|
|
33
|
-
const nr = r[n];
|
|
34
|
-
if (nr && typeof nr === 'object' && nr.requested !== undefined)
|
|
35
|
-
next[n] = setting(on(nr.requested));
|
|
36
|
-
}
|
|
37
|
-
features[f] = next;
|
|
38
|
-
}
|
|
39
|
-
const active = [];
|
|
40
|
-
for (const t of TOGGLES)
|
|
41
|
-
if (features[t]?.status === 'active')
|
|
42
|
-
active.push(t);
|
|
43
|
-
for (const [f, nets] of Object.entries(NETWORKS))
|
|
44
|
-
for (const n of nets)
|
|
45
|
-
if (features[f]?.[n]?.status === 'active')
|
|
46
|
-
active.push(`${f}.${n}`);
|
|
47
|
-
return { features, active };
|
|
48
|
-
}
|
|
49
|
-
/** The account's bank address once financial_addresses.aba is active: "The FinancialAccount gains a FinancialAddress
|
|
50
|
-
* when the `financial_addresses.aba` feature is active", and "A FinancialAddress is not added until the
|
|
51
|
-
* financial_addresses.aba feature has been activated" (docs.stripe.com/treasury/account-management/financial-accounts),
|
|
52
|
-
* whose test example answers "bank_name": "Stripe Test Bank", "routing_number": "000000001" and the networks "ach",
|
|
53
|
-
* "us_domestic_wire", "rtp"; "The full account_number is only returned if the request expands it" (the same page).
|
|
54
|
-
* Where the documentation stops and the twin decides: the account number is ten digits drawn from the account's id,
|
|
55
|
-
* answered by its last four (the full number is null: expanding it is not modelled), and the holder's name is the
|
|
56
|
-
* platform account's business name, its dashboard name or, with neither, its id. */
|
|
57
|
-
function abaAddress(ctx, faId) {
|
|
58
|
-
let n = 0;
|
|
59
|
-
for (const ch of faId)
|
|
60
|
-
n = (n * 31 + ch.charCodeAt(0)) % 10_000_000_000;
|
|
61
|
-
const number = String(n).padStart(10, '0');
|
|
62
|
-
const platform = ctx.get('account', PLATFORM_ACCOUNT_ID);
|
|
63
|
-
const name = (platform?.business_profile?.name ?? platform?.settings?.dashboard?.display_name ?? PLATFORM_ACCOUNT_ID);
|
|
64
|
-
return { type: 'aba', supported_networks: ['ach', 'us_domestic_wire', 'rtp'], aba: { account_holder_name: name, account_number: null, account_number_last4: number.slice(-4), bank_name: 'Stripe Test Bank', routing_number: '000000001' } };
|
|
65
|
-
}
|
|
66
|
-
const addressesOf = (ctx, faId, active) => (active.includes('financial_addresses.aba') ? [abaAddress(ctx, faId)] : []);
|
|
67
|
-
// an account opens with a zero balance in each supported currency and the requested features active
|
|
68
|
-
const open = async (ctx) => {
|
|
69
|
-
const p = ctx.params;
|
|
70
|
-
const currencies = Array.isArray(p.supported_currencies) ? p.supported_currencies.map(String) : typeof p.supported_currencies === 'string' ? [p.supported_currencies] : [];
|
|
71
|
-
if (currencies.length === 0)
|
|
72
|
-
return fail(ctx, 'Missing required param: supported_currencies.', 400, 'parameter_missing');
|
|
73
|
-
const requested = p.features && typeof p.features === 'object' ? p.features : {};
|
|
74
|
-
const { features, active } = featuresOf(requested);
|
|
75
|
-
const zero = Object.fromEntries(currencies.map((c) => [c, 0]));
|
|
76
|
-
const id = ctx.mint(FA);
|
|
77
|
-
return ctx.reply(await created(ctx, FA, { id }, {
|
|
78
|
-
livemode: false, status: 'open', country: p.country ?? 'US',
|
|
79
|
-
supported_currencies: currencies, active_features: active, pending_features: [], restricted_features: [],
|
|
80
|
-
features, balance: { cash: zero, inbound_pending: zero, outbound_pending: zero },
|
|
81
|
-
financial_addresses: addressesOf(ctx, id, active), platform_restrictions: null,
|
|
82
|
-
status_details: { closed: null },
|
|
83
|
-
metadata: metadataOf(ctx),
|
|
84
|
-
}));
|
|
85
|
-
};
|
|
86
|
-
// reading the features answers them; updating them lays the request over them (featuresOf)
|
|
87
|
-
const features = async (ctx) => {
|
|
88
|
-
const id = at(ctx, 'financial_account');
|
|
89
|
-
const f = ctx.get(FA, id);
|
|
90
|
-
if (!f)
|
|
91
|
-
return faMissing(ctx, id);
|
|
92
|
-
if (ctx.call.request.method === 'GET')
|
|
93
|
-
return ctx.reply({ object: 'treasury.financial_account_features', ...f.features });
|
|
94
|
-
const next = featuresOf(ctx.params, f.features ?? {});
|
|
95
|
-
const updated = await ctx.write(FA, id, { features: next.features, active_features: next.active, financial_addresses: addressesOf(ctx, id, next.active) }, 'financial_account.features_updated');
|
|
96
|
-
return ctx.reply({ object: 'treasury.financial_account_features', ...updated.features });
|
|
97
|
-
};
|
|
98
|
-
// only metadata and platform restrictions change
|
|
99
|
-
const updateAccount = async (ctx) => {
|
|
100
|
-
const id = at(ctx, 'financial_account');
|
|
101
|
-
if (!ctx.get(FA, id))
|
|
102
|
-
return faMissing(ctx, id);
|
|
103
|
-
const patch = {};
|
|
104
|
-
if (ctx.params.metadata !== undefined)
|
|
105
|
-
patch.metadata = ctx.params.metadata;
|
|
106
|
-
if (ctx.params.platform_restrictions !== undefined)
|
|
107
|
-
patch.platform_restrictions = ctx.params.platform_restrictions;
|
|
108
|
-
return ctx.reply(await ctx.write(FA, id, patch, 'financial_account.updated'));
|
|
109
|
-
};
|
|
110
|
-
/** Post a flow's double entry: a ledger transaction with its net balance impact, and its one entry. */
|
|
111
|
-
async function post(ctx, o) {
|
|
112
|
-
const now = ctx.now();
|
|
113
|
-
const txnId = ctx.mint(TX);
|
|
114
|
-
await ctx.write(TX, txnId, {
|
|
115
|
-
object: 'treasury.transaction', created: now, livemode: false,
|
|
116
|
-
amount: o.amount, currency: o.currency, financial_account: o.financial_account,
|
|
117
|
-
flow: o.flow, flow_type: o.flow_type, flow_details: null,
|
|
118
|
-
status: o.status, balance_impact: o.balance_impact, description: o.description,
|
|
119
|
-
entries: { object: 'list', has_more: false, url: `/v1/treasury/transaction_entries?transaction=${txnId}`, data: [] },
|
|
120
|
-
status_transitions: { posted_at: o.status === 'posted' ? now : null, void_at: null },
|
|
121
|
-
}, 'treasury_transaction.created');
|
|
122
|
-
await ctx.write(ENTRY, ctx.mint(ENTRY), {
|
|
123
|
-
object: 'treasury.transaction_entry', created: now, livemode: false,
|
|
124
|
-
amount: o.amount, currency: o.currency, financial_account: o.financial_account,
|
|
125
|
-
flow: o.flow, flow_type: o.flow_type,
|
|
126
|
-
flow_details: o.flowDetailKey ? { type: o.flowDetailKey, [o.flowDetailKey]: o.flow } : null,
|
|
127
|
-
transaction: txnId, effective_at: now,
|
|
128
|
-
balance_impact: o.balance_impact,
|
|
129
|
-
// what the entry records: its flow's kind where Stripe names one (docs.stripe.com/api/treasury/transaction_entries/object);
|
|
130
|
-
// a vendor `type`, kept apart from the kernel's row type
|
|
131
|
-
_stripe_type: ENTRY_TYPES.has(String(o.flow_type)) ? o.flow_type : 'other',
|
|
132
|
-
}, 'treasury_transaction_entry.created');
|
|
133
|
-
// the account's balance moves by the entry's impact: cash "Funds the user can spend right now", inbound_pending
|
|
134
|
-
// "Funds not spendable yet", outbound_pending "held for pending outbound flows" (the served spec's balance)
|
|
135
|
-
const account = ctx.get(FA, o.financial_account);
|
|
136
|
-
if (account) {
|
|
137
|
-
const bal = account.balance ?? { cash: {}, inbound_pending: {}, outbound_pending: {} };
|
|
138
|
-
const moved = Object.fromEntries(['cash', 'inbound_pending', 'outbound_pending'].map((k) => [k, { ...(bal[k] ?? {}), [o.currency]: ((bal[k] ?? {})[o.currency] ?? 0) + (Number(o.balance_impact[k]) || 0) }]));
|
|
139
|
-
await ctx.write(FA, o.financial_account, { balance: moved }, 'financial_account.balance_updated');
|
|
140
|
-
}
|
|
141
|
-
return txnId;
|
|
142
|
-
}
|
|
143
|
-
/** The request's funding account, or the refusal. */
|
|
144
|
-
function fundingAccount(ctx) {
|
|
145
|
-
const bad = validateMoney(ctx.params);
|
|
146
|
-
if (bad)
|
|
147
|
-
return send(ctx, bad);
|
|
148
|
-
const fa = typeof ctx.params.financial_account === 'string' ? ctx.params.financial_account : '';
|
|
149
|
-
if (!fa)
|
|
150
|
-
return fail(ctx, 'Missing required param: financial_account.', 400, 'parameter_missing');
|
|
151
|
-
return ctx.get(FA, fa) ? fa : faMissing(ctx, fa, 400);
|
|
152
|
-
}
|
|
153
|
-
const noTransitions = { canceled_at: null, failed_at: null, posted_at: null, returned_at: null };
|
|
154
|
-
/** Where an outbound flow sends its money, as the served spec's payment method details give it: the bank account's
|
|
155
|
-
* holder type, last four digits and routing number from what the request sent (a saved method's, or the test bank's
|
|
156
|
-
* when none is given), and its billing details (required): the name and email sent, the address lines null where not
|
|
157
|
-
* given. Where the documentation stops and the twin decides: a saved method's details are the test bank's. */
|
|
158
|
-
function destinationDetails(ctx, dest) {
|
|
159
|
-
const data = ctx.params.destination_payment_method_data && typeof ctx.params.destination_payment_method_data === 'object' ? ctx.params.destination_payment_method_data : {};
|
|
160
|
-
const bank = data.us_bank_account && typeof data.us_bank_account === 'object' ? data.us_bank_account : {};
|
|
161
|
-
const billing = data.billing_details && typeof data.billing_details === 'object' ? data.billing_details : {};
|
|
162
|
-
const address = billing.address && typeof billing.address === 'object' ? billing.address : {};
|
|
163
|
-
const number = typeof bank.account_number === 'string' ? bank.account_number.replace(/\D/g, '') : '';
|
|
164
|
-
const billing_details = { name: billing.name ?? null, email: billing.email ?? null, address: Object.fromEntries(['city', 'country', 'line1', 'line2', 'postal_code', 'state'].map((k) => [k, address[k] ?? null])) };
|
|
165
|
-
if (dest && data.type === undefined && dest.startsWith('fa_'))
|
|
166
|
-
return { type: 'financial_account', financial_account: { id: dest, network: 'stripe' }, billing_details };
|
|
167
|
-
return {
|
|
168
|
-
type: 'us_bank_account', billing_details,
|
|
169
|
-
us_bank_account: { last4: number ? number.slice(-4) : '6789', network: 'ach', routing_number: typeof bank.routing_number === 'string' ? bank.routing_number : '110000000', account_holder_type: typeof bank.account_holder_type === 'string' ? bank.account_holder_type : 'individual', account_type: typeof bank.account_type === 'string' ? bank.account_type : 'checking', bank_name: 'STRIPE TEST BANK', mandate: null },
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
/** An outbound payment that names no destination: "You must provide either destination_payment_method or
|
|
173
|
-
* destination_payment_method_data" (the served spec's outbound payment create). */
|
|
174
|
-
function noDestination(ctx) {
|
|
175
|
-
return fail(ctx, 'You must provide either `destination_payment_method` or `destination_payment_method_data`.', 400, 'parameter_missing');
|
|
176
|
-
}
|
|
177
|
-
const outboundPayment = async (ctx) => {
|
|
178
|
-
const fa = fundingAccount(ctx);
|
|
179
|
-
if (fa instanceof Response)
|
|
180
|
-
return fa;
|
|
181
|
-
const dest = typeof ctx.params.destination_payment_method === 'string' ? ctx.params.destination_payment_method : null;
|
|
182
|
-
if (!dest && !(ctx.params.destination_payment_method_data && typeof ctx.params.destination_payment_method_data === 'object'))
|
|
183
|
-
return noDestination(ctx);
|
|
184
|
-
const amount = ctx.params.amount;
|
|
185
|
-
const currency = String(ctx.params.currency);
|
|
186
|
-
const id = ctx.mint('treasury.outbound_payment');
|
|
187
|
-
const txn = await post(ctx, { financial_account: fa, amount: -amount, currency, flow: id, flow_type: 'outbound_payment', status: 'open', balance_impact: { cash: -amount, inbound_pending: 0, outbound_pending: amount }, description: 'OutboundPayment' });
|
|
188
|
-
return ctx.reply(await created(ctx, 'treasury.outbound_payment', { id }, {
|
|
189
|
-
amount, currency, financial_account: fa, livemode: false, status: 'processing', cancelable: true,
|
|
190
|
-
destination_payment_method: dest,
|
|
191
|
-
destination_payment_method_details: destinationDetails(ctx, dest),
|
|
192
|
-
destination_payment_method_data: null, end_user_details: null, expected_arrival_date: Number(ctx.now()) + 2 * 86400,
|
|
193
|
-
hosted_regulatory_receipt_url: null, returned_details: null, statement_descriptor: ctx.params.statement_descriptor ?? 'payment',
|
|
194
|
-
description: typeof ctx.params.description === 'string' ? ctx.params.description : null, transaction: txn, status_transitions: noTransitions, metadata: metadataOf(ctx),
|
|
195
|
-
}));
|
|
196
|
-
};
|
|
197
|
-
const outboundTransfer = async (ctx) => {
|
|
198
|
-
const fa = fundingAccount(ctx);
|
|
199
|
-
if (fa instanceof Response)
|
|
200
|
-
return fa;
|
|
201
|
-
const dest = typeof ctx.params.destination_payment_method === 'string' ? ctx.params.destination_payment_method : null;
|
|
202
|
-
const amount = ctx.params.amount;
|
|
203
|
-
const currency = String(ctx.params.currency);
|
|
204
|
-
const id = ctx.mint('treasury.outbound_transfer');
|
|
205
|
-
const txn = await post(ctx, { financial_account: fa, amount: -amount, currency, flow: id, flow_type: 'outbound_transfer', status: 'open', balance_impact: { cash: -amount, inbound_pending: 0, outbound_pending: amount }, description: 'OutboundTransfer' });
|
|
206
|
-
return ctx.reply(await created(ctx, 'treasury.outbound_transfer', { id }, {
|
|
207
|
-
amount, currency, financial_account: fa, livemode: false, status: 'processing', cancelable: true,
|
|
208
|
-
destination_payment_method: dest, destination_payment_method_details: destinationDetails(ctx, dest),
|
|
209
|
-
expected_arrival_date: Number(ctx.now()) + 2 * 86400, hosted_regulatory_receipt_url: null,
|
|
210
|
-
returned_details: null, statement_descriptor: ctx.params.statement_descriptor ?? 'transfer',
|
|
211
|
-
description: typeof ctx.params.description === 'string' ? ctx.params.description : null, transaction: txn, status_transitions: noTransitions, metadata: metadataOf(ctx),
|
|
212
|
-
}));
|
|
213
|
-
};
|
|
214
|
-
/** The bank account an inbound transfer may pull from: "You must first set up the account-attached payment method for
|
|
215
|
-
* inbound flows and verify the bank account using a SetupIntent", and invalid ones ("of unsupported types, containing
|
|
216
|
-
* an unverified bank account, or not set up for inbound flows") "throw the same errors as in live mode"
|
|
217
|
-
* (docs.stripe.com/treasury/moving-money/financial-accounts/into/inbound-transfers). A payment method is set up for
|
|
218
|
-
* inbound flows by a SetupIntent whose flow_directions holds `inbound`, and verified when that SetupIntent succeeded
|
|
219
|
-
* ("The bank account has been instantly verified or verification isn't necessary", its `succeeded` status,
|
|
220
|
-
* docs.stripe.com/treasury/connect/legacy/v1/moving-money/working-with-bankaccount-objects).
|
|
221
|
-
* Where the documentation stops and the twin decides: the page quotes no error, so the twin answers the documented code
|
|
222
|
-
* for the class, payment_method_unexpected_state ("The provided payment method's state was incompatible with the
|
|
223
|
-
* operation you were trying to perform", docs.stripe.com/error-codes), in its own words; an existing verified
|
|
224
|
-
* BankAccount (`ba_`), which the page also allows, is not checked. Answers the payment method, or the refusal. */
|
|
225
|
-
function inboundOrigin(ctx, origin) {
|
|
226
|
-
if (origin.startsWith('ba_'))
|
|
227
|
-
return undefined;
|
|
228
|
-
const pm = ctx.get('payment_method', origin);
|
|
229
|
-
if (!pm)
|
|
230
|
-
return fail(ctx, `No such PaymentMethod: '${origin}'`, 400, 'resource_missing');
|
|
231
|
-
if (pm.type !== 'us_bank_account')
|
|
232
|
-
return fail(ctx, `The PaymentMethod '${origin}' is of type ${String(pm.type)}; an InboundTransfer pulls from a us_bank_account.`, 400, 'payment_method_unexpected_state');
|
|
233
|
-
const setUp = ctx.rows('setup_intent').some((si) => si.payment_method === origin && si.status === 'succeeded' && Array.isArray(si.flow_directions) && si.flow_directions.map(String).includes('inbound'));
|
|
234
|
-
if (!setUp)
|
|
235
|
-
return fail(ctx, `The PaymentMethod '${origin}' has not been set up for inbound flows and verified with a SetupIntent.`, 400, 'payment_method_unexpected_state');
|
|
236
|
-
return pm;
|
|
237
|
-
}
|
|
238
|
-
const inboundTransfer = async (ctx) => {
|
|
239
|
-
const fa = fundingAccount(ctx);
|
|
240
|
-
if (fa instanceof Response)
|
|
241
|
-
return fa;
|
|
242
|
-
const origin = typeof ctx.params.origin_payment_method === 'string' ? ctx.params.origin_payment_method : '';
|
|
243
|
-
if (!origin)
|
|
244
|
-
return fail(ctx, 'Missing required param: origin_payment_method.', 400, 'parameter_missing');
|
|
245
|
-
const pm = inboundOrigin(ctx, origin);
|
|
246
|
-
if (pm instanceof Response)
|
|
247
|
-
return pm;
|
|
248
|
-
const bank = (pm?.us_bank_account ?? {});
|
|
249
|
-
const billing = (pm?.billing_details ?? {});
|
|
250
|
-
// the debit mandate the account's verification made (docs.stripe.com/payments/setup-intents#mandates)
|
|
251
|
-
const mandate = ctx.rows('setup_intent').find((si) => si.payment_method === origin && si.status === 'succeeded' && typeof si.mandate === 'string')?.mandate;
|
|
252
|
-
const amount = ctx.params.amount;
|
|
253
|
-
const currency = String(ctx.params.currency);
|
|
254
|
-
const id = ctx.mint('treasury.inbound_transfer');
|
|
255
|
-
// it starts processing, its funds pending inbound, until confirmed (succeedInbound)
|
|
256
|
-
const txn = await post(ctx, { financial_account: fa, amount, currency, flow: id, flow_type: 'inbound_transfer', status: 'open', balance_impact: { cash: 0, inbound_pending: amount, outbound_pending: 0 }, description: 'InboundTransfer', flowDetailKey: 'inbound_transfer' });
|
|
257
|
-
return ctx.reply(await created(ctx, 'treasury.inbound_transfer', { id }, {
|
|
258
|
-
amount, currency, financial_account: fa, origin_payment_method: origin, livemode: false,
|
|
259
|
-
status: 'processing', cancelable: true, returned: null,
|
|
260
|
-
// the pulled account as its payment method holds it
|
|
261
|
-
origin_payment_method_details: {
|
|
262
|
-
type: 'us_bank_account',
|
|
263
|
-
us_bank_account: { last4: bank.last4 ?? '6789', routing_number: bank.routing_number ?? '110000000', bank_name: bank.bank_name ?? 'STRIPE TEST BANK', account_holder_type: bank.account_holder_type ?? null, account_type: bank.account_type ?? null, fingerprint: bank.fingerprint ?? null, ...(mandate ? { mandate } : {}), network: 'ach' },
|
|
264
|
-
billing_details: { address: billing.address ?? {}, email: billing.email ?? null, name: billing.name ?? null },
|
|
265
|
-
},
|
|
266
|
-
failure_details: null, hosted_regulatory_receipt_url: null,
|
|
267
|
-
linked_flows: { received_debit: null }, statement_descriptor: ctx.params.statement_descriptor ?? 'transfer',
|
|
268
|
-
description: typeof ctx.params.description === 'string' ? ctx.params.description : null, transaction: txn, status_transitions: { succeeded_at: null, failed_at: null, canceled_at: null },
|
|
269
|
-
metadata: metadataOf(ctx),
|
|
270
|
-
}));
|
|
271
|
-
};
|
|
272
|
-
// test mode's stand-ins for money another party sends to the account or pulls from it
|
|
273
|
-
// (docs.stripe.com/treasury/moving-money/receiving-funds, docs.stripe.com/api/treasury/received_credits/test_mode_create)
|
|
274
|
-
function received(kind) {
|
|
275
|
-
return async (ctx) => {
|
|
276
|
-
const fa = fundingAccount(ctx);
|
|
277
|
-
if (fa instanceof Response)
|
|
278
|
-
return fa;
|
|
279
|
-
const network = typeof ctx.params.network === 'string' ? ctx.params.network : '';
|
|
280
|
-
if (!['ach', 'us_domestic_wire', 'stripe'].includes(network))
|
|
281
|
-
return fail(ctx, 'Missing required param: network.', 400, 'parameter_missing');
|
|
282
|
-
const amount = ctx.params.amount;
|
|
283
|
-
const currency = String(ctx.params.currency);
|
|
284
|
-
const id = ctx.mint(`treasury.${kind}`);
|
|
285
|
-
const signed = kind === 'received_credit' ? amount : -amount;
|
|
286
|
-
const txn = await post(ctx, { financial_account: fa, amount: signed, currency, flow: id, flow_type: kind, status: 'posted', balance_impact: { cash: signed, inbound_pending: 0, outbound_pending: 0 }, description: kind === 'received_credit' ? 'ReceivedCredit' : 'ReceivedDebit', flowDetailKey: kind });
|
|
287
|
-
return ctx.reply(await created(ctx, `treasury.${kind}`, { id }, {
|
|
288
|
-
amount, currency, financial_account: fa, network, livemode: false, status: 'succeeded', failure_code: null,
|
|
289
|
-
description: typeof ctx.params.description === 'string' ? ctx.params.description : kind === 'received_credit' ? 'Received credit' : 'Received debit',
|
|
290
|
-
hosted_regulatory_receipt_url: null, transaction: txn, reversal_details: null,
|
|
291
|
-
initiating_payment_method_details: { type: 'us_bank_account', billing_details: { address: {}, email: null, name: null }, us_bank_account: { bank_name: 'STRIPE TEST BANK', last4: '6789', routing_number: '110000000' } },
|
|
292
|
-
linked_flows: kind === 'received_credit' ? { credit_reversal: null, issuing_authorization: null, issuing_transaction: null, source_flow: null, source_flow_type: null } : { debit_reversal: null, inbound_transfer: null, issuing_authorization: null, issuing_transaction: null, payout: null },
|
|
293
|
-
}));
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
/** Test mode's confirmation of a processing inbound transfer: it succeeds, its transaction posts and the pending funds
|
|
297
|
-
* become cash ("The status changes to succeeded once the funds have been "confirmed" and a transaction is created and
|
|
298
|
-
* posted", docs.stripe.com/api/treasury/inbound_transfers). */
|
|
299
|
-
const succeedInbound = async (ctx) => {
|
|
300
|
-
const id = at(ctx, 'id');
|
|
301
|
-
const flow = ctx.get('treasury.inbound_transfer', id);
|
|
302
|
-
if (!flow)
|
|
303
|
-
return fail(ctx, `No such inbound transfer: '${id}'`, 404, 'resource_missing');
|
|
304
|
-
const refused = ctx.legal('treasury.inbound_transfer', 'status', 'PostTestHelpersTreasuryInboundTransfersIdSucceed', flow.status, 'succeeded', id);
|
|
305
|
-
if (refused)
|
|
306
|
-
return ctx.refuse(refused);
|
|
307
|
-
const txn = typeof flow.transaction === 'string' ? ctx.get(TX, flow.transaction) : undefined;
|
|
308
|
-
const amount = Number(flow.amount) || 0;
|
|
309
|
-
const cur = String(flow.currency ?? 'usd');
|
|
310
|
-
if (txn && txn.status === 'open') {
|
|
311
|
-
await ctx.write(TX, String(txn.id), { status: 'posted', balance_impact: { cash: amount, inbound_pending: 0, outbound_pending: 0 }, status_transitions: { ...txn.status_transitions, posted_at: ctx.now() } }, 'treasury_transaction.posted');
|
|
312
|
-
const account = ctx.get(FA, String(txn.financial_account));
|
|
313
|
-
if (account) {
|
|
314
|
-
const bal = account.balance ?? { cash: {}, inbound_pending: {}, outbound_pending: {} };
|
|
315
|
-
await ctx.write(FA, String(account.id), { balance: { ...bal, cash: { ...(bal.cash ?? {}), [cur]: ((bal.cash ?? {})[cur] ?? 0) + amount }, inbound_pending: { ...(bal.inbound_pending ?? {}), [cur]: ((bal.inbound_pending ?? {})[cur] ?? 0) - amount } } }, 'financial_account.balance_updated');
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
return ctx.reply(await ctx.write('treasury.inbound_transfer', id, { status: 'succeeded', cancelable: false, status_transitions: { ...flow.status_transitions, succeeded_at: ctx.now() } }, 'inbound_transfer.succeeded'));
|
|
319
|
-
};
|
|
320
|
-
/** Cancel a flow still processing; an outbound one must also still be cancelable. */
|
|
321
|
-
function cancel(resource, param, name, operationId, op, checkCancelable) {
|
|
322
|
-
return async (ctx) => {
|
|
323
|
-
const id = at(ctx, param);
|
|
324
|
-
const flow = ctx.get(resource, id);
|
|
325
|
-
if (!flow)
|
|
326
|
-
return fail(ctx, `No such ${name}: '${id}'`, 404, 'resource_missing');
|
|
327
|
-
// a processing flow that is no longer cancelable is refused as one no longer processing
|
|
328
|
-
const refused = ctx.legal(resource, 'status', operationId, checkCancelable && flow.cancelable !== true ? 'not_cancelable' : flow.status, undefined, id);
|
|
329
|
-
if (refused)
|
|
330
|
-
return ctx.refuse(refused);
|
|
331
|
-
// a canceled flow's open transaction is voided and what it held returns to the account (the reverse of its
|
|
332
|
-
// balance impact). Where the documentation stops and the twin decides: a flow already posted is only marked canceled
|
|
333
|
-
const txn = typeof flow.transaction === 'string' ? ctx.get(TX, flow.transaction) : undefined;
|
|
334
|
-
if (txn && txn.status === 'open') {
|
|
335
|
-
await ctx.write(TX, String(txn.id), { status: 'void', status_transitions: { ...txn.status_transitions, void_at: ctx.now() } }, 'treasury_transaction.voided');
|
|
336
|
-
const account = ctx.get(FA, String(txn.financial_account));
|
|
337
|
-
const impact = txn.balance_impact ?? {};
|
|
338
|
-
const cur = String(txn.currency ?? 'usd');
|
|
339
|
-
if (account) {
|
|
340
|
-
const bal = account.balance ?? { cash: {}, inbound_pending: {}, outbound_pending: {} };
|
|
341
|
-
const back = Object.fromEntries(['cash', 'inbound_pending', 'outbound_pending'].map((k) => [k, { ...(bal[k] ?? {}), [cur]: ((bal[k] ?? {})[cur] ?? 0) - (Number(impact[k]) || 0) }]));
|
|
342
|
-
await ctx.write(FA, String(account.id), { balance: back }, 'financial_account.balance_updated');
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
return ctx.reply(await ctx.write(resource, id, { status: 'canceled', cancelable: false, status_transitions: { ...flow.status_transitions, canceled_at: ctx.now() } }, op));
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
// the ledger is always read for one account
|
|
349
|
-
function ledger(resource, spec) {
|
|
350
|
-
return async (ctx) => {
|
|
351
|
-
if (typeof ctx.params.financial_account !== 'string' || !ctx.params.financial_account)
|
|
352
|
-
return fail(ctx, 'Missing required param: financial_account.', 400, 'parameter_missing');
|
|
353
|
-
return list(ctx, resource, where(ctx, newest(ctx, resource), spec));
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
export const treasury = {
|
|
357
|
-
PostTreasuryFinancialAccounts: open,
|
|
358
|
-
GetTreasuryFinancialAccountsFinancialAccountFeatures: features,
|
|
359
|
-
PostTreasuryFinancialAccountsFinancialAccountFeatures: features,
|
|
360
|
-
PostTreasuryFinancialAccountsFinancialAccount: updateAccount,
|
|
361
|
-
PostTreasuryOutboundPayments: outboundPayment,
|
|
362
|
-
PostTreasuryOutboundPaymentsIdCancel: cancel('treasury.outbound_payment', 'id', 'outbound payment', 'PostTreasuryOutboundPaymentsIdCancel', 'outbound_payment.canceled', true),
|
|
363
|
-
PostTreasuryOutboundTransfers: outboundTransfer,
|
|
364
|
-
PostTreasuryOutboundTransfersOutboundTransferCancel: cancel('treasury.outbound_transfer', 'outbound_transfer', 'outbound transfer', 'PostTreasuryOutboundTransfersOutboundTransferCancel', 'outbound_transfer.canceled', true),
|
|
365
|
-
PostTreasuryInboundTransfers: inboundTransfer,
|
|
366
|
-
PostTestHelpersTreasuryInboundTransfersIdSucceed: succeedInbound,
|
|
367
|
-
PostTestHelpersTreasuryReceivedCredits: received('received_credit'),
|
|
368
|
-
PostTestHelpersTreasuryReceivedDebits: received('received_debit'),
|
|
369
|
-
PostTreasuryInboundTransfersInboundTransferCancel: cancel('treasury.inbound_transfer', 'inbound_transfer', 'inbound transfer', 'PostTreasuryInboundTransfersInboundTransferCancel', 'inbound_transfer.canceled', false),
|
|
370
|
-
GetTreasuryTransactions: ledger(TX, {
|
|
371
|
-
financial_account: (r, v) => r.financial_account === v, status: (r, v) => r.status === v,
|
|
372
|
-
flow: (r, v) => r.flow === v, flow_type: (r, v) => r.flow_type === v,
|
|
373
|
-
}),
|
|
374
|
-
GetTreasuryTransactionEntries: ledger(ENTRY, {
|
|
375
|
-
financial_account: (r, v) => r.financial_account === v, flow: (r, v) => r.flow === v, transaction: (r, v) => r.transaction === v,
|
|
376
|
-
}),
|
|
377
|
-
};
|