@volter/twin-stripe 3.0.0 → 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 +51 -21
- package/dist/generated/pack-facts.json +3 -3
- 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 +5 -1
- package/dist/spec/doc-examples.json +445 -0
- package/dist/spec/sources.json +135 -207
- package/dist/src/cli.d.ts +0 -1
- package/dist/src/cli.js +0 -2
- package/dist/src/engine/after-payment.d.ts +0 -1
- package/dist/src/engine/after-payment.js +0 -1
- package/dist/src/engine/common.d.ts +5 -5
- package/dist/src/engine/common.js +13 -11
- package/dist/src/engine/connect.d.ts +0 -21
- package/dist/src/engine/connect.js +1 -82
- package/dist/src/engine/customers.d.ts +0 -8
- package/dist/src/engine/customers.js +0 -22
- package/dist/src/engine/display.d.ts +0 -3
- package/dist/src/engine/display.js +0 -3
- package/dist/src/engine/files.d.ts +0 -7
- package/dist/src/engine/files.js +0 -10
- package/dist/src/engine/invoices.d.ts +6 -9
- package/dist/src/engine/invoices.js +56 -29
- package/dist/src/engine/issuing.d.ts +6 -8
- package/dist/src/engine/issuing.js +19 -10
- package/dist/src/engine/ledger.d.ts +0 -1
- package/dist/src/engine/ledger.js +0 -1
- package/dist/src/engine/oauth.js +1 -2
- package/dist/src/engine/refunds.js +2 -27
- package/dist/src/engine/stripe.d.ts +2 -7
- package/dist/src/engine/stripe.js +34 -83
- package/dist/src/engine/subscriptions.d.ts +0 -1
- package/dist/src/engine/subscriptions.js +0 -1
- package/dist/src/engine/version.d.ts +0 -2
- package/dist/src/engine/version.js +17 -6
- package/dist/src/fetch.js +2 -2
- package/dist/src/generated/surface.gen.json +1 -1
- package/dist/src/generated/ui.gen.json +1 -1
- package/dist/src/index.js +5 -3
- package/dist/src/manifest.js +348 -220
- package/dist/src/screens/billing-portal.js +5 -5
- package/dist/src/screens/{connect-oauth.d.ts → checkout-js.d.ts} +0 -1
- package/dist/src/screens/checkout-js.js +84 -0
- package/dist/src/screens/connect-onboarding.js +3 -3
- package/dist/src/screens/dashboard.d.ts +1 -1
- package/dist/src/screens/dashboard.js +52 -53
- package/dist/src/screens/stripe-js.js +53 -93
- package/dist/src/semantics/account.d.ts +2 -0
- package/dist/src/semantics/account.js +8 -0
- package/dist/src/semantics/accounts.js +3 -1
- package/dist/src/semantics/around.js +3 -6
- package/dist/src/semantics/billing_portal.d.ts +0 -1
- package/dist/src/semantics/billing_portal.js +1 -7
- package/dist/src/semantics/checkout.d.ts +1 -1
- package/dist/src/semantics/checkout.js +18 -15
- package/dist/src/semantics/clock.js +1 -7
- package/dist/src/semantics/customers.d.ts +1 -2
- package/dist/src/semantics/customers.js +32 -38
- package/dist/src/semantics/entitlements.d.ts +0 -2
- package/dist/src/semantics/entitlements.js +4 -50
- package/dist/src/semantics/events.d.ts +2 -3
- package/dist/src/semantics/events.js +6 -11
- package/dist/src/semantics/index.js +16 -38
- package/dist/src/semantics/invoices.d.ts +3 -8
- package/dist/src/semantics/invoices.js +134 -127
- package/dist/src/semantics/issuing.d.ts +6 -0
- package/dist/src/semantics/issuing.js +77 -0
- package/dist/src/semantics/payment_intents.d.ts +0 -1
- package/dist/src/semantics/payment_intents.js +5 -13
- package/dist/src/semantics/payment_methods.d.ts +0 -2
- package/dist/src/semantics/payment_methods.js +2 -26
- package/dist/src/semantics/payouts.d.ts +1 -0
- package/dist/src/semantics/payouts.js +8 -1
- package/dist/src/semantics/products.d.ts +1 -3
- package/dist/src/semantics/products.js +3 -14
- package/dist/src/semantics/refunds.d.ts +1 -2
- package/dist/src/semantics/refunds.js +1 -5
- package/dist/src/semantics/shared.d.ts +64 -336
- package/dist/src/semantics/shared.js +326 -1336
- package/dist/src/semantics/states.d.ts +20 -89
- package/dist/src/semantics/states.js +116 -334
- package/dist/src/semantics/subscriptions.d.ts +1 -2
- package/dist/src/semantics/subscriptions.js +23 -145
- package/dist/src/semantics/test_helpers.d.ts +2 -5
- package/dist/src/semantics/test_helpers.js +121 -43
- package/dist/src/semantics/webhook_endpoints.d.ts +2 -0
- package/dist/src/semantics/webhook_endpoints.js +26 -0
- package/generated/pack-facts.json +3 -3
- 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 +4 -3
- package/spec/SOURCE.md +5 -1
- package/spec/doc-examples.json +445 -0
- package/spec/sources.json +135 -207
- package/src/cli.ts +0 -3
- package/src/engine/after-payment.ts +0 -2
- package/src/engine/balance.ts +0 -1
- package/src/engine/checkout.ts +0 -1
- package/src/engine/common.ts +19 -17
- package/src/engine/connect.ts +1 -79
- package/src/engine/customers.ts +0 -25
- package/src/engine/display.ts +2 -3
- package/src/engine/files.ts +0 -13
- package/src/engine/invoices.ts +64 -33
- package/src/engine/issuing.ts +16 -11
- package/src/engine/ledger.ts +0 -2
- package/src/engine/oauth.ts +1 -2
- package/src/engine/onboarding.ts +0 -1
- package/src/engine/params.ts +0 -1
- package/src/engine/payment-intents.ts +0 -1
- package/src/engine/payment-methods.ts +0 -1
- package/src/engine/refunds.ts +2 -28
- package/src/engine/renewals.ts +0 -1
- package/src/engine/stripe.ts +35 -83
- package/src/engine/subscriptions.ts +0 -3
- package/src/engine/transfers.ts +0 -1
- package/src/engine/version.ts +15 -7
- package/src/engine/webhooks.ts +0 -2
- package/src/fetch.ts +2 -2
- package/src/generated/surface.gen.json +1 -1
- package/src/generated/ui.gen.json +1 -1
- package/src/index.ts +6 -4
- package/src/manifest.ts +348 -220
- package/src/screens/billing-portal.tsx +5 -7
- package/src/screens/checkout-js.ts +80 -0
- package/src/screens/checkout.tsx +0 -2
- package/src/screens/connect-onboarding.tsx +3 -5
- package/src/screens/dashboard.tsx +53 -73
- package/src/screens/stripe-js.ts +57 -125
- package/src/semantics/account.ts +10 -0
- package/src/semantics/account_links.ts +0 -1
- package/src/semantics/accounts.ts +8 -7
- package/src/semantics/apps.ts +1 -2
- package/src/semantics/around.ts +3 -6
- package/src/semantics/balance.ts +0 -1
- package/src/semantics/balance_transactions.ts +1 -3
- package/src/semantics/billing_portal.ts +1 -7
- package/src/semantics/charges.ts +1 -6
- package/src/semantics/checkout.ts +16 -12
- package/src/semantics/clock.ts +1 -8
- package/src/semantics/coupons.ts +0 -1
- package/src/semantics/customers.ts +32 -35
- package/src/semantics/doors.ts +2 -3
- package/src/semantics/entitlements.ts +1 -45
- package/src/semantics/events.ts +7 -14
- package/src/semantics/index.ts +16 -38
- package/src/semantics/invoice_payments.ts +1 -3
- package/src/semantics/invoices.ts +124 -119
- package/src/semantics/issuing.ts +74 -0
- package/src/semantics/payment_intents.ts +10 -17
- package/src/semantics/payment_methods.ts +2 -25
- package/src/semantics/payouts.ts +9 -3
- package/src/semantics/prices.ts +0 -1
- package/src/semantics/products.ts +4 -14
- package/src/semantics/promotion_codes.ts +0 -1
- package/src/semantics/radar.ts +1 -3
- package/src/semantics/refunds.ts +2 -11
- package/src/semantics/shared.ts +350 -1382
- package/src/semantics/states.ts +109 -330
- package/src/semantics/subscriptions.ts +25 -145
- package/src/semantics/test_helpers.ts +109 -43
- package/src/semantics/transfers.ts +1 -3
- package/src/semantics/webhook_endpoints.ts +27 -0
- package/src/server.ts +0 -1
- package/dist/src/engine/billing.d.ts +0 -5
- package/dist/src/engine/billing.js +0 -6
- package/dist/src/engine/charges.d.ts +0 -6
- package/dist/src/engine/charges.js +0 -13
- package/dist/src/engine/connect-settings.d.ts +0 -16
- package/dist/src/engine/connect-settings.js +0 -26
- package/dist/src/engine/coupons.d.ts +0 -0
- package/dist/src/engine/coupons.js +0 -5
- package/dist/src/engine/credit-notes.d.ts +0 -1
- package/dist/src/engine/credit-notes.js +0 -5
- package/dist/src/engine/disputes.d.ts +0 -0
- package/dist/src/engine/disputes.js +0 -9
- package/dist/src/engine/entitlements.d.ts +0 -1
- package/dist/src/engine/entitlements.js +0 -4
- package/dist/src/engine/ephemeral-keys.d.ts +0 -0
- package/dist/src/engine/ephemeral-keys.js +0 -4
- package/dist/src/engine/front.d.ts +0 -5
- package/dist/src/engine/front.js +0 -25
- package/dist/src/engine/payment-links.d.ts +0 -1
- package/dist/src/engine/payment-links.js +0 -5
- package/dist/src/engine/plans.d.ts +0 -6
- package/dist/src/engine/plans.js +0 -26
- package/dist/src/engine/platform.d.ts +0 -39
- package/dist/src/engine/platform.js +0 -34
- package/dist/src/engine/products.d.ts +0 -0
- package/dist/src/engine/products.js +0 -4
- package/dist/src/engine/setup-intents.d.ts +0 -1
- package/dist/src/engine/setup-intents.js +0 -4
- package/dist/src/engine/subscription-schedules.d.ts +0 -3
- package/dist/src/engine/subscription-schedules.js +0 -1
- package/dist/src/engine/tax.d.ts +0 -7
- package/dist/src/engine/tax.js +0 -28
- package/dist/src/engine/terminal.d.ts +0 -4
- package/dist/src/engine/terminal.js +0 -20
- package/dist/src/engine/test-clocks.d.ts +0 -5
- package/dist/src/engine/test-clocks.js +0 -9
- package/dist/src/engine/tokens.d.ts +0 -7
- package/dist/src/engine/tokens.js +0 -20
- package/dist/src/engine/treasury.d.ts +0 -23
- package/dist/src/engine/treasury.js +0 -45
- package/dist/src/screens/connect-oauth.js +0 -333
- package/dist/src/semantics/disputes.d.ts +0 -2
- package/dist/src/semantics/disputes.js +0 -5
- package/dist/src/semantics/invoiceitems.d.ts +0 -3
- package/dist/src/semantics/invoiceitems.js +0 -59
- package/dist/src/semantics/setup_intents.d.ts +0 -3
- package/dist/src/semantics/setup_intents.js +0 -41
- package/dist/src/semantics/subscription_items.d.ts +0 -5
- package/dist/src/semantics/subscription_items.js +0 -55
- package/dist/src/semantics/subscription_schedules.d.ts +0 -4
- package/dist/src/semantics/subscription_schedules.js +0 -60
- package/src/engine/billing.ts +0 -16
- package/src/engine/charges.ts +0 -19
- package/src/engine/connect-settings.ts +0 -23
- package/src/engine/coupons.ts +0 -5
- package/src/engine/credit-notes.ts +0 -6
- package/src/engine/disputes.ts +0 -9
- package/src/engine/entitlements.ts +0 -5
- package/src/engine/ephemeral-keys.ts +0 -3
- package/src/engine/front.ts +0 -31
- package/src/engine/payment-links.ts +0 -6
- package/src/engine/plans.ts +0 -42
- package/src/engine/platform.ts +0 -47
- package/src/engine/products.ts +0 -3
- package/src/engine/setup-intents.ts +0 -5
- package/src/engine/subscription-schedules.ts +0 -10
- package/src/engine/tax.ts +0 -39
- package/src/engine/terminal.ts +0 -23
- package/src/engine/test-clocks.ts +0 -11
- package/src/engine/tokens.ts +0 -22
- package/src/engine/treasury.ts +0 -47
- package/src/screens/connect-oauth.tsx +0 -376
- package/src/semantics/disputes.ts +0 -12
- package/src/semantics/invoiceitems.ts +0 -59
- package/src/semantics/setup_intents.ts +0 -45
- package/src/semantics/subscription_items.ts +0 -54
- package/src/semantics/subscription_schedules.ts +0 -66
package/src/semantics/states.ts
CHANGED
|
@@ -2,11 +2,6 @@
|
|
|
2
2
|
// author writes": states), each move cited; the manifest takes each resource's whole.
|
|
3
3
|
import type { StateField, Transition } from '@volter/world-core';
|
|
4
4
|
|
|
5
|
-
const unexpected = (verb: string, allowed: string) => ({
|
|
6
|
-
status: 400,
|
|
7
|
-
code: 'payment_intent_unexpected_state',
|
|
8
|
-
message: `This PaymentIntent could not be ${verb} because it has a status of {from}. ${allowed}`,
|
|
9
|
-
});
|
|
10
5
|
const OPEN = ['requires_payment_method', 'requires_confirmation', 'requires_action', 'requires_capture', 'processing'];
|
|
11
6
|
|
|
12
7
|
/** A move no API call makes: Stripe's own (`vendor`), the clock's (`time`), or the person on a
|
|
@@ -29,18 +24,9 @@ const paymentIntentStatus: StateField = {
|
|
|
29
24
|
transitions: [
|
|
30
25
|
// created with its payment method, it "enters the `requires_confirmation` status and is ready to confirm" (the same page)
|
|
31
26
|
{ operation: 'PostPaymentIntents', from: ['requires_payment_method'], to: 'requires_confirmation', source: 'https://docs.stripe.com/payments/paymentintents/lifecycle' },
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
from: ['requires_payment_method', 'requires_confirmation', 'requires_action'],
|
|
36
|
-
to,
|
|
37
|
-
refusal: { status: 400, code: 'payment_intent_unexpected_state', message: 'You cannot confirm this PaymentIntent because it has a status of {from}.' },
|
|
38
|
-
source: 'https://docs.stripe.com/api/payment_intents/confirm',
|
|
39
|
-
})),
|
|
40
|
-
// a bank debit confirmed (created with `confirm`, confirmed, or verified by micro-deposits) is submitted and
|
|
41
|
-
// processes: "The PaymentIntent you create initially has a status of `processing`"; "When the bank account is
|
|
42
|
-
// successfully verified, Stripe returns the PaymentIntent object with a status of `processing`"
|
|
43
|
-
...['PostPaymentIntents', 'PostPaymentIntentsIntentConfirm'].map((operation) => ({
|
|
27
|
+
// a bank debit confirmed (created with `confirm`, or confirmed) is submitted and processes: "The PaymentIntent you
|
|
28
|
+
// create initially has a status of `processing`"
|
|
29
|
+
...['PostPaymentIntents',].map((operation) => ({
|
|
44
30
|
operation, from: ['requires_payment_method', 'requires_confirmation', 'requires_action'], to: 'processing',
|
|
45
31
|
source: 'https://docs.stripe.com/payments/ach-direct-debit/accept-a-payment?payment-ui=direct-api',
|
|
46
32
|
})),
|
|
@@ -55,33 +41,10 @@ const paymentIntentStatus: StateField = {
|
|
|
55
41
|
source: 'spec:PostPaymentIntents "Set to `true` to attempt to [confirm this PaymentIntent](https://docs.stripe.com/api/payment_intents/confirm) immediately."',
|
|
56
42
|
})),
|
|
57
43
|
{
|
|
58
|
-
operation: '
|
|
59
|
-
from:
|
|
60
|
-
to: 'succeeded',
|
|
61
|
-
refusal: unexpected('captured', 'Only a PaymentIntent with one of the following statuses may be captured: requires_capture.'),
|
|
62
|
-
source: 'https://docs.stripe.com/api/payment_intents/capture',
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
operation: 'PostPaymentIntentsIntentIncrementAuthorization',
|
|
66
|
-
from: ['requires_capture'],
|
|
67
|
-
to: 'requires_capture',
|
|
68
|
-
refusal: unexpected('incremented', 'Only a PaymentIntent with a status of requires_capture may be incremented.'),
|
|
69
|
-
source: 'https://docs.stripe.com/api/payment_intents/increment_authorization',
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
operation: 'PostPaymentIntentsIntentCancel',
|
|
73
|
-
from: OPEN,
|
|
74
|
-
to: 'canceled',
|
|
75
|
-
refusal: unexpected('canceled', 'Only a PaymentIntent with one of the following statuses may be canceled: requires_payment_method, requires_capture, requires_confirmation, requires_action, processing.'),
|
|
44
|
+
operation: 'PostPaymentIntentsIntentCancel', from: OPEN, to: 'canceled',
|
|
45
|
+
refusal: { status: 400, code: 'payment_intent_unexpected_state', message: 'You cannot cancel this PaymentIntent because it has a status of {from}. Only a PaymentIntent with one of the following statuses may be canceled: requires_payment_method, requires_capture, requires_confirmation, requires_action, processing.' },
|
|
76
46
|
source: 'https://docs.stripe.com/api/payment_intents/cancel',
|
|
77
47
|
},
|
|
78
|
-
{
|
|
79
|
-
operation: 'PostPaymentIntentsIntentVerifyMicrodeposits',
|
|
80
|
-
from: ['requires_action'],
|
|
81
|
-
to: 'processing',
|
|
82
|
-
refusal: { status: 400, code: 'payment_intent_unexpected_state', message: 'This PaymentIntent could not be verified because it has a status of {from}.' },
|
|
83
|
-
source: 'https://docs.stripe.com/payments/ach-direct-debit/accept-a-payment?payment-ui=direct-api',
|
|
84
|
-
},
|
|
85
48
|
{
|
|
86
49
|
// paying the invoice an intent collects for settles the intent
|
|
87
50
|
operation: 'PostInvoicesInvoicePay',
|
|
@@ -89,51 +52,32 @@ const paymentIntentStatus: StateField = {
|
|
|
89
52
|
to: 'succeeded',
|
|
90
53
|
source: 'https://docs.stripe.com/api/invoices/pay',
|
|
91
54
|
},
|
|
92
|
-
...['succeeded', 'requires_action'].map((to) => ({
|
|
93
|
-
operation: 'PostPaymentIntentsIntentApplyCustomerBalance',
|
|
94
|
-
from: OPEN,
|
|
95
|
-
to,
|
|
96
|
-
refusal: { status: 400, code: 'payment_intent_unexpected_state', message: 'This PaymentIntent could not be updated because it has a status of {from}.' },
|
|
97
|
-
source: 'https://docs.stripe.com/api/payment_intents/apply_customer_balance',
|
|
98
|
-
})),
|
|
99
|
-
// a card presented to the Terminal reader processing an intent pays it (held, under manual capture)
|
|
100
|
-
...['succeeded', 'requires_capture'].map((to) => ({
|
|
101
|
-
operation: 'PostTestHelpersTerminalReadersReaderPresentPaymentMethod', from: ['requires_payment_method', 'requires_confirmation'], to,
|
|
102
|
-
source: 'https://docs.stripe.com/api/terminal/readers/present_payment_method',
|
|
103
|
-
})),
|
|
104
55
|
// the customer pays, in the billing portal, the invoice an intent collects for after its charge was declined
|
|
105
56
|
by('external', ['requires_payment_method'], 'succeeded', 'https://docs.stripe.com/customer-management'),
|
|
106
57
|
// Stripe also fails a bank debit back to needing a payment method, completes 3-D Secure on the bank's page and
|
|
107
58
|
// cancels a hold left uncaptured; the twin's bank debits succeed unless their test account stays processing, it has no 3-D Secure page, and it keeps
|
|
108
|
-
// holds until they are
|
|
59
|
+
// holds until they are canceled. Capture, increment_authorization, verify_microdeposits, apply_customer_balance and
|
|
60
|
+
// the Terminal reader's present_payment_method are the gap (no demand, life step or refresh sends them:
|
|
61
|
+
// ../manifest.ts unmodeled), so no move of theirs is declared.
|
|
109
62
|
],
|
|
110
63
|
};
|
|
111
64
|
|
|
112
|
-
/** A charge's `captured`: an authorization (
|
|
65
|
+
/** A charge's `captured`: an authorization (capture_method=manual) is captured once, by the Charges API's capture or
|
|
66
|
+
* its PaymentIntent's; both are the gap here (no demand, life step or refresh sends them: ../manifest.ts unmodeled), so
|
|
67
|
+
* a held charge stays uncaptured until its intent is canceled, and no move is declared. */
|
|
113
68
|
const chargeCaptured: StateField = {
|
|
114
69
|
initial: true,
|
|
115
|
-
transitions: [
|
|
116
|
-
{
|
|
117
|
-
operation: 'PostChargesChargeCapture',
|
|
118
|
-
from: ['false'],
|
|
119
|
-
to: 'true',
|
|
120
|
-
refusal: { status: 400, code: 'charge_already_captured', message: 'Charge ch_xxx has already been captured.' },
|
|
121
|
-
source: 'https://docs.stripe.com/api/charges/capture',
|
|
122
|
-
},
|
|
123
|
-
// a manual-capture intent's capture captures the charge its confirm authorized
|
|
124
|
-
{ operation: 'PostPaymentIntentsIntentCapture', from: ['false'], to: 'true', source: 'https://docs.stripe.com/api/payment_intents/capture' },
|
|
125
|
-
],
|
|
70
|
+
transitions: [],
|
|
126
71
|
};
|
|
127
72
|
|
|
128
|
-
/** A charge's `refunded`: true once refunds take its whole amount
|
|
129
|
-
* canceled. A capture or a cancel never makes it so: "`refunded` will no longer be `true` for payment cancellation
|
|
73
|
+
/** A charge's `refunded`: true once refunds take its whole amount. A capture or a cancel never makes it so: "`refunded` will no longer be `true` for payment cancellation
|
|
130
74
|
* flows", and a partial capture makes no Refund (docs.stripe.com/changelog/basil/2025-03-31/remove-refund-from-partial-
|
|
131
75
|
* capture-and-payment-cancellation-flow). */
|
|
132
76
|
const chargeRefunded: StateField = {
|
|
133
77
|
initial: false,
|
|
134
78
|
transitions: [
|
|
135
|
-
|
|
136
|
-
|
|
79
|
+
{ operation: 'PostRefunds', from: ['false'], to: 'true', source: 'https://docs.stripe.com/api/charges/object#charge_object-refunded' },
|
|
80
|
+
// (a charge's own refunds path, a credit note's refund and a refund's cancel are the gap here: ../manifest.ts unmodeled)
|
|
137
81
|
// (an authorization left uncaptured is canceled after seven days, "Uncaptured PaymentIntents are cancelled a set
|
|
138
82
|
// number of days (7 by default) after their creation", docs.stripe.com/api/payment_intents/capture; the twin keeps it)
|
|
139
83
|
],
|
|
@@ -155,63 +99,25 @@ const chargePaid: StateField = {
|
|
|
155
99
|
transitions: [],
|
|
156
100
|
};
|
|
157
101
|
|
|
158
|
-
/** A refund's `status`:
|
|
159
|
-
*
|
|
102
|
+
/** A refund's `status`: held pending for want of balance, then made; the rest is the network's. (Canceling one that
|
|
103
|
+
* waits on the customer is the gap here: ../manifest.ts unmodeled.) */
|
|
160
104
|
const refundStatus: StateField = {
|
|
161
105
|
initial: 'succeeded',
|
|
162
106
|
transitions: [
|
|
163
|
-
{
|
|
164
|
-
operation: 'PostRefundsRefundCancel',
|
|
165
|
-
from: ['requires_action'],
|
|
166
|
-
to: 'canceled',
|
|
167
|
-
refusal: { status: 400, code: 'refund_status_invalid', message: 'Refund {id} cannot be canceled because its status is {from}.' },
|
|
168
|
-
source: 'https://docs.stripe.com/api/refunds/cancel',
|
|
169
|
-
},
|
|
170
|
-
// a card refund the available balance does not cover is held pending, and made once the balance covers it: "Stripe
|
|
171
|
-
// holds the refund as pending for card transactions ... until your Stripe balance becomes sufficient"
|
|
172
|
-
// (docs.stripe.com/refunds; semantics/refunds.ts)
|
|
173
|
-
// (a paid invoice's credit note makes its refund as a refund call does: "Refunds: create a new refund (using
|
|
174
|
-
// refund_amount)", docs.stripe.com/api/credit_notes/create)
|
|
175
|
-
...['PostRefunds', 'PostChargesChargeRefunds', 'PostCreditNotes'].map((operation) => ({ operation, from: ['succeeded'], to: 'pending', source: 'https://docs.stripe.com/refunds' })),
|
|
107
|
+
{ operation: 'PostRefunds', from: ['succeeded'], to: 'pending', source: 'https://docs.stripe.com/refunds' },
|
|
176
108
|
by('vendor', ['pending'], 'succeeded', 'https://docs.stripe.com/refunds'),
|
|
177
109
|
// a bank-transfer payment's refund waits in requires_action for the customer's bank details, and is made so
|
|
178
110
|
// (semantics/refunds.ts); (Stripe also fails some refunds; the twin's do not fail)
|
|
179
111
|
],
|
|
180
112
|
};
|
|
181
113
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
/** A SetupIntent's `status`: confirm settles it (or leaves a bank account awaiting micro-deposits),
|
|
186
|
-
* cancel ends an open one, and verification completes the micro-deposit wait. */
|
|
114
|
+
/** A SetupIntent's `status`: confirm settles it, or leaves a bank account awaiting micro-deposits. (Its cancel and its
|
|
115
|
+
* micro-deposit verification are the gap here: ../manifest.ts unmodeled.) */
|
|
187
116
|
const setupIntentStatus: StateField = {
|
|
188
117
|
// "After you create the SetupIntent, it has a status of `requires_payment_method` until you attach a payment method"
|
|
189
118
|
// (docs.stripe.com/payments/paymentintents/lifecycle)
|
|
190
119
|
initial: 'requires_payment_method',
|
|
191
120
|
transitions: [
|
|
192
|
-
// created with its payment method, it "enters the `requires_confirmation` status and is ready to confirm" (the same page)
|
|
193
|
-
{ operation: 'PostSetupIntents', from: ['requires_payment_method'], to: 'requires_confirmation', source: 'https://docs.stripe.com/payments/paymentintents/lifecycle' },
|
|
194
|
-
...['requires_action', 'succeeded'].map((to) => ({
|
|
195
|
-
operation: 'PostSetupIntentsIntentConfirm',
|
|
196
|
-
from: SETUP_OPEN,
|
|
197
|
-
to,
|
|
198
|
-
refusal: setupUnexpected('confirm'),
|
|
199
|
-
source: 'https://docs.stripe.com/api/setup_intents/confirm',
|
|
200
|
-
})),
|
|
201
|
-
{
|
|
202
|
-
operation: 'PostSetupIntentsIntentCancel',
|
|
203
|
-
from: SETUP_OPEN,
|
|
204
|
-
to: 'canceled',
|
|
205
|
-
refusal: setupUnexpected('cancel'),
|
|
206
|
-
source: 'https://docs.stripe.com/api/setup_intents/cancel',
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
operation: 'PostSetupIntentsIntentVerifyMicrodeposits',
|
|
210
|
-
from: ['requires_action'],
|
|
211
|
-
to: 'succeeded',
|
|
212
|
-
refusal: { status: 400, code: 'setup_intent_unexpected_state', message: 'This SetupIntent could not be verified because it has a status of {from}.' },
|
|
213
|
-
source: 'https://docs.stripe.com/api/setup_intents/verify_microdeposits',
|
|
214
|
-
},
|
|
215
121
|
// (Stripe completes 3-D Secure on the bank's page and settles an asynchronous method from processing; the
|
|
216
122
|
// twin has no such page and settles every method at once)
|
|
217
123
|
],
|
|
@@ -224,38 +130,20 @@ const invoiceRefusal = (message: string) => ({ status: 400, code: 'invoice_not_e
|
|
|
224
130
|
const invoiceStatus: StateField = {
|
|
225
131
|
initial: 'draft',
|
|
226
132
|
transitions: [
|
|
133
|
+
{ operation: 'PostInvoicesInvoicePay', from: ['draft'], to: 'open', source: 'https://docs.stripe.com/invoicing/integration' },
|
|
227
134
|
{
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
to: 'open',
|
|
231
|
-
refusal: invoiceRefusal('This invoice cannot be finalized because it has status {from}.'),
|
|
232
|
-
source: 'https://docs.stripe.com/api/invoices/finalize',
|
|
233
|
-
},
|
|
234
|
-
// docs.stripe.com/invoicing/integration: "To finalize a draft invoice, use the Dashboard, send it to the customer, or pay it."
|
|
235
|
-
{ operation: 'PostInvoicesInvoiceSend', from: ['draft'], to: 'open', source: 'https://docs.stripe.com/invoicing/integration' },
|
|
236
|
-
{ operation: 'PostInvoicesInvoiceSend', from: ['open', 'paid', 'uncollectible', 'void'], source: 'https://docs.stripe.com/api/invoices/send' },
|
|
237
|
-
{
|
|
135
|
+
// paying a draft finalizes it on the way: "To finalize a draft invoice, use the Dashboard, send it to the customer, or
|
|
136
|
+
// pay it" (docs.stripe.com/invoicing/integration)
|
|
238
137
|
operation: 'PostInvoicesInvoicePay',
|
|
239
|
-
from: ['open', 'uncollectible'],
|
|
138
|
+
from: ['draft', 'open', 'uncollectible'],
|
|
240
139
|
to: 'paid',
|
|
241
140
|
refusal: invoiceRefusal('This invoice cannot be paid because it has status {from}.'),
|
|
242
141
|
refusals: { paid: { status: 400, code: 'invoice_payment_intent_requires_action', message: 'This invoice has already been paid.' } },
|
|
243
142
|
source: 'https://docs.stripe.com/api/invoices/pay',
|
|
244
143
|
},
|
|
245
|
-
|
|
246
|
-
operation: 'PostInvoicesInvoiceMarkUncollectible',
|
|
247
|
-
from: ['open'],
|
|
248
|
-
to: 'uncollectible',
|
|
249
|
-
refusal: invoiceRefusal('Only open invoices can be marked uncollectible (this one is {from}).'),
|
|
250
|
-
source: 'https://docs.stripe.com/api/invoices/mark_uncollectible',
|
|
251
|
-
},
|
|
144
|
+
// (marking one uncollectible is the gap here: ../manifest.ts unmodeled)
|
|
252
145
|
// a guard: only a draft is deleted; a finalized invoice is voided instead
|
|
253
|
-
|
|
254
|
-
operation: 'DeleteInvoicesInvoice',
|
|
255
|
-
from: ['draft'],
|
|
256
|
-
refusal: invoiceRefusal('You can only delete draft invoices.'),
|
|
257
|
-
source: 'https://docs.stripe.com/api/invoices/delete',
|
|
258
|
-
},
|
|
146
|
+
// in any other state it is refused (a refusal with no `to` refuses where it stands)
|
|
259
147
|
{
|
|
260
148
|
// only a finalized invoice is voided; a draft is deleted
|
|
261
149
|
operation: 'PostInvoicesInvoiceVoid',
|
|
@@ -264,15 +152,11 @@ const invoiceStatus: StateField = {
|
|
|
264
152
|
refusal: invoiceRefusal('This invoice cannot be voided because it has status {from}.'),
|
|
265
153
|
source: 'https://docs.stripe.com/api/invoices/void',
|
|
266
154
|
},
|
|
267
|
-
// confirming
|
|
268
|
-
...['PostPaymentIntentsIntentConfirm', 'PostPaymentIntentsIntentCapture'].map((operation) => ({
|
|
269
|
-
operation, from: ['draft', 'open', 'uncollectible'], to: 'paid', source: 'https://docs.stripe.com/billing/subscriptions/build-subscriptions',
|
|
270
|
-
})),
|
|
155
|
+
// confirming the PaymentIntent an invoice collects through pays the invoice
|
|
271
156
|
// as does its bank debit succeeding once submitted (../engine/payment-intents.ts settleBankDebits)
|
|
272
157
|
by('vendor', ['draft', 'open', 'uncollectible'], 'paid', 'https://docs.stripe.com/billing/subscriptions/build-subscriptions'),
|
|
273
|
-
// a bank transfer
|
|
274
|
-
//
|
|
275
|
-
{ operation: 'PostTestHelpersCustomersCustomerFundCashBalance', from: ['open'], to: 'paid', source: 'https://docs.stripe.com/payments/customer-balance/reconciliation' },
|
|
158
|
+
// (a bank transfer reconciled from the customer's cash balance pays one at Stripe; funding a cash balance is the gap
|
|
159
|
+
// here: ../manifest.ts unmodeled)
|
|
276
160
|
// a subscription's first invoice is paid on the Checkout page that started it
|
|
277
161
|
by('external', ['draft'], 'paid', 'https://docs.stripe.com/payments/checkout/how-checkout-works'),
|
|
278
162
|
// a subscription's renewal, drafted at its period's end, is finalized about an hour later and charged: paid, or
|
|
@@ -284,7 +168,6 @@ const invoiceStatus: StateField = {
|
|
|
284
168
|
{ operation: 'PostSubscriptionsSubscriptionExposedId', from: ['draft'], to: 'paid', source: 'https://docs.stripe.com/billing/subscriptions/upgrade-downgrade#immediate-payment' },
|
|
285
169
|
// a trialing subscription's first invoice, for nothing, is paid as it is made ("An immediate invoice is still
|
|
286
170
|
// created, but the amount is 0", docs.stripe.com/billing/subscriptions/trials/free-trials)
|
|
287
|
-
{ operation: 'PostSubscriptions', from: ['draft'], to: 'paid', source: 'https://docs.stripe.com/billing/subscriptions/trials/free-trials' },
|
|
288
171
|
{ operation: 'PostSubscriptionsSubscriptionExposedId', from: ['draft'], to: 'open', source: 'https://docs.stripe.com/billing/subscriptions/upgrade-downgrade#immediate-payment' },
|
|
289
172
|
// the customer pays an open invoice in the billing portal
|
|
290
173
|
by('external', ['open'], 'paid', 'https://docs.stripe.com/customer-management'),
|
|
@@ -301,16 +184,8 @@ const subscriptionStatus: StateField = {
|
|
|
301
184
|
{ operation: 'DeleteSubscriptionsSubscriptionExposedId', from: '*', to: 'canceled', source: 'https://docs.stripe.com/api/subscriptions/cancel' },
|
|
302
185
|
// (a test clock's advance moves no subscription itself: when the clock reaches its target, a trial that ended there
|
|
303
186
|
// ends by time's move below, on the next request; ../engine/test-clocks.ts finishClockAdvances)
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
{ operation: 'DeleteTestHelpersTestClocksTestClock', from: '*', to: 'canceled', source: 'https://docs.stripe.com/billing/testing/test-clocks/api-advanced-usage' },
|
|
307
|
-
// "Cancels a subscription schedule and its associated subscription immediately" (docs.stripe.com/api/subscription_schedules/cancel)
|
|
308
|
-
{ operation: 'PostSubscriptionSchedulesScheduleCancel', from: ['active', 'trialing', 'past_due', 'incomplete'], to: 'canceled', source: 'https://docs.stripe.com/api/subscription_schedules/cancel' },
|
|
309
|
-
// confirming or capturing the first invoice's PaymentIntent, or paying the invoice itself, pays it and
|
|
310
|
-
// activates the subscription
|
|
311
|
-
...['PostPaymentIntentsIntentConfirm', 'PostPaymentIntentsIntentCapture'].map((operation) => ({
|
|
312
|
-
operation, from: ['incomplete'], to: 'active', source: 'https://docs.stripe.com/billing/subscriptions/build-subscriptions',
|
|
313
|
-
})),
|
|
187
|
+
// (canceling a subscription schedule, which cancels its subscription, is the gap here: ../manifest.ts unmodeled)
|
|
188
|
+
// confirming the first invoice's PaymentIntent, or paying the invoice itself, pays it and activates the subscription
|
|
314
189
|
// or when its bank debit succeeds once submitted (../engine/payment-intents.ts settleBankDebits)
|
|
315
190
|
by('vendor', ['incomplete'], 'active', 'https://docs.stripe.com/billing/subscriptions/build-subscriptions'),
|
|
316
191
|
// paying the invoice a subscription waits on (its first, or a renewal that failed) makes it active
|
|
@@ -351,7 +226,6 @@ const notOpen = (verb: string) => ({ status: 400, code: 'checkout_session_not_op
|
|
|
351
226
|
const checkoutStatus: StateField = {
|
|
352
227
|
initial: 'open',
|
|
353
228
|
transitions: [
|
|
354
|
-
{ operation: 'PostCheckoutSessionsSessionExpire', from: ['open'], to: 'expired', refusal: notOpen('expire'), source: 'https://docs.stripe.com/api/checkout/sessions/expire' },
|
|
355
229
|
{ actor: 'external', from: ['open'], to: 'complete', refusal: notOpen('complete'), source: 'https://docs.stripe.com/api/checkout/sessions/object#checkout_session_object-status' },
|
|
356
230
|
// (Stripe expires an open session at expires_at; the twin keeps it open until asked)
|
|
357
231
|
],
|
|
@@ -367,60 +241,39 @@ const checkoutPaymentStatus: StateField = {
|
|
|
367
241
|
],
|
|
368
242
|
};
|
|
369
243
|
|
|
370
|
-
/** A dispute's `status`:
|
|
371
|
-
*
|
|
244
|
+
/** A dispute's `status`: a disputed test card's charge opens one (needs_response, or warning_needs_response for an
|
|
245
|
+
* inquiry). Submitting evidence and closing (conceding) are the gap here (../manifest.ts unmodeled), so nothing puts a
|
|
246
|
+
* dispute under review for the bank to decide; the bank escalates an inquiry and loses one left unanswered at
|
|
247
|
+
* Stripe, and the twin decides none. */
|
|
372
248
|
const disputeStatus: StateField = {
|
|
373
249
|
initial: 'warning_needs_response',
|
|
374
|
-
transitions: [
|
|
375
|
-
{ operation: 'PostDisputesDisputeClose', from: '*', to: 'lost', source: 'https://docs.stripe.com/api/disputes/close' },
|
|
376
|
-
// submitting evidence puts a dispute (or an inquiry) under review; in test mode the evidence decides it
|
|
377
|
-
{ operation: 'PostDisputesDispute', from: ['needs_response'], to: 'under_review', source: 'https://docs.stripe.com/api/disputes/update#update_dispute-submit' },
|
|
378
|
-
{ operation: 'PostDisputesDispute', from: ['warning_needs_response'], to: 'warning_under_review', source: 'https://docs.stripe.com/api/disputes/update#update_dispute-submit' },
|
|
379
|
-
by('vendor', ['under_review'], 'won', 'https://docs.stripe.com/testing#evidence'),
|
|
380
|
-
by('vendor', ['under_review'], 'lost', 'https://docs.stripe.com/testing#evidence'),
|
|
381
|
-
// (the bank decides a dispute under review, escalates an inquiry and loses one left unanswered; the twin
|
|
382
|
-
// decides none)
|
|
383
|
-
// Stripe also closes an inquiry the bank drops (warning_closed); no inquiry the twin holds is dropped
|
|
384
|
-
],
|
|
250
|
+
transitions: [],
|
|
385
251
|
};
|
|
386
252
|
|
|
387
253
|
/** A payout's `status`: cancel ends it; reversing cancels it and pays a reversal, never twice. */
|
|
388
254
|
const payoutStatus: StateField = {
|
|
389
255
|
initial: 'pending',
|
|
390
256
|
transitions: [
|
|
391
|
-
//
|
|
392
|
-
// payout and leaves the original paid (semantics/balance.ts)
|
|
393
|
-
{ operation: 'PostPayoutsPayoutCancel', from: ['pending'], to: 'canceled', source: 'https://docs.stripe.com/api/payouts/cancel' },
|
|
257
|
+
// (canceling a pending payout and reversing a paid one are the gap here: ../manifest.ts unmodeled)
|
|
394
258
|
// it arrives in the bank on its arrival date
|
|
395
259
|
by('time', ['pending'], 'paid', 'https://docs.stripe.com/payouts#payout-statuses'),
|
|
396
260
|
// (a bank may fail a payout at Stripe; the twin's arrive)
|
|
397
261
|
],
|
|
398
262
|
};
|
|
399
263
|
|
|
400
|
-
/** A credit note's `status`: issued, then voided once.
|
|
264
|
+
/** A credit note's `status`: issued, then voided once at Stripe. Issuing and voiding one are the gap here
|
|
265
|
+
* (../manifest.ts unmodeled): a vendor-backed World reads credit notes back from its root (GetCreditNotes). */
|
|
401
266
|
const creditNoteStatus: StateField = {
|
|
402
267
|
initial: 'issued',
|
|
403
|
-
transitions: [
|
|
404
|
-
{
|
|
405
|
-
operation: 'PostCreditNotesIdVoid',
|
|
406
|
-
from: ['issued'],
|
|
407
|
-
to: 'void',
|
|
408
|
-
refusal: { status: 400, code: 'credit_note_already_voided', message: 'This credit note has already been voided.' },
|
|
409
|
-
source: 'https://docs.stripe.com/api/credit_notes/void',
|
|
410
|
-
},
|
|
411
|
-
],
|
|
268
|
+
transitions: [],
|
|
412
269
|
};
|
|
413
270
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
/** A subscription schedule's `status`: canceling or releasing ends it; an ended one refuses both. */
|
|
271
|
+
/** A subscription schedule's `status`: releasing ends it, and an ended one refuses it. (Canceling one is the gap here:
|
|
272
|
+
* ../manifest.ts unmodeled.) */
|
|
417
273
|
const scheduleStatus: StateField = {
|
|
418
274
|
initial: 'active',
|
|
419
275
|
transitions: [
|
|
420
|
-
// "A
|
|
421
|
-
// released if its status is not_started or active" (the cancel and release pages)
|
|
422
|
-
{ operation: 'PostSubscriptionSchedulesScheduleCancel', from: ['active', 'not_started'], to: 'canceled', refusal: scheduleEnded('canceled'), source: 'https://docs.stripe.com/api/subscription_schedules/cancel' },
|
|
423
|
-
{ operation: 'PostSubscriptionSchedulesScheduleRelease', from: ['active', 'not_started'], to: 'released', refusal: scheduleEnded('released'), source: 'https://docs.stripe.com/api/subscription_schedules/release' },
|
|
276
|
+
// "A schedule can only be released if its status is not_started or active" (the release page)
|
|
424
277
|
// its phases run on the clock: it starts at its start date and ends with its last phase
|
|
425
278
|
// (a schedule whose end_behavior is cancel completes at its end at Stripe; the twin's release)
|
|
426
279
|
// (Stripe starts a schedule at its start date and ends it with its last phase; the twin's start at once
|
|
@@ -428,18 +281,12 @@ const scheduleStatus: StateField = {
|
|
|
428
281
|
],
|
|
429
282
|
};
|
|
430
283
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
284
|
+
/** A quote's `status`: at Stripe a draft is finalized into open, an open one accepted, and either canceled. Quotes are
|
|
285
|
+
* the gap here (../manifest.ts unmodeled): a vendor-backed World reads them back from its root (GetQuotes), so no
|
|
286
|
+
* move is the twin's. */
|
|
434
287
|
const quoteStatus: StateField = {
|
|
435
288
|
initial: 'draft',
|
|
436
|
-
transitions: [
|
|
437
|
-
{ operation: 'PostQuotesQuoteFinalize', from: ['draft'], to: 'open', refusal: quoteRefusal('finalized'), source: 'https://docs.stripe.com/api/quotes/finalize' },
|
|
438
|
-
{ operation: 'PostQuotesQuoteAccept', from: ['open'], to: 'accepted', refusal: quoteRefusal('accepted'), source: 'https://docs.stripe.com/api/quotes/accept' },
|
|
439
|
-
{ operation: 'PostQuotesQuoteCancel', from: ['draft', 'open'], to: 'canceled', refusal: quoteRefusal('canceled'), source: 'https://docs.stripe.com/api/quotes/cancel' },
|
|
440
|
-
// an open quote not accepted by its expires_at is canceled
|
|
441
|
-
// (Stripe cancels an open quote at its expires_at; the twin keeps it open)
|
|
442
|
-
],
|
|
289
|
+
transitions: [],
|
|
443
290
|
};
|
|
444
291
|
|
|
445
292
|
const setBy = (operation: string, values: string[], source: string): Transition[] => values.map((to) => ({ operation, from: '*' as const, to, source }));
|
|
@@ -450,7 +297,9 @@ const issuingAuthorizationStatus: StateField = {
|
|
|
450
297
|
initial: 'pending',
|
|
451
298
|
transitions: [
|
|
452
299
|
// approving keeps it pending (approved) until the merchant captures; declining closes it
|
|
453
|
-
{ operation: 'PostIssuingAuthorizationsAuthorizationApprove', from: ['pending'],
|
|
300
|
+
{ operation: 'PostIssuingAuthorizationsAuthorizationApprove', from: ['pending'], source: 'https://docs.stripe.com/issuing/purchases/authorizations' },
|
|
301
|
+
// in any other state it is refused (a refusal with no `to` refuses where it stands)
|
|
302
|
+
{ operation: 'PostIssuingAuthorizationsAuthorizationApprove', from: '*', refusal: { status: 400, code: 'authorization_already_finalized', message: 'This authorization has already been finalized (status {from}).' }, source: 'https://docs.stripe.com/issuing/purchases/authorizations' },
|
|
454
303
|
{ operation: 'PostIssuingAuthorizationsAuthorizationDecline', from: ['pending'], to: 'closed', refusal: { status: 400, code: 'authorization_already_finalized', message: 'This authorization has already been finalized (status {from}).' }, source: 'https://docs.stripe.com/api/issuing/authorizations/decline' },
|
|
455
304
|
{ operation: 'PostTestHelpersIssuingAuthorizationsAuthorizationCapture', from: ['pending'], to: 'closed', source: 'https://docs.stripe.com/api/issuing/authorizations/test_mode_capture' },
|
|
456
305
|
{ operation: 'PostTestHelpersIssuingAuthorizationsAuthorizationCapture', from: ['pending'], source: 'https://docs.stripe.com/api/issuing/authorizations/test_mode_capture' },
|
|
@@ -463,35 +312,23 @@ const issuingAuthorizationStatus: StateField = {
|
|
|
463
312
|
],
|
|
464
313
|
};
|
|
465
314
|
|
|
466
|
-
/** A Treasury flow's `status
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
{ operation, from: ['processing'], to: 'canceled', refusal: { status: 400, code, message: `This ${name} cannot be canceled because it is no longer processing.` }, source: 'https://docs.stripe.com/api/treasury' },
|
|
471
|
-
...network,
|
|
472
|
-
],
|
|
473
|
-
});
|
|
474
|
-
/** An outbound flow on the network: Stripe posts or fails it and a posted one may come back returned (in
|
|
475
|
-
* test mode by test helpers the twin does not serve), so none of those moves is the twin's. */
|
|
476
|
-
const outbound = (_doc: string): Transition[] => [];
|
|
315
|
+
/** A Treasury flow's `status`: at Stripe a processing flow cancels, the network posts or fails it, and a posted one may
|
|
316
|
+
* come back returned. Treasury's flows are the gap here (../manifest.ts unmodeled): a vendor-backed World reads them
|
|
317
|
+
* back from its root, so no move is the twin's. */
|
|
318
|
+
const flow: StateField = { initial: 'processing', transitions: [] };
|
|
477
319
|
|
|
478
320
|
export const states = {
|
|
479
321
|
'billing.meter': {
|
|
480
322
|
notState: ['event_time_window'],
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
{ operation: 'PostBillingMetersIdReactivate', from: '*', to: 'active', source: 'https://docs.stripe.com/api/billing/meter/reactivate' },
|
|
484
|
-
] } },
|
|
323
|
+
// (deactivating and reactivating a meter are the gap here: ../manifest.ts unmodeled)
|
|
324
|
+
state: { status: { initial: 'active', transitions: [] } },
|
|
485
325
|
},
|
|
486
326
|
'billing.credit_grant': {
|
|
487
327
|
notState: ['category'],
|
|
488
328
|
},
|
|
489
329
|
'billing.alert': {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
{ operation: 'PostBillingAlertsIdDeactivate', from: '*', to: 'inactive', source: 'https://docs.stripe.com/api/billing/alert/deactivate' },
|
|
493
|
-
{ operation: 'PostBillingAlertsIdArchive', from: '*', to: 'archived', source: 'https://docs.stripe.com/api/billing/alert/archive' },
|
|
494
|
-
] } },
|
|
330
|
+
// (activating, deactivating and archiving an alert are the gap here: ../manifest.ts unmodeled)
|
|
331
|
+
state: { status: { initial: 'active', transitions: [] } },
|
|
495
332
|
},
|
|
496
333
|
payment_intent: {
|
|
497
334
|
state: { status: paymentIntentStatus },
|
|
@@ -554,17 +391,10 @@ export const states = {
|
|
|
554
391
|
notState: ['billing_address_collection', 'customer_creation', 'locale', 'mode', 'origin_context', 'payment_method_collection', 'redirect_on_completion', 'submit_type', 'ui_mode'],
|
|
555
392
|
},
|
|
556
393
|
mandate: {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
// a single-use mandate is spent by its payment: inactive "The mandate was rejected, revoked, or previously
|
|
562
|
-
// used, and may not be used to initiate future payments" (../engine/payment-intents.ts settleBankDebits)
|
|
563
|
-
by('vendor', ['active'], 'inactive', 'https://docs.stripe.com/api/mandates/object'),
|
|
564
|
-
// (Stripe also activates a pending mandate and deactivates one revoked; the twin's are active when made)
|
|
565
|
-
],
|
|
566
|
-
},
|
|
567
|
-
},
|
|
394
|
+
// Stripe activates a pending mandate, spends a single-use one with its payment and deactivates one revoked; the twin
|
|
395
|
+
// makes no mandate (micro-deposit verification, which accepts one, is the gap here: ../manifest.ts unmodeled), so no
|
|
396
|
+
// move is the twin's
|
|
397
|
+
state: { status: { initial: 'active', transitions: [] } },
|
|
568
398
|
notState: ['type'],
|
|
569
399
|
},
|
|
570
400
|
dispute: {
|
|
@@ -602,7 +432,8 @@ export const states = {
|
|
|
602
432
|
notState: ['end_behavior'],
|
|
603
433
|
},
|
|
604
434
|
'entitlements.feature': {
|
|
605
|
-
|
|
435
|
+
// (updating a feature, which "permanently" deactivates it, is the gap here: ../manifest.ts unmodeled)
|
|
436
|
+
state: { active: { initial: true, transitions: [] } },
|
|
606
437
|
},
|
|
607
438
|
'test_helpers.test_clock': {
|
|
608
439
|
state: {
|
|
@@ -611,7 +442,6 @@ export const states = {
|
|
|
611
442
|
transitions: [
|
|
612
443
|
// "advancing: The clock has started to advance but hasn't reached the specified time"; "ready: The clock has
|
|
613
444
|
// completed advancing to the specified time" (docs.stripe.com/billing/testing/test-clocks/api-advanced-usage)
|
|
614
|
-
{ operation: 'PostTestHelpersTestClocksTestClockAdvance', from: ['ready'], to: 'advancing', refusal: { status: 400, code: 'test_clock_not_ready', message: 'This test clock is still advancing.' }, source: 'https://docs.stripe.com/api/test_clocks/advance' },
|
|
615
445
|
by('vendor', ['advancing'], 'ready', 'https://docs.stripe.com/billing/testing/test-clocks/api-advanced-usage'),
|
|
616
446
|
// (Stripe may also end an advance in internal_failure; the twin's advances complete)
|
|
617
447
|
],
|
|
@@ -619,14 +449,8 @@ export const states = {
|
|
|
619
449
|
},
|
|
620
450
|
},
|
|
621
451
|
webhook_endpoint: {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
initial: 'enabled',
|
|
625
|
-
transitions: [
|
|
626
|
-
...['enabled', 'disabled'].map((to) => ({ operation: 'PostWebhookEndpointsWebhookEndpoint', from: '*' as const, to, source: 'https://docs.stripe.com/api/webhook_endpoints/update#update_webhook_endpoint-disabled' })),
|
|
627
|
-
],
|
|
628
|
-
},
|
|
629
|
-
},
|
|
452
|
+
// (disabling and enabling an endpoint by its update is the gap here: ../manifest.ts unmodeled)
|
|
453
|
+
state: { status: { initial: 'enabled', transitions: [] } },
|
|
630
454
|
},
|
|
631
455
|
account: {
|
|
632
456
|
notState: ['business_type', 'type'],
|
|
@@ -639,10 +463,8 @@ export const states = {
|
|
|
639
463
|
status: {
|
|
640
464
|
initial: 'unrequested',
|
|
641
465
|
transitions: [
|
|
642
|
-
|
|
466
|
+
// (requesting or unrequesting a capability by its update is the gap here: ../manifest.ts unmodeled)
|
|
643
467
|
// Stripe reviews the account's requirements
|
|
644
|
-
by('vendor', ['pending', 'inactive'], 'active', 'https://docs.stripe.com/connect/account-capabilities#capability-status'),
|
|
645
|
-
by('vendor', ['active', 'pending'], 'inactive', 'https://docs.stripe.com/connect/account-capabilities#capability-status'),
|
|
646
468
|
],
|
|
647
469
|
},
|
|
648
470
|
},
|
|
@@ -653,25 +475,17 @@ export const states = {
|
|
|
653
475
|
},
|
|
654
476
|
application_fee: {
|
|
655
477
|
state: { refunded: { initial: false, transitions: [
|
|
656
|
-
|
|
657
|
-
// a refund of its charge with refund_application_fee refunds it in proportion
|
|
658
|
-
...['PostRefunds', 'PostChargesChargeRefunds'].map((operation) => ({ operation, from: ['false'], to: 'true', source: 'https://docs.stripe.com/api/refunds/create#create_refund-refund_application_fee' })),
|
|
478
|
+
{ operation: 'PostRefunds', from: ['false'], to: 'true', source: 'https://docs.stripe.com/connect/destination-charges#issue-refunds' },
|
|
659
479
|
] } },
|
|
660
480
|
},
|
|
661
481
|
file: {
|
|
662
482
|
notState: ['purpose'],
|
|
663
483
|
},
|
|
664
484
|
'identity.verification_session': {
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
by('external', ['requires_input'], 'processing', 'https://docs.stripe.com/identity/how-sessions-work'),
|
|
670
|
-
by('vendor', ['processing'], 'verified', 'https://docs.stripe.com/identity/how-sessions-work'),
|
|
671
|
-
by('vendor', ['processing'], 'requires_input', 'https://docs.stripe.com/identity/how-sessions-work'),
|
|
672
|
-
],
|
|
673
|
-
},
|
|
674
|
-
},
|
|
485
|
+
// the person verifies on Stripe's hosted page, and Stripe decides (docs.stripe.com/identity/how-sessions-work);
|
|
486
|
+
// sessions are the gap here (../manifest.ts unmodeled) and a vendor-backed World reads them back from its root,
|
|
487
|
+
// so no move is the twin's
|
|
488
|
+
state: { status: { initial: 'requires_input', transitions: [] } },
|
|
675
489
|
notState: ['type'],
|
|
676
490
|
},
|
|
677
491
|
'identity.verification_report': {
|
|
@@ -681,11 +495,12 @@ export const states = {
|
|
|
681
495
|
notState: ['locale'],
|
|
682
496
|
},
|
|
683
497
|
'billing_portal.configuration': {
|
|
684
|
-
state: { active: { initial: true, transitions:
|
|
498
|
+
state: { active: { initial: true, transitions: [] } },
|
|
685
499
|
notState: ['is_default'],
|
|
686
500
|
},
|
|
687
501
|
tax_rate: {
|
|
688
|
-
|
|
502
|
+
// (archiving a rate by its update is the gap here: ../manifest.ts unmodeled)
|
|
503
|
+
state: { active: { initial: true, transitions: [] } },
|
|
689
504
|
notState: ['jurisdiction_level', 'rate_type', 'state', 'tax_type'],
|
|
690
505
|
},
|
|
691
506
|
'tax.settings': {
|
|
@@ -702,7 +517,8 @@ export const states = {
|
|
|
702
517
|
notState: ['type'],
|
|
703
518
|
},
|
|
704
519
|
payment_link: {
|
|
705
|
-
|
|
520
|
+
// (switching a link off by its update is the gap here: ../manifest.ts unmodeled)
|
|
521
|
+
state: { active: { initial: true, transitions: [] } },
|
|
706
522
|
notState: ['billing_address_collection', 'customer_creation', 'payment_method_collection', 'submit_type'],
|
|
707
523
|
},
|
|
708
524
|
quote: {
|
|
@@ -710,26 +526,22 @@ export const states = {
|
|
|
710
526
|
notState: ['collection_method'],
|
|
711
527
|
},
|
|
712
528
|
review: {
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
transitions: [
|
|
717
|
-
// (Stripe also closes one when the payment is refunded or disputed; the twin's close only on approval)
|
|
718
|
-
{ operation: 'PostReviewsReviewApprove', from: ['true'], to: 'false', refusal: { status: 400, code: 'review_already_closed', message: 'This review has already been closed.' }, source: 'https://docs.stripe.com/api/radar/reviews/approve' },
|
|
719
|
-
],
|
|
720
|
-
},
|
|
721
|
-
},
|
|
529
|
+
// an elevated-risk card's charge opens one (shared.ts openReview); approving it is the gap here (../manifest.ts
|
|
530
|
+
// unmodeled), and Stripe closes one when the payment is refunded or disputed, which the twin does not
|
|
531
|
+
state: { open: { initial: true, transitions: [] } },
|
|
722
532
|
notState: ['closed_reason', 'opened_reason'],
|
|
723
533
|
},
|
|
724
534
|
'radar.value_list': {
|
|
725
535
|
notState: ['item_type'],
|
|
726
536
|
},
|
|
727
537
|
'issuing.cardholder': {
|
|
728
|
-
|
|
538
|
+
// (a cardholder's status set by its update is the gap here: ../manifest.ts unmodeled)
|
|
539
|
+
state: { status: { initial: 'active', transitions: [] } },
|
|
729
540
|
notState: ['type'],
|
|
730
541
|
},
|
|
731
542
|
'issuing.card': {
|
|
732
|
-
|
|
543
|
+
// a card is made inactive unless its create names a status: "Defaults to inactive" (docs.stripe.com/api/issuing/cards/create)
|
|
544
|
+
state: { status: { initial: 'inactive', transitions: setBy('PostIssuingCardsCard', ['active', 'inactive', 'canceled'], 'https://docs.stripe.com/api/issuing/cards/update') } },
|
|
733
545
|
notState: ['cancellation_reason', 'replacement_reason', 'type'],
|
|
734
546
|
},
|
|
735
547
|
'issuing.authorization': {
|
|
@@ -740,34 +552,15 @@ export const states = {
|
|
|
740
552
|
notState: ['type', 'wallet'],
|
|
741
553
|
},
|
|
742
554
|
'issuing.dispute': {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
transitions: [
|
|
747
|
-
{ operation: 'PostIssuingDisputesDisputeSubmit', from: ['unsubmitted'], to: 'submitted', refusal: { status: 400, code: 'dispute_unsubmitted_required', message: 'This dispute has already been submitted (status {from}).' }, source: 'spec:PostIssuingDisputesDisputeSubmit "Submits an Issuing <code>Dispute</code> to the card network."' },
|
|
748
|
-
// (Stripe decides a submitted dispute won or lost and expires one never submitted; the twin decides none)
|
|
749
|
-
],
|
|
750
|
-
},
|
|
751
|
-
},
|
|
555
|
+
// (submitting one to the network is the gap here, ../manifest.ts unmodeled, and Stripe decides a submitted dispute
|
|
556
|
+
// won or lost and expires one never submitted: a vendor-backed World reads them back from its root)
|
|
557
|
+
state: { status: { initial: 'unsubmitted', transitions: [] } },
|
|
752
558
|
notState: ['loss_reason'],
|
|
753
559
|
},
|
|
754
|
-
'issuing.token': {
|
|
755
|
-
state: { status: { initial: 'active', transitions: [...setBy('PostIssuingTokensToken', ['active', 'deleted', 'suspended'], 'https://docs.stripe.com/api/issuing/tokens/update')] } },
|
|
756
|
-
notState: ['network', 'wallet_provider'],
|
|
757
|
-
},
|
|
758
560
|
'issuing.personalization_design': {
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
initial: 'review',
|
|
763
|
-
transitions: [
|
|
764
|
-
...setBy('PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignActivate', ['active'], 'spec:PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignActivate "to <code>active</code>"'),
|
|
765
|
-
...setBy('PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignDeactivate', ['inactive'], 'spec:PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignDeactivate "to <code>inactive</code>"'),
|
|
766
|
-
...setBy('PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignReject', ['rejected'], 'spec:PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignReject "to <code>rejected</code>"'),
|
|
767
|
-
// (outside test mode Stripe's review decides it; the twin is test mode, where these helpers do)
|
|
768
|
-
],
|
|
769
|
-
},
|
|
770
|
-
},
|
|
561
|
+
// a new design waits on Stripe's review (docs.stripe.com/issuing/cards/physical/personalization-design); designs and
|
|
562
|
+
// the test helpers that decide them are the gap here (../manifest.ts unmodeled), read back from a vendor-backed root
|
|
563
|
+
state: { status: { initial: 'review', transitions: [] } },
|
|
771
564
|
},
|
|
772
565
|
'terminal.reader': {
|
|
773
566
|
notState: ['device_type'],
|
|
@@ -784,7 +577,7 @@ export const states = {
|
|
|
784
577
|
// later: "USA (USD) ACH Debit Transfer 5 days" (docs.stripe.com/connect/top-ups; semantics/terminal.ts)
|
|
785
578
|
initial: 'pending',
|
|
786
579
|
transitions: [
|
|
787
|
-
|
|
580
|
+
// (canceling a pending top-up is the gap here: ../manifest.ts unmodeled)
|
|
788
581
|
// to the Issuing balance alike: "Your top-ups can take up to 5 business days to become available"
|
|
789
582
|
// (docs.stripe.com/issuing/funding/balance)
|
|
790
583
|
by('vendor', ['pending'], 'succeeded', 'https://docs.stripe.com/connect/top-ups'),
|
|
@@ -799,17 +592,13 @@ export const states = {
|
|
|
799
592
|
notState: ['is_default'],
|
|
800
593
|
},
|
|
801
594
|
'treasury.outbound_payment': {
|
|
802
|
-
state: { status:
|
|
595
|
+
state: { status: flow },
|
|
803
596
|
},
|
|
804
597
|
'treasury.outbound_transfer': {
|
|
805
|
-
state: { status:
|
|
598
|
+
state: { status: flow },
|
|
806
599
|
},
|
|
807
600
|
'treasury.inbound_transfer': {
|
|
808
|
-
|
|
809
|
-
// been "confirmed""; in test mode the succeed helper confirms them (docs.stripe.com/api/treasury/inbound_transfers)
|
|
810
|
-
state: { status: flowCancel('PostTreasuryInboundTransfersInboundTransferCancel', 'inbound_transfer_not_cancelable', 'InboundTransfer', [
|
|
811
|
-
{ operation: 'PostTestHelpersTreasuryInboundTransfersIdSucceed', from: ['processing'], to: 'succeeded', refusal: { status: 400, code: 'inbound_transfer_not_processing', message: 'This InboundTransfer cannot succeed because it is no longer processing.' }, source: 'https://docs.stripe.com/api/treasury/inbound_transfers' },
|
|
812
|
-
]) },
|
|
601
|
+
state: { status: flow },
|
|
813
602
|
},
|
|
814
603
|
'treasury.received_credit': {
|
|
815
604
|
state: { status: { initial: 'succeeded', transitions: [] } },
|
|
@@ -821,15 +610,9 @@ export const states = {
|
|
|
821
610
|
},
|
|
822
611
|
'treasury.transaction': {
|
|
823
612
|
notState: ['flow_type'],
|
|
824
|
-
//
|
|
825
|
-
//
|
|
826
|
-
|
|
827
|
-
// flow settles; the twin's outbound flows stay processing)
|
|
828
|
-
state: { status: { initial: 'open', transitions: [
|
|
829
|
-
...['PostTreasuryOutboundPaymentsIdCancel', 'PostTreasuryOutboundTransfersOutboundTransferCancel', 'PostTreasuryInboundTransfersInboundTransferCancel'].map((operation) => ({ operation, from: ['open'], to: 'void', source: 'https://docs.stripe.com/api/treasury/transactions/object' })),
|
|
830
|
-
// an inbound transfer's transaction posts when it succeeds ("a transaction is created and posted")
|
|
831
|
-
{ operation: 'PostTestHelpersTreasuryInboundTransfersIdSucceed', from: ['open'], to: 'posted', source: 'https://docs.stripe.com/api/treasury/inbound_transfers' },
|
|
832
|
-
] } },
|
|
613
|
+
// a transaction posts as its flow settles and is voided when its flow is canceled, both Stripe's (the flows are the
|
|
614
|
+
// gap here: ../manifest.ts unmodeled)
|
|
615
|
+
state: { status: { initial: 'open', transitions: [] } },
|
|
833
616
|
},
|
|
834
617
|
'treasury.transaction_entry': {
|
|
835
618
|
notState: ['flow_type', 'type'],
|
|
@@ -840,11 +623,9 @@ export const states = {
|
|
|
840
623
|
// an order is created confirmed: the create page's example answers "status": "confirmed" with confirmed_at set
|
|
841
624
|
// (docs.stripe.com/api/climate/order/create)
|
|
842
625
|
initial: 'confirmed',
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
// (Stripe funds, confirms and delivers an order over months; the twin's orders stay where they are made)
|
|
847
|
-
],
|
|
626
|
+
// (Stripe funds, confirms and delivers an order over months, and cancels one asked to; orders are the gap here,
|
|
627
|
+
// ../manifest.ts unmodeled, read back from a vendor-backed root)
|
|
628
|
+
transitions: [],
|
|
848
629
|
},
|
|
849
630
|
},
|
|
850
631
|
notState: ['cancellation_reason'],
|
|
@@ -853,10 +634,9 @@ export const states = {
|
|
|
853
634
|
state: {
|
|
854
635
|
status: {
|
|
855
636
|
initial: 'active',
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
],
|
|
637
|
+
// (disconnecting one is the gap here, ../manifest.ts unmodeled; Stripe marks an account inactive when the bank
|
|
638
|
+
// link breaks, and active again)
|
|
639
|
+
transitions: [],
|
|
860
640
|
},
|
|
861
641
|
},
|
|
862
642
|
notState: ['category', 'subcategory'],
|
|
@@ -867,17 +647,16 @@ export const states = {
|
|
|
867
647
|
},
|
|
868
648
|
'reporting.report_run': {
|
|
869
649
|
// "When first created, the object appears with status="pending""; "When the run completes, Stripe updates the
|
|
870
|
-
// object, and it has a status of succeeded" (docs.stripe.com/reports/api
|
|
871
|
-
// (
|
|
872
|
-
state: { status: { initial: 'pending', transitions: [
|
|
650
|
+
// object, and it has a status of succeeded" (docs.stripe.com/reports/api). Report runs are the gap here
|
|
651
|
+
// (../manifest.ts unmodeled), read back from a vendor-backed root, so Stripe's move is not the twin's.
|
|
652
|
+
state: { status: { initial: 'pending', transitions: [] } },
|
|
873
653
|
},
|
|
874
654
|
product: {
|
|
875
|
-
state: { active: { initial: true, transitions:
|
|
655
|
+
state: { active: { initial: true, transitions: [] } },
|
|
876
656
|
},
|
|
877
657
|
price: {
|
|
878
658
|
notState: ['billing_scheme', 'tax_behavior', 'tiers_mode', 'type'],
|
|
879
|
-
// deleting a plan leaves its price
|
|
880
|
-
|
|
881
|
-
state: { active: { initial: true, transitions: [...flag('PostPricesPrice', 'https://docs.stripe.com/api/prices/update#update_price-active'), { operation: 'DeletePlansPlan', from: ['true'], to: 'false', source: 'https://docs.stripe.com/api/plans/delete' }] } },
|
|
659
|
+
// (deleting a plan, which leaves its price inactive, is the gap here: ../manifest.ts unmodeled)
|
|
660
|
+
state: { active: { initial: true, transitions: [] } },
|
|
882
661
|
},
|
|
883
662
|
} satisfies Record<string, { state?: Record<string, StateField>; notState?: string[] }>;
|