@volter/twin-stripe 2.0.38 → 3.0.1
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 +112 -207
- package/dist/generated/pack-facts.json +94 -0
- package/dist/journeys/coverage.json +596 -0
- package/dist/journeys/customer-life.json +6108 -0
- package/dist/journeys/customer-life.outline.md +371 -0
- package/dist/journeys/decisions.json +677 -0
- package/dist/journeys/demand.json +706 -0
- package/dist/journeys/score.json +853 -0
- package/dist/journeys/unreachable.json +102 -0
- package/dist/journeys/vendor-examples.json +8010 -0
- package/dist/spec/SOURCE.md +18 -0
- package/dist/spec/doc-examples.json +445 -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 +306 -0
- package/dist/src/cli.d.ts +0 -1
- package/dist/src/cli.js +11 -31
- package/dist/src/engine/after-payment.d.ts +8 -0
- package/dist/src/engine/after-payment.js +27 -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/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 +34 -0
- package/dist/src/engine/connect.d.ts +45 -0
- package/dist/src/engine/connect.js +108 -0
- package/dist/src/engine/customers.d.ts +4 -0
- package/dist/src/engine/customers.js +8 -0
- package/dist/src/engine/display.d.ts +11 -0
- package/dist/src/engine/display.js +13 -0
- package/dist/src/engine/files.d.ts +11 -0
- package/dist/src/engine/files.js +23 -0
- package/dist/src/engine/invoices.d.ts +21 -0
- package/dist/src/engine/invoices.js +84 -0
- package/dist/src/engine/issuing.d.ts +33 -0
- package/dist/src/engine/issuing.js +54 -0
- package/dist/src/engine/ledger.d.ts +8 -0
- package/dist/src/engine/ledger.js +8 -0
- package/dist/src/engine/oauth.d.ts +4 -0
- package/dist/src/engine/oauth.js +24 -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-methods.d.ts +16 -0
- package/dist/src/engine/payment-methods.js +22 -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 +3 -0
- package/dist/src/engine/renewals.d.ts +36 -0
- package/dist/src/engine/renewals.js +65 -0
- package/dist/src/{stripe-twin.d.ts → engine/stripe.d.ts} +12 -45
- package/dist/src/{stripe-twin.js → engine/stripe.js} +53 -387
- package/dist/src/engine/subscriptions.d.ts +11 -0
- package/dist/src/engine/subscriptions.js +30 -0
- package/dist/src/engine/transfers.d.ts +2 -0
- package/dist/src/engine/transfers.js +1 -0
- package/dist/src/{stripe-version.d.ts → engine/version.d.ts} +0 -2
- package/dist/src/{stripe-version.js → engine/version.js} +21 -9
- 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 +10 -75
- package/dist/src/manifest.js +478 -876
- package/dist/src/screens/billing-portal.d.ts +3 -0
- package/dist/src/screens/{portal.js → billing-portal.js} +32 -58
- package/dist/src/screens/checkout-js.d.ts +2 -0
- package/dist/src/screens/checkout-js.js +84 -0
- package/dist/src/screens/checkout.d.ts +2 -31
- package/dist/src/screens/checkout.js +32 -121
- package/dist/src/screens/connect-onboarding.d.ts +3 -0
- package/dist/src/screens/{onboarding.js → connect-onboarding.js} +38 -104
- package/dist/src/screens/dashboard.d.ts +3 -0
- package/dist/src/screens/dashboard.js +116 -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 +95 -0
- package/dist/src/semantics/account.d.ts +2 -0
- package/dist/src/semantics/account.js +8 -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 +40 -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 +13 -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 +4 -0
- package/dist/src/semantics/billing_portal.js +36 -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 +21 -271
- package/dist/src/semantics/clock.d.ts +2 -0
- package/dist/src/semantics/clock.js +15 -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 +5 -6
- package/dist/src/semantics/customers.js +47 -413
- package/dist/src/semantics/doors.d.ts +23 -0
- package/dist/src/semantics/doors.js +73 -0
- package/dist/src/semantics/entitlements.d.ts +0 -2
- package/dist/src/semantics/entitlements.js +4 -95
- package/dist/src/semantics/events.d.ts +11 -0
- package/dist/src/semantics/events.js +31 -0
- package/dist/src/semantics/index.d.ts +2 -0
- package/dist/src/semantics/index.js +85 -0
- package/dist/src/semantics/invoice_payments.d.ts +2 -0
- package/dist/src/semantics/invoice_payments.js +12 -0
- package/dist/src/semantics/invoices.d.ts +11 -18
- package/dist/src/semantics/invoices.js +121 -440
- package/dist/src/semantics/issuing.d.ts +6 -13
- package/dist/src/semantics/issuing.js +58 -556
- package/dist/src/semantics/payment_intents.d.ts +3 -0
- package/dist/src/semantics/payment_intents.js +71 -0
- package/dist/src/semantics/payment_methods.d.ts +3 -0
- package/dist/src/semantics/payment_methods.js +15 -0
- package/dist/src/semantics/payouts.d.ts +3 -0
- package/dist/src/semantics/payouts.js +28 -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 +4 -2
- package/dist/src/semantics/products.js +12 -138
- 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 +2 -9
- package/dist/src/semantics/refunds.js +3 -193
- package/dist/src/semantics/shared.d.ts +558 -55
- package/dist/src/semantics/shared.js +2070 -114
- package/dist/src/semantics/states.d.ts +462 -0
- package/dist/src/semantics/states.js +634 -0
- package/dist/src/semantics/subscriptions.d.ts +6 -11
- package/dist/src/semantics/subscriptions.js +37 -530
- package/dist/src/semantics/test_helpers.d.ts +3 -0
- package/dist/src/semantics/test_helpers.js +125 -0
- package/dist/src/semantics/transfers.d.ts +2 -2
- package/dist/src/semantics/transfers.js +6 -101
- package/dist/src/semantics/webhook_endpoints.d.ts +2 -0
- package/dist/src/semantics/webhook_endpoints.js +26 -0
- package/dist/src/server.d.ts +7 -0
- package/dist/src/server.js +7 -0
- package/generated/pack-facts.json +94 -0
- package/journeys/coverage.json +596 -0
- package/journeys/customer-life.json +6108 -0
- package/journeys/customer-life.outline.md +371 -0
- package/journeys/decisions.json +677 -0
- package/journeys/demand.json +706 -0
- package/journeys/score.json +853 -0
- package/journeys/unreachable.json +102 -0
- package/journeys/vendor-examples.json +8010 -0
- package/package.json +11 -20
- package/spec/SOURCE.md +18 -0
- package/spec/doc-examples.json +445 -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 +306 -0
- package/src/cli.ts +11 -28
- package/src/engine/after-payment.ts +29 -0
- package/src/engine/apps-secrets.ts +4 -0
- package/src/engine/balance.ts +32 -0
- package/src/engine/checkout.ts +39 -0
- package/src/engine/common.ts +49 -0
- package/src/engine/connect.ts +106 -0
- package/src/engine/customers.ts +10 -0
- package/src/engine/display.ts +18 -0
- package/src/engine/files.ts +28 -0
- package/src/engine/invoices.ts +102 -0
- package/src/engine/issuing.ts +66 -0
- package/src/engine/ledger.ts +48 -0
- package/src/engine/oauth.ts +18 -0
- package/src/engine/onboarding.ts +53 -0
- package/src/{stripe-params.ts → engine/params.ts} +7 -56
- package/src/engine/payment-intents.ts +32 -0
- package/src/engine/payment-methods.ts +24 -0
- package/src/engine/radar.ts +6 -0
- package/src/engine/refunds.ts +3 -0
- package/src/engine/renewals.ts +72 -0
- package/src/{stripe-twin.ts → engine/stripe.ts} +57 -400
- package/src/engine/subscriptions.ts +37 -0
- package/src/engine/transfers.ts +22 -0
- package/src/{stripe-version.ts → engine/version.ts} +19 -10
- package/src/engine/webhook-endpoints.ts +14 -0
- package/src/engine/webhooks.ts +70 -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 +10 -116
- package/src/manifest.ts +480 -904
- package/src/screens/{portal.tsx → billing-portal.tsx} +35 -42
- package/src/screens/checkout-js.ts +80 -0
- package/src/screens/checkout.tsx +32 -96
- package/src/screens/{onboarding.tsx → connect-onboarding.tsx} +39 -85
- package/src/screens/dashboard.tsx +178 -0
- package/src/screens/shared.tsx +84 -0
- package/src/screens/stripe-js.ts +102 -0
- package/src/semantics/account.ts +10 -0
- package/src/semantics/account_links.ts +20 -0
- package/src/semantics/accounts.ts +44 -0
- package/src/semantics/{apps-secrets.ts → apps.ts} +17 -33
- package/src/semantics/around.ts +17 -0
- package/src/semantics/balance.ts +6 -208
- package/src/semantics/balance_transactions.ts +18 -0
- package/src/semantics/billing_portal.ts +45 -0
- package/src/semantics/charges.ts +8 -212
- package/src/semantics/checkout.ts +25 -276
- package/src/semantics/clock.ts +19 -0
- package/src/semantics/coupons.ts +5 -68
- package/src/semantics/customers.ts +48 -414
- package/src/semantics/doors.ts +79 -0
- package/src/semantics/entitlements.ts +3 -94
- package/src/semantics/events.ts +37 -0
- package/src/semantics/index.ts +89 -0
- package/src/semantics/invoice_payments.ts +16 -0
- package/src/semantics/invoices.ts +112 -441
- package/src/semantics/issuing.ts +57 -573
- package/src/semantics/payment_intents.ts +76 -0
- package/src/semantics/payment_methods.ts +20 -0
- package/src/semantics/payouts.ts +29 -0
- package/src/semantics/prices.ts +41 -0
- package/src/semantics/products.ts +12 -146
- package/src/semantics/promotion_codes.ts +38 -0
- package/src/semantics/radar.ts +8 -79
- package/src/semantics/refunds.ts +4 -216
- package/src/semantics/shared.ts +2089 -135
- package/src/semantics/states.ts +662 -0
- package/src/semantics/subscriptions.ts +39 -542
- package/src/semantics/test_helpers.ts +119 -0
- package/src/semantics/transfers.ts +10 -132
- package/src/semantics/webhook_endpoints.ts +27 -0
- package/src/server.ts +7 -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-oauth.d.ts +0 -27
- package/dist/src/screens/connect-oauth.js +0 -414
- package/dist/src/screens/connect-settings.d.ts +0 -22
- 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/disputes.d.ts +0 -2
- package/dist/src/semantics/disputes.js +0 -51
- 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/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/setup-intents.js +0 -84
- 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/test-clocks.js +0 -73
- 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-oauth.tsx +0 -400
- 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/disputes.ts +0 -62
- package/src/semantics/ephemeral-keys.ts +0 -34
- package/src/semantics/files.ts +0 -143
- 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/test-clocks.ts +0 -77
- 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/src/{screens → engine}/industries.ts +0 -0
- /package/src/{semantics → engine}/test-cards.ts +0 -0
|
@@ -1,28 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
// body}. The whole point: this is backed by the event/action-log kernel and its
|
|
5
|
-
// response SHAPES are conformant to real Stripe (see stripe-conformance), instead
|
|
6
|
-
// of being invented — so the SDK/API call shapes the app relies on actually match
|
|
7
|
-
// Stripe. HTTP wrapper: stripe-server.ts → createStripeTwinServer.
|
|
8
|
-
//
|
|
9
|
-
// State lives in the action log (R18): writes are local actions, reads are the
|
|
10
|
-
// projection. No real Stripe is ever called.
|
|
11
|
-
import { applyTwinWrite } from '@volter/world-core';
|
|
12
|
-
import { projectResources } from '@volter/world-core';
|
|
13
|
-
import { createStripeTwinFetch } from './stripe-server.ts';
|
|
14
|
-
import { emitStripeEvent, eventTypeFor, type StripeWebhookTarget } from './stripe-events.ts';
|
|
15
|
-
import { SERVED_VERSION } from './stripe-version.ts';
|
|
16
|
-
import { planOf } from './semantics/plans.ts';
|
|
17
|
-
import { platformAccountDefault } from './semantics/connect.ts';
|
|
18
|
-
import { OAUTH_CONNECTIONS } from './stripe-shared.ts';
|
|
19
|
-
|
|
20
|
-
const SERVICE = 'stripe';
|
|
1
|
+
import { digest } from '@volter/world-core';
|
|
2
|
+
import { ownFields, type TwinResource } from '@volter/world-core';
|
|
3
|
+
import { SERVED_VERSION } from './version.ts';
|
|
21
4
|
|
|
22
|
-
export type StripeRequest = { method: string; path: string; body?: string; occurredAt?: string; root?: string; readOnly?: boolean; idempotencyKey?: string; apiVersion?: string; stripeAccount?: string };
|
|
23
5
|
export type StripeResponse = { status: number; body: unknown };
|
|
24
6
|
|
|
25
|
-
|
|
7
|
+
/** Stripe's error body: an invalid_request_error with the message, and the code when there is one. */
|
|
8
|
+
export function err(message: string, status = 404, code?: string): StripeResponse {
|
|
9
|
+
return { status, body: { error: { type: 'invalid_request_error', message, ...(code ? { code } : {}) } } };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// The Stripe API version a request that pins none is served in: the vendored spec's (./version.ts renders
|
|
26
13
|
// every answer in it). A request may override it via the `Stripe-Version` header (apiVersion);
|
|
27
14
|
// the value is echoed onto every event the request produces, exactly like real Stripe,
|
|
28
15
|
// whose stored Event.api_version reflects the version in force when the event was created.
|
|
@@ -34,63 +21,6 @@ export function isValidApiVersion(v: string): boolean {
|
|
|
34
21
|
return API_VERSION_RE.test(v);
|
|
35
22
|
}
|
|
36
23
|
|
|
37
|
-
// resource type → id prefix (Stripe's conventions).
|
|
38
|
-
const PREFIX: Record<string, string> = {
|
|
39
|
-
charge: 'ch', customer: 'cus', payment_intent: 'pi', setup_intent: 'seti', payment_method: 'pm',
|
|
40
|
-
subscription: 'sub', price: 'price', product: 'prod', invoice: 'in', invoiceitem: 'ii',
|
|
41
|
-
refund: 're', verification_session: 'vs', ephemeral_key: 'ephkey', file_link: 'link',
|
|
42
|
-
dispute: 'dp', payout: 'po', balance_transaction: 'txn', event: 'evt',
|
|
43
|
-
checkout_session: 'cs', billing_portal_session: 'bps', billing_portal_configuration: 'bpc',
|
|
44
|
-
account: 'acct', transfer: 'tr',
|
|
45
|
-
tax_rate: 'txr', tax_calculation: 'taxcalc', tax_registration: 'taxreg',
|
|
46
|
-
credit_note: 'cn', tax_id: 'txi', customer_balance_transaction: 'cbtxn',
|
|
47
|
-
coupon: 'coupon', promotion_code: 'promo', payment_link: 'plink', quote: 'qt',
|
|
48
|
-
webhook_endpoint: 'we',
|
|
49
|
-
subscription_schedule: 'sub_sched', billing_meter: 'mtr', billing_meter_event: 'mtr_evt',
|
|
50
|
-
usage_record: 'mbur', test_clock: 'clock',
|
|
51
|
-
token: 'tok', mandate: 'mandate', application_fee: 'fee', fee_refund: 'fr',
|
|
52
|
-
transfer_reversal: 'trr', account_link: 'acctlink', person: 'person',
|
|
53
|
-
external_account: 'ba', file: 'file',
|
|
54
|
-
subscription_item: 'si', tax_transaction: 'tax', radar_review: 'prv',
|
|
55
|
-
radar_value_list: 'rsl', radar_value_list_item: 'rsli', radar_rule: 'rule',
|
|
56
|
-
// Issuing family (vendor-faithful Stripe id prefixes).
|
|
57
|
-
issuing_cardholder: 'ich', issuing_card: 'ic', issuing_authorization: 'iauth',
|
|
58
|
-
issuing_transaction: 'ipi', issuing_dispute: 'idp',
|
|
59
|
-
// Terminal family.
|
|
60
|
-
terminal_location: 'tml', terminal_reader: 'tmr', terminal_configuration: 'tmc',
|
|
61
|
-
// Top-ups, identity reports, reporting runs.
|
|
62
|
-
topup: 'tu', verification_report: 'vr', report_run: 'frr',
|
|
63
|
-
// Customer cash balance ledger + legacy sources.
|
|
64
|
-
cash_balance_transaction: 'ccsbtxn', source: 'src',
|
|
65
|
-
// Billing: prepaid credit grants + usage alerts.
|
|
66
|
-
credit_grant: 'credgr', billing_alert: 'alert',
|
|
67
|
-
// Entitlements: account-level features + per-customer active entitlements.
|
|
68
|
-
entitlements_feature: 'feat', active_entitlement: 'ent',
|
|
69
|
-
// Connect: embedded account sessions + apps secret store.
|
|
70
|
-
account_session: 'accts', apps_secret: 'apmc',
|
|
71
|
-
// Issuing: network tokens + card personalization designs.
|
|
72
|
-
issuing_token: 'iss_tok', personalization_design: 'pd',
|
|
73
|
-
// Treasury: financial accounts + money-movement flows + ledger.
|
|
74
|
-
financial_account: 'fa',
|
|
75
|
-
outbound_payment: 'obp', outbound_transfer: 'obt', inbound_transfer: 'ibt',
|
|
76
|
-
received_credit: 'rc', received_debit: 'rd',
|
|
77
|
-
treasury_transaction: 'trxn', treasury_transaction_entry: 'trxne',
|
|
78
|
-
// Climate (carbon removal): orders. (products/suppliers are a fixed read-only catalog.)
|
|
79
|
-
climate_order: 'climorder',
|
|
80
|
-
// Financial Connections: sessions + linked accounts + account transactions.
|
|
81
|
-
fc_session: 'fcsess', fc_account: 'fca', fc_transaction: 'fctxn',
|
|
82
|
-
// Forwarding (PAN forwarding) requests + Crypto onramp sessions.
|
|
83
|
-
forwarding_request: 'fwdr', onramp_session: 'cos',
|
|
84
|
-
// Entitlements: a product↔feature grant.
|
|
85
|
-
product_feature: 'prodft',
|
|
86
|
-
// Tax settings singleton.
|
|
87
|
-
tax_settings: 'taxset',
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
function err(message: string, status = 404, code?: string): StripeResponse {
|
|
91
|
-
return { status, body: { error: { type: status === 404 ? 'invalid_request_error' : 'invalid_request_error', message, ...(code ? { code } : {}) } } };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
24
|
// Fidelity: real Stripe rejects a charge/payment_intent missing or with a non-positive
|
|
95
25
|
// amount, or missing currency, before any state change.
|
|
96
26
|
export function validateMoney(params: Record<string, unknown>): StripeResponse | null {
|
|
@@ -188,7 +118,7 @@ function resolveCardRef(params: Record<string, unknown>): string | undefined {
|
|
|
188
118
|
// that references it purely by id (the normal shape for a saved-card/off-session charge,
|
|
189
119
|
// e.g. an invoice retry) could never reproduce the decline.
|
|
190
120
|
/** A stored subject by stored type and id, in the pack's view (tombstones included): how the
|
|
191
|
-
* semantics hand these helpers the tree they read (
|
|
121
|
+
* semantics hand these helpers the tree they read (./common.ts `finder`). */
|
|
192
122
|
export type FindStored = (storedType: string, id: string) => Record<string, unknown> | undefined;
|
|
193
123
|
|
|
194
124
|
export function declineFor(find: FindStored, ...paramSets: Array<Record<string, unknown>>): DeclineOutcome | null | undefined {
|
|
@@ -237,8 +167,6 @@ export function threeDsNextAction(): Record<string, unknown> {
|
|
|
237
167
|
// a `descriptor_code` (the test value `SM11AA`). We model that exact pair so the verify
|
|
238
168
|
// can FAIL with the wrong amounts. The well-known token that triggers this flow:
|
|
239
169
|
const MICRODEPOSIT_TOKENS = new Set(['pm_usBankAccount_requiresVerification', 'pm_us_bank_account']);
|
|
240
|
-
const MICRODEPOSIT_AMOUNTS = [32, 45];
|
|
241
|
-
const MICRODEPOSIT_DESCRIPTOR = 'SM11AA';
|
|
242
170
|
export function requiresMicrodeposits(...paramSets: Array<Record<string, unknown>>): boolean {
|
|
243
171
|
for (const params of paramSets) {
|
|
244
172
|
const ref = resolveCardRef(params);
|
|
@@ -255,31 +183,6 @@ export function microdepositsNextAction(): Record<string, unknown> {
|
|
|
255
183
|
},
|
|
256
184
|
};
|
|
257
185
|
}
|
|
258
|
-
/** The two deposits' amounts as sent: the test values (docs.stripe.com/payments/ach-direct-debit/accept-a-payment). */
|
|
259
|
-
function amountsMatch(params: Record<string, unknown>): boolean {
|
|
260
|
-
const got = (params.amounts as unknown[]).map((a) => Math.trunc(Number(a) || 0));
|
|
261
|
-
return got.length === 2 && got[0] === MICRODEPOSIT_AMOUNTS[0] && got[1] === MICRODEPOSIT_AMOUNTS[1];
|
|
262
|
-
}
|
|
263
|
-
/** The deposit's descriptor code as sent: the test value. */
|
|
264
|
-
function descriptorMatches(params: Record<string, unknown>): boolean {
|
|
265
|
-
return String(params.descriptor_code) === MICRODEPOSIT_DESCRIPTOR;
|
|
266
|
-
}
|
|
267
|
-
/** Stripe's refusal of deposits that do not match what was sent. */
|
|
268
|
-
function depositMismatch(byAmounts: boolean): StripeResponse {
|
|
269
|
-
return byAmounts
|
|
270
|
-
? err('The amounts provided do not match the amounts that were sent to the bank account.', 400, 'payment_intent_microdeposit_verification_amounts_mismatch')
|
|
271
|
-
: err('The descriptor code provided does not match the one that was sent to the bank account.', 400, 'payment_intent_microdeposit_verification_descriptor_code_mismatch');
|
|
272
|
-
}
|
|
273
|
-
// Validate the submitted micro-deposit verification params against the modeled test values.
|
|
274
|
-
// Returns a vendor 400 on missing/both/wrong inputs, or null when the verification matches.
|
|
275
|
-
export function verifyMicrodeposits(params: Record<string, unknown>, _existing: Record<string, unknown>): StripeResponse | null {
|
|
276
|
-
const hasAmounts = Array.isArray(params.amounts);
|
|
277
|
-
const hasDescriptor = typeof params.descriptor_code === 'string' && params.descriptor_code !== '';
|
|
278
|
-
if (hasAmounts === hasDescriptor) return err('You must pass exactly one of `amounts` and `descriptor_code`.', 400, 'parameter_missing');
|
|
279
|
-
const matches = hasAmounts ? amountsMatch(params) : descriptorMatches(params);
|
|
280
|
-
return matches ? null : depositMismatch(hasAmounts);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
186
|
// Build the real Stripe card-error envelope (HTTP 402). `param` is 'card' for card
|
|
284
187
|
// errors; charge/payment_intent ids are attached when known so SDK error objects carry
|
|
285
188
|
// them (Stripe does this for confirm/charge failures).
|
|
@@ -304,19 +207,6 @@ export function cardError(outcome: DeclineOutcome, attach: { charge?: string; pa
|
|
|
304
207
|
export function nowUnix(occurredAt?: string): number {
|
|
305
208
|
return Math.floor((occurredAt ? Date.parse(occurredAt) : 0) / 1000);
|
|
306
209
|
}
|
|
307
|
-
|
|
308
|
-
function rows(type: string, root?: string) {
|
|
309
|
-
return projectResources(SERVICE, root).filter((r) => r.type === type);
|
|
310
|
-
}
|
|
311
|
-
function nextId(type: string, root?: string): string {
|
|
312
|
-
const prefix = PREFIX[type] ?? type;
|
|
313
|
-
let max = 0;
|
|
314
|
-
for (const r of rows(type, root)) {
|
|
315
|
-
const m = new RegExp(`^${prefix}_twin_(\\d+)$`).exec(r.id);
|
|
316
|
-
if (m) max = Math.max(max, Number(m[1]));
|
|
317
|
-
}
|
|
318
|
-
return `${prefix}_twin_${max + 1}`;
|
|
319
|
-
}
|
|
320
210
|
// Real Stripe embeds the resource's OWN id inside its client_secret (format
|
|
321
211
|
// `<id>_secret_<random>`) — Stripe.js's confirmPayment/confirmSetup parse the id back
|
|
322
212
|
// out of the client_secret (splitting on `_secret`) to build the same-origin confirm
|
|
@@ -324,25 +214,14 @@ function nextId(type: string, root?: string): string {
|
|
|
324
214
|
// A secret that doesn't carry the real id breaks that round trip: a browser Stripe.js
|
|
325
215
|
// call would try to confirm a resource whose id it invented from the string
|
|
326
216
|
// (`'pi_twin_secret'` → `'pi_twin'`, which was never actually created). Every PI/SI a
|
|
327
|
-
// caller might confirm CLIENT-SIDE (real Stripe.js
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
// stored vendor field literally named `type` is dropped on projection (see actions.ts
|
|
336
|
-
// META). Several Stripe objects (payout.type, balance_transaction.type, event.type,
|
|
337
|
-
// dispute uses `reason` not `type`) DO have a vendor `type`, so the create path stashes
|
|
338
|
-
// it under the reserved key `_stripe_type` (a `_`-prefixed twin-internal field that the
|
|
339
|
-
// conformance gate exempts) and we restore it to `type` here. This keeps the vendor
|
|
340
|
-
// field faithful without touching the kernel discriminator. (price.type stays a declared
|
|
341
|
-
// deviation — its create path predates this and one_time/recurring is derivable.)
|
|
342
|
-
// A few Stripe objects have a DOTTED `object` value that differs from the twin's
|
|
343
|
-
// (identifier-safe) resource type — e.g. type 'checkout_session' emits object
|
|
344
|
-
// 'checkout.session'. Map them on emit so the `object` field is vendor-faithful.
|
|
345
|
-
// (stripe-conformance.ts emitted() mirrors this map.)
|
|
217
|
+
// caller might confirm CLIENT-SIDE (real Stripe.js) must mint a secret this way.
|
|
218
|
+
/** An intent's client secret: its id, `_secret_`, and a secret the World holds for it (ctx.secret, passed in), so no one
|
|
219
|
+
* derives it from the intent's id; Stripe.js confirms an intent with it and the publishable key. */
|
|
220
|
+
export function mintClientSecret(id: string, secret: string): string {
|
|
221
|
+
return `${id}_secret_${secret.replace(/[^A-Za-z0-9]/g, '').slice(0, 24)}`;
|
|
222
|
+
}
|
|
223
|
+
// A few Stripe objects have a DOTTED `object` value that differs from the twin's (identifier-safe) resource type: type
|
|
224
|
+
// 'checkout_session' is object 'checkout.session'.
|
|
346
225
|
export const OBJECT_NAME: Record<string, string> = {
|
|
347
226
|
checkout_session: 'checkout.session',
|
|
348
227
|
billing_portal_session: 'billing_portal.session',
|
|
@@ -403,17 +282,11 @@ export const OBJECT_NAME: Record<string, string> = {
|
|
|
403
282
|
// Forwarding + Crypto onramp.
|
|
404
283
|
forwarding_request: 'forwarding.request', onramp_session: 'crypto.onramp_session',
|
|
405
284
|
};
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
// subscription_data, held for the completion transition to copy onto the created
|
|
412
|
-
// subscription) — real Stripe never returns it on the Session, so strip it here.
|
|
413
|
-
const { type: _t, updatedAt: _u, _stripe_type, _subscription_data, ...rest } = r;
|
|
414
|
-
const out: Record<string, unknown> = { object: OBJECT_NAME[type] ?? type, ...rest, id: r.id };
|
|
415
|
-
if (_stripe_type !== undefined) out.type = _stripe_type;
|
|
416
|
-
return out;
|
|
285
|
+
/** A stored row as Stripe answers it: its `object`, its own fields (the vendor's `type` among them, which the tree's
|
|
286
|
+
* shadows: world-core ownFields) without the twin's `_` bookkeeping, and its id. */
|
|
287
|
+
export function view(type: string, r: TwinResource): Record<string, unknown> {
|
|
288
|
+
const own = Object.fromEntries(Object.entries(ownFields(r)).filter(([k]) => !k.startsWith('_') && k !== 'updatedAt'));
|
|
289
|
+
return { object: OBJECT_NAME[type] ?? type, ...own, id: r.id };
|
|
417
290
|
}
|
|
418
291
|
// Real Stripe list pagination. Honors limit (default 10, max 100, min 1),
|
|
419
292
|
// starting_after (cursor: exclude up to & including that id), ending_before
|
|
@@ -448,101 +321,6 @@ export function paginate(items: Array<Record<string, unknown>>, params: Record<s
|
|
|
448
321
|
const page = items.slice(from, from + limit);
|
|
449
322
|
return { page, hasMore: from + limit < items.length };
|
|
450
323
|
}
|
|
451
|
-
|
|
452
|
-
async function writeResource(
|
|
453
|
-
type: string,
|
|
454
|
-
id: string,
|
|
455
|
-
fields: Record<string, unknown>,
|
|
456
|
-
op: string,
|
|
457
|
-
root?: string,
|
|
458
|
-
occurredAt?: string,
|
|
459
|
-
apiVersion?: string,
|
|
460
|
-
): Promise<Record<string, unknown>> {
|
|
461
|
-
const { resource } = await applyTwinWrite(SERVICE, { operation: op, subjectType: type, subjectId: id, fields, ...(occurredAt ? { occurredAt } : {}), actor: { kind: 'agent' } }, root);
|
|
462
|
-
const out = view(type, resource);
|
|
463
|
-
await afterStripeWrite(type, op, out, root, occurredAt, apiVersion);
|
|
464
|
-
return out;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/** What follows every stored Stripe write: the webhook for the state change and the stored Event
|
|
468
|
-
* the Events API lists. The derived pack's write hook (manifest.ts) is this same function. */
|
|
469
|
-
export async function afterStripeWrite(type: string, op: string, out: Record<string, unknown>, root?: string, occurredAt?: string, apiVersion?: string, stripeAccount?: string): Promise<void> {
|
|
470
|
-
// R17: fire the Stripe event/webhook for this state change (no-op if none registered). An event is a connected
|
|
471
|
-
// account's when its resource lives in one (a request made as it, the Stripe-Account header) or is a connected
|
|
472
|
-
// account itself (its account.updated): docs.stripe.com/connect/webhooks, "Connected accounts" scope.
|
|
473
|
-
// A write with no header is still a connected account's when the row it wrote is kept on that account's books (a
|
|
474
|
-
// payout or ledger entry's `_account`: an automatic payout time makes for it, semantics/balance.ts).
|
|
475
|
-
const account = stripeAccount ?? (type === 'account' && typeof out.id === 'string' && out.id !== PLATFORM_ACCOUNT_ID ? out.id : undefined) ?? ownerOf(type, out.id, root);
|
|
476
|
-
// a plan is stored as the recurring price it is; its own events carry the plan object (semantics/plans.ts)
|
|
477
|
-
const object = type === 'price' && op.startsWith('plan.') ? planOf(out) : out;
|
|
478
|
-
// events.full_types: the Stripe `event` envelope for this state change is also stored, so it appears in GET
|
|
479
|
-
// /v1/events (independent of webhook registration), exactly like real Stripe's stored Events API. Skip when the op
|
|
480
|
-
// has no mapped event type, and never for event/_idempotency writes themselves (no recursion / no meta-events). The
|
|
481
|
-
// event's id is minted once and is the delivered webhook's id and the stored event's alike: one event, one id, which
|
|
482
|
-
// a consumer deduplicates by and retrieves by.
|
|
483
|
-
const eventType = type !== 'event' && type !== IDEMPOTENCY_TYPE ? eventTypeFor(op) : null;
|
|
484
|
-
const id = eventType ? nextStripeEventId(root) : undefined;
|
|
485
|
-
// stored before it is delivered: a consumer that looks its webhook's event up finds it
|
|
486
|
-
if (eventType) await persistStripeEvent(eventType, object, root, occurredAt, apiVersion, account, id);
|
|
487
|
-
// a connected account whose OAuth connection was revoked "can't be accessed by your platform" (docs.stripe.com/connect/
|
|
488
|
-
// oauth-reference): its events (a payout time makes, a renewal) are its own and no longer reach the platform's Connect
|
|
489
|
-
// endpoints, except the account.application.deauthorized that says so (screens/connect-oauth.tsx)
|
|
490
|
-
if (account && eventType !== 'account.application.deauthorized' && rows(OAUTH_CONNECTIONS, root).some((c) => (c as Record<string, unknown>).id === account && (c as Record<string, unknown>).revoked === true)) return;
|
|
491
|
-
await emitStripeEvent(op, object, { occurredAt: occurredAt ?? '1970-01-01T00:00:00.000Z', endpoints: webhookTargets(root), ...(account ? { account } : {}), ...(id ? { id } : {}) });
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/** Event numbers taken in this process, per root, before their events are stored: two writes whose events overlap
|
|
495
|
-
* (a webhook handler calling back in, concurrent requests) never take the same number. */
|
|
496
|
-
const takenEventNumbers = new Map<string, number>();
|
|
497
|
-
/** The id the World's next event takes: its place in the World's events, so it is deterministic and never repeats
|
|
498
|
-
* within a World (a Balance has no id of its own, so nothing about the object can tell two events apart). The number
|
|
499
|
-
* is taken synchronously — the count of stored events, or the last number taken, whichever is higher. */
|
|
500
|
-
export function nextStripeEventId(root?: string): string {
|
|
501
|
-
const key = root ?? '';
|
|
502
|
-
const n = Math.max(rows('event', root).length, takenEventNumbers.get(key) ?? 0) + 1;
|
|
503
|
-
takenEventNumbers.set(key, n);
|
|
504
|
-
return `evt_twin_${n}`;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/** The connected account whose books a stored row is kept on (its `_account`), if any. */
|
|
508
|
-
function ownerOf(type: string, id: unknown, root?: string): string | undefined {
|
|
509
|
-
if (typeof id !== 'string') return undefined;
|
|
510
|
-
const owner = (rows(type, root).find((r) => r.id === id) as Record<string, unknown> | undefined)?._account;
|
|
511
|
-
return typeof owner === 'string' ? owner : undefined;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// Persist a Stripe `event` resource (object 'event') for a state change into the action
|
|
515
|
-
// log so the Events API (GET /v1/events) lists/retrieves it. Deterministic id derived
|
|
516
|
-
// from the per-root event count; data.object carries the resource snapshot. Written via
|
|
517
|
-
// applyTwinWrite directly (NOT writeResource) to avoid re-emitting an event for the event.
|
|
518
|
-
export async function persistStripeEvent(eventType: string, resource: Record<string, unknown>, root?: string, occurredAt?: string, apiVersion?: string, account?: string, id?: string): Promise<void> {
|
|
519
|
-
const eventId = id ?? nextStripeEventId(root);
|
|
520
|
-
const fields = {
|
|
521
|
-
object: 'event', _stripe_type: eventType, created: nowUnix(occurredAt), livemode: false,
|
|
522
|
-
pending_webhooks: 0, api_version: apiVersion && isValidApiVersion(apiVersion) ? apiVersion : TWIN_API_VERSION,
|
|
523
|
-
request: { id: null, idempotency_key: null },
|
|
524
|
-
data: { object: resource },
|
|
525
|
-
// a connected account's event names it, and is listed to requests made as it (semantics/webhook-endpoints.ts)
|
|
526
|
-
...(account ? { account } : {}),
|
|
527
|
-
};
|
|
528
|
-
await applyTwinWrite(SERVICE, { operation: 'event.record', subjectType: 'event', subjectId: eventId, fields, ...(occurredAt ? { occurredAt } : {}), actor: { kind: 'system' } }, root);
|
|
529
|
-
}
|
|
530
|
-
function getOne(type: string, id: string, root?: string): Record<string, unknown> | undefined {
|
|
531
|
-
const r = rows(type, root).find((x) => x.id === id);
|
|
532
|
-
return r ? view(type, r) : undefined;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// Stripe list order: newest first by `created` (or `date` for invoiceitem), ties
|
|
536
|
-
// broken by id descending so the order is stable for cursor pagination.
|
|
537
|
-
/** The World's live webhook endpoints, from its tree: delivery survives a restart and belongs to
|
|
538
|
-
* this World alone. A disabled or deleted endpoint receives nothing, like the vendor. */
|
|
539
|
-
function webhookTargets(root: string | undefined): StripeWebhookTarget[] {
|
|
540
|
-
// `connect` is a create parameter the endpoint object does not answer, so it is read off the stored row
|
|
541
|
-
return rows('webhook_endpoint', root)
|
|
542
|
-
.map((r) => ({ w: view('webhook_endpoint', r), connect: asBool((r as Record<string, unknown>).connect) }))
|
|
543
|
-
.filter(({ w }) => !w.deleted && w.status !== 'disabled' && typeof w.url === 'string')
|
|
544
|
-
.map(({ w, connect }) => ({ url: String(w.url), ...(typeof w.secret === 'string' ? { secret: w.secret } : {}), ...(Array.isArray(w.enabled_events) ? { enabledEvents: w.enabled_events.map(String) } : {}), connect }));
|
|
545
|
-
}
|
|
546
324
|
// Stripe's `active=true|false` filters arrive as strings/booleans; normalize.
|
|
547
325
|
export function asBool(v: unknown): boolean {
|
|
548
326
|
return v === true || v === 'true' || v === 1 || v === '1';
|
|
@@ -556,7 +334,7 @@ export function asBool(v: unknown): boolean {
|
|
|
556
334
|
// resource view. This is the vendor's OWN per-field semantics (A4: not a cross-vendor query
|
|
557
335
|
// engine — it lives in the Stripe pack and speaks Stripe's language only). Unsupported
|
|
558
336
|
// constructs cause the clause to not match (safe), never a fabricated hit.
|
|
559
|
-
type SearchClause = { field: string; op: ':' | '>' | '<' | '>=' | '<='; value: string };
|
|
337
|
+
type SearchClause = { field: string; op: ':' | '~' | '>' | '<' | '>=' | '<='; value: string };
|
|
560
338
|
// Tokenize on top-level AND/OR (we treat the whole query as either all-AND or all-OR; mixed
|
|
561
339
|
// precedence is uncommon in practice and Stripe groups with parens we don't model — kept simple).
|
|
562
340
|
function parseSearchQuery(query: string): { clauses: SearchClause[]; disjunction: boolean } | null {
|
|
@@ -565,12 +343,13 @@ function parseSearchQuery(query: string): { clauses: SearchClause[]; disjunction
|
|
|
565
343
|
const disjunction = / OR /i.test(trimmed) && !/ AND /i.test(trimmed);
|
|
566
344
|
const parts = trimmed.split(disjunction ? / OR /i : / AND /i);
|
|
567
345
|
const clauses: SearchClause[] = [];
|
|
568
|
-
const re = /^\s*([A-Za-z0-9_]+(?:\[[^\]]+\])?)\s*(
|
|
346
|
+
const re = /^\s*([A-Za-z0-9_]+(?:\[[^\]]+\])?)\s*(>=|<=|>|<|:|~)\s*(.+?)\s*$/;
|
|
569
347
|
for (const part of parts) {
|
|
570
348
|
const m = re.exec(part);
|
|
571
349
|
if (!m) return null;
|
|
572
350
|
let value = m[3]!.trim();
|
|
573
351
|
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) value = value.slice(1, -1);
|
|
352
|
+
if (m[2] === '~' && (!['email', 'name'].includes(m[1]!) || value.length < 3)) return null;
|
|
574
353
|
clauses.push({ field: m[1]!, op: m[2] as SearchClause['op'], value });
|
|
575
354
|
}
|
|
576
355
|
return { clauses, disjunction };
|
|
@@ -588,22 +367,19 @@ function searchFieldValue(item: Record<string, unknown>, field: string): unknown
|
|
|
588
367
|
}
|
|
589
368
|
/** A search's numeric comparison other than `>=` (docs.stripe.com/search#search-query-language). */
|
|
590
369
|
function otherComparison(op: string, a: number, n: number): boolean {
|
|
591
|
-
|
|
592
|
-
case '>': return a > n;
|
|
593
|
-
case '<': return a < n;
|
|
594
|
-
case '<=': return a <= n;
|
|
595
|
-
}
|
|
596
|
-
return false;
|
|
370
|
+
return op === '>' ? a > n : op === '<' ? a < n : a <= n;
|
|
597
371
|
}
|
|
598
372
|
|
|
599
373
|
function evalSearchClause(item: Record<string, unknown>, c: SearchClause): boolean {
|
|
600
374
|
const actual = searchFieldValue(item, c.field);
|
|
375
|
+
// source: https://docs.stripe.com/search "Substring match operator (substrings must be a minimum of 3 characters)"
|
|
376
|
+
if (c.op === '~') return actual != null && String(actual).toLowerCase().includes(c.value.toLowerCase());
|
|
601
377
|
if (c.op === ':') {
|
|
602
378
|
if (c.value === 'null') return actual === null || actual === undefined;
|
|
603
379
|
// numeric exact when both look numeric; else string-equality (case-sensitive, like Stripe tokens)
|
|
604
380
|
if (typeof actual === 'number' && /^-?\d+(\.\d+)?$/.test(c.value)) return actual === Number(c.value);
|
|
605
|
-
if (typeof actual === 'boolean') return String(actual) === c.value;
|
|
606
|
-
return String(actual) === c.value;
|
|
381
|
+
if (typeof actual === 'boolean') return String(actual).toLowerCase() === c.value.toLowerCase();
|
|
382
|
+
return String(actual).toLowerCase() === c.value.toLowerCase();
|
|
607
383
|
}
|
|
608
384
|
const n = Number(c.value);
|
|
609
385
|
const a = Number(actual);
|
|
@@ -662,23 +438,6 @@ export function buildDiscount(coupon: Record<string, unknown>, customer: string,
|
|
|
662
438
|
};
|
|
663
439
|
}
|
|
664
440
|
|
|
665
|
-
// The (price id, quantity) pairs a subscription bills, normalized across the shapes the
|
|
666
|
-
// twin stores items in (the create form array items:[{price,quantity}], or the canonical
|
|
667
|
-
// items:{data:[{price}]} list). Used to compute upcoming-invoice lines.
|
|
668
|
-
export function subscriptionItemPairs(sub: Record<string, unknown>): Array<{ price: string; quantity: number }> {
|
|
669
|
-
const items = sub.items as { data?: unknown } | undefined;
|
|
670
|
-
const data = Array.isArray(items?.data) ? items!.data : Array.isArray(sub.items) ? (sub.items as unknown[]) : [];
|
|
671
|
-
const out: Array<{ price: string; quantity: number }> = [];
|
|
672
|
-
for (const it of data as Array<Record<string, unknown>>) {
|
|
673
|
-
const p = it?.price;
|
|
674
|
-
const id = typeof p === 'string' ? p : (p && typeof p === 'object' ? String((p as Record<string, unknown>).id ?? '') : '');
|
|
675
|
-
if (id) out.push({ price: id, quantity: Math.max(1, Math.trunc(Number(it.quantity) || 1)) });
|
|
676
|
-
}
|
|
677
|
-
// a bare `price` field (the connector's flattened shape) counts as one item.
|
|
678
|
-
if (out.length === 0 && typeof sub.price === 'string' && sub.price) out.push({ price: sub.price, quantity: 1 });
|
|
679
|
-
return out;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
441
|
// The raw item entries a subscription/checkout-derived-subscription create sent,
|
|
683
442
|
// normalized to an ordered array. The form reader already turns the bracket form
|
|
684
443
|
// items[0][price]=… into an array of objects (see lineItemEntries() above for the
|
|
@@ -740,18 +499,13 @@ export function addBillingInterval(atUnix: number, interval: BillingInterval, co
|
|
|
740
499
|
// response includes the actual items, mirroring what was requested.
|
|
741
500
|
/** What a subscription item carries of its price: the Price itself and its plan view (a subscription item answers both,
|
|
742
501
|
* docs.stripe.com/api/subscription_items/object), by the price's id or an already-resolved Price. */
|
|
502
|
+
// source: https://docs.stripe.com/api/plans/create "It replaces the Plans API and is backwards compatible to simplify your migration."
|
|
503
|
+
// source: spec:/components/schemas/plan "amount_decimal"
|
|
504
|
+
|
|
743
505
|
export function subscriptionItemPrice(p: unknown, find: FindStored): { price: Record<string, unknown> | string | null; plan: Record<string, unknown> | null } {
|
|
744
506
|
const priceId = typeof p === 'string' ? p : (p && typeof p === 'object' ? String((p as Record<string, unknown>).id ?? '') : '');
|
|
745
507
|
const price = priceId ? find('price', priceId) : undefined;
|
|
746
|
-
const
|
|
747
|
-
const plan = price ? {
|
|
748
|
-
id: price.id, object: 'plan', active: price.active ?? true, amount: price.unit_amount ?? null,
|
|
749
|
-
amount_decimal: price.unit_amount_decimal ?? null, billing_scheme: price.billing_scheme ?? 'per_unit',
|
|
750
|
-
currency: price.currency ?? 'usd', interval: recurring?.interval ?? null,
|
|
751
|
-
interval_count: recurring?.interval_count ?? 1, livemode: false,
|
|
752
|
-
metadata: price.metadata ?? {}, nickname: price.nickname ?? null,
|
|
753
|
-
product: price.product ?? null, tiers_mode: price.tiers_mode ?? null, usage_type: 'licensed',
|
|
754
|
-
} : null;
|
|
508
|
+
const plan = price ? planView(price) : null;
|
|
755
509
|
return { price: price ?? (priceId || null), plan };
|
|
756
510
|
}
|
|
757
511
|
|
|
@@ -804,41 +558,6 @@ export function resolveTrial(params: Record<string, unknown>, at: number): { sta
|
|
|
804
558
|
return undefined;
|
|
805
559
|
}
|
|
806
560
|
|
|
807
|
-
// Create a resource from form params, stamping object + id + created + the given
|
|
808
|
-
// defaults. Only real Stripe field names are emitted (conformance-checked).
|
|
809
|
-
// A vendor `type` field (a price's one_time or recurring, a payout's bank_account) always collides with the kernel's
|
|
810
|
-
// row type, so it is stashed under `_stripe_type` and restored by view().
|
|
811
|
-
export function stashVendorType(_type: string, fields: Record<string, unknown>): Record<string, unknown> {
|
|
812
|
-
if (!('type' in fields)) return fields;
|
|
813
|
-
const { type: vt, ...rest } = fields;
|
|
814
|
-
return { ...rest, _stripe_type: vt };
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
async function create(type: string, params: Record<string, unknown>, defaults: Record<string, unknown>, req: StripeRequest, opts: { timeField?: string } = {}): Promise<StripeResponse> {
|
|
818
|
-
// honor a caller-provided id (seeding uses the catalog's real prod_*/price_* ids);
|
|
819
|
-
// otherwise assign one. The id is identity, not a stored field.
|
|
820
|
-
const { id: providedId, ...rest } = params as { id?: unknown } & Record<string, unknown>;
|
|
821
|
-
const id = typeof providedId === 'string' && providedId ? providedId : nextId(type, req.root);
|
|
822
|
-
// Most Stripe objects stamp `created`; a few (invoiceitem) use a differently-named
|
|
823
|
-
// timestamp instead (`date`) and have NO `created` field — honor that, don't fabricate.
|
|
824
|
-
const fields = stashVendorType(type, { object: OBJECT_NAME[type] ?? type, [opts.timeField ?? 'created']: nowUnix(req.occurredAt), ...defaults, ...rest });
|
|
825
|
-
return { status: 200, body: await writeResource(type, id, fields, `${type}.create`, req.root, req.occurredAt, req.apiVersion) };
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
// ── ISSUING helpers: spending controls, real-time-auth shapes ─────────────────────────
|
|
829
|
-
// Authorization.card is the FULL Card object in every vendor egress (API responses and
|
|
830
|
-
// webhook payloads) — it is not expandable (stripe@22.3.0 Issuing.Authorization.card:
|
|
831
|
-
// Stripe.Issuing.Card). The twin stores the id internally and embeds at the boundary;
|
|
832
|
-
// cardholder stays an id string (expandable, unexpanded default). Caught live: the
|
|
833
|
-
// issuing-bridge reads event.data.object.card.id and refused every twin presentment.
|
|
834
|
-
export function embedIssuingAuthorizationCard(body: Record<string, unknown>, root?: string): Record<string, unknown> {
|
|
835
|
-
if (typeof body.card === 'string') {
|
|
836
|
-
const cardRow = getOne('issuing_card', body.card, root);
|
|
837
|
-
if (cardRow !== undefined) body.card = cardRow;
|
|
838
|
-
}
|
|
839
|
-
return body;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
561
|
// The closed interval set for spending_controls[spending_limits][][interval]
|
|
843
562
|
// (stripe@22.3.0 Issuing/Cards.d.ts SpendingLimit.Interval — a documented closed enum).
|
|
844
563
|
const SPENDING_LIMIT_INTERVALS = new Set(['all_time', 'daily', 'monthly', 'per_authorization', 'weekly', 'yearly']);
|
|
@@ -869,10 +588,10 @@ function controlsRefused(what: 'dictionary' | 'categories' | 'countries' | 'limi
|
|
|
869
588
|
// Vendor rules enforced: interval must be in the closed enum; a spending limit amount is a
|
|
870
589
|
// positive integer; allowed_categories "Cannot be set with blocked_categories" (and the
|
|
871
590
|
// merchant-country pair likewise) — both restrictions verbatim from the SDK's field docs.
|
|
872
|
-
export function normalizeSpendingControls(raw: unknown, currency: string | null): { controls?: Record<string, unknown>; error?: StripeResponse } {
|
|
591
|
+
export function normalizeSpendingControls(raw: unknown, currency: string | null, previous?: Record<string, unknown>): { controls?: Record<string, unknown>; error?: StripeResponse } {
|
|
873
592
|
if (raw === undefined) return {};
|
|
874
593
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return controlsRefused('dictionary');
|
|
875
|
-
const sc = raw as Record<string, unknown
|
|
594
|
+
const sc = { ...previous, ...(raw as Record<string, unknown>) };
|
|
876
595
|
const strArr = (v: unknown): string[] | undefined => (Array.isArray(v) ? v.map(String) : undefined);
|
|
877
596
|
const allowedCats = strArr(sc.allowed_categories);
|
|
878
597
|
const blockedCats = strArr(sc.blocked_categories);
|
|
@@ -894,6 +613,9 @@ export function normalizeSpendingControls(raw: unknown, currency: string | null)
|
|
|
894
613
|
const arrOrNull = (v: string[] | undefined) => (v === undefined ? undefined : v.length ? v : null);
|
|
895
614
|
const controls: Record<string, unknown> = {
|
|
896
615
|
...emptySpendingControls(),
|
|
616
|
+
// source: spec:/paths/~1v1~1issuing~1cards~1{card}/post/requestBody/content/application~1x-www-form-urlencoded/schema/properties/spending_controls "Rules that control spending for this card."
|
|
617
|
+
// Keep every documented control, including allowed_card_presences and blocked_card_presences.
|
|
618
|
+
...sc,
|
|
897
619
|
spending_limits: limits,
|
|
898
620
|
...(limits.length ? { spending_limits_currency: typeof sc.spending_limits_currency === 'string' ? sc.spending_limits_currency : currency } : {}),
|
|
899
621
|
};
|
|
@@ -1002,6 +724,9 @@ function cardSubObject(params: Params): Params {
|
|
|
1002
724
|
brand: 'visa', last4: number ? number.slice(-4) : '4242',
|
|
1003
725
|
exp_month: Number(c.exp_month) || 12, exp_year: Number(c.exp_year) || 2034,
|
|
1004
726
|
funding: 'credit', country: 'US',
|
|
727
|
+
// source: spec:/components/schemas/payment_method_card/properties/fingerprint/description "Uniquely identifies this particular card number."
|
|
728
|
+
// The synthetic fingerprint is stable for a number; Stripe's opaque fingerprint algorithm is not published.
|
|
729
|
+
fingerprint: digest('sha256', number || '4242424242424242').slice(0, 16),
|
|
1005
730
|
checks: { address_line1_check: null, address_postal_code_check: null, cvc_check: 'pass' },
|
|
1006
731
|
networks: { available: ['visa'], preferred: null },
|
|
1007
732
|
three_d_secure_usage: { supported: true }, wallet: null,
|
|
@@ -1202,19 +927,6 @@ export function lineItemEntries(params: Record<string, unknown>): Array<Record<s
|
|
|
1202
927
|
return Array.isArray(li) ? (li.filter((x) => x && typeof x === 'object') as Array<Record<string, unknown>>) : [];
|
|
1203
928
|
}
|
|
1204
929
|
|
|
1205
|
-
// ---- IDEMPOTENCY KEYS (vendor-faithful) ----
|
|
1206
|
-
// Real Stripe honors the `Idempotency-Key` header on POST: the first request with a
|
|
1207
|
-
// key executes and the (status + body) response is stored against the key; a replay
|
|
1208
|
-
// with the SAME key returns the SAME stored response WITHOUT re-applying the write.
|
|
1209
|
-
// We persist the stored response as an `_idempotency` resource in the action log
|
|
1210
|
-
// (per-root, so a fork has its own keyspace). `_idempotency` is a twin-internal type
|
|
1211
|
-
// (never served as a Stripe object), so it does not affect spec conformance.
|
|
1212
|
-
//
|
|
1213
|
-
// Note: real Stripe only persists idempotency for responses it considers "completed"
|
|
1214
|
-
// (2xx and most 4xx, including card 402s) and scopes the key to the account; here we
|
|
1215
|
-
// store every executed POST response (success and error alike), keyed per-root.
|
|
1216
|
-
const IDEMPOTENCY_TYPE = '_idempotency';
|
|
1217
|
-
|
|
1218
930
|
// ── THE MONEY MODEL: a charge and the refunds taken out of it ──────────────────────────
|
|
1219
931
|
//
|
|
1220
932
|
// The rule this section exists to hold: A FACT RECORDED ON ONE MONEY OBJECT IS VISIBLE FROM
|
|
@@ -1237,75 +949,20 @@ export function chargeDefaults(chargeId: string, amount: number, captured: boole
|
|
|
1237
949
|
};
|
|
1238
950
|
}
|
|
1239
951
|
|
|
1240
|
-
/**
|
|
1241
|
-
* Public entry: honors the Idempotency-Key (replay → stored response, no re-write)
|
|
1242
|
-
* then delegates to the router. Only POSTs are idempotent (matches Stripe; GET/DELETE
|
|
1243
|
-
* pass straight through). A read-only twin never stores (it cannot write).
|
|
1244
|
-
*/
|
|
1245
|
-
/** Stripe's API as one call: the request goes through the pack's own dispatch (the derived dispatch over
|
|
1246
|
-
* its semantics, its derived core and the hand-written routes below), so a caller that holds no
|
|
1247
|
-
* HTTP server reaches exactly what an SDK does. `occurredAt` pins the World instant. */
|
|
1248
|
-
export async function handleStripeTwinRequest(req: StripeRequest): Promise<StripeResponse> {
|
|
1249
|
-
const twin = createStripeTwinFetch({ ...(req.root !== undefined ? { root: req.root } : {}), readOnly: req.readOnly ?? false, ...(req.occurredAt ? { clock: () => req.occurredAt! } : {}) });
|
|
1250
|
-
const method = req.method.toUpperCase();
|
|
1251
|
-
const headers: Record<string, string> = { 'content-type': 'application/x-www-form-urlencoded' };
|
|
1252
|
-
if (req.idempotencyKey) headers['idempotency-key'] = req.idempotencyKey;
|
|
1253
|
-
if (req.apiVersion !== undefined) headers['stripe-version'] = req.apiVersion;
|
|
1254
|
-
if (req.stripeAccount) headers['stripe-account'] = req.stripeAccount;
|
|
1255
|
-
// a GET carries its parameters in the query; a caller that handed them as a body keeps them
|
|
1256
|
-
let path = req.path.startsWith('/') ? req.path : `/${req.path}`;
|
|
1257
|
-
if ((method === 'GET' || method === 'HEAD') && req.body) path += (path.includes('?') ? '&' : '?') + req.body;
|
|
1258
|
-
const response = await twin(new Request(`http://stripe.test${path}`, { method, headers, ...(method !== 'GET' && method !== 'HEAD' && req.body ? { body: req.body } : {}) }));
|
|
1259
|
-
const text = await response.text();
|
|
1260
|
-
return { status: response.status, body: text ? JSON.parse(text) : null };
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
/**
|
|
1264
|
-
* The twin's doors: what stands in for an act Stripe's API does not have, each under `/_twin/`. The
|
|
1265
|
-
* platform's own account settings (its payout schedule) change on Stripe's Dashboard, which has no API:
|
|
1266
|
-
* POST /_twin/account stands in for that page. Answers nothing for any other path.
|
|
1267
|
-
*/
|
|
1268
|
-
export async function handleStripeDoor(req: StripeRequest, params: Record<string, unknown>): Promise<StripeResponse | undefined> {
|
|
1269
|
-
const method = req.method.toUpperCase();
|
|
1270
|
-
const path = (req.path.split('?')[0] ?? '/').replace(/\/+$/, '') || '/';
|
|
1271
|
-
const seg = path.replace(/^\/+/, '').split('/');
|
|
1272
|
-
if (seg[0] !== '_twin') return undefined;
|
|
1273
|
-
if (req.readOnly) return err('twin is read-only; omit readOnly to accept writes', 405);
|
|
1274
|
-
if (path === '/_twin/account' && method === 'POST') {
|
|
1275
|
-
const ex = getOne('account', PLATFORM_ACCOUNT_ID, req.root);
|
|
1276
|
-
const { type: _t, settings: rawSettings, business_profile: rawProfile, ...rest } = params as Record<string, unknown>;
|
|
1277
|
-
// the platform's account is Stripe's default until the page first changes it (semantics/connect.ts, the same
|
|
1278
|
-
// default GET /v1/account answers and the Public details page stores)
|
|
1279
|
-
const fields = {
|
|
1280
|
-
...(ex ? {} : (({ id: _id, ...base }) => base)(platformAccountDefault())),
|
|
1281
|
-
...rest,
|
|
1282
|
-
settings: accountSettings(rawSettings, ex?.settings as Record<string, unknown> | undefined),
|
|
1283
|
-
// a hash is merged into what the account holds, as an update merges it (the name the Public details page saved stays)
|
|
1284
|
-
...(rawProfile && typeof rawProfile === 'object' ? { business_profile: { ...((ex?.business_profile as Record<string, unknown> | undefined) ?? {}), ...(rawProfile as Record<string, unknown>) } } : {}),
|
|
1285
|
-
};
|
|
1286
|
-
return { status: 200, body: await writeResource('account', PLATFORM_ACCOUNT_ID, stashVendorType('account', fields), 'account.update', req.root, req.occurredAt) };
|
|
1287
|
-
}
|
|
1288
|
-
// Issuing network tokens are minted by the card network when a cardholder adds the card to a
|
|
1289
|
-
// phone's wallet (docs.stripe.com/issuing/cards/digital-wallets): there is no API create, and the
|
|
1290
|
-
// act happens in the phone's wallet app, not on a Stripe page. POST /_twin/issuing/cards/:card/wallets
|
|
1291
|
-
// stands in for that act ({wallet_provider, network}); the API reads the token and moves its status.
|
|
1292
|
-
// Where the documentation stops and the twin decides: the network approves at once (the token is active).
|
|
1293
|
-
if (seg[0] === '_twin' && seg[1] === 'issuing' && seg[2] === 'cards' && seg[3] && seg[4] === 'wallets' && seg.length === 5 && method === 'POST') {
|
|
1294
|
-
const card = decodeURIComponent(seg[3]);
|
|
1295
|
-
const c = getOne('issuing_card', card, req.root);
|
|
1296
|
-
if (!c) return err(`No such card: '${card}'`, 400, 'resource_missing');
|
|
1297
|
-
const wallet = typeof params.wallet_provider === 'string' && ['apple_pay', 'google_pay', 'samsung_pay'].includes(params.wallet_provider)
|
|
1298
|
-
? params.wallet_provider : 'apple_pay';
|
|
1299
|
-
const network = typeof params.network === 'string' && ['visa', 'mastercard'].includes(params.network) ? params.network : 'visa';
|
|
1300
|
-
const last4 = typeof c.last4 === 'string' ? c.last4 : '0000';
|
|
1301
|
-
return create('issuing_token', { card, cardholder: typeof c.cardholder === 'string' ? c.cardholder : ((c.cardholder as Record<string, unknown> | undefined)?.id ?? null) }, {
|
|
1302
|
-
status: 'active', wallet_provider: wallet, network, last4, livemode: false,
|
|
1303
|
-
device_fingerprint: null, network_data: null, network_updated_at: nowUnix(req.occurredAt),
|
|
1304
|
-
}, req);
|
|
1305
|
-
}
|
|
1306
|
-
return undefined;
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
952
|
// collection path segment → resource type, for the generic list of a collection Stripe does not
|
|
1310
953
|
// list at that path (the twin's test clocks, which Stripe lists under /v1/test_helpers).
|
|
1311
954
|
|
|
955
|
+
|
|
956
|
+
export function planView(price: Record<string, unknown>): Record<string, unknown> {
|
|
957
|
+
const recurring = price.recurring as Record<string, unknown> | undefined;
|
|
958
|
+
return {
|
|
959
|
+
id: price.id, object: 'plan', active: price.active ?? true, amount: price.unit_amount ?? null,
|
|
960
|
+
amount_decimal: price.unit_amount_decimal ?? (price.unit_amount == null ? null : String(price.unit_amount)),
|
|
961
|
+
billing_scheme: price.billing_scheme ?? 'per_unit', created: price.created, currency: price.currency,
|
|
962
|
+
interval: recurring?.interval ?? null, interval_count: recurring?.interval_count ?? 1,
|
|
963
|
+
livemode: price.livemode ?? false, metadata: price.metadata ?? {}, nickname: price.nickname ?? null,
|
|
964
|
+
product: price.product ?? null, tiers_mode: price.tiers_mode ?? null,
|
|
965
|
+
meter: recurring?.meter ?? null, trial_period_days: recurring?.trial_period_days ?? null,
|
|
966
|
+
transform_usage: price.transform_quantity ?? null, usage_type: recurring?.usage_type ?? 'licensed',
|
|
967
|
+
};
|
|
968
|
+
}
|