@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
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// What Stripe's pages show of an account, and where its Connect OAuth connections are kept.
|
|
2
|
+
|
|
3
|
+
// What Stripe's pages show of an account, and where its Connect OAuth connections are kept.
|
|
4
|
+
|
|
5
|
+
type Row = Record<string, any>;
|
|
6
|
+
|
|
7
|
+
/** The name an account shows its customers (Checkout, the customer portal): its public business name,
|
|
8
|
+
* `business_profile.name` ("The customer-facing business name", docs.stripe.com/api/accounts/object), which the
|
|
9
|
+
* operator sets on the Dashboard ("You can change a Checkout page's name by modifying the Business name field",
|
|
10
|
+
* docs.stripe.com/payments/checkout/customization/appearance). Where the documentation stops and the twin decides:
|
|
11
|
+
* with none set it falls back to the Dashboard's account name, `settings.dashboard.display_name` ("used on the Stripe
|
|
12
|
+
* Dashboard to differentiate between accounts", the same object page), and with neither to "Twin Inc.", the name the
|
|
13
|
+
* twin has always given the World's own account. */
|
|
14
|
+
export const PLATFORM_DEFAULT_NAME = 'Twin Inc.';
|
|
15
|
+
export function publicBusinessName(account: Row | undefined): string {
|
|
16
|
+
const named = (v: unknown): string | undefined => (typeof v === 'string' && v.trim() ? v : undefined);
|
|
17
|
+
return named(account?.business_profile?.name) ?? named(account?.settings?.dashboard?.display_name) ?? PLATFORM_DEFAULT_NAME;
|
|
18
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// File, file link, Identity and billing-portal semantics. A file is the uploaded document's object
|
|
2
|
+
// (the twin keeps no bytes); a file link is answered, not kept. An Identity verification session is
|
|
3
|
+
// created here and verified through the twin's own helper outside Stripe's surface; its reports
|
|
4
|
+
// are the derived core's to list and read, as are file list and retrieve, session retrieve and
|
|
5
|
+
// portal-configuration retrieve.
|
|
6
|
+
import type { Row } from './common.ts';
|
|
7
|
+
export const PBC = 'billing_portal.configuration';
|
|
8
|
+
|
|
9
|
+
/** A portal configuration's features, each filled where the request leaves it: every feature is answered (the served
|
|
10
|
+
* spec requires all five), as the create page's example answers a request that sets only customer_update and
|
|
11
|
+
* invoice_history: payment_method_update disabled, subscription_cancel disabled at_period_end with proration_behavior
|
|
12
|
+
* none and its five default reasons, subscription_update disabled with no allowed updates and proration_behavior none
|
|
13
|
+
* (docs.stripe.com/api/customer_portal/configurations/create); trial_update_behavior "Defaults to a value of
|
|
14
|
+
* `end_trial` if you don't set it during creation" (the served spec). Where the documentation stops and the twin
|
|
15
|
+
* decides: a feature the request leaves out is disabled, and schedule_at_period_end has no conditions. */
|
|
16
|
+
export const PORTAL_FEATURES: Row = {
|
|
17
|
+
customer_update: { allowed_updates: [], enabled: false },
|
|
18
|
+
invoice_history: { enabled: false },
|
|
19
|
+
payment_method_update: { enabled: false, payment_method_configuration: null },
|
|
20
|
+
subscription_cancel: { cancellation_reason: { enabled: false, options: ['too_expensive', 'missing_features', 'switched_service', 'unused', 'other'] }, enabled: false, mode: 'at_period_end', proration_behavior: 'none' },
|
|
21
|
+
subscription_update: { default_allowed_updates: [], enabled: false, proration_behavior: 'none', schedule_at_period_end: { conditions: [] }, trial_update_behavior: 'end_trial' },
|
|
22
|
+
};
|
|
23
|
+
export const filled = (base: unknown, given: unknown): unknown => {
|
|
24
|
+
if (!given || typeof given !== 'object' || Array.isArray(given) || !base || typeof base !== 'object' || Array.isArray(base)) return given === undefined ? base : given;
|
|
25
|
+
const out: Row = { ...(base as Row) };
|
|
26
|
+
for (const [k, v] of Object.entries(given as Row)) out[k] = filled((base as Row)[k], v);
|
|
27
|
+
return out;
|
|
28
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Invoice and InvoiceItem semantics. The machine in ../manifest.ts says which moves of an invoice's
|
|
2
|
+
// `status` are legal (finalize, send, pay, mark_uncollectible, void) and what Stripe answers
|
|
3
|
+
// otherwise; these handlers compute each move, keep a draft's lines and totals in step with its
|
|
4
|
+
// items, and mint the PaymentIntent a finalized invoice collects through. The invoice list and the
|
|
5
|
+
// item list are the derived core's.
|
|
6
|
+
import type { Row } from './common.ts';
|
|
7
|
+
import { applyCouponDiscount } from './stripe.ts';
|
|
8
|
+
|
|
9
|
+
export const INV = 'invoice';
|
|
10
|
+
|
|
11
|
+
/** Whether leaving draft mints a PaymentIntent: only a charge_automatically invoice with a balance. */
|
|
12
|
+
// "Finalizing an invoice does the following: ... It creates an incomplete payment intent for the invoice"
|
|
13
|
+
// (docs.stripe.com/invoicing/integration/workflow-transitions), whichever its collection method
|
|
14
|
+
export const collectsThroughIntent = (inv: Row): boolean => inv.status === 'draft' && (Number(inv.amount_due) || 0) > 0;
|
|
15
|
+
|
|
16
|
+
export const transitions = (inv: Row): object => (inv.status_transitions as object) ?? {};
|
|
17
|
+
|
|
18
|
+
/** What finalizing assigns: the invoice's `number`, the customer's invoice_prefix and a sequence
|
|
19
|
+
* (https://docs.stripe.com/api/invoices/object#invoice_object-number), and for send_invoice a
|
|
20
|
+
* `due_date` days_until_due after finalization (#invoice_object-due_date). */
|
|
21
|
+
/** A customer's invoice_prefix, "The prefix for the customer used to generate unique invoice numbers"
|
|
22
|
+
* (docs.stripe.com/api/customers/object): the twin's, from the customer's id. */
|
|
23
|
+
export const invoicePrefix = (customerId: string): string => customerId.replace(/[^a-z0-9]/gi, '').slice(-8).toUpperCase() || 'TWIN';
|
|
24
|
+
|
|
25
|
+
/** An invoice's payment as Stripe answers it (docs.stripe.com/api/invoice-payment/object): the PaymentIntent that
|
|
26
|
+
* collects the invoice, open until the invoice is paid. Where the documentation stops and the twin decides: an
|
|
27
|
+
* invoice has one payment, its default, and the payment's id is the invoice's with `inpay_`. */
|
|
28
|
+
export function invoicePayment(inv: Row): Row | undefined {
|
|
29
|
+
if (typeof inv.payment_intent !== 'string') return undefined;
|
|
30
|
+
const paid = inv.status === 'paid';
|
|
31
|
+
const transitions = (inv.status_transitions as Row | undefined) ?? {};
|
|
32
|
+
return {
|
|
33
|
+
id: `inpay_${String(inv.id).replace(/^in_/, '')}`, object: 'invoice_payment', invoice: inv.id, is_default: true, livemode: false,
|
|
34
|
+
amount_requested: Number(inv.amount_due ?? 0), amount_paid: paid ? Number(inv.amount_paid ?? inv.amount_due ?? 0) : null, currency: inv.currency,
|
|
35
|
+
created: inv.created, payment: { type: 'payment_intent', payment_intent: inv.payment_intent },
|
|
36
|
+
status: paid ? 'paid' : inv.status === 'void' ? 'canceled' : 'open',
|
|
37
|
+
status_transitions: { paid_at: paid ? (transitions.paid_at ?? null) : null, canceled_at: inv.status === 'void' ? (transitions.voided_at ?? null) : null },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
export const linesOf = (inv: Row): Row[] => (inv.lines as { data?: Row[] } | undefined)?.data ?? [];
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
export const sumLines = (lines: Row[]): number => lines.reduce((s, l) => s + (Number(l.amount) || 0), 0);
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
/** Credit the previous price and debit the replacement for the same remaining service time. */
|
|
50
|
+
// source: https://docs.stripe.com/billing/subscriptions/prorations "Unused time on original 10 USD plan (credit)"
|
|
51
|
+
// source: https://docs.stripe.com/billing/subscriptions/prorations "Remaining time on new 20 USD plan (debit)"
|
|
52
|
+
// source: https://docs.stripe.com/billing/subscriptions/prorations "The current billing period’s start and end times are used to calculate the cost of the subscription before and after the change."
|
|
53
|
+
// source: https://docs.stripe.com/billing/subscriptions/prorations "By default, Stripe calculates prorations down to the second."
|
|
54
|
+
export function prorationLines(before: Row[], after: Row[], time: number, start: number, end: number, coupon: Row | undefined): Row[] {
|
|
55
|
+
const out: Row[] = [];
|
|
56
|
+
const discounted = (line: Row, lines: Row[]): number => {
|
|
57
|
+
const total = sumLines(lines);
|
|
58
|
+
// source: https://docs.stripe.com/billing/subscriptions/prorations "When calculating proration credits or debits, Stripe uses the subscription’s discounted price, not the original price."
|
|
59
|
+
return Number(line.amount) - (total > 0 ? applyCouponDiscount(total, coupon) * Number(line.amount) / total : 0);
|
|
60
|
+
};
|
|
61
|
+
const priceId = (line: Row): unknown => typeof line.price === 'object' ? (line.price as Row).id : line.price;
|
|
62
|
+
const append = (line: Row, lines: Row[], credit: boolean): void => {
|
|
63
|
+
const billingPeriod = line.period as Row;
|
|
64
|
+
const periodStart = Number(billingPeriod.start ?? start), periodEnd = Number(billingPeriod.end ?? end);
|
|
65
|
+
const fraction = Math.max(0, Math.min(1, (periodEnd - time) / Math.max(1, periodEnd - periodStart)));
|
|
66
|
+
const amount = Math.round(discounted(line, lines) * fraction) * (credit ? -1 : 1);
|
|
67
|
+
if (!amount) return;
|
|
68
|
+
out.push({ ...line, id: `${line.id}_${credit ? 'unused' : 'remaining'}`, amount, proration: true, discountable: false,
|
|
69
|
+
period: { start: time, end: periodEnd }, description: `${credit ? 'Unused' : 'Remaining'} time on ${String((line.price as Row)?.nickname ?? priceId(line))}` });
|
|
70
|
+
};
|
|
71
|
+
for (const old of before) {
|
|
72
|
+
const replacement = after.find(line => line.subscription_item === old.subscription_item);
|
|
73
|
+
if (replacement && priceId(replacement) === priceId(old) && replacement.quantity === old.quantity) continue;
|
|
74
|
+
append(old, before, true);
|
|
75
|
+
}
|
|
76
|
+
for (const next of after) {
|
|
77
|
+
const previous = before.find(line => line.subscription_item && line.subscription_item === next.subscription_item);
|
|
78
|
+
if (previous && priceId(previous) === priceId(next) && previous.quantity === next.quantity) continue;
|
|
79
|
+
append(next, after, false);
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
// ── invoices ──
|
|
87
|
+
|
|
88
|
+
/** A draft invoice's fields before anything is on it: what a new invoice and a preview start from. */
|
|
89
|
+
export const draft = (now: number): Row => ({
|
|
90
|
+
status: 'draft', livemode: false, currency: 'usd', collection_method: 'charge_automatically',
|
|
91
|
+
auto_advance: false, attempt_count: 0, attempted: false,
|
|
92
|
+
amount_due: 0, amount_paid: 0, amount_remaining: 0, amount_overpaid: 0, amount_paid_off_stripe: 0, amount_shipping: 0,
|
|
93
|
+
subtotal: 0, total: 0, starting_balance: 0, post_payment_credit_notes_amount: 0, pre_payment_credit_notes_amount: 0,
|
|
94
|
+
period_start: now, period_end: now, default_tax_rates: [], discounts: [],
|
|
95
|
+
lines: { object: 'list', data: [], has_more: false, total_count: 0 },
|
|
96
|
+
automatic_tax: { enabled: false, liability: null, status: null },
|
|
97
|
+
status_transitions: { finalized_at: null, marked_uncollectible_at: null, paid_at: null, voided_at: null },
|
|
98
|
+
issuer: { type: 'self' },
|
|
99
|
+
payment_settings: { default_mandate: null, payment_method_options: null, payment_method_types: null },
|
|
100
|
+
// a draft has no PaymentIntent: finalize mints it
|
|
101
|
+
payment_intent: null,
|
|
102
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Issuing semantics: cardholders, cards, authorizations, transactions, disputes, network tokens and
|
|
2
|
+
// personalization designs. An authorization's `card` is the full Card in every answer and webhook
|
|
3
|
+
// (the manifest's write hook embeds it on writes; reads embed it here). The machines in
|
|
4
|
+
// ../manifest.ts say how authorizations, disputes, cards, cardholders, tokens and designs move.
|
|
5
|
+
//
|
|
6
|
+
// The test-helper routes here (/v1/test_helpers/issuing/…) are Stripe's own doors for what no API
|
|
7
|
+
// verb makes: a card presented at a merchant, a capture, a force capture, a design's review. A
|
|
8
|
+
// presented authorization is decided in Stripe's order: card status, then spending controls
|
|
9
|
+
// (card, then cardholder), then the real-time issuing_authorization.request webhook delivered
|
|
10
|
+
// synchronously to the first enrolled endpoint; with none it is approved (card_active).
|
|
11
|
+
import type { Row } from './common.ts';
|
|
12
|
+
export const CH = 'issuing.cardholder';
|
|
13
|
+
/** The id of a card's cardholder: the card answers the cardholder in full (the served spec's issuing.card.cardholder is
|
|
14
|
+
* the Cardholder object, not an id); its id is read from it for filtering and for the objects that name it by id. */
|
|
15
|
+
export const holderId = (c: Row): string => (typeof c.cardholder === 'string' ? c.cardholder : String((c.cardholder as Row | undefined)?.id ?? ''));
|
|
16
|
+
export const CARD = 'issuing.card';
|
|
17
|
+
export const AUTH = 'issuing.authorization';
|
|
18
|
+
export const TXN = 'issuing.transaction';
|
|
19
|
+
|
|
20
|
+
/** One request_history entry; the authorization code is derived from the request time (Stripe's is not unique either). */
|
|
21
|
+
export function historyEntry(o: { amount: number; currency: string; approved: boolean; reason: string; reasonMessage: string | null; createdSec: number; merchantAmount: number; merchantCurrency: string }): Row {
|
|
22
|
+
return {
|
|
23
|
+
amount: o.amount, amount_details: null, approved: o.approved,
|
|
24
|
+
authorization_code: o.approved ? `S${String(100000 + (o.createdSec % 900000))}` : null,
|
|
25
|
+
created: o.createdSec, currency: o.currency,
|
|
26
|
+
merchant_amount: o.merchantAmount, merchant_currency: o.merchantCurrency,
|
|
27
|
+
network_risk_score: null, reason: o.reason, reason_message: o.reasonMessage,
|
|
28
|
+
requested_at: o.createdSec,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The test helper's default merchant, under any merchant_data the caller gives. */
|
|
33
|
+
export function merchantData(param: unknown): Row {
|
|
34
|
+
const base: Row = {
|
|
35
|
+
category: 'general', category_code: '5399', city: 'San Francisco', country: 'US',
|
|
36
|
+
name: 'Twin Test Merchant', network_id: '1234567890', postal_code: '94103', state: 'CA',
|
|
37
|
+
tax_id: null, terminal_id: null, url: null,
|
|
38
|
+
};
|
|
39
|
+
if (param && typeof param === 'object' && !Array.isArray(param)) Object.assign(base, param);
|
|
40
|
+
return base;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The card's stored id (an embedded card answers with its id). */
|
|
44
|
+
export const cardId = (auth: Row): unknown => (auth.card && typeof auth.card === 'object' ? (auth.card as Row).id : auth.card);
|
|
45
|
+
|
|
46
|
+
/** The real-time window: "If Stripe doesn't receive your approve or decline response within 2 seconds, the
|
|
47
|
+
* Authorization is automatically approved or declined based on your timeout settings"
|
|
48
|
+
* (docs.stripe.com/issuing/controls/real-time-authorizations). */
|
|
49
|
+
export const REALTIME_WINDOW_SECONDS = 2;
|
|
50
|
+
|
|
51
|
+
/** A virtual card's number, drawn from the World: a 16-digit Visa number ("The brand of the card": the twin issues
|
|
52
|
+
* Visa) whose last digit is the Luhn check digit every card number carries. Where the documentation stops and the twin
|
|
53
|
+
* decides: the digits are the World's draw, Stripe's are the issuer's. */
|
|
54
|
+
export function cardNumberOf(drawn: string): string {
|
|
55
|
+
const body = `4${[...drawn].map((ch) => String(ch.charCodeAt(0) % 10)).join('').padEnd(14, '0').slice(0, 14)}`;
|
|
56
|
+
let sum = 0;
|
|
57
|
+
for (let i = 0; i < body.length; i++) {
|
|
58
|
+
let d = Number(body[body.length - 1 - i]);
|
|
59
|
+
if (i % 2 === 0) { d *= 2; if (d > 9) d -= 9; }
|
|
60
|
+
sum += d;
|
|
61
|
+
}
|
|
62
|
+
return `${body}${(10 - (sum % 10)) % 10}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** A virtual card's CVC, drawn from the World: three digits. */
|
|
66
|
+
export const cvcOf = (drawn: string): string => [...drawn].map((ch) => String(ch.charCodeAt(0) % 10)).join('').padEnd(3, '0').slice(0, 3);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Stripe's balance ledger (docs.stripe.com/api/balance_transactions): every movement of the account's money writes
|
|
2
|
+
// a balance transaction, and the balance is their sum. A captured charge adds its amount less Stripe's fee, pending
|
|
3
|
+
// until its available_on; a refund takes its amount back at once; a payout takes its amount out of what is available
|
|
4
|
+
// and is refused beyond it (balance_insufficient). A transaction's status is what the World clock says of its
|
|
5
|
+
// available_on (the machine's time move, pending → available), so a `wait` in a story settles funds as time does.
|
|
6
|
+
//
|
|
7
|
+
// Where the documentation stops and the twin decides: the fee is Stripe's standard US card pricing, 2.9% + 30¢
|
|
8
|
+
// (stripe.com/pricing), for every charge; funds become available two days after capture (Stripe's standard US
|
|
9
|
+
// payout schedule counts business days).
|
|
10
|
+
//
|
|
11
|
+
// Test mode (the twin serves livemode: false), as docs.stripe.com/testing documents it:
|
|
12
|
+
// - cards keep that delay but two: "Other test cards send funds from a successful payment to your pending balance",
|
|
13
|
+
// while 4000000000000077 and 4000003720000278 (and their test names pm_card_bypassPending,
|
|
14
|
+
// pm_card_bypassPendingInternational, tok_bypassPending, tok_bypassPendingInternational) "succeed. Funds are added
|
|
15
|
+
// directly to your available balance, bypassing your pending balance" (#available-balance; test-cards.ts). A
|
|
16
|
+
// PaymentMethod made from one keeps that (payment-methods.ts, after-payment.ts), so a saved card charged later by
|
|
17
|
+
// id bypasses too;
|
|
18
|
+
// - a US bank account debit: "Test transactions settle instantly and are added to your available test balance. This
|
|
19
|
+
// behavior differs from live mode" (ACH Direct Debit, "Test settlement behavior");
|
|
20
|
+
// - a source_transaction transfer "takes on the pending status of the associated charge"
|
|
21
|
+
// (docs.stripe.com/connect/separate-charges-and-transfers), so it is available at once when its charge is;
|
|
22
|
+
// - "Test payouts simulate a live payout but aren't processed with the bank" (docs.stripe.com/payouts#test-payouts):
|
|
23
|
+
// the payout clock (semantics/balance.ts) is live's.
|
|
24
|
+
// No other test-mode speed-up is documented, so 4242 4242 4242 4242 funds sit pending the two days.
|
|
25
|
+
//
|
|
26
|
+
// Where the documentation stops and the twin decides: every credit written available at once (a bypass or bank-debit
|
|
27
|
+
// charge, a transfer, a transfer from such a charge, an application fee, a top-up, an Issuing top-up included, a reversal) is marked not yet
|
|
28
|
+
// sent, and the drain sends its account balance.available for it ("Occurs whenever your Stripe balance has been
|
|
29
|
+
// updated (e.g., when a charge is available to be paid out). ... This event is not fired for negative
|
|
30
|
+
// transactions", docs.stripe.com/api/events/types), as it does for funds that came due after the delay. The Balance
|
|
31
|
+
// it carries is the account's at the drain: a debit or an automatic payout landing first shows in it.
|
|
32
|
+
//
|
|
33
|
+
// Each connected account keeps its own balance (docs.stripe.com/connect/account-balances): a transaction belongs to
|
|
34
|
+
// the account the request acts for (the Stripe-Account header), else to the platform. A transfer takes its amount out
|
|
35
|
+
// of the platform's available balance and adds it to the destination's; a destination charge transfers its amount
|
|
36
|
+
// less the application fee, available when the charge's funds are; a direct charge's application fee moves from the
|
|
37
|
+
// connected account to the platform.
|
|
38
|
+
import type { Row } from './common.ts';
|
|
39
|
+
import { BYPASS_PENDING_CARDS } from './test-cards.ts';
|
|
40
|
+
export const BT = 'balance_transaction';
|
|
41
|
+
/** A ledger entry's mark: available at once, its balance.available not yet sent (settleDueEntries). */
|
|
42
|
+
export const UNSENT = '_availableUnsent';
|
|
43
|
+
export const BYPASS_NUMBERS = new Set(Object.values(BYPASS_PENDING_CARDS).map((c) => c.number));
|
|
44
|
+
|
|
45
|
+
export const feeOf = (amount: number): number => (amount > 0 ? Math.round(amount * 0.029) + 30 : 0);
|
|
46
|
+
|
|
47
|
+
/** Whether a stored ledger entry is on this account's balance (undefined: the platform's). */
|
|
48
|
+
export const onAccount = (t: Row, account: string | undefined): boolean => (typeof t._account === 'string' ? t._account : undefined) === account;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Stripe API key parsing and error redaction, from the API authentication documentation.
|
|
2
|
+
|
|
3
|
+
/** A key shown the way Stripe's errors show one: its prefix and its last four. */
|
|
4
|
+
export const redactKey = (key: string): string => {
|
|
5
|
+
const prefix = /^(sk|rk|pk)_(test|live)_/.exec(key)?.[0] ?? '';
|
|
6
|
+
const rest = key.slice(prefix.length);
|
|
7
|
+
return rest.length > 4 ? `${prefix}${'*'.repeat(rest.length - 4)}${rest.slice(-4)}` : `${prefix}${'*'.repeat(rest.length)}`;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/** The API key a request carries: Bearer, or Basic's user (`curl -u sk_…:`, docs.stripe.com/api/authentication). */
|
|
11
|
+
export function requestKey(request: Request): string {
|
|
12
|
+
const header = request.headers.get('authorization') ?? '';
|
|
13
|
+
const bearer = /^bearer\s+(\S+)/i.exec(header)?.[1];
|
|
14
|
+
if (bearer) return bearer;
|
|
15
|
+
const basic = /^basic\s+(\S+)/i.exec(header)?.[1];
|
|
16
|
+
if (basic) { try { return atob(basic).split(':')[0] ?? ''; } catch { return ''; } }
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// What a connected account owes Stripe before it can take payments (the requirements its onboarding collects,
|
|
2
|
+
// ../screens/connect-onboarding.tsx), as the Accounts API reports them (`requirements.currently_due`): the two US sets the twin
|
|
3
|
+
// models, a company's and an individual's.
|
|
4
|
+
import { INDUSTRIES as MCCS } from './industries.ts';
|
|
5
|
+
type Row = Record<string, unknown>;
|
|
6
|
+
|
|
7
|
+
export const OFFERED = new Set(MCCS.map(([mcc]) => mcc));
|
|
8
|
+
|
|
9
|
+
export type Form = Record<string, string>;
|
|
10
|
+
|
|
11
|
+
export type Kind = 'company' | 'individual';
|
|
12
|
+
|
|
13
|
+
export const has = (v: Form, k: string): boolean => (v[k] ?? '').trim() !== '';
|
|
14
|
+
|
|
15
|
+
export const dobPart = (v: Form, i: number): boolean => /^\d{4}-\d{2}-\d{2}$/.test(v.dob ?? '') && Number((v.dob ?? '').split('-')[i]) > 0;
|
|
16
|
+
|
|
17
|
+
/** Each requirement Stripe names, the form's answer that meets it, and which of the two sets it belongs to. */
|
|
18
|
+
export const REQUIREMENTS: Array<[string, (v: Form) => boolean, Kind[]]> = [
|
|
19
|
+
['business_profile.mcc', (v) => OFFERED.has((v.mcc ?? '').trim()), ['company', 'individual']],
|
|
20
|
+
['business_profile.url', (v) => has(v, 'url'), ['company', 'individual']],
|
|
21
|
+
['settings.payments.statement_descriptor', (v) => has(v, 'statement_descriptor'), ['company', 'individual']],
|
|
22
|
+
['company.name', (v) => has(v, 'company_name'), ['company']],
|
|
23
|
+
['company.tax_id', (v) => has(v, 'company_tax_id'), ['company']],
|
|
24
|
+
['company.phone', (v) => has(v, 'company_phone'), ['company']],
|
|
25
|
+
['company.address.line1', (v) => has(v, 'company_line1'), ['company']],
|
|
26
|
+
['company.address.city', (v) => has(v, 'company_city'), ['company']],
|
|
27
|
+
['company.address.state', (v) => has(v, 'company_state'), ['company']],
|
|
28
|
+
['company.address.postal_code', (v) => has(v, 'company_postal_code'), ['company']],
|
|
29
|
+
['company.owners_provided', (v) => v.owners_provided === 'provided', ['company']],
|
|
30
|
+
['owners.first_name', (v) => has(v, 'owner_first_name'), ['company']],
|
|
31
|
+
['owners.last_name', (v) => has(v, 'owner_last_name'), ['company']],
|
|
32
|
+
['owners.email', (v) => has(v, 'owner_email'), ['company']],
|
|
33
|
+
...(['first_name', 'last_name', 'email', 'phone'] as const).flatMap((f): Array<[string, (v: Form) => boolean, Kind[]]> => [[`representative.${f}`, (v) => has(v, f), ['company']], [`individual.${f}`, (v) => has(v, f), ['individual']]]),
|
|
34
|
+
...(['day', 'month', 'year'] as const).flatMap((f, i): Array<[string, (v: Form) => boolean, Kind[]]> => [[`representative.dob.${f}`, (v) => dobPart(v, 2 - i), ['company']], [`individual.dob.${f}`, (v) => dobPart(v, 2 - i), ['individual']]]),
|
|
35
|
+
...(['line1', 'city', 'state', 'postal_code'] as const).flatMap((f): Array<[string, (v: Form) => boolean, Kind[]]> => [[`representative.address.${f}`, (v) => has(v, f), ['company']], [`individual.address.${f}`, (v) => has(v, f), ['individual']]]),
|
|
36
|
+
['representative.relationship.title', (v) => has(v, 'title'), ['company']],
|
|
37
|
+
['representative.ssn_last_4', (v) => /^\d{4}$/.test(v.ssn_last_4 ?? ''), ['company']],
|
|
38
|
+
['individual.ssn_last_4', (v) => /^\d{4}$/.test(v.ssn_last_4 ?? ''), ['individual']],
|
|
39
|
+
['tos_acceptance.date', (v) => v.tos === 'accepted', ['company', 'individual']],
|
|
40
|
+
['tos_acceptance.ip', (v) => v.tos === 'accepted', ['company', 'individual']],
|
|
41
|
+
['external_account', (v) => has(v, 'routing_number') && has(v, 'account_number'), ['company', 'individual']],
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/** The set an account onboards against: the two US sets the twin models, or none. */
|
|
45
|
+
export function kindOf(account: Row): Kind | undefined {
|
|
46
|
+
const type = account.business_type ?? 'individual';
|
|
47
|
+
return (account.country ?? 'US') === 'US' && (type === 'company' || type === 'individual') ? type : undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** What an account of this kind still owes, given the form's answers (none: all of it). */
|
|
51
|
+
export function currentlyDue(kind: Kind, v: Form = {}): string[] {
|
|
52
|
+
return REQUIREMENTS.filter(([, met, kinds]) => kinds.includes(kind) && !met(v)).map(([req]) => req);
|
|
53
|
+
}
|
|
@@ -9,17 +9,14 @@
|
|
|
9
9
|
// Where the evidence stops: the check knows only the served version's parameters (the spec's, surface.version). A
|
|
10
10
|
// request pinning an earlier version with Stripe-Version is not checked, since the pack holds no earlier spec, beyond
|
|
11
11
|
// the values of the payment_method_types it still takes (PAYMENT_METHOD_TYPES below); nested parameters are not checked.
|
|
12
|
-
import { readParams, vendorError, type DerivedCall } from '@volter/world-core';
|
|
13
|
-
import surface from './generated/surface.gen.json' with { type: 'json' };
|
|
14
|
-
import { manifest } from './manifest.ts';
|
|
15
|
-
import { SERVED_VERSION } from './stripe-version.ts';
|
|
16
12
|
|
|
13
|
+
import surface from '../generated/surface.gen.json' with { type: 'json' };
|
|
17
14
|
type Param = { name: string; required?: boolean };
|
|
18
15
|
type Op = { id: string; method: string; query?: Param[]; body?: Param[] };
|
|
19
|
-
const OPS = new Map((surface.operations as Op[]).map((o) => [o.id, o]));
|
|
16
|
+
export const OPS = new Map((surface.operations as Op[]).map((o) => [o.id, o]));
|
|
20
17
|
|
|
21
18
|
/** Parameters Stripe documents outside its published spec, each with the page that documents it. */
|
|
22
|
-
const DOCUMENTED: Record<string, string[]> = {
|
|
19
|
+
export const DOCUMENTED: Record<string, string[]> = {
|
|
23
20
|
// a top-up into the Issuing balance (docs.stripe.com/issuing/funding/balance: "destination_balance=issuing")
|
|
24
21
|
PostTopups: ['destination_balance'],
|
|
25
22
|
};
|
|
@@ -50,8 +47,8 @@ const DOCUMENTED: Record<string, string[]> = {
|
|
|
50
47
|
export const METHOD_TYPES_REMOVED_IN = '2026-08-26.preview';
|
|
51
48
|
/** Whether a pinned version is at or after the removal: versions order by their date (YYYY-MM-DD). */
|
|
52
49
|
export const methodTypesRemoved = (pinned: string): boolean => pinned.slice(0, 10) >= METHOD_TYPES_REMOVED_IN.slice(0, 10);
|
|
53
|
-
const REMOVED_TYPES = new Set(['PostPaymentIntents', 'PostPaymentIntentsIntent', 'PostPaymentIntentsIntentConfirm', 'PostSetupIntents', 'PostSetupIntentsIntent']);
|
|
54
|
-
const TYPED = new Set([...REMOVED_TYPES, 'PostCheckoutSessions']);
|
|
50
|
+
export const REMOVED_TYPES = new Set(['PostPaymentIntents', 'PostPaymentIntentsIntent', 'PostPaymentIntentsIntentConfirm', 'PostSetupIntents', 'PostSetupIntentsIntent']);
|
|
51
|
+
export const TYPED = new Set([...REMOVED_TYPES, 'PostCheckoutSessions']);
|
|
55
52
|
type Resource = { name: string; fields: Array<{ name: string; enum?: string[] }> };
|
|
56
53
|
/** The payment method types there are: the PaymentMethod object's `type` in the served spec. */
|
|
57
54
|
export const PAYMENT_METHOD_TYPES: readonly string[] = (surface.resources as Resource[]).find((r) => r.name === 'payment_method')?.fields.find((f) => f.name === 'type')?.enum ?? [];
|
|
@@ -61,56 +58,10 @@ export const PAYMENT_METHOD_TYPES: readonly string[] = (surface.resources as Res
|
|
|
61
58
|
* interac_present) and no `custom`. The surface keeps no request enums, so it is copied here. */
|
|
62
59
|
export const CHECKOUT_METHOD_TYPES: readonly string[] = ['acss_debit', 'affirm', 'afterpay_clearpay', 'alipay', 'alma', 'amazon_pay', 'au_becs_debit', 'bacs_debit', 'bancontact', 'billie', 'bizum', 'blik', 'boleto', 'card', 'cashapp', 'crypto', 'customer_balance', 'eps', 'fpx', 'giropay', 'grabpay', 'ideal', 'kakao_pay', 'klarna', 'konbini', 'kr_card', 'link', 'mb_way', 'mobilepay', 'multibanco', 'naver_pay', 'nz_bank_account', 'oxxo', 'p24', 'pay_by_bank', 'payco', 'paynow', 'paypal', 'paypay', 'payto', 'pix', 'promptpay', 'revolut_pay', 'samsung_pay', 'satispay', 'scalapay', 'sepa_debit', 'sequra', 'sofort', 'sunbit', 'swish', 'twint', 'upi', 'us_bank_account', 'wechat_pay', 'zip'];
|
|
63
60
|
/** The create page each intent's refusal links to. */
|
|
64
|
-
const TYPES_DOC: Record<string, string> = {
|
|
61
|
+
export const TYPES_DOC: Record<string, string> = {
|
|
65
62
|
payment_intents: 'https://stripe.com/docs/api/payment_intents/create#create_payment_intent-payment_method_types',
|
|
66
63
|
setup_intents: 'https://stripe.com/docs/api/setup_intents/create#create_setup_intent-payment_method_types',
|
|
67
64
|
};
|
|
68
65
|
|
|
69
66
|
/** An enum's values as Stripe lists them in a refusal: "a, b, or c". */
|
|
70
|
-
const oneOf = (values: readonly string[]): string => (values.length > 1 ? `${values.slice(0, -1).join(', ')}, or ${values.at(-1)}` : values.join(''));
|
|
71
|
-
|
|
72
|
-
/** An earlier version's payment_method_types refused: the first value that names no payment method type the
|
|
73
|
-
* operation takes, in the intent's words or (a Checkout Session) the enum's. */
|
|
74
|
-
function unknownMethodType(opId: string, given: unknown): Response | undefined {
|
|
75
|
-
const values = Array.isArray(given) ? given : given && typeof given === 'object' ? Object.values(given) : [given];
|
|
76
|
-
const checkout = opId === 'PostCheckoutSessions';
|
|
77
|
-
const known = checkout ? CHECKOUT_METHOD_TYPES : PAYMENT_METHOD_TYPES;
|
|
78
|
-
const i = values.findIndex((v) => !known.includes(String(v)));
|
|
79
|
-
if (i < 0) return undefined;
|
|
80
|
-
if (checkout) {
|
|
81
|
-
const param = `payment_method_types[${i}]`;
|
|
82
|
-
return vendorError(manifest, { status: 400, param, message: `Invalid ${param}: must be one of ${oneOf(known)}` });
|
|
83
|
-
}
|
|
84
|
-
const doc = TYPES_DOC[opId.startsWith('PostSetupIntents') ? 'setup_intents' : 'payment_intents'];
|
|
85
|
-
return vendorError(manifest, { status: 400, param: 'payment_method_types', message: `The payment method type "${String(values[i])}" is invalid. See ${doc} for the full list of supported payment method types.` });
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** payment_method_types refused where the caller's version removed it. */
|
|
89
|
-
const noLongerSupported = (version: string): Response =>
|
|
90
|
-
vendorError(manifest, { status: 400, code: 'payment_method_types_no_longer_supported', param: 'payment_method_types', message: `payment_method_types is no longer supported in API version ${version}. Use allowed_payment_method_types, or dynamic payment methods, instead.` });
|
|
91
|
-
|
|
92
|
-
/** Stripe's refusal of a request whose top-level parameters the operation does not take, or undefined. */
|
|
93
|
-
export async function refuseParameters(call: DerivedCall): Promise<Response | undefined> {
|
|
94
|
-
const op = OPS.get(call.operation.id);
|
|
95
|
-
if (!op) return undefined;
|
|
96
|
-
const pinned = call.request.headers.get('stripe-version');
|
|
97
|
-
if (pinned && pinned < SERVED_VERSION) {
|
|
98
|
-
if (!TYPED.has(op.id)) return undefined;
|
|
99
|
-
const earlier = await readParams(manifest, call.request.clone(), call.operation);
|
|
100
|
-
if (earlier.payment_method_types === undefined) return undefined;
|
|
101
|
-
if (REMOVED_TYPES.has(op.id) && methodTypesRemoved(pinned)) return noLongerSupported(pinned);
|
|
102
|
-
return unknownMethodType(op.id, earlier.payment_method_types);
|
|
103
|
-
}
|
|
104
|
-
const method = call.request.method.toUpperCase();
|
|
105
|
-
const known = new Set([...(op.query ?? []), ...(op.body ?? [])].map((p) => p.name).concat(DOCUMENTED[op.id] ?? []));
|
|
106
|
-
const params = await readParams(manifest, call.request.clone(), call.operation);
|
|
107
|
-
if (REMOVED_TYPES.has(op.id) && params.payment_method_types !== undefined) return noLongerSupported(pinned ?? SERVED_VERSION);
|
|
108
|
-
for (const name of Object.keys(params)) {
|
|
109
|
-
if (!known.has(name)) return vendorError(manifest, { status: 400, code: 'parameter_unknown', param: name, message: `Received unknown parameter: ${name}` });
|
|
110
|
-
}
|
|
111
|
-
const required = method === 'GET' || method === 'DELETE' ? (op.query ?? []) : (op.body ?? []);
|
|
112
|
-
for (const p of required) {
|
|
113
|
-
if (p.required && params[p.name] === undefined) return vendorError(manifest, { status: 400, code: 'parameter_missing', param: p.name, message: `Missing required param: ${p.name}.` });
|
|
114
|
-
}
|
|
115
|
-
return undefined;
|
|
116
|
-
}
|
|
67
|
+
export const oneOf = (values: readonly string[]): string => (values.length > 1 ? `${values.slice(0, -1).join(', ')}, or ${values.at(-1)}` : values.join(''));
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// PaymentIntent semantics. The machine in ../manifest.ts says which moves of `status` are legal and
|
|
2
|
+
// what Stripe answers otherwise; these handlers compute what each move does. List, retrieve and
|
|
3
|
+
// update are the derived core's; search is Stripe's query language over the intents.
|
|
4
|
+
import type { Row } from './common.ts';
|
|
5
|
+
export const PI = 'payment_intent';
|
|
6
|
+
/** Bookkeeping: the intents whose caller named the methods (payment_method_types, allowed_payment_method_types) on
|
|
7
|
+
* create. The twin's default list (['card']) stands for Stripe's choice and restricts nothing, so only a named list
|
|
8
|
+
* refuses a confirm with a method of another type. Kept apart from the intent: a `_` subject is never pushed
|
|
9
|
+
* (world-core isTwinBookkeeping). */
|
|
10
|
+
export const TYPES_GIVEN = '_payment_intent_types_given';
|
|
11
|
+
|
|
12
|
+
// ── THE MONEY MODEL, continued: a succeeded intent has a Charge ──
|
|
13
|
+
//
|
|
14
|
+
// Real Stripe always materializes a Charge when a PaymentIntent succeeds and points the intent's
|
|
15
|
+
// `latest_charge` at it: the charge is what a refund lands on, so an intent that succeeds without
|
|
16
|
+
// one cannot answer "refund this payment" at all. Idempotent against a repeated confirm: an intent
|
|
17
|
+
// that already names a successful charge keeps it (a second one would double the money collected); a
|
|
18
|
+
// declined attempt's failed charge is history, and the payment that succeeds makes its own. The id is
|
|
19
|
+
// returned so the caller folds `latest_charge` into the same intent write (one write, one event). The
|
|
20
|
+
// charge is written as the event Stripe sends for it, `charge.succeeded` ("Occurs whenever a charge is
|
|
21
|
+
// successful", docs.stripe.com/api/events/types); Stripe has no `charge.created`.
|
|
22
|
+
/** What every charge an intent makes carries of it: its transfer_group, which "identifies the resulting payment as part
|
|
23
|
+
* of a group" (docs.stripe.com/api/payment_intents/object), its description and its metadata. */
|
|
24
|
+
export function intentCarries(pi: Row): Row {
|
|
25
|
+
return {
|
|
26
|
+
...(typeof pi.transfer_group === 'string' ? { transfer_group: pi.transfer_group } : {}),
|
|
27
|
+
...(typeof pi.description === 'string' ? { description: pi.description } : {}),
|
|
28
|
+
// "When a PaymentIntent creates a Charge, the metadata copies to the Charge in a one-time snapshot"
|
|
29
|
+
// (docs.stripe.com/metadata, "Copy metadata to another object")
|
|
30
|
+
...(pi.metadata && typeof pi.metadata === 'object' ? { metadata: { ...(pi.metadata as Row) } } : {}),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// PaymentMethod semantics: a PaymentMethod is created detached, attached to a customer, and
|
|
2
|
+
// detached again. The list is the derived core's.
|
|
3
|
+
import { paymentMethodSubObject } from './stripe.ts';
|
|
4
|
+
import { AFTER_SUCCESS_CARDS } from './after-payment.ts';
|
|
5
|
+
import type { Row } from './common.ts';
|
|
6
|
+
export const PM = 'payment_method';
|
|
7
|
+
export const noBilling = { address: null, email: null, name: null, phone: null };
|
|
8
|
+
|
|
9
|
+
export const TEST_PM_CARDS: Record<string, { brand: string; number: string }> = {
|
|
10
|
+
...AFTER_SUCCESS_CARDS,
|
|
11
|
+
pm_card_mastercard: { brand: 'mastercard', number: '5555555555554444' },
|
|
12
|
+
pm_card_amex: { brand: 'amex', number: '378282246310005' },
|
|
13
|
+
pm_card_discover: { brand: 'discover', number: '6011111111111117' },
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** Attach a payment method to a customer (both known to exist or be attachable): a stored one moves to the
|
|
17
|
+
* customer; a test name mints a new PaymentMethod on the customer carrying that card's outcome. */
|
|
18
|
+
/** The card a test name (pm_card_*, tok_*) stands for, never stored as a PaymentMethod (docs.stripe.com/testing#cards). */
|
|
19
|
+
export function testCardOf(ref: string): Row | undefined {
|
|
20
|
+
const name = ref.replace(/^tok_/, 'pm_card_');
|
|
21
|
+
if (!name.startsWith('pm_card_')) return undefined;
|
|
22
|
+
const test = TEST_PM_CARDS[name] ?? { brand: 'visa', number: '4242424242424242' };
|
|
23
|
+
return { ...(paymentMethodSubObject('card', { card: { number: test.number } }).card as Row), brand: test.brand };
|
|
24
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Radar semantics: reviews of payments Radar held (approving closes one, the machine in
|
|
2
|
+
// ../manifest.ts) and the value lists custom rules reference, with their items. Review list and
|
|
3
|
+
// retrieve, value-list list and retrieve, and value-list-item retrieve are the derived core's.
|
|
4
|
+
|
|
5
|
+
export const VL = 'radar.value_list';
|
|
6
|
+
export const VLI = 'radar.value_list_item';
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// The published refund reasons; expired_uncaptured_charge is Stripe's own, not a request reason.
|
|
2
|
+
// source: spec:/paths/~1v1~1refunds/post/requestBody/content/application~1x-www-form-urlencoded/schema/properties/reason "String indicating the reason for the refund."
|
|
3
|
+
export const REFUND_REASONS = new Set(['duplicate', 'fraudulent', 'requested_by_customer']);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// A subscription's invoices over time: the first one Checkout pays, and each renewal. Time's moves are made
|
|
2
|
+
// as the twin's trial end already made them: caught up to the customer's clock (the World's, or its test
|
|
3
|
+
// clock) whenever the twin is asked anything (./front.ts), each checked against the machines in
|
|
4
|
+
// ../manifest.ts.
|
|
5
|
+
//
|
|
6
|
+
// - Checkout's subscription session pays the first invoice with the card the customer entered, which becomes the
|
|
7
|
+
// subscription's default payment method (docs.stripe.com/payments/checkout/how-checkout-works); nothing is due
|
|
8
|
+
// on a trial's.
|
|
9
|
+
// - At each period's end Stripe drafts a `subscription_cycle` invoice for the next period and advances the
|
|
10
|
+
// period; a trial's end is the first such renewal, and the subscription is active from then
|
|
11
|
+
// (docs.stripe.com/billing/subscriptions/overview#how-payments-work-subscriptions,
|
|
12
|
+
// docs.stripe.com/billing/subscriptions/trials).
|
|
13
|
+
// - About an hour after, Stripe finalizes the draft and charges the subscription's default payment method,
|
|
14
|
+
// else the customer's (docs.stripe.com/invoicing/integration/workflow-transitions#finalized,
|
|
15
|
+
// docs.stripe.com/api/subscriptions/object#subscription_object-default_payment_method).
|
|
16
|
+
// - A declined renewal leaves the invoice open with its attempt counted and the subscription past_due
|
|
17
|
+
// (docs.stripe.com/billing/subscriptions/overview#payment-status); paying the invoice later, through the API
|
|
18
|
+
// (docs.stripe.com/api/invoices/pay) or on the customer portal (docs.stripe.com/customer-management), makes it
|
|
19
|
+
// active again.
|
|
20
|
+
//
|
|
21
|
+
// Where the documentation stops and the twin decides: Stripe retries a failed renewal on its Smart Retries
|
|
22
|
+
// schedule and may then cancel or mark the subscription unpaid; the twin makes no retry (next_payment_attempt
|
|
23
|
+
// is null) and leaves it past_due until the invoice is paid. A paused subscription (pause_collection) is not renewed.
|
|
24
|
+
// - One set to cancel at its period's end is canceled when the period ends, with no renewal
|
|
25
|
+
// (docs.stripe.com/billing/subscriptions/cancel#cancel-at-end-of-cycle).
|
|
26
|
+
import { priceAmount } from './stripe.ts';
|
|
27
|
+
import type { Row } from './common.ts';
|
|
28
|
+
export const SUB = 'subscription';
|
|
29
|
+
export const INV = 'invoice';
|
|
30
|
+
export const HOUR = 3600;
|
|
31
|
+
/** The statuses a subscription renews in. */
|
|
32
|
+
export const RENEWING = ['trialing', 'active', 'past_due'];
|
|
33
|
+
|
|
34
|
+
export const itemsOf = (sub: Row): Row[] => ((sub.items as { data?: Row[] } | undefined)?.data) ?? [];
|
|
35
|
+
|
|
36
|
+
/** An item's quantity as it bills: 1 when none is set, and 0 when 0 is (a quantity of 0 bills nothing). */
|
|
37
|
+
export function itemQuantity(it: Row): number {
|
|
38
|
+
return it.quantity === undefined || it.quantity === null ? 1 : Math.max(0, Math.trunc(Number(it.quantity) || 0));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The subscription's discounts with a `once` coupon's spent: "When a subscription uses a coupon with `duration=once`, the
|
|
42
|
+
* coupon is considered used after the invoice finalizes and is removed from the subscription's `discounts` array"
|
|
43
|
+
* (docs.stripe.com/billing/subscriptions/coupons#coupon-duration). */
|
|
44
|
+
export function unspentDiscounts(discounts: unknown): Row[] {
|
|
45
|
+
return ((discounts as Row[] | undefined) ?? []).filter((d) => !(d && typeof d === 'object' && (d.coupon as Row | undefined)?.duration === 'once'));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The coupon ids an invoice's discounts name. */
|
|
49
|
+
export const couponsOf = (discounts: unknown): Set<string> => new Set(((discounts as Row[] | undefined) ?? []).map((d) => {
|
|
50
|
+
const c = d && typeof d === 'object' ? d.coupon : undefined;
|
|
51
|
+
return typeof c === 'string' ? c : c && typeof c === 'object' ? String((c as Row).id ?? '') : '';
|
|
52
|
+
}).filter(Boolean));
|
|
53
|
+
|
|
54
|
+
/** A subscription's discounts after `invoice` finalizes: less the `once` coupons it took. */
|
|
55
|
+
export function keptAfter(discounts: Row[], invoice: Row): Row[] {
|
|
56
|
+
const taken = couponsOf(invoice.discounts);
|
|
57
|
+
return discounts.filter((d) => !(unspentDiscounts([d]).length === 0 && [...couponsOf([d])].some((c) => taken.has(c))));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** A Checkout session's one-time line items as lines of its subscription's first invoice, numbered after its `after` lines. */
|
|
61
|
+
export function oneTimeLines(invoiceId: string, sub: Row, items: Row[], after: number, at: number, currency: string) {
|
|
62
|
+
return items.map((it, i) => {
|
|
63
|
+
const price = it.price && typeof it.price === 'object' ? (it.price as Row) : null;
|
|
64
|
+
const quantity = itemQuantity(it);
|
|
65
|
+
return {
|
|
66
|
+
id: `il_twin_${invoiceId}_${after + i + 1}`, object: 'line_item', type: 'subscription',
|
|
67
|
+
amount: priceAmount(price ?? undefined, quantity), currency, quantity, proration: false,
|
|
68
|
+
price, subscription: sub.id, subscription_item: null, invoice_item: null,
|
|
69
|
+
period: { start: at, end: at }, description: typeof it.description === 'string' ? it.description : null,
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|