@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,15 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
/** A coupon for a fixed amount off, in its currency (docs.stripe.com/api/coupons/create#create_coupon-amount_off). */
|
|
4
|
-
function amountOffOf(ctx, params) {
|
|
5
|
-
const amountOff = Number(params.amount_off);
|
|
6
|
-
if (!Number.isInteger(amountOff) || amountOff <= 0)
|
|
7
|
-
return fail(ctx, 'Invalid integer: amount_off must be a positive integer.', 400, 'parameter_invalid_integer');
|
|
8
|
-
if (params.currency === undefined || params.currency === '')
|
|
9
|
-
return fail(ctx, 'Missing required param: currency.', 400, 'parameter_missing');
|
|
10
|
-
return amountOff;
|
|
11
|
-
}
|
|
12
|
-
const createCoupon = async (ctx) => {
|
|
1
|
+
import { amountOffOf, created, fail } from "./shared.js";
|
|
2
|
+
export async function PostCoupons(ctx) {
|
|
13
3
|
const params = ctx.params;
|
|
14
4
|
const hasPct = params.percent_off !== undefined;
|
|
15
5
|
const hasAmt = params.amount_off !== undefined;
|
|
@@ -41,52 +31,4 @@ const createCoupon = async (ctx) => {
|
|
|
41
31
|
redeem_by: params.redeem_by !== undefined ? Number(params.redeem_by) : null,
|
|
42
32
|
applies_to: null,
|
|
43
33
|
}));
|
|
44
|
-
};
|
|
45
|
-
// a code the caller does not give is derived from the promotion code's id
|
|
46
|
-
const createPromotionCode = async (ctx) => {
|
|
47
|
-
// what it promotes: promotion[type]=coupon and promotion[coupon] (2025-09-30.clover,
|
|
48
|
-
// docs.stripe.com/changelog/clover/2025-09-30/polymorphic-coupon), or the top-level coupon a caller pinned to an
|
|
49
|
-
// earlier version sends; the twin keeps the coupon's id on the code for its rules
|
|
50
|
-
const { promotion, coupon: legacy, ...params } = ctx.params;
|
|
51
|
-
const promo = promotion && typeof promotion === 'object' ? promotion : undefined;
|
|
52
|
-
if (promo && promo.type !== 'coupon')
|
|
53
|
-
return fail(ctx, 'Invalid promotion[type]: must be coupon.', 400, 'parameter_invalid_string_enum');
|
|
54
|
-
const coupon = typeof promo?.coupon === 'string' ? promo.coupon : typeof legacy === 'string' ? legacy : '';
|
|
55
|
-
if (!coupon)
|
|
56
|
-
return fail(ctx, promo ? 'Missing required param: promotion[coupon].' : 'Missing required param: promotion.', 400, 'parameter_missing');
|
|
57
|
-
if (!ctx.get('coupon', coupon))
|
|
58
|
-
return fail(ctx, `No such coupon: '${coupon}'`, 400, 'resource_missing');
|
|
59
|
-
const id = ctx.mint('promotion_code');
|
|
60
|
-
const code = typeof params.code === 'string' && params.code ? params.code : `TWIN${id.toUpperCase().replace(/[^A-Z0-9]/g, '')}`;
|
|
61
|
-
return ctx.reply(await created(ctx, 'promotion_code', { ...params, coupon, promotion: { type: 'coupon', coupon }, id, code }, {
|
|
62
|
-
active: params.active !== undefined ? asBool(params.active) : true,
|
|
63
|
-
customer: typeof params.customer === 'string' ? params.customer : null,
|
|
64
|
-
expires_at: params.expires_at !== undefined ? Number(params.expires_at) : null,
|
|
65
|
-
max_redemptions: params.max_redemptions !== undefined ? Number(params.max_redemptions) : null,
|
|
66
|
-
times_redeemed: 0, livemode: false, metadata: {}, restrictions: { first_time_transaction: false, minimum_amount: null, minimum_amount_currency: null },
|
|
67
|
-
}));
|
|
68
|
-
};
|
|
69
|
-
/** Time's lapsing of coupons, caught up to the World's clock: a valid coupon whose redeem_by has passed is no longer
|
|
70
|
-
* valid from that moment (manifest.ts). Where the documentation stops and the twin decides: the lapse sends no event
|
|
71
|
-
* (the events page names none for it). */
|
|
72
|
-
export async function lapseCoupons(ctx) {
|
|
73
|
-
const now = Number(ctx.now());
|
|
74
|
-
for (const c of ctx.rowsRaw('coupon')) {
|
|
75
|
-
if (c.valid !== true || typeof c.redeem_by !== 'number' || c.redeem_by > now)
|
|
76
|
-
continue;
|
|
77
|
-
const t = await at_(ctx)(c.redeem_by);
|
|
78
|
-
t.legal('coupon', 'valid', ctx.call.operation.id, 'true', 'false', String(c.id), 'time');
|
|
79
|
-
await t.write('coupon', String(c.id), { valid: false }, 'coupon.lapsed');
|
|
80
|
-
}
|
|
81
34
|
}
|
|
82
|
-
const listPromotionCodes = async (ctx) => list(ctx, 'promotion_code', where(ctx, newest(ctx, 'promotion_code'), {
|
|
83
|
-
code: (p, v) => p.code === v,
|
|
84
|
-
active: (p, v) => asBool(p.active) === asBool(v),
|
|
85
|
-
coupon: (p, v) => p.coupon === v,
|
|
86
|
-
customer: (p, v) => p.customer === v,
|
|
87
|
-
}));
|
|
88
|
-
export const coupons = {
|
|
89
|
-
PostCoupons: createCoupon,
|
|
90
|
-
PostPromotionCodes: createPromotionCode,
|
|
91
|
-
GetPromotionCodes: listPromotionCodes,
|
|
92
|
-
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export declare
|
|
6
|
-
export declare const customers: Record<string, Semantics>;
|
|
1
|
+
import type { HandlerContext } from '@volter/world-core';
|
|
2
|
+
export declare function GetCustomersCustomer(ctx: HandlerContext): Promise<Response>;
|
|
3
|
+
export declare function GetCustomersSearch(ctx: HandlerContext): Promise<Response>;
|
|
4
|
+
export declare function PostCustomers(ctx: HandlerContext): Promise<Response>;
|
|
5
|
+
export declare function PostCustomersCustomer(ctx: HandlerContext): Promise<Response>;
|
|
@@ -1,429 +1,63 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { newCustomer } from "../engine/customers.js";
|
|
2
|
+
import { nowUnix } from "../engine/stripe.js";
|
|
3
|
+
import { at, attachable, attachPaymentMethod, created, customerMissing, expanded, fail, search } from "./shared.js";
|
|
4
|
+
// "If it’s for a deleted Customer, a subset of the customer’s information is returned, including a `deleted` property
|
|
5
|
+
// that’s set to true" (docs.stripe.com/api/customers/retrieve)
|
|
6
|
+
export async function GetCustomersCustomer(ctx) {
|
|
7
|
+
const id = at(ctx, 'customer');
|
|
8
|
+
const row = ctx.row('customer', id, { withDeleted: true });
|
|
9
|
+
if (!row)
|
|
10
|
+
return customerMissing(ctx, id);
|
|
11
|
+
if (row.deleted === true)
|
|
12
|
+
return ctx.reply({ id, object: 'customer', deleted: true });
|
|
13
|
+
return ctx.reply(expanded(ctx, 'customer', ctx.get('customer', id)));
|
|
14
|
+
}
|
|
15
|
+
export async function GetCustomersSearch(ctx) {
|
|
16
|
+
return search(ctx, 'customer');
|
|
11
17
|
}
|
|
12
|
-
|
|
13
|
-
* no metadata (docs.stripe.com/api/customers/object). */
|
|
14
|
-
export const newCustomer = (id) => ({ invoice_prefix: invoicePrefix(id), next_invoice_sequence: 1, balance: 0, metadata: {}, invoice_settings: {} });
|
|
15
|
-
const create = async (ctx) => {
|
|
16
|
-
// a `coupon` on create materializes the discount object on the customer (the coupon must exist)
|
|
17
|
-
const couponId = typeof ctx.params.coupon === 'string' ? ctx.params.coupon : '';
|
|
18
|
-
const discount = couponId ? customerDiscount(ctx, couponId) : null;
|
|
19
|
-
if (discount instanceof Response)
|
|
20
|
-
return discount;
|
|
18
|
+
export async function PostCustomers(ctx) {
|
|
21
19
|
// `payment_method` attaches that method to the new customer; it is not a field of the customer
|
|
22
20
|
// (docs.stripe.com/api/customers/create#create_customer-payment_method)
|
|
23
21
|
const pmId = typeof ctx.params.payment_method === 'string' ? ctx.params.payment_method : '';
|
|
24
22
|
if (pmId && !attachable(ctx, pmId))
|
|
25
23
|
return fail(ctx, `No such PaymentMethod: '${pmId}'`, 400, 'resource_missing');
|
|
26
|
-
const
|
|
24
|
+
const given = ctx.params;
|
|
25
|
+
const { coupon: _c, payment_method: _pm, ...rest } = given;
|
|
27
26
|
const id = ctx.mint('customer');
|
|
28
|
-
const customer = await created(ctx, 'customer', { ...rest, id }, { livemode: false, discount:
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
const customer = await created(ctx, 'customer', { ...rest, id }, { livemode: false, discount: null, ...newCustomer(id) });
|
|
28
|
+
// source: https://docs.stripe.com/billing/customer/balance "Represents the starting value of the customer invoice balance when a customer is created using the API with a non-zero invoice balance."
|
|
29
|
+
if (Number(customer.balance ?? 0) !== 0) {
|
|
30
|
+
await created(ctx, 'customer_balance_transaction', {
|
|
31
|
+
customer: id, amount: Number(customer.balance), ending_balance: Number(customer.balance),
|
|
32
|
+
currency: customer.currency ?? 'usd', type: 'initial', description: null, metadata: {},
|
|
33
|
+
livemode: false, invoice: null, credit_note: null, checkout_session: null, customer_account: null,
|
|
34
|
+
}, {});
|
|
35
|
+
}
|
|
31
36
|
if (pmId)
|
|
32
37
|
await attachPaymentMethod(ctx, pmId, String(customer.id));
|
|
33
38
|
return ctx.reply(customer);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
const id = at(ctx, 'customer');
|
|
40
|
-
const row = ctx.row('customer', id, { withDeleted: true });
|
|
41
|
-
if (!row)
|
|
42
|
-
return customerMissing(ctx, id);
|
|
43
|
-
if (row.deleted === true)
|
|
44
|
-
return ctx.reply({ id, object: 'customer', deleted: true });
|
|
45
|
-
return ctx.reply(expanded(ctx, 'customer', ctx.get('customer', id)));
|
|
46
|
-
};
|
|
47
|
-
// the payment methods attached to a customer (customers.listPaymentMethods), optionally by `type`
|
|
48
|
-
const paymentMethods = async (ctx) => {
|
|
49
|
-
const customer = at(ctx, 'customer');
|
|
50
|
-
if (!ctx.get('customer', customer))
|
|
51
|
-
return customerMissing(ctx, customer);
|
|
52
|
-
const items = where(ctx, newest(ctx, 'payment_method').filter((m) => m.customer === customer), { type: (m, v) => m.type === v });
|
|
53
|
-
return list(ctx, 'payment_method', items);
|
|
54
|
-
};
|
|
55
|
-
const paymentMethod = async (ctx) => {
|
|
56
|
-
const customer = at(ctx, 'customer');
|
|
57
|
-
if (!ctx.get('customer', customer))
|
|
58
|
-
return customerMissing(ctx, customer);
|
|
59
|
-
const m = ctx.get('payment_method', at(ctx, 'payment_method'));
|
|
60
|
-
return m && m.customer === customer ? ctx.reply(m) : fail(ctx, `No such PaymentMethod: '${at(ctx, 'payment_method')}'`, 404, 'resource_missing');
|
|
61
|
-
};
|
|
62
|
-
// removing the customer's discount answers the deleted-discount stub
|
|
63
|
-
const deleteDiscount = async (ctx) => {
|
|
39
|
+
}
|
|
40
|
+
// ── the credit-balance ledger: a negative amount is a credit; `ending_balance` is the running sum,
|
|
41
|
+
// mirrored onto the customer's `balance` ──
|
|
42
|
+
// source: spec:PostCustomersCustomer "Updates the specified customer by setting the values of the parameters passed."
|
|
43
|
+
export async function PostCustomersCustomer(ctx) {
|
|
64
44
|
const id = at(ctx, 'customer');
|
|
65
45
|
const customer = ctx.get('customer', id);
|
|
66
46
|
if (!customer)
|
|
67
47
|
return customerMissing(ctx, id);
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
HU: /^\d{8}$/, IE: /^(\d{7}[A-W][A-I]?|\d[A-Z+*]\d{5}[A-W])$/, IT: /^\d{11}$/, LT: /^(\d{9}|\d{12})$/, LU: /^\d{8}$/,
|
|
82
|
-
LV: /^\d{11}$/, MT: /^\d{8}$/, NL: /^\d{9}B\d{2}$/, PL: /^\d{10}$/, PT: /^\d{9}$/, RO: /^\d{2,10}$/, SE: /^\d{12}$/,
|
|
83
|
-
SI: /^\d{8}$/, SK: /^\d{10}$/, XI: /^(\d{9}|\d{12}|GD\d{3}|HA\d{3})$/,
|
|
84
|
-
};
|
|
85
|
-
const euVatCountry = (value) => {
|
|
86
|
-
const v = value.replace(/[\s.-]/g, '').toUpperCase();
|
|
87
|
-
const prefix = v.slice(0, 2);
|
|
88
|
-
const format = EU_VAT[prefix];
|
|
89
|
-
return format && format.test(v.slice(2)) ? (prefix === 'EL' ? 'GR' : prefix) : undefined;
|
|
90
|
-
};
|
|
91
|
-
/** The tax ID types Stripe checks against a government register: "Stripe automatically validates all Australian
|
|
92
|
-
* Business Numbers (ABNs)", "all European Value-Added-Tax (EU VAT) numbers" (VIES) and "all UK Value-Added-Tax (GB VAT)
|
|
93
|
-
* numbers" (HMRC); a verification's status "is pending, verified, unverified, or unavailable", and "When automatic
|
|
94
|
-
* validation is unavailable, you must manually verify the tax ID" (docs.stripe.com/billing/customer/tax-ids). Any other
|
|
95
|
-
* type answers unavailable; one of these waits pending. */
|
|
96
|
-
const AUTO_VALIDATED = new Set(['au_abn', 'eu_vat', 'gb_vat']);
|
|
97
|
-
/** A tax ID for its owner: the account itself, or one of its customers. */
|
|
98
|
-
async function makeTaxId(ctx, customer) {
|
|
99
|
-
const { owner: _owner, ...params } = ctx.params;
|
|
100
|
-
if (params.type === undefined || params.type === '')
|
|
101
|
-
return fail(ctx, 'Missing required param: type.', 400, 'parameter_missing');
|
|
102
|
-
if (params.value === undefined || params.value === '')
|
|
103
|
-
return fail(ctx, 'Missing required param: value.', 400, 'parameter_missing');
|
|
104
|
-
const vatCountry = params.type === 'eu_vat' ? euVatCountry(String(params.value)) : undefined;
|
|
105
|
-
if (params.type === 'eu_vat' && !vatCountry)
|
|
106
|
-
return ctx.refuse({ status: 400, code: 'tax_id_invalid', param: 'value', message: `Invalid value for eu_vat.` });
|
|
107
|
-
return ctx.reply(await created(ctx, 'tax_id', { ...params, ...(customer ? { customer } : {}) }, {
|
|
108
|
-
livemode: false,
|
|
109
|
-
country: vatCountry ?? (typeof params.country === 'string' ? params.country : null),
|
|
110
|
-
owner: customer ? { type: 'customer', customer } : { type: 'self' },
|
|
111
|
-
verification: { status: AUTO_VALIDATED.has(String(params.type)) ? 'pending' : 'unavailable', verified_address: null, verified_name: null },
|
|
112
|
-
}));
|
|
113
|
-
}
|
|
114
|
-
const createTaxId = async (ctx) => {
|
|
115
|
-
const customer = at(ctx, 'customer');
|
|
116
|
-
if (!ctx.get('customer', customer))
|
|
117
|
-
return customerMissing(ctx, customer);
|
|
118
|
-
return makeTaxId(ctx, customer);
|
|
119
|
-
};
|
|
120
|
-
/** The owner a top-level tax-ID call names: the account itself unless `owner[type]=customer` names a customer
|
|
121
|
-
* (docs.stripe.com/api/tax_ids/list: owner defaults to self). */
|
|
122
|
-
function ownerOf(ctx) {
|
|
123
|
-
const owner = (ctx.params.owner ?? {});
|
|
124
|
-
const type = String(owner.type ?? 'self');
|
|
125
|
-
if (type === 'self')
|
|
126
|
-
return {};
|
|
127
|
-
if (type !== 'customer')
|
|
128
|
-
return fail(ctx, `Invalid owner[type]: must be one of self or customer.`, 400, 'parameter_invalid_string_enum');
|
|
129
|
-
const customer = typeof owner.customer === 'string' ? owner.customer : '';
|
|
130
|
-
if (!customer)
|
|
131
|
-
return fail(ctx, 'Missing required param: owner[customer].', 400, 'parameter_missing');
|
|
132
|
-
if (!ctx.get('customer', customer))
|
|
133
|
-
return customerMissing(ctx, customer);
|
|
134
|
-
return { customer };
|
|
135
|
-
}
|
|
136
|
-
const createOwnedTaxId = async (ctx) => {
|
|
137
|
-
const owner = ownerOf(ctx);
|
|
138
|
-
return owner instanceof Response ? owner : makeTaxId(ctx, owner.customer);
|
|
139
|
-
};
|
|
140
|
-
const listOwnedTaxIds = async (ctx) => {
|
|
141
|
-
const owner = ownerOf(ctx);
|
|
142
|
-
if (owner instanceof Response)
|
|
143
|
-
return owner;
|
|
144
|
-
return list(ctx, 'tax_id', newest(ctx, 'tax_id').filter((t) => (owner.customer ? t.customer === owner.customer : !t.customer)));
|
|
145
|
-
};
|
|
146
|
-
const taxIds = async (ctx) => {
|
|
147
|
-
const customer = at(ctx, 'customer');
|
|
148
|
-
if (!ctx.get('customer', customer))
|
|
149
|
-
return customerMissing(ctx, customer);
|
|
150
|
-
return list(ctx, 'tax_id', newest(ctx, 'tax_id').filter((t) => t.customer === customer));
|
|
151
|
-
};
|
|
152
|
-
const taxId = async (ctx) => {
|
|
153
|
-
const t = ctx.get('tax_id', at(ctx, 'id'));
|
|
154
|
-
return t && t.customer === at(ctx, 'customer') ? ctx.reply(t) : fail(ctx, `No such tax_id: '${at(ctx, 'id')}'`, 404, 'resource_missing');
|
|
155
|
-
};
|
|
156
|
-
const deleteTaxId = async (ctx) => {
|
|
157
|
-
const id = at(ctx, 'id');
|
|
158
|
-
const t = ctx.get('tax_id', id);
|
|
159
|
-
if (!t || t.customer !== at(ctx, 'customer'))
|
|
160
|
-
return fail(ctx, `No such tax_id: '${id}'`, 404, 'resource_missing');
|
|
161
|
-
await ctx.write('tax_id', id, { deleted: true }, 'tax_id.delete');
|
|
162
|
-
return ctx.reply({ id, object: 'tax_id', deleted: true });
|
|
163
|
-
};
|
|
164
|
-
// ── the credit-balance ledger: a negative amount is a credit; `ending_balance` is the running sum,
|
|
165
|
-
// mirrored onto the customer's `balance` ──
|
|
166
|
-
const createBalanceTransaction = async (ctx) => {
|
|
167
|
-
const customer = at(ctx, 'customer');
|
|
168
|
-
if (!ctx.get('customer', customer))
|
|
169
|
-
return customerMissing(ctx, customer);
|
|
170
|
-
const params = ctx.params;
|
|
171
|
-
if (params.amount === undefined)
|
|
172
|
-
return fail(ctx, 'Missing required param: amount.', 400, 'parameter_missing');
|
|
173
|
-
const amount = Number(params.amount);
|
|
174
|
-
if (!Number.isInteger(amount))
|
|
175
|
-
return fail(ctx, 'Invalid integer: amount.', 400, 'parameter_invalid_integer');
|
|
176
|
-
if (params.currency === undefined || params.currency === '')
|
|
177
|
-
return fail(ctx, 'Missing required param: currency.', 400, 'parameter_missing');
|
|
178
|
-
const prior = ctx.rows('customer_balance_transaction').filter((t) => t.customer === customer).reduce((s, t) => s + (Number(t.amount) || 0), 0);
|
|
179
|
-
const ending = prior + amount;
|
|
180
|
-
const body = await created(ctx, 'customer_balance_transaction', { ...params, customer, amount }, {
|
|
181
|
-
ending_balance: ending, type: 'adjustment', livemode: false, metadata: {},
|
|
182
|
-
description: params.description ?? null, credit_note: null, invoice: null, checkout_session: null,
|
|
183
|
-
});
|
|
184
|
-
// keyed by the new transaction so two adjustments at one instant are two writes, the last winning
|
|
185
|
-
await ctx.write('customer', customer, { balance: ending }, `customer.balance_adjusted:${String(body.id)}`);
|
|
186
|
-
return ctx.reply(body);
|
|
187
|
-
};
|
|
188
|
-
const balanceTransactions = async (ctx) => {
|
|
189
|
-
const customer = at(ctx, 'customer');
|
|
190
|
-
if (!ctx.get('customer', customer))
|
|
191
|
-
return customerMissing(ctx, customer);
|
|
192
|
-
return list(ctx, 'customer_balance_transaction', newest(ctx, 'customer_balance_transaction').filter((t) => t.customer === customer));
|
|
193
|
-
};
|
|
194
|
-
const balanceTransaction = async (ctx) => {
|
|
195
|
-
const t = ctx.get('customer_balance_transaction', at(ctx, 'transaction'));
|
|
196
|
-
return t && t.customer === at(ctx, 'customer') ? ctx.reply(t) : fail(ctx, `No such balance transaction: '${at(ctx, 'transaction')}'`, 404, 'resource_missing');
|
|
197
|
-
};
|
|
198
|
-
// ── the cash balance: per-currency sums of the funding ledger, and the reconciliation setting ──
|
|
199
|
-
function cashBalance(ctx, customer) {
|
|
200
|
-
const byCurrency = {};
|
|
201
|
-
for (const t of ctx.rows('customer_cash_balance_transaction')) {
|
|
202
|
-
if (t.customer !== customer)
|
|
203
|
-
continue;
|
|
204
|
-
const cur = String(t.currency ?? 'usd');
|
|
205
|
-
byCurrency[cur] = (byCurrency[cur] ?? 0) + (Number(t.net_amount) || 0);
|
|
206
|
-
}
|
|
207
|
-
const cust = ctx.row('customer', customer);
|
|
208
|
-
const mode = typeof cust?._cash_balance_mode === 'string' ? cust._cash_balance_mode : 'automatic';
|
|
209
|
-
return {
|
|
210
|
-
object: 'cash_balance', customer,
|
|
211
|
-
available: Object.keys(byCurrency).length ? byCurrency : null,
|
|
212
|
-
settings: { reconciliation_mode: mode, using_merchant_default: mode === 'merchant_default' },
|
|
213
|
-
livemode: false,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
const getCashBalance = async (ctx) => {
|
|
217
|
-
const customer = at(ctx, 'customer');
|
|
218
|
-
if (!ctx.get('customer', customer))
|
|
219
|
-
return customerMissing(ctx, customer);
|
|
220
|
-
return ctx.reply(cashBalance(ctx, customer));
|
|
221
|
-
};
|
|
222
|
-
const updateCashBalance = async (ctx) => {
|
|
223
|
-
const customer = at(ctx, 'customer');
|
|
224
|
-
if (!ctx.get('customer', customer))
|
|
225
|
-
return customerMissing(ctx, customer);
|
|
226
|
-
const settings = ctx.params.settings && typeof ctx.params.settings === 'object' ? ctx.params.settings : {};
|
|
227
|
-
const mode = typeof settings.reconciliation_mode === 'string' ? settings.reconciliation_mode : 'automatic';
|
|
228
|
-
if (!['automatic', 'manual', 'merchant_default'].includes(mode))
|
|
229
|
-
return fail(ctx, 'Invalid settings[reconciliation_mode].', 400, 'parameter_invalid_string_enum');
|
|
230
|
-
await ctx.write('customer', customer, { _cash_balance_mode: mode }, 'cash_balance.updated');
|
|
231
|
-
return ctx.reply(cashBalance(ctx, customer));
|
|
232
|
-
};
|
|
233
|
-
const cashBalanceTransactions = async (ctx) => {
|
|
234
|
-
const customer = at(ctx, 'customer');
|
|
235
|
-
if (!ctx.get('customer', customer))
|
|
236
|
-
return customerMissing(ctx, customer);
|
|
237
|
-
return list(ctx, 'customer_cash_balance_transaction', newest(ctx, 'customer_cash_balance_transaction').filter((t) => t.customer === customer));
|
|
238
|
-
};
|
|
239
|
-
/** Automatic reconciliation (docs.stripe.com/payments/customer-balance/reconciliation): funds that arrive pay the
|
|
240
|
-
* customer's open invoices that take bank transfers (payment_method_types customer_balance), the one whose number
|
|
241
|
-
* the transfer's reference names first, then the oldest. Where the documentation stops and the twin decides: an
|
|
242
|
-
* invoice is paid only when the balance covers all of what remains; a smaller balance waits for more funds. */
|
|
243
|
-
async function reconcile(ctx, customer, currency, reference) {
|
|
244
|
-
const takesTransfers = (inv) => inv.payment_settings?.payment_method_types?.includes('customer_balance') === true;
|
|
245
|
-
const open = ctx.rows('invoice').filter((inv) => inv.customer === customer && inv.status === 'open' && String(inv.currency ?? 'usd') === currency && takesTransfers(inv))
|
|
246
|
-
.sort((a, b) => (reference && a.number === reference ? -1 : reference && b.number === reference ? 1 : Number(a.created) - Number(b.created)));
|
|
247
|
-
for (const inv of open) {
|
|
248
|
-
const available = Number((cashBalance(ctx, customer).available ?? {})[currency] ?? 0);
|
|
249
|
-
const amount = Number(inv.amount_remaining ?? inv.amount_due) || 0;
|
|
250
|
-
if (amount <= 0 || available < amount)
|
|
251
|
-
continue;
|
|
252
|
-
const id = String(inv.id);
|
|
253
|
-
if (ctx.legal('invoice', 'status', 'PostTestHelpersCustomersCustomerFundCashBalance', inv.status, 'paid', id))
|
|
254
|
-
continue;
|
|
255
|
-
// the payment the balance makes: the invoice's own PaymentIntent (every invoice finalized with an amount due has one,
|
|
256
|
-
// semantics/invoices.ts), paid from the cash balance, its charge, and the ledger's entry
|
|
257
|
-
const piId = String(inv.payment_intent);
|
|
258
|
-
await created(ctx, 'customer_cash_balance_transaction', { customer }, { currency, type: 'applied_to_payment', net_amount: -amount, ending_balance: available - amount, applied_to_payment: { payment_intent: piId }, livemode: false });
|
|
259
|
-
const charge = await mintCharge(ctx, piId, { currency, customer, invoice: id }, amount);
|
|
260
|
-
await ctx.write('payment_intent', piId, { status: 'succeeded', amount_received: amount, latest_charge: charge }, 'payment_intent.succeeded');
|
|
261
|
-
await ctx.write('invoice', id, {
|
|
262
|
-
status: 'paid', paid: true, amount_paid: amount, amount_remaining: 0, payment_intent: piId, charge,
|
|
263
|
-
status_transitions: { ...(inv.status_transitions ?? {}), paid_at: ctx.now() },
|
|
264
|
-
}, 'invoice.paid');
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
// test mode's stand-in for a bank transfer arriving (docs.stripe.com/payments/customer-balance/funding#test):
|
|
268
|
-
// the customer's cash balance is funded by `amount`, and in automatic reconciliation the funds pay what waits on them
|
|
269
|
-
const fundCashBalance = async (ctx) => {
|
|
270
|
-
const customer = at(ctx, 'customer');
|
|
271
|
-
if (!ctx.get('customer', customer))
|
|
272
|
-
return customerMissing(ctx, customer);
|
|
273
|
-
const amount = Math.trunc(Number(ctx.params.amount) || 0);
|
|
274
|
-
if (amount <= 0)
|
|
275
|
-
return fail(ctx, 'Missing required param: amount.', 400, 'parameter_missing');
|
|
276
|
-
const currency = typeof ctx.params.currency === 'string' ? ctx.params.currency : '';
|
|
277
|
-
if (!currency)
|
|
278
|
-
return fail(ctx, 'Missing required param: currency.', 400, 'parameter_missing');
|
|
279
|
-
const before = Number((cashBalance(ctx, customer).available ?? {})[currency] ?? 0);
|
|
280
|
-
const reference = typeof ctx.params.reference === 'string' ? ctx.params.reference : null;
|
|
281
|
-
const funded = await created(ctx, 'customer_cash_balance_transaction', { customer }, {
|
|
282
|
-
currency, type: 'funded', net_amount: amount, ending_balance: before + amount, livemode: false,
|
|
283
|
-
funded: { bank_transfer: { reference, type: 'us_bank_transfer', us_bank_transfer: { network: 'ach', sender_name: null } } },
|
|
284
|
-
});
|
|
285
|
-
if (cashBalance(ctx, customer).settings.reconciliation_mode !== 'manual')
|
|
286
|
-
await reconcile(ctx, customer, currency, reference);
|
|
287
|
-
return ctx.reply(funded);
|
|
288
|
-
};
|
|
289
|
-
const cashBalanceTransaction = async (ctx) => {
|
|
290
|
-
const t = ctx.get('customer_cash_balance_transaction', at(ctx, 'transaction'));
|
|
291
|
-
return t && t.customer === at(ctx, 'customer') ? ctx.reply(t) : fail(ctx, `No such cash balance transaction: '${at(ctx, 'transaction')}'`, 404, 'resource_missing');
|
|
292
|
-
};
|
|
293
|
-
// ── legacy sources: attaching a token mints a card with only the safe fields; the first becomes
|
|
294
|
-
// the customer's default_source, and detaching the default promotes the next ──
|
|
295
|
-
const attachSource = async (ctx) => {
|
|
296
|
-
const customer = at(ctx, 'customer');
|
|
297
|
-
const c = ctx.get('customer', customer);
|
|
298
|
-
if (!c)
|
|
299
|
-
return customerMissing(ctx, customer);
|
|
300
|
-
const src = typeof ctx.params.source === 'string' ? ctx.params.source : '';
|
|
301
|
-
if (!src)
|
|
302
|
-
return fail(ctx, 'Missing required param: source.', 400, 'parameter_missing');
|
|
303
|
-
// a card token becomes the card it carries, the token spent: "you can only use tokens once" (the served spec's
|
|
304
|
-
// token.used); any other source reference keeps the twin's stand-in card
|
|
305
|
-
const token = src.startsWith('tok_') ? ctx.get('token', src) : undefined;
|
|
306
|
-
if (token?.used === true)
|
|
307
|
-
return fail(ctx, `You cannot use a Stripe token more than once: ${src}.`, 400, 'token_already_used');
|
|
308
|
-
const tokenCard = token?.card && typeof token.card === 'object' ? token.card : undefined;
|
|
309
|
-
const last4 = String(4242 + ctx.rows('source').length + 1).slice(-4);
|
|
310
|
-
const body = await created(ctx, 'source', { customer, ...(tokenCard ? { id: String(tokenCard.id) } : {}) }, tokenCard
|
|
311
|
-
? { ...tokenCard, customer, _source_token: src }
|
|
312
|
-
: { object: 'card', last4, brand: 'Visa', exp_month: 12, exp_year: 2030, funding: 'credit', country: 'US', metadata: {}, _source_token: src });
|
|
313
|
-
if (token)
|
|
314
|
-
await ctx.write('token', src, { used: true }, 'token.used');
|
|
315
|
-
const existing = ctx.rows('source').filter((s) => s.customer === customer);
|
|
316
|
-
if (!c.default_source || existing.length === 1)
|
|
317
|
-
await ctx.write('customer', customer, { default_source: body.id }, 'customer.updated');
|
|
318
|
-
return ctx.reply(body);
|
|
319
|
-
};
|
|
320
|
-
const sources = async (ctx) => {
|
|
321
|
-
const customer = at(ctx, 'customer');
|
|
322
|
-
if (!ctx.get('customer', customer))
|
|
323
|
-
return customerMissing(ctx, customer);
|
|
324
|
-
const items = newest(ctx, 'source').filter((s) => s.customer === customer);
|
|
325
|
-
return list(ctx, 'source', typeof ctx.params.object === 'string' ? items.filter((s) => s.object === ctx.params.object) : items);
|
|
326
|
-
};
|
|
327
|
-
/** The customer's source the path names, or undefined. */
|
|
328
|
-
const customerSource = (ctx) => {
|
|
329
|
-
const s = ctx.get('source', at(ctx, 'id'));
|
|
330
|
-
return s && s.customer === at(ctx, 'customer') ? s : undefined;
|
|
331
|
-
};
|
|
332
|
-
const sourceMissing = (ctx) => fail(ctx, `No such source: '${at(ctx, 'id')}'`, 404, 'resource_missing');
|
|
333
|
-
const source = async (ctx) => {
|
|
334
|
-
const s = customerSource(ctx);
|
|
335
|
-
return s ? ctx.reply(s) : sourceMissing(ctx);
|
|
336
|
-
};
|
|
337
|
-
const updateSource = async (ctx) => {
|
|
338
|
-
if (!customerSource(ctx))
|
|
339
|
-
return sourceMissing(ctx);
|
|
340
|
-
return ctx.reply(await ctx.write('source', at(ctx, 'id'), ctx.params, 'source.updated'));
|
|
341
|
-
};
|
|
342
|
-
const detachSource = async (ctx) => {
|
|
343
|
-
const customer = at(ctx, 'customer');
|
|
344
|
-
const id = at(ctx, 'id');
|
|
345
|
-
const c = ctx.get('customer', customer);
|
|
346
|
-
const s = customerSource(ctx);
|
|
347
|
-
if (!c || !s)
|
|
348
|
-
return sourceMissing(ctx);
|
|
349
|
-
await ctx.write('source', id, { deleted: true, customer: null }, 'source.detached');
|
|
350
|
-
if (c.default_source === id) {
|
|
351
|
-
const remaining = ctx.rows('source').filter((x) => x.customer === customer && x.id !== id);
|
|
352
|
-
await ctx.write('customer', customer, { default_source: remaining[0]?.id ?? null }, 'customer.updated');
|
|
48
|
+
const { customer: _pathCustomer, ...fields } = ctx.params;
|
|
49
|
+
// source: https://docs.stripe.com/billing/customer/balance "All modifications to the invoice balance are recorded as Transactions"
|
|
50
|
+
// source: https://docs.stripe.com/billing/customer/balance "All Transactions created with the API or in the Dashboard have a type value of adjustment"
|
|
51
|
+
if (typeof fields.balance === 'number' && fields.balance !== Number(customer.balance ?? 0)) {
|
|
52
|
+
const balance = fields.balance;
|
|
53
|
+
await created(ctx, 'customer_balance_transaction', {
|
|
54
|
+
customer: id, amount: balance - Number(customer.balance ?? 0), ending_balance: balance,
|
|
55
|
+
// Stripe inherits the customer's billing currency. Where no invoice has set it, this World's invoice currency is USD.
|
|
56
|
+
currency: customer.currency ?? 'usd', type: 'adjustment',
|
|
57
|
+
description: null, metadata: {}, livemode: false,
|
|
58
|
+
invoice: null, credit_note: null, checkout_session: null, customer_account: null,
|
|
59
|
+
created: nowUnix(ctx.occurredAt),
|
|
60
|
+
}, {});
|
|
353
61
|
}
|
|
354
|
-
return ctx.reply(
|
|
355
|
-
};
|
|
356
|
-
// ── the top-level Source: `type` is required; a card is chargeable at once, anything else pending ──
|
|
357
|
-
const createSource = async (ctx) => {
|
|
358
|
-
const params = ctx.params;
|
|
359
|
-
const srcType = typeof params.type === 'string' ? params.type : '';
|
|
360
|
-
if (!srcType)
|
|
361
|
-
return fail(ctx, 'Missing required param: type.', 400, 'parameter_missing');
|
|
362
|
-
return ctx.reply(await created(ctx, 'source', { customer: null }, {
|
|
363
|
-
object: 'source',
|
|
364
|
-
status: srcType === 'card' ? 'chargeable' : 'pending',
|
|
365
|
-
flow: srcType === 'card' ? 'none' : 'receiver',
|
|
366
|
-
usage: params.usage ?? 'reusable',
|
|
367
|
-
currency: typeof params.currency === 'string' ? params.currency : null,
|
|
368
|
-
livemode: false,
|
|
369
|
-
type: srcType,
|
|
370
|
-
client_secret: `src_client_secret_twin_${ctx.rows('source').length + 1}`,
|
|
371
|
-
owner: params.owner && typeof params.owner === 'object' ? params.owner : { name: null, email: null, address: null, phone: null, verified_name: null, verified_email: null, verified_address: null, verified_phone: null },
|
|
372
|
-
metadata: params.metadata && typeof params.metadata === 'object' ? params.metadata : {},
|
|
373
|
-
statement_descriptor: null,
|
|
374
|
-
...(srcType === 'ach_credit_transfer' ? achCreditTransfer(ctx) : {}),
|
|
375
|
-
}));
|
|
376
|
-
};
|
|
377
|
-
/** An ACH credit transfer source's account for the customer to wire to, and its receiver flow, as the sources create
|
|
378
|
-
* page's example answers one (docs.stripe.com/api/sources/create): a test account number, "TEST BANK", routing
|
|
379
|
-
* 110000000 and SWIFT TSTEZ122; the receiver's address "routing-account" with nothing yet charged, received or
|
|
380
|
-
* returned, refund attributes by email and missing. Where the documentation stops and the twin decides: the account
|
|
381
|
-
* number is the source's own. */
|
|
382
|
-
function achCreditTransfer(ctx) {
|
|
383
|
-
const account = `test_${String(ctx.rows('source').length + 1).padStart(12, '0')}`;
|
|
384
|
-
return {
|
|
385
|
-
ach_credit_transfer: { account_number: account, bank_name: 'TEST BANK', fingerprint: `twin_acct_${account}`, refund_account_holder_name: null, refund_account_holder_type: null, refund_routing_number: null, routing_number: '110000000', swift_code: 'TSTEZ122' },
|
|
386
|
-
receiver: { address: `110000000-${account}`, amount_charged: 0, amount_received: 0, amount_returned: 0, refund_attributes_method: 'email', refund_attributes_status: 'missing' },
|
|
387
|
-
};
|
|
62
|
+
return ctx.reply(ctx.expand('customer', await ctx.write('customer', id, fields, 'customer.update')));
|
|
388
63
|
}
|
|
389
|
-
// a Source update changes only its metadata and owner
|
|
390
|
-
const updateTopSource = async (ctx) => {
|
|
391
|
-
const id = at(ctx, 'source');
|
|
392
|
-
if (!ctx.get('source', id))
|
|
393
|
-
return fail(ctx, `No such source: '${id}'`, 404, 'resource_missing');
|
|
394
|
-
const patch = {};
|
|
395
|
-
if (ctx.params.metadata !== undefined)
|
|
396
|
-
patch.metadata = ctx.params.metadata;
|
|
397
|
-
if (ctx.params.owner !== undefined)
|
|
398
|
-
patch.owner = ctx.params.owner;
|
|
399
|
-
return ctx.reply(await ctx.write('source', id, patch, 'source.updated'));
|
|
400
|
-
};
|
|
401
|
-
export const customers = {
|
|
402
|
-
PostCustomers: create,
|
|
403
|
-
GetCustomersCustomer: retrieve,
|
|
404
|
-
GetCustomersSearch: searchCustomers,
|
|
405
|
-
GetCustomersCustomerPaymentMethods: paymentMethods,
|
|
406
|
-
GetCustomersCustomerPaymentMethodsPaymentMethod: paymentMethod,
|
|
407
|
-
DeleteCustomersCustomerDiscount: deleteDiscount,
|
|
408
|
-
PostCustomersCustomerTaxIds: createTaxId,
|
|
409
|
-
PostTaxIds: createOwnedTaxId,
|
|
410
|
-
GetTaxIds: listOwnedTaxIds,
|
|
411
|
-
GetCustomersCustomerTaxIds: taxIds,
|
|
412
|
-
GetCustomersCustomerTaxIdsId: taxId,
|
|
413
|
-
DeleteCustomersCustomerTaxIdsId: deleteTaxId,
|
|
414
|
-
PostCustomersCustomerBalanceTransactions: createBalanceTransaction,
|
|
415
|
-
GetCustomersCustomerBalanceTransactions: balanceTransactions,
|
|
416
|
-
GetCustomersCustomerBalanceTransactionsTransaction: balanceTransaction,
|
|
417
|
-
GetCustomersCustomerCashBalance: getCashBalance,
|
|
418
|
-
PostCustomersCustomerCashBalance: updateCashBalance,
|
|
419
|
-
GetCustomersCustomerCashBalanceTransactions: cashBalanceTransactions,
|
|
420
|
-
GetCustomersCustomerCashBalanceTransactionsTransaction: cashBalanceTransaction,
|
|
421
|
-
PostTestHelpersCustomersCustomerFundCashBalance: fundCashBalance,
|
|
422
|
-
PostCustomersCustomerSources: attachSource,
|
|
423
|
-
GetCustomersCustomerSources: sources,
|
|
424
|
-
GetCustomersCustomerSourcesId: source,
|
|
425
|
-
PostCustomersCustomerSourcesId: updateSource,
|
|
426
|
-
DeleteCustomersCustomerSourcesId: detachSource,
|
|
427
|
-
PostSources: createSource,
|
|
428
|
-
PostSourcesSource: updateTopSource,
|
|
429
|
-
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { HandlerContext } from '@volter/world-core';
|
|
2
|
+
/** The platform's own account settings (its payout schedule, its public name) change on Stripe's Dashboard, which has no
|
|
3
|
+
* API: this stands in for that page. The account is Stripe's default until the page first changes it (the same default
|
|
4
|
+
* GET /v1/account answers); a hash is merged into what the account holds, as an update merges it. */
|
|
5
|
+
export declare function account(ctx: HandlerContext): Promise<Response>;
|
|
6
|
+
/** Time's events, sent when a caller drives them (a runner's drainer): what time settled on each account is written —
|
|
7
|
+
* payouts whose arrival date came (pending → paid, sent as payout.paid) and funds that came due (pending → available),
|
|
8
|
+
* for which the account is sent one balance.available ("Occurs whenever your Stripe balance has been updated (e.g., when
|
|
9
|
+
* a charge is available to be paid out)", docs.stripe.com/api/events/types) carrying its Balance, when an entry that
|
|
10
|
+
* settled adds to it ("This event is not fired for negative transactions", the same page); a connected account's
|
|
11
|
+
* events go to Connect endpoints. A read already sees these moves; the drain is what makes Stripe's events about them
|
|
12
|
+
* arrive. Answers what it sent. */
|
|
13
|
+
export declare function drain(ctx: HandlerContext): Promise<Response>;
|
|
14
|
+
/** POST /_twin/app-credentials: the platform's API keys as the dashboard's API keys page shows them, a secret key and a
|
|
15
|
+
* publishable key (`sk_test_…`, `pk_test_…`), held by their SHA-256, and the signing secrets its webhook endpoints
|
|
16
|
+
* are made with. The runtime issues them to a World's applications at every boot (the descriptor's credentialDoor), so
|
|
17
|
+
* the answer is the same each time: the account's one pair of keys, made the first time. Where the documentation
|
|
18
|
+
* stops: the keys' length and alphabet after the prefix. */
|
|
19
|
+
export declare function appCredentials(ctx: HandlerContext): Promise<Response>;
|
|
20
|
+
/** POST /_twin/webhook-endpoints {url, events, connect?}: an endpoint added on the Dashboard's Webhooks page, the events
|
|
21
|
+
* it takes, signed with the secret the World's application is issued (./doors.ts `appCredentials`): the twin's
|
|
22
|
+
* decision, since Stripe mints an endpoint's secret and lets no one choose one. */
|
|
23
|
+
export declare function webhookEndpoints(ctx: HandlerContext): Promise<Response>;
|