@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.
Files changed (253) hide show
  1. package/README.md +51 -21
  2. package/dist/generated/pack-facts.json +3 -3
  3. package/dist/journeys/coverage.json +596 -0
  4. package/dist/journeys/customer-life.json +6108 -0
  5. package/dist/journeys/customer-life.outline.md +371 -0
  6. package/dist/journeys/decisions.json +677 -0
  7. package/dist/journeys/demand.json +706 -0
  8. package/dist/journeys/score.json +853 -0
  9. package/dist/journeys/unreachable.json +102 -0
  10. package/dist/journeys/vendor-examples.json +8010 -0
  11. package/dist/spec/SOURCE.md +5 -1
  12. package/dist/spec/doc-examples.json +445 -0
  13. package/dist/spec/sources.json +135 -207
  14. package/dist/src/cli.d.ts +0 -1
  15. package/dist/src/cli.js +0 -2
  16. package/dist/src/engine/after-payment.d.ts +0 -1
  17. package/dist/src/engine/after-payment.js +0 -1
  18. package/dist/src/engine/common.d.ts +5 -5
  19. package/dist/src/engine/common.js +13 -11
  20. package/dist/src/engine/connect.d.ts +0 -21
  21. package/dist/src/engine/connect.js +1 -82
  22. package/dist/src/engine/customers.d.ts +0 -8
  23. package/dist/src/engine/customers.js +0 -22
  24. package/dist/src/engine/display.d.ts +0 -3
  25. package/dist/src/engine/display.js +0 -3
  26. package/dist/src/engine/files.d.ts +0 -7
  27. package/dist/src/engine/files.js +0 -10
  28. package/dist/src/engine/invoices.d.ts +6 -9
  29. package/dist/src/engine/invoices.js +56 -29
  30. package/dist/src/engine/issuing.d.ts +6 -8
  31. package/dist/src/engine/issuing.js +19 -10
  32. package/dist/src/engine/ledger.d.ts +0 -1
  33. package/dist/src/engine/ledger.js +0 -1
  34. package/dist/src/engine/oauth.js +1 -2
  35. package/dist/src/engine/refunds.js +2 -27
  36. package/dist/src/engine/stripe.d.ts +2 -7
  37. package/dist/src/engine/stripe.js +34 -83
  38. package/dist/src/engine/subscriptions.d.ts +0 -1
  39. package/dist/src/engine/subscriptions.js +0 -1
  40. package/dist/src/engine/version.d.ts +0 -2
  41. package/dist/src/engine/version.js +17 -6
  42. package/dist/src/fetch.js +2 -2
  43. package/dist/src/generated/surface.gen.json +1 -1
  44. package/dist/src/generated/ui.gen.json +1 -1
  45. package/dist/src/index.js +5 -3
  46. package/dist/src/manifest.js +348 -220
  47. package/dist/src/screens/billing-portal.js +5 -5
  48. package/dist/src/screens/{connect-oauth.d.ts → checkout-js.d.ts} +0 -1
  49. package/dist/src/screens/checkout-js.js +84 -0
  50. package/dist/src/screens/connect-onboarding.js +3 -3
  51. package/dist/src/screens/dashboard.d.ts +1 -1
  52. package/dist/src/screens/dashboard.js +52 -53
  53. package/dist/src/screens/stripe-js.js +53 -93
  54. package/dist/src/semantics/account.d.ts +2 -0
  55. package/dist/src/semantics/account.js +8 -0
  56. package/dist/src/semantics/accounts.js +3 -1
  57. package/dist/src/semantics/around.js +3 -6
  58. package/dist/src/semantics/billing_portal.d.ts +0 -1
  59. package/dist/src/semantics/billing_portal.js +1 -7
  60. package/dist/src/semantics/checkout.d.ts +1 -1
  61. package/dist/src/semantics/checkout.js +18 -15
  62. package/dist/src/semantics/clock.js +1 -7
  63. package/dist/src/semantics/customers.d.ts +1 -2
  64. package/dist/src/semantics/customers.js +32 -38
  65. package/dist/src/semantics/entitlements.d.ts +0 -2
  66. package/dist/src/semantics/entitlements.js +4 -50
  67. package/dist/src/semantics/events.d.ts +2 -3
  68. package/dist/src/semantics/events.js +6 -11
  69. package/dist/src/semantics/index.js +16 -38
  70. package/dist/src/semantics/invoices.d.ts +3 -8
  71. package/dist/src/semantics/invoices.js +134 -127
  72. package/dist/src/semantics/issuing.d.ts +6 -0
  73. package/dist/src/semantics/issuing.js +77 -0
  74. package/dist/src/semantics/payment_intents.d.ts +0 -1
  75. package/dist/src/semantics/payment_intents.js +5 -13
  76. package/dist/src/semantics/payment_methods.d.ts +0 -2
  77. package/dist/src/semantics/payment_methods.js +2 -26
  78. package/dist/src/semantics/payouts.d.ts +1 -0
  79. package/dist/src/semantics/payouts.js +8 -1
  80. package/dist/src/semantics/products.d.ts +1 -3
  81. package/dist/src/semantics/products.js +3 -14
  82. package/dist/src/semantics/refunds.d.ts +1 -2
  83. package/dist/src/semantics/refunds.js +1 -5
  84. package/dist/src/semantics/shared.d.ts +64 -336
  85. package/dist/src/semantics/shared.js +326 -1336
  86. package/dist/src/semantics/states.d.ts +20 -89
  87. package/dist/src/semantics/states.js +116 -334
  88. package/dist/src/semantics/subscriptions.d.ts +1 -2
  89. package/dist/src/semantics/subscriptions.js +23 -145
  90. package/dist/src/semantics/test_helpers.d.ts +2 -5
  91. package/dist/src/semantics/test_helpers.js +121 -43
  92. package/dist/src/semantics/webhook_endpoints.d.ts +2 -0
  93. package/dist/src/semantics/webhook_endpoints.js +26 -0
  94. package/generated/pack-facts.json +3 -3
  95. package/journeys/coverage.json +596 -0
  96. package/journeys/customer-life.json +6108 -0
  97. package/journeys/customer-life.outline.md +371 -0
  98. package/journeys/decisions.json +677 -0
  99. package/journeys/demand.json +706 -0
  100. package/journeys/score.json +853 -0
  101. package/journeys/unreachable.json +102 -0
  102. package/journeys/vendor-examples.json +8010 -0
  103. package/package.json +4 -3
  104. package/spec/SOURCE.md +5 -1
  105. package/spec/doc-examples.json +445 -0
  106. package/spec/sources.json +135 -207
  107. package/src/cli.ts +0 -3
  108. package/src/engine/after-payment.ts +0 -2
  109. package/src/engine/balance.ts +0 -1
  110. package/src/engine/checkout.ts +0 -1
  111. package/src/engine/common.ts +19 -17
  112. package/src/engine/connect.ts +1 -79
  113. package/src/engine/customers.ts +0 -25
  114. package/src/engine/display.ts +2 -3
  115. package/src/engine/files.ts +0 -13
  116. package/src/engine/invoices.ts +64 -33
  117. package/src/engine/issuing.ts +16 -11
  118. package/src/engine/ledger.ts +0 -2
  119. package/src/engine/oauth.ts +1 -2
  120. package/src/engine/onboarding.ts +0 -1
  121. package/src/engine/params.ts +0 -1
  122. package/src/engine/payment-intents.ts +0 -1
  123. package/src/engine/payment-methods.ts +0 -1
  124. package/src/engine/refunds.ts +2 -28
  125. package/src/engine/renewals.ts +0 -1
  126. package/src/engine/stripe.ts +35 -83
  127. package/src/engine/subscriptions.ts +0 -3
  128. package/src/engine/transfers.ts +0 -1
  129. package/src/engine/version.ts +15 -7
  130. package/src/engine/webhooks.ts +0 -2
  131. package/src/fetch.ts +2 -2
  132. package/src/generated/surface.gen.json +1 -1
  133. package/src/generated/ui.gen.json +1 -1
  134. package/src/index.ts +6 -4
  135. package/src/manifest.ts +348 -220
  136. package/src/screens/billing-portal.tsx +5 -7
  137. package/src/screens/checkout-js.ts +80 -0
  138. package/src/screens/checkout.tsx +0 -2
  139. package/src/screens/connect-onboarding.tsx +3 -5
  140. package/src/screens/dashboard.tsx +53 -73
  141. package/src/screens/stripe-js.ts +57 -125
  142. package/src/semantics/account.ts +10 -0
  143. package/src/semantics/account_links.ts +0 -1
  144. package/src/semantics/accounts.ts +8 -7
  145. package/src/semantics/apps.ts +1 -2
  146. package/src/semantics/around.ts +3 -6
  147. package/src/semantics/balance.ts +0 -1
  148. package/src/semantics/balance_transactions.ts +1 -3
  149. package/src/semantics/billing_portal.ts +1 -7
  150. package/src/semantics/charges.ts +1 -6
  151. package/src/semantics/checkout.ts +16 -12
  152. package/src/semantics/clock.ts +1 -8
  153. package/src/semantics/coupons.ts +0 -1
  154. package/src/semantics/customers.ts +32 -35
  155. package/src/semantics/doors.ts +2 -3
  156. package/src/semantics/entitlements.ts +1 -45
  157. package/src/semantics/events.ts +7 -14
  158. package/src/semantics/index.ts +16 -38
  159. package/src/semantics/invoice_payments.ts +1 -3
  160. package/src/semantics/invoices.ts +124 -119
  161. package/src/semantics/issuing.ts +74 -0
  162. package/src/semantics/payment_intents.ts +10 -17
  163. package/src/semantics/payment_methods.ts +2 -25
  164. package/src/semantics/payouts.ts +9 -3
  165. package/src/semantics/prices.ts +0 -1
  166. package/src/semantics/products.ts +4 -14
  167. package/src/semantics/promotion_codes.ts +0 -1
  168. package/src/semantics/radar.ts +1 -3
  169. package/src/semantics/refunds.ts +2 -11
  170. package/src/semantics/shared.ts +350 -1382
  171. package/src/semantics/states.ts +109 -330
  172. package/src/semantics/subscriptions.ts +25 -145
  173. package/src/semantics/test_helpers.ts +109 -43
  174. package/src/semantics/transfers.ts +1 -3
  175. package/src/semantics/webhook_endpoints.ts +27 -0
  176. package/src/server.ts +0 -1
  177. package/dist/src/engine/billing.d.ts +0 -5
  178. package/dist/src/engine/billing.js +0 -6
  179. package/dist/src/engine/charges.d.ts +0 -6
  180. package/dist/src/engine/charges.js +0 -13
  181. package/dist/src/engine/connect-settings.d.ts +0 -16
  182. package/dist/src/engine/connect-settings.js +0 -26
  183. package/dist/src/engine/coupons.d.ts +0 -0
  184. package/dist/src/engine/coupons.js +0 -5
  185. package/dist/src/engine/credit-notes.d.ts +0 -1
  186. package/dist/src/engine/credit-notes.js +0 -5
  187. package/dist/src/engine/disputes.d.ts +0 -0
  188. package/dist/src/engine/disputes.js +0 -9
  189. package/dist/src/engine/entitlements.d.ts +0 -1
  190. package/dist/src/engine/entitlements.js +0 -4
  191. package/dist/src/engine/ephemeral-keys.d.ts +0 -0
  192. package/dist/src/engine/ephemeral-keys.js +0 -4
  193. package/dist/src/engine/front.d.ts +0 -5
  194. package/dist/src/engine/front.js +0 -25
  195. package/dist/src/engine/payment-links.d.ts +0 -1
  196. package/dist/src/engine/payment-links.js +0 -5
  197. package/dist/src/engine/plans.d.ts +0 -6
  198. package/dist/src/engine/plans.js +0 -26
  199. package/dist/src/engine/platform.d.ts +0 -39
  200. package/dist/src/engine/platform.js +0 -34
  201. package/dist/src/engine/products.d.ts +0 -0
  202. package/dist/src/engine/products.js +0 -4
  203. package/dist/src/engine/setup-intents.d.ts +0 -1
  204. package/dist/src/engine/setup-intents.js +0 -4
  205. package/dist/src/engine/subscription-schedules.d.ts +0 -3
  206. package/dist/src/engine/subscription-schedules.js +0 -1
  207. package/dist/src/engine/tax.d.ts +0 -7
  208. package/dist/src/engine/tax.js +0 -28
  209. package/dist/src/engine/terminal.d.ts +0 -4
  210. package/dist/src/engine/terminal.js +0 -20
  211. package/dist/src/engine/test-clocks.d.ts +0 -5
  212. package/dist/src/engine/test-clocks.js +0 -9
  213. package/dist/src/engine/tokens.d.ts +0 -7
  214. package/dist/src/engine/tokens.js +0 -20
  215. package/dist/src/engine/treasury.d.ts +0 -23
  216. package/dist/src/engine/treasury.js +0 -45
  217. package/dist/src/screens/connect-oauth.js +0 -333
  218. package/dist/src/semantics/disputes.d.ts +0 -2
  219. package/dist/src/semantics/disputes.js +0 -5
  220. package/dist/src/semantics/invoiceitems.d.ts +0 -3
  221. package/dist/src/semantics/invoiceitems.js +0 -59
  222. package/dist/src/semantics/setup_intents.d.ts +0 -3
  223. package/dist/src/semantics/setup_intents.js +0 -41
  224. package/dist/src/semantics/subscription_items.d.ts +0 -5
  225. package/dist/src/semantics/subscription_items.js +0 -55
  226. package/dist/src/semantics/subscription_schedules.d.ts +0 -4
  227. package/dist/src/semantics/subscription_schedules.js +0 -60
  228. package/src/engine/billing.ts +0 -16
  229. package/src/engine/charges.ts +0 -19
  230. package/src/engine/connect-settings.ts +0 -23
  231. package/src/engine/coupons.ts +0 -5
  232. package/src/engine/credit-notes.ts +0 -6
  233. package/src/engine/disputes.ts +0 -9
  234. package/src/engine/entitlements.ts +0 -5
  235. package/src/engine/ephemeral-keys.ts +0 -3
  236. package/src/engine/front.ts +0 -31
  237. package/src/engine/payment-links.ts +0 -6
  238. package/src/engine/plans.ts +0 -42
  239. package/src/engine/platform.ts +0 -47
  240. package/src/engine/products.ts +0 -3
  241. package/src/engine/setup-intents.ts +0 -5
  242. package/src/engine/subscription-schedules.ts +0 -10
  243. package/src/engine/tax.ts +0 -39
  244. package/src/engine/terminal.ts +0 -23
  245. package/src/engine/test-clocks.ts +0 -11
  246. package/src/engine/tokens.ts +0 -22
  247. package/src/engine/treasury.ts +0 -47
  248. package/src/screens/connect-oauth.tsx +0 -376
  249. package/src/semantics/disputes.ts +0 -12
  250. package/src/semantics/invoiceitems.ts +0 -59
  251. package/src/semantics/setup_intents.ts +0 -45
  252. package/src/semantics/subscription_items.ts +0 -54
  253. package/src/semantics/subscription_schedules.ts +0 -66
@@ -1,8 +1,3 @@
1
- const unexpected = (verb, allowed) => ({
2
- status: 400,
3
- code: 'payment_intent_unexpected_state',
4
- message: `This PaymentIntent could not be ${verb} because it has a status of {from}. ${allowed}`,
5
- });
6
1
  const OPEN = ['requires_payment_method', 'requires_confirmation', 'requires_action', 'requires_capture', 'processing'];
7
2
  /** A move no API call makes: Stripe's own (`vendor`), the clock's (`time`), or the person on a
8
3
  * Stripe-hosted page (`external`: Checkout, the billing portal, a hosted invoice or 3DS page). */
@@ -22,18 +17,9 @@ const paymentIntentStatus = {
22
17
  transitions: [
23
18
  // created with its payment method, it "enters the `requires_confirmation` status and is ready to confirm" (the same page)
24
19
  { operation: 'PostPaymentIntents', from: ['requires_payment_method'], to: 'requires_confirmation', source: 'https://docs.stripe.com/payments/paymentintents/lifecycle' },
25
- // confirm lands on one of its outcomes, decided by the payment method and the capture method
26
- ...['requires_payment_method', 'requires_action', 'requires_capture', 'succeeded'].map((to) => ({
27
- operation: 'PostPaymentIntentsIntentConfirm',
28
- from: ['requires_payment_method', 'requires_confirmation', 'requires_action'],
29
- to,
30
- refusal: { status: 400, code: 'payment_intent_unexpected_state', message: 'You cannot confirm this PaymentIntent because it has a status of {from}.' },
31
- source: 'https://docs.stripe.com/api/payment_intents/confirm',
32
- })),
33
- // a bank debit confirmed (created with `confirm`, confirmed, or verified by micro-deposits) is submitted and
34
- // processes: "The PaymentIntent you create initially has a status of `processing`"; "When the bank account is
35
- // successfully verified, Stripe returns the PaymentIntent object with a status of `processing`"
36
- ...['PostPaymentIntents', 'PostPaymentIntentsIntentConfirm'].map((operation) => ({
20
+ // a bank debit confirmed (created with `confirm`, or confirmed) is submitted and processes: "The PaymentIntent you
21
+ // create initially has a status of `processing`"
22
+ ...['PostPaymentIntents',].map((operation) => ({
37
23
  operation, from: ['requires_payment_method', 'requires_confirmation', 'requires_action'], to: 'processing',
38
24
  source: 'https://docs.stripe.com/payments/ach-direct-debit/accept-a-payment?payment-ui=direct-api',
39
25
  })),
@@ -48,33 +34,10 @@ const paymentIntentStatus = {
48
34
  source: 'spec:PostPaymentIntents "Set to `true` to attempt to [confirm this PaymentIntent](https://docs.stripe.com/api/payment_intents/confirm) immediately."',
49
35
  })),
50
36
  {
51
- operation: 'PostPaymentIntentsIntentCapture',
52
- from: ['requires_capture'],
53
- to: 'succeeded',
54
- refusal: unexpected('captured', 'Only a PaymentIntent with one of the following statuses may be captured: requires_capture.'),
55
- source: 'https://docs.stripe.com/api/payment_intents/capture',
56
- },
57
- {
58
- operation: 'PostPaymentIntentsIntentIncrementAuthorization',
59
- from: ['requires_capture'],
60
- to: 'requires_capture',
61
- refusal: unexpected('incremented', 'Only a PaymentIntent with a status of requires_capture may be incremented.'),
62
- source: 'https://docs.stripe.com/api/payment_intents/increment_authorization',
63
- },
64
- {
65
- operation: 'PostPaymentIntentsIntentCancel',
66
- from: OPEN,
67
- to: 'canceled',
68
- 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.'),
37
+ operation: 'PostPaymentIntentsIntentCancel', from: OPEN, to: 'canceled',
38
+ 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.' },
69
39
  source: 'https://docs.stripe.com/api/payment_intents/cancel',
70
40
  },
71
- {
72
- operation: 'PostPaymentIntentsIntentVerifyMicrodeposits',
73
- from: ['requires_action'],
74
- to: 'processing',
75
- refusal: { status: 400, code: 'payment_intent_unexpected_state', message: 'This PaymentIntent could not be verified because it has a status of {from}.' },
76
- source: 'https://docs.stripe.com/payments/ach-direct-debit/accept-a-payment?payment-ui=direct-api',
77
- },
78
41
  {
79
42
  // paying the invoice an intent collects for settles the intent
80
43
  operation: 'PostInvoicesInvoicePay',
@@ -82,49 +45,30 @@ const paymentIntentStatus = {
82
45
  to: 'succeeded',
83
46
  source: 'https://docs.stripe.com/api/invoices/pay',
84
47
  },
85
- ...['succeeded', 'requires_action'].map((to) => ({
86
- operation: 'PostPaymentIntentsIntentApplyCustomerBalance',
87
- from: OPEN,
88
- to,
89
- refusal: { status: 400, code: 'payment_intent_unexpected_state', message: 'This PaymentIntent could not be updated because it has a status of {from}.' },
90
- source: 'https://docs.stripe.com/api/payment_intents/apply_customer_balance',
91
- })),
92
- // a card presented to the Terminal reader processing an intent pays it (held, under manual capture)
93
- ...['succeeded', 'requires_capture'].map((to) => ({
94
- operation: 'PostTestHelpersTerminalReadersReaderPresentPaymentMethod', from: ['requires_payment_method', 'requires_confirmation'], to,
95
- source: 'https://docs.stripe.com/api/terminal/readers/present_payment_method',
96
- })),
97
48
  // the customer pays, in the billing portal, the invoice an intent collects for after its charge was declined
98
49
  by('external', ['requires_payment_method'], 'succeeded', 'https://docs.stripe.com/customer-management'),
99
50
  // Stripe also fails a bank debit back to needing a payment method, completes 3-D Secure on the bank's page and
100
51
  // 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
101
- // holds until they are captured or canceled
52
+ // holds until they are canceled. Capture, increment_authorization, verify_microdeposits, apply_customer_balance and
53
+ // the Terminal reader's present_payment_method are the gap (no demand, life step or refresh sends them:
54
+ // ../manifest.ts unmodeled), so no move of theirs is declared.
102
55
  ],
103
56
  };
104
- /** A charge's `captured`: an authorization (capture=false) is captured once. */
57
+ /** A charge's `captured`: an authorization (capture_method=manual) is captured once, by the Charges API's capture or
58
+ * its PaymentIntent's; both are the gap here (no demand, life step or refresh sends them: ../manifest.ts unmodeled), so
59
+ * a held charge stays uncaptured until its intent is canceled, and no move is declared. */
105
60
  const chargeCaptured = {
106
61
  initial: true,
107
- transitions: [
108
- {
109
- operation: 'PostChargesChargeCapture',
110
- from: ['false'],
111
- to: 'true',
112
- refusal: { status: 400, code: 'charge_already_captured', message: 'Charge ch_xxx has already been captured.' },
113
- source: 'https://docs.stripe.com/api/charges/capture',
114
- },
115
- // a manual-capture intent's capture captures the charge its confirm authorized
116
- { operation: 'PostPaymentIntentsIntentCapture', from: ['false'], to: 'true', source: 'https://docs.stripe.com/api/payment_intents/capture' },
117
- ],
62
+ transitions: [],
118
63
  };
119
- /** A charge's `refunded`: true once refunds take its whole amount, false again when a refund that made it so is
120
- * canceled. A capture or a cancel never makes it so: "`refunded` will no longer be `true` for payment cancellation
64
+ /** 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
121
65
  * flows", and a partial capture makes no Refund (docs.stripe.com/changelog/basil/2025-03-31/remove-refund-from-partial-
122
66
  * capture-and-payment-cancellation-flow). */
123
67
  const chargeRefunded = {
124
68
  initial: false,
125
69
  transitions: [
126
- ...['PostRefunds', 'PostChargesChargeRefunds', 'PostCreditNotes'].map((operation) => ({ operation, from: ['false'], to: 'true', source: 'https://docs.stripe.com/api/charges/object#charge_object-refunded' })),
127
- { operation: 'PostRefundsRefundCancel', from: ['true'], to: 'false', source: 'https://docs.stripe.com/api/refunds/cancel' },
70
+ { operation: 'PostRefunds', from: ['false'], to: 'true', source: 'https://docs.stripe.com/api/charges/object#charge_object-refunded' },
71
+ // (a charge's own refunds path, a credit note's refund and a refund's cancel are the gap here: ../manifest.ts unmodeled)
128
72
  // (an authorization left uncaptured is canceled after seven days, "Uncaptured PaymentIntents are cancelled a set
129
73
  // number of days (7 by default) after their creation", docs.stripe.com/api/payment_intents/capture; the twin keeps it)
130
74
  ],
@@ -143,63 +87,26 @@ const chargePaid = {
143
87
  // (a charge is paid when it settles; the twin's settle when made)
144
88
  transitions: [],
145
89
  };
146
- /** A refund's `status`: only one waiting on the customer (requires_action) may be canceled; the
147
- * rest is the network's. */
90
+ /** A refund's `status`: held pending for want of balance, then made; the rest is the network's. (Canceling one that
91
+ * waits on the customer is the gap here: ../manifest.ts unmodeled.) */
148
92
  const refundStatus = {
149
93
  initial: 'succeeded',
150
94
  transitions: [
151
- {
152
- operation: 'PostRefundsRefundCancel',
153
- from: ['requires_action'],
154
- to: 'canceled',
155
- refusal: { status: 400, code: 'refund_status_invalid', message: 'Refund {id} cannot be canceled because its status is {from}.' },
156
- source: 'https://docs.stripe.com/api/refunds/cancel',
157
- },
158
- // a card refund the available balance does not cover is held pending, and made once the balance covers it: "Stripe
159
- // holds the refund as pending for card transactions ... until your Stripe balance becomes sufficient"
160
- // (docs.stripe.com/refunds; semantics/refunds.ts)
161
- // (a paid invoice's credit note makes its refund as a refund call does: "Refunds: create a new refund (using
162
- // refund_amount)", docs.stripe.com/api/credit_notes/create)
163
- ...['PostRefunds', 'PostChargesChargeRefunds', 'PostCreditNotes'].map((operation) => ({ operation, from: ['succeeded'], to: 'pending', source: 'https://docs.stripe.com/refunds' })),
95
+ { operation: 'PostRefunds', from: ['succeeded'], to: 'pending', source: 'https://docs.stripe.com/refunds' },
164
96
  by('vendor', ['pending'], 'succeeded', 'https://docs.stripe.com/refunds'),
165
97
  // a bank-transfer payment's refund waits in requires_action for the customer's bank details, and is made so
166
98
  // (semantics/refunds.ts); (Stripe also fails some refunds; the twin's do not fail)
167
99
  ],
168
100
  };
169
- const SETUP_OPEN = ['requires_payment_method', 'requires_confirmation', 'requires_action'];
170
- const setupUnexpected = (verb) => ({ status: 400, code: 'setup_intent_unexpected_state', message: `You cannot ${verb} this SetupIntent because it has a status of {from}.` });
171
- /** A SetupIntent's `status`: confirm settles it (or leaves a bank account awaiting micro-deposits),
172
- * cancel ends an open one, and verification completes the micro-deposit wait. */
101
+ /** A SetupIntent's `status`: confirm settles it, or leaves a bank account awaiting micro-deposits. (Its cancel and its
102
+ * micro-deposit verification are the gap here: ../manifest.ts unmodeled.) */
173
103
  const setupIntentStatus = {
174
104
  // "After you create the SetupIntent, it has a status of `requires_payment_method` until you attach a payment method"
175
105
  // (docs.stripe.com/payments/paymentintents/lifecycle)
176
106
  initial: 'requires_payment_method',
177
107
  transitions: [
178
- // created with its payment method, it "enters the `requires_confirmation` status and is ready to confirm" (the same page)
179
- { operation: 'PostSetupIntents', from: ['requires_payment_method'], to: 'requires_confirmation', source: 'https://docs.stripe.com/payments/paymentintents/lifecycle' },
180
- ...['requires_action', 'succeeded'].map((to) => ({
181
- operation: 'PostSetupIntentsIntentConfirm',
182
- from: SETUP_OPEN,
183
- to,
184
- refusal: setupUnexpected('confirm'),
185
- source: 'https://docs.stripe.com/api/setup_intents/confirm',
186
- })),
187
- {
188
- operation: 'PostSetupIntentsIntentCancel',
189
- from: SETUP_OPEN,
190
- to: 'canceled',
191
- refusal: setupUnexpected('cancel'),
192
- source: 'https://docs.stripe.com/api/setup_intents/cancel',
193
- },
194
- {
195
- operation: 'PostSetupIntentsIntentVerifyMicrodeposits',
196
- from: ['requires_action'],
197
- to: 'succeeded',
198
- refusal: { status: 400, code: 'setup_intent_unexpected_state', message: 'This SetupIntent could not be verified because it has a status of {from}.' },
199
- source: 'https://docs.stripe.com/api/setup_intents/verify_microdeposits',
200
- },
201
- // (Stripe completes 3-D Secure on the bank's page and settles an asynchronous method from processing; the
202
- // twin has no such page and settles every method at once)
108
+ // (Stripe completes 3-D Secure on the bank's page and settles an asynchronous method from processing; the
109
+ // twin has no such page and settles every method at once)
203
110
  ],
204
111
  };
205
112
  const invoiceRefusal = (message) => ({ status: 400, code: 'invoice_not_editable', message });
@@ -208,38 +115,20 @@ const invoiceRefusal = (message) => ({ status: 400, code: 'invoice_not_editable'
208
115
  const invoiceStatus = {
209
116
  initial: 'draft',
210
117
  transitions: [
118
+ { operation: 'PostInvoicesInvoicePay', from: ['draft'], to: 'open', source: 'https://docs.stripe.com/invoicing/integration' },
211
119
  {
212
- operation: 'PostInvoicesInvoiceFinalize',
213
- from: ['draft'],
214
- to: 'open',
215
- refusal: invoiceRefusal('This invoice cannot be finalized because it has status {from}.'),
216
- source: 'https://docs.stripe.com/api/invoices/finalize',
217
- },
218
- // docs.stripe.com/invoicing/integration: "To finalize a draft invoice, use the Dashboard, send it to the customer, or pay it."
219
- { operation: 'PostInvoicesInvoiceSend', from: ['draft'], to: 'open', source: 'https://docs.stripe.com/invoicing/integration' },
220
- { operation: 'PostInvoicesInvoiceSend', from: ['open', 'paid', 'uncollectible', 'void'], source: 'https://docs.stripe.com/api/invoices/send' },
221
- {
120
+ // paying a draft finalizes it on the way: "To finalize a draft invoice, use the Dashboard, send it to the customer, or
121
+ // pay it" (docs.stripe.com/invoicing/integration)
222
122
  operation: 'PostInvoicesInvoicePay',
223
- from: ['open', 'uncollectible'],
123
+ from: ['draft', 'open', 'uncollectible'],
224
124
  to: 'paid',
225
125
  refusal: invoiceRefusal('This invoice cannot be paid because it has status {from}.'),
226
126
  refusals: { paid: { status: 400, code: 'invoice_payment_intent_requires_action', message: 'This invoice has already been paid.' } },
227
127
  source: 'https://docs.stripe.com/api/invoices/pay',
228
128
  },
229
- {
230
- operation: 'PostInvoicesInvoiceMarkUncollectible',
231
- from: ['open'],
232
- to: 'uncollectible',
233
- refusal: invoiceRefusal('Only open invoices can be marked uncollectible (this one is {from}).'),
234
- source: 'https://docs.stripe.com/api/invoices/mark_uncollectible',
235
- },
129
+ // (marking one uncollectible is the gap here: ../manifest.ts unmodeled)
236
130
  // a guard: only a draft is deleted; a finalized invoice is voided instead
237
- {
238
- operation: 'DeleteInvoicesInvoice',
239
- from: ['draft'],
240
- refusal: invoiceRefusal('You can only delete draft invoices.'),
241
- source: 'https://docs.stripe.com/api/invoices/delete',
242
- },
131
+ // in any other state it is refused (a refusal with no `to` refuses where it stands)
243
132
  {
244
133
  // only a finalized invoice is voided; a draft is deleted
245
134
  operation: 'PostInvoicesInvoiceVoid',
@@ -248,15 +137,11 @@ const invoiceStatus = {
248
137
  refusal: invoiceRefusal('This invoice cannot be voided because it has status {from}.'),
249
138
  source: 'https://docs.stripe.com/api/invoices/void',
250
139
  },
251
- // confirming or capturing the PaymentIntent an invoice collects through pays the invoice
252
- ...['PostPaymentIntentsIntentConfirm', 'PostPaymentIntentsIntentCapture'].map((operation) => ({
253
- operation, from: ['draft', 'open', 'uncollectible'], to: 'paid', source: 'https://docs.stripe.com/billing/subscriptions/build-subscriptions',
254
- })),
140
+ // confirming the PaymentIntent an invoice collects through pays the invoice
255
141
  // as does its bank debit succeeding once submitted (../engine/payment-intents.ts settleBankDebits)
256
142
  by('vendor', ['draft', 'open', 'uncollectible'], 'paid', 'https://docs.stripe.com/billing/subscriptions/build-subscriptions'),
257
- // a bank transfer that arrives in the customer's cash balance pays an open invoice that takes transfers
258
- // (automatic reconciliation; in test mode the funding helper stands in for the transfer)
259
- { operation: 'PostTestHelpersCustomersCustomerFundCashBalance', from: ['open'], to: 'paid', source: 'https://docs.stripe.com/payments/customer-balance/reconciliation' },
143
+ // (a bank transfer reconciled from the customer's cash balance pays one at Stripe; funding a cash balance is the gap
144
+ // here: ../manifest.ts unmodeled)
260
145
  // a subscription's first invoice is paid on the Checkout page that started it
261
146
  by('external', ['draft'], 'paid', 'https://docs.stripe.com/payments/checkout/how-checkout-works'),
262
147
  // a subscription's renewal, drafted at its period's end, is finalized about an hour later and charged: paid, or
@@ -268,7 +153,6 @@ const invoiceStatus = {
268
153
  { operation: 'PostSubscriptionsSubscriptionExposedId', from: ['draft'], to: 'paid', source: 'https://docs.stripe.com/billing/subscriptions/upgrade-downgrade#immediate-payment' },
269
154
  // a trialing subscription's first invoice, for nothing, is paid as it is made ("An immediate invoice is still
270
155
  // created, but the amount is 0", docs.stripe.com/billing/subscriptions/trials/free-trials)
271
- { operation: 'PostSubscriptions', from: ['draft'], to: 'paid', source: 'https://docs.stripe.com/billing/subscriptions/trials/free-trials' },
272
156
  { operation: 'PostSubscriptionsSubscriptionExposedId', from: ['draft'], to: 'open', source: 'https://docs.stripe.com/billing/subscriptions/upgrade-downgrade#immediate-payment' },
273
157
  // the customer pays an open invoice in the billing portal
274
158
  by('external', ['open'], 'paid', 'https://docs.stripe.com/customer-management'),
@@ -284,16 +168,8 @@ const subscriptionStatus = {
284
168
  { operation: 'DeleteSubscriptionsSubscriptionExposedId', from: '*', to: 'canceled', source: 'https://docs.stripe.com/api/subscriptions/cancel' },
285
169
  // (a test clock's advance moves no subscription itself: when the clock reaches its target, a trial that ended there
286
170
  // ends by time's move below, on the next request; ../engine/test-clocks.ts finishClockAdvances)
287
- // "Deleting the simulation also deletes the test customers associated with the clock and cancels their
288
- // subscriptions" (docs.stripe.com/billing/testing/test-clocks/api-advanced-usage)
289
- { operation: 'DeleteTestHelpersTestClocksTestClock', from: '*', to: 'canceled', source: 'https://docs.stripe.com/billing/testing/test-clocks/api-advanced-usage' },
290
- // "Cancels a subscription schedule and its associated subscription immediately" (docs.stripe.com/api/subscription_schedules/cancel)
291
- { operation: 'PostSubscriptionSchedulesScheduleCancel', from: ['active', 'trialing', 'past_due', 'incomplete'], to: 'canceled', source: 'https://docs.stripe.com/api/subscription_schedules/cancel' },
292
- // confirming or capturing the first invoice's PaymentIntent, or paying the invoice itself, pays it and
293
- // activates the subscription
294
- ...['PostPaymentIntentsIntentConfirm', 'PostPaymentIntentsIntentCapture'].map((operation) => ({
295
- operation, from: ['incomplete'], to: 'active', source: 'https://docs.stripe.com/billing/subscriptions/build-subscriptions',
296
- })),
171
+ // (canceling a subscription schedule, which cancels its subscription, is the gap here: ../manifest.ts unmodeled)
172
+ // confirming the first invoice's PaymentIntent, or paying the invoice itself, pays it and activates the subscription
297
173
  // or when its bank debit succeeds once submitted (../engine/payment-intents.ts settleBankDebits)
298
174
  by('vendor', ['incomplete'], 'active', 'https://docs.stripe.com/billing/subscriptions/build-subscriptions'),
299
175
  // paying the invoice a subscription waits on (its first, or a renewal that failed) makes it active
@@ -332,7 +208,6 @@ const notOpen = (verb) => ({ status: 400, code: 'checkout_session_not_open', mes
332
208
  const checkoutStatus = {
333
209
  initial: 'open',
334
210
  transitions: [
335
- { operation: 'PostCheckoutSessionsSessionExpire', from: ['open'], to: 'expired', refusal: notOpen('expire'), source: 'https://docs.stripe.com/api/checkout/sessions/expire' },
336
211
  { actor: 'external', from: ['open'], to: 'complete', refusal: notOpen('complete'), source: 'https://docs.stripe.com/api/checkout/sessions/object#checkout_session_object-status' },
337
212
  // (Stripe expires an open session at expires_at; the twin keeps it open until asked)
338
213
  ],
@@ -346,73 +221,48 @@ const checkoutPaymentStatus = {
346
221
  // (a delayed method settles a session later at Stripe; the twin's settle on the page)
347
222
  ],
348
223
  };
349
- /** A dispute's `status`: closing concedes it; the bank decides the rest, and an unanswered one is
350
- * lost when its evidence is due. */
224
+ /** A dispute's `status`: a disputed test card's charge opens one (needs_response, or warning_needs_response for an
225
+ * inquiry). Submitting evidence and closing (conceding) are the gap here (../manifest.ts unmodeled), so nothing puts a
226
+ * dispute under review for the bank to decide; the bank escalates an inquiry and loses one left unanswered at
227
+ * Stripe, and the twin decides none. */
351
228
  const disputeStatus = {
352
229
  initial: 'warning_needs_response',
353
- transitions: [
354
- { operation: 'PostDisputesDisputeClose', from: '*', to: 'lost', source: 'https://docs.stripe.com/api/disputes/close' },
355
- // submitting evidence puts a dispute (or an inquiry) under review; in test mode the evidence decides it
356
- { operation: 'PostDisputesDispute', from: ['needs_response'], to: 'under_review', source: 'https://docs.stripe.com/api/disputes/update#update_dispute-submit' },
357
- { operation: 'PostDisputesDispute', from: ['warning_needs_response'], to: 'warning_under_review', source: 'https://docs.stripe.com/api/disputes/update#update_dispute-submit' },
358
- by('vendor', ['under_review'], 'won', 'https://docs.stripe.com/testing#evidence'),
359
- by('vendor', ['under_review'], 'lost', 'https://docs.stripe.com/testing#evidence'),
360
- // (the bank decides a dispute under review, escalates an inquiry and loses one left unanswered; the twin
361
- // decides none)
362
- // Stripe also closes an inquiry the bank drops (warning_closed); no inquiry the twin holds is dropped
363
- ],
230
+ transitions: [],
364
231
  };
365
232
  /** A payout's `status`: cancel ends it; reversing cancels it and pays a reversal, never twice. */
366
233
  const payoutStatus = {
367
234
  initial: 'pending',
368
235
  transitions: [
369
- // only a pending payout can be canceled (docs.stripe.com/api/payouts/cancel); reversing a paid one makes a new
370
- // payout and leaves the original paid (semantics/balance.ts)
371
- { operation: 'PostPayoutsPayoutCancel', from: ['pending'], to: 'canceled', source: 'https://docs.stripe.com/api/payouts/cancel' },
236
+ // (canceling a pending payout and reversing a paid one are the gap here: ../manifest.ts unmodeled)
372
237
  // it arrives in the bank on its arrival date
373
238
  by('time', ['pending'], 'paid', 'https://docs.stripe.com/payouts#payout-statuses'),
374
239
  // (a bank may fail a payout at Stripe; the twin's arrive)
375
240
  ],
376
241
  };
377
- /** A credit note's `status`: issued, then voided once. */
242
+ /** A credit note's `status`: issued, then voided once at Stripe. Issuing and voiding one are the gap here
243
+ * (../manifest.ts unmodeled): a vendor-backed World reads credit notes back from its root (GetCreditNotes). */
378
244
  const creditNoteStatus = {
379
245
  initial: 'issued',
380
- transitions: [
381
- {
382
- operation: 'PostCreditNotesIdVoid',
383
- from: ['issued'],
384
- to: 'void',
385
- refusal: { status: 400, code: 'credit_note_already_voided', message: 'This credit note has already been voided.' },
386
- source: 'https://docs.stripe.com/api/credit_notes/void',
387
- },
388
- ],
246
+ transitions: [],
389
247
  };
390
- const scheduleEnded = (verb) => ({ status: 400, code: 'subscription_schedule_status_invalid', message: `This subscription schedule has status {from} and cannot be ${verb}.` });
391
- /** A subscription schedule's `status`: canceling or releasing ends it; an ended one refuses both. */
248
+ /** A subscription schedule's `status`: releasing ends it, and an ended one refuses it. (Canceling one is the gap here:
249
+ * ../manifest.ts unmodeled.) */
392
250
  const scheduleStatus = {
393
251
  initial: 'active',
394
252
  transitions: [
395
- // "A subscription schedule can only be canceled if its status is not_started or active"; "A schedule can only be
396
- // released if its status is not_started or active" (the cancel and release pages)
397
- { operation: 'PostSubscriptionSchedulesScheduleCancel', from: ['active', 'not_started'], to: 'canceled', refusal: scheduleEnded('canceled'), source: 'https://docs.stripe.com/api/subscription_schedules/cancel' },
398
- { operation: 'PostSubscriptionSchedulesScheduleRelease', from: ['active', 'not_started'], to: 'released', refusal: scheduleEnded('released'), source: 'https://docs.stripe.com/api/subscription_schedules/release' },
399
- // its phases run on the clock: it starts at its start date and ends with its last phase
400
- // (a schedule whose end_behavior is cancel completes at its end at Stripe; the twin's release)
401
- // (Stripe starts a schedule at its start date and ends it with its last phase; the twin's start at once
402
- // and end when canceled or released)
253
+ // "A schedule can only be released if its status is not_started or active" (the release page)
254
+ // its phases run on the clock: it starts at its start date and ends with its last phase
255
+ // (a schedule whose end_behavior is cancel completes at its end at Stripe; the twin's release)
256
+ // (Stripe starts a schedule at its start date and ends it with its last phase; the twin's start at once
257
+ // and end when canceled or released)
403
258
  ],
404
259
  };
405
- const quoteRefusal = (verb) => ({ status: 400, code: 'quote_invalid_status', message: `This quote cannot be ${verb} because it has status {from}.` });
406
- /** A quote's `status`: a draft is finalized into open, an open one accepted, and either canceled. */
260
+ /** A quote's `status`: at Stripe a draft is finalized into open, an open one accepted, and either canceled. Quotes are
261
+ * the gap here (../manifest.ts unmodeled): a vendor-backed World reads them back from its root (GetQuotes), so no
262
+ * move is the twin's. */
407
263
  const quoteStatus = {
408
264
  initial: 'draft',
409
- transitions: [
410
- { operation: 'PostQuotesQuoteFinalize', from: ['draft'], to: 'open', refusal: quoteRefusal('finalized'), source: 'https://docs.stripe.com/api/quotes/finalize' },
411
- { operation: 'PostQuotesQuoteAccept', from: ['open'], to: 'accepted', refusal: quoteRefusal('accepted'), source: 'https://docs.stripe.com/api/quotes/accept' },
412
- { operation: 'PostQuotesQuoteCancel', from: ['draft', 'open'], to: 'canceled', refusal: quoteRefusal('canceled'), source: 'https://docs.stripe.com/api/quotes/cancel' },
413
- // an open quote not accepted by its expires_at is canceled
414
- // (Stripe cancels an open quote at its expires_at; the twin keeps it open)
415
- ],
265
+ transitions: [],
416
266
  };
417
267
  const setBy = (operation, values, source) => values.map((to) => ({ operation, from: '*', to, source }));
418
268
  /** An Issuing authorization's `status`: approve and decline close a pending one, once; a capture
@@ -421,7 +271,9 @@ const issuingAuthorizationStatus = {
421
271
  initial: 'pending',
422
272
  transitions: [
423
273
  // approving keeps it pending (approved) until the merchant captures; declining closes it
424
- { operation: 'PostIssuingAuthorizationsAuthorizationApprove', from: ['pending'], refusal: { status: 400, code: 'authorization_already_finalized', message: 'This authorization has already been finalized (status {from}).' }, source: 'https://docs.stripe.com/issuing/purchases/authorizations' },
274
+ { operation: 'PostIssuingAuthorizationsAuthorizationApprove', from: ['pending'], source: 'https://docs.stripe.com/issuing/purchases/authorizations' },
275
+ // in any other state it is refused (a refusal with no `to` refuses where it stands)
276
+ { 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' },
425
277
  { 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' },
426
278
  { operation: 'PostTestHelpersIssuingAuthorizationsAuthorizationCapture', from: ['pending'], to: 'closed', source: 'https://docs.stripe.com/api/issuing/authorizations/test_mode_capture' },
427
279
  { operation: 'PostTestHelpersIssuingAuthorizationsAuthorizationCapture', from: ['pending'], source: 'https://docs.stripe.com/api/issuing/authorizations/test_mode_capture' },
@@ -433,34 +285,22 @@ const issuingAuthorizationStatus = {
433
285
  // capture helper above closes it, and the twin serves no other)
434
286
  ],
435
287
  };
436
- /** A Treasury flow's `status` as the twin's own operations move it: only a processing flow cancels. */
437
- const flowCancel = (operation, code, name, network) => ({
438
- initial: 'processing',
439
- transitions: [
440
- { 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' },
441
- ...network,
442
- ],
443
- });
444
- /** An outbound flow on the network: Stripe posts or fails it and a posted one may come back returned (in
445
- * test mode by test helpers the twin does not serve), so none of those moves is the twin's. */
446
- const outbound = (_doc) => [];
288
+ /** A Treasury flow's `status`: at Stripe a processing flow cancels, the network posts or fails it, and a posted one may
289
+ * come back returned. Treasury's flows are the gap here (../manifest.ts unmodeled): a vendor-backed World reads them
290
+ * back from its root, so no move is the twin's. */
291
+ const flow = { initial: 'processing', transitions: [] };
447
292
  export const states = {
448
293
  'billing.meter': {
449
294
  notState: ['event_time_window'],
450
- state: { status: { initial: 'active', transitions: [
451
- { operation: 'PostBillingMetersIdDeactivate', from: '*', to: 'inactive', source: 'https://docs.stripe.com/api/billing/meter/deactivate' },
452
- { operation: 'PostBillingMetersIdReactivate', from: '*', to: 'active', source: 'https://docs.stripe.com/api/billing/meter/reactivate' },
453
- ] } },
295
+ // (deactivating and reactivating a meter are the gap here: ../manifest.ts unmodeled)
296
+ state: { status: { initial: 'active', transitions: [] } },
454
297
  },
455
298
  'billing.credit_grant': {
456
299
  notState: ['category'],
457
300
  },
458
301
  'billing.alert': {
459
- state: { status: { initial: 'active', transitions: [
460
- { operation: 'PostBillingAlertsIdActivate', from: '*', to: 'active', source: 'https://docs.stripe.com/api/billing/alert/activate' },
461
- { operation: 'PostBillingAlertsIdDeactivate', from: '*', to: 'inactive', source: 'https://docs.stripe.com/api/billing/alert/deactivate' },
462
- { operation: 'PostBillingAlertsIdArchive', from: '*', to: 'archived', source: 'https://docs.stripe.com/api/billing/alert/archive' },
463
- ] } },
302
+ // (activating, deactivating and archiving an alert are the gap here: ../manifest.ts unmodeled)
303
+ state: { status: { initial: 'active', transitions: [] } },
464
304
  },
465
305
  payment_intent: {
466
306
  state: { status: paymentIntentStatus },
@@ -523,17 +363,10 @@ export const states = {
523
363
  notState: ['billing_address_collection', 'customer_creation', 'locale', 'mode', 'origin_context', 'payment_method_collection', 'redirect_on_completion', 'submit_type', 'ui_mode'],
524
364
  },
525
365
  mandate: {
526
- state: {
527
- status: {
528
- initial: 'active',
529
- transitions: [
530
- // a single-use mandate is spent by its payment: inactive "The mandate was rejected, revoked, or previously
531
- // used, and may not be used to initiate future payments" (../engine/payment-intents.ts settleBankDebits)
532
- by('vendor', ['active'], 'inactive', 'https://docs.stripe.com/api/mandates/object'),
533
- // (Stripe also activates a pending mandate and deactivates one revoked; the twin's are active when made)
534
- ],
535
- },
536
- },
366
+ // Stripe activates a pending mandate, spends a single-use one with its payment and deactivates one revoked; the twin
367
+ // makes no mandate (micro-deposit verification, which accepts one, is the gap here: ../manifest.ts unmodeled), so no
368
+ // move is the twin's
369
+ state: { status: { initial: 'active', transitions: [] } },
537
370
  notState: ['type'],
538
371
  },
539
372
  dispute: {
@@ -571,7 +404,8 @@ export const states = {
571
404
  notState: ['end_behavior'],
572
405
  },
573
406
  'entitlements.feature': {
574
- state: { active: { initial: true, transitions: flag('PostEntitlementsFeaturesId', 'spec:PostEntitlementsFeaturesId "Update a feature’s metadata or permanently deactivate it."') } },
407
+ // (updating a feature, which "permanently" deactivates it, is the gap here: ../manifest.ts unmodeled)
408
+ state: { active: { initial: true, transitions: [] } },
575
409
  },
576
410
  'test_helpers.test_clock': {
577
411
  state: {
@@ -580,7 +414,6 @@ export const states = {
580
414
  transitions: [
581
415
  // "advancing: The clock has started to advance but hasn't reached the specified time"; "ready: The clock has
582
416
  // completed advancing to the specified time" (docs.stripe.com/billing/testing/test-clocks/api-advanced-usage)
583
- { 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' },
584
417
  by('vendor', ['advancing'], 'ready', 'https://docs.stripe.com/billing/testing/test-clocks/api-advanced-usage'),
585
418
  // (Stripe may also end an advance in internal_failure; the twin's advances complete)
586
419
  ],
@@ -588,14 +421,8 @@ export const states = {
588
421
  },
589
422
  },
590
423
  webhook_endpoint: {
591
- state: {
592
- status: {
593
- initial: 'enabled',
594
- transitions: [
595
- ...['enabled', 'disabled'].map((to) => ({ operation: 'PostWebhookEndpointsWebhookEndpoint', from: '*', to, source: 'https://docs.stripe.com/api/webhook_endpoints/update#update_webhook_endpoint-disabled' })),
596
- ],
597
- },
598
- },
424
+ // (disabling and enabling an endpoint by its update is the gap here: ../manifest.ts unmodeled)
425
+ state: { status: { initial: 'enabled', transitions: [] } },
599
426
  },
600
427
  account: {
601
428
  notState: ['business_type', 'type'],
@@ -608,10 +435,8 @@ export const states = {
608
435
  status: {
609
436
  initial: 'unrequested',
610
437
  transitions: [
611
- ...['pending', 'inactive'].map((to) => ({ operation: 'PostAccountsAccountCapabilitiesCapability', from: '*', to, source: 'https://docs.stripe.com/api/capabilities/update' })),
612
- // Stripe reviews the account's requirements
613
- by('vendor', ['pending', 'inactive'], 'active', 'https://docs.stripe.com/connect/account-capabilities#capability-status'),
614
- by('vendor', ['active', 'pending'], 'inactive', 'https://docs.stripe.com/connect/account-capabilities#capability-status'),
438
+ // (requesting or unrequesting a capability by its update is the gap here: ../manifest.ts unmodeled)
439
+ // Stripe reviews the account's requirements
615
440
  ],
616
441
  },
617
442
  },
@@ -622,25 +447,17 @@ export const states = {
622
447
  },
623
448
  application_fee: {
624
449
  state: { refunded: { initial: false, transitions: [
625
- { operation: 'PostApplicationFeesIdRefunds', from: ['false'], to: 'true', source: 'https://docs.stripe.com/api/fee_refunds/create' },
626
- // a refund of its charge with refund_application_fee refunds it in proportion
627
- ...['PostRefunds', 'PostChargesChargeRefunds'].map((operation) => ({ operation, from: ['false'], to: 'true', source: 'https://docs.stripe.com/api/refunds/create#create_refund-refund_application_fee' })),
450
+ { operation: 'PostRefunds', from: ['false'], to: 'true', source: 'https://docs.stripe.com/connect/destination-charges#issue-refunds' },
628
451
  ] } },
629
452
  },
630
453
  file: {
631
454
  notState: ['purpose'],
632
455
  },
633
456
  'identity.verification_session': {
634
- state: {
635
- status: {
636
- initial: 'requires_input',
637
- transitions: [
638
- by('external', ['requires_input'], 'processing', 'https://docs.stripe.com/identity/how-sessions-work'),
639
- by('vendor', ['processing'], 'verified', 'https://docs.stripe.com/identity/how-sessions-work'),
640
- by('vendor', ['processing'], 'requires_input', 'https://docs.stripe.com/identity/how-sessions-work'),
641
- ],
642
- },
643
- },
457
+ // the person verifies on Stripe's hosted page, and Stripe decides (docs.stripe.com/identity/how-sessions-work);
458
+ // sessions are the gap here (../manifest.ts unmodeled) and a vendor-backed World reads them back from its root,
459
+ // so no move is the twin's
460
+ state: { status: { initial: 'requires_input', transitions: [] } },
644
461
  notState: ['type'],
645
462
  },
646
463
  'identity.verification_report': {
@@ -650,11 +467,12 @@ export const states = {
650
467
  notState: ['locale'],
651
468
  },
652
469
  'billing_portal.configuration': {
653
- state: { active: { initial: true, transitions: flag('PostBillingPortalConfigurationsConfiguration', 'https://docs.stripe.com/api/customer_portal/configurations/update') } },
470
+ state: { active: { initial: true, transitions: [] } },
654
471
  notState: ['is_default'],
655
472
  },
656
473
  tax_rate: {
657
- state: { active: { initial: true, transitions: flag('PostTaxRatesTaxRate', 'https://docs.stripe.com/api/tax_rates/update') } },
474
+ // (archiving a rate by its update is the gap here: ../manifest.ts unmodeled)
475
+ state: { active: { initial: true, transitions: [] } },
658
476
  notState: ['jurisdiction_level', 'rate_type', 'state', 'tax_type'],
659
477
  },
660
478
  'tax.settings': {
@@ -671,7 +489,8 @@ export const states = {
671
489
  notState: ['type'],
672
490
  },
673
491
  payment_link: {
674
- state: { active: { initial: true, transitions: flag('PostPaymentLinksPaymentLink', 'https://docs.stripe.com/api/payment_links/payment_links/update') } },
492
+ // (switching a link off by its update is the gap here: ../manifest.ts unmodeled)
493
+ state: { active: { initial: true, transitions: [] } },
675
494
  notState: ['billing_address_collection', 'customer_creation', 'payment_method_collection', 'submit_type'],
676
495
  },
677
496
  quote: {
@@ -679,26 +498,22 @@ export const states = {
679
498
  notState: ['collection_method'],
680
499
  },
681
500
  review: {
682
- state: {
683
- open: {
684
- initial: true,
685
- transitions: [
686
- // (Stripe also closes one when the payment is refunded or disputed; the twin's close only on approval)
687
- { 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' },
688
- ],
689
- },
690
- },
501
+ // an elevated-risk card's charge opens one (shared.ts openReview); approving it is the gap here (../manifest.ts
502
+ // unmodeled), and Stripe closes one when the payment is refunded or disputed, which the twin does not
503
+ state: { open: { initial: true, transitions: [] } },
691
504
  notState: ['closed_reason', 'opened_reason'],
692
505
  },
693
506
  'radar.value_list': {
694
507
  notState: ['item_type'],
695
508
  },
696
509
  'issuing.cardholder': {
697
- state: { status: { initial: 'active', transitions: setBy('PostIssuingCardholdersCardholder', ['active', 'inactive', 'blocked'], 'https://docs.stripe.com/api/issuing/cardholders/update') } },
510
+ // (a cardholder's status set by its update is the gap here: ../manifest.ts unmodeled)
511
+ state: { status: { initial: 'active', transitions: [] } },
698
512
  notState: ['type'],
699
513
  },
700
514
  'issuing.card': {
701
- state: { status: { initial: 'active', transitions: setBy('PostIssuingCardsCard', ['active', 'inactive', 'canceled'], 'https://docs.stripe.com/api/issuing/cards/update') } },
515
+ // a card is made inactive unless its create names a status: "Defaults to inactive" (docs.stripe.com/api/issuing/cards/create)
516
+ state: { status: { initial: 'inactive', transitions: setBy('PostIssuingCardsCard', ['active', 'inactive', 'canceled'], 'https://docs.stripe.com/api/issuing/cards/update') } },
702
517
  notState: ['cancellation_reason', 'replacement_reason', 'type'],
703
518
  },
704
519
  'issuing.authorization': {
@@ -709,34 +524,15 @@ export const states = {
709
524
  notState: ['type', 'wallet'],
710
525
  },
711
526
  'issuing.dispute': {
712
- state: {
713
- status: {
714
- initial: 'unsubmitted',
715
- transitions: [
716
- { 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."' },
717
- // (Stripe decides a submitted dispute won or lost and expires one never submitted; the twin decides none)
718
- ],
719
- },
720
- },
527
+ // (submitting one to the network is the gap here, ../manifest.ts unmodeled, and Stripe decides a submitted dispute
528
+ // won or lost and expires one never submitted: a vendor-backed World reads them back from its root)
529
+ state: { status: { initial: 'unsubmitted', transitions: [] } },
721
530
  notState: ['loss_reason'],
722
531
  },
723
- 'issuing.token': {
724
- state: { status: { initial: 'active', transitions: [...setBy('PostIssuingTokensToken', ['active', 'deleted', 'suspended'], 'https://docs.stripe.com/api/issuing/tokens/update')] } },
725
- notState: ['network', 'wallet_provider'],
726
- },
727
532
  'issuing.personalization_design': {
728
- state: {
729
- status: {
730
- // a new design waits on Stripe's review (docs.stripe.com/issuing/cards/physical/personalization-design)
731
- initial: 'review',
732
- transitions: [
733
- ...setBy('PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignActivate', ['active'], 'spec:PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignActivate "to <code>active</code>"'),
734
- ...setBy('PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignDeactivate', ['inactive'], 'spec:PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignDeactivate "to <code>inactive</code>"'),
735
- ...setBy('PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignReject', ['rejected'], 'spec:PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignReject "to <code>rejected</code>"'),
736
- // (outside test mode Stripe's review decides it; the twin is test mode, where these helpers do)
737
- ],
738
- },
739
- },
533
+ // a new design waits on Stripe's review (docs.stripe.com/issuing/cards/physical/personalization-design); designs and
534
+ // the test helpers that decide them are the gap here (../manifest.ts unmodeled), read back from a vendor-backed root
535
+ state: { status: { initial: 'review', transitions: [] } },
740
536
  },
741
537
  'terminal.reader': {
742
538
  notState: ['device_type'],
@@ -753,7 +549,7 @@ export const states = {
753
549
  // later: "USA (USD) ACH Debit Transfer 5 days" (docs.stripe.com/connect/top-ups; semantics/terminal.ts)
754
550
  initial: 'pending',
755
551
  transitions: [
756
- { operation: 'PostTopupsTopupCancel', from: ['pending'], to: 'canceled', refusal: { status: 400, code: 'topup_unexpected_state', message: 'This top-up cannot be canceled because it has a status of {from}.' }, source: 'https://docs.stripe.com/api/topups/cancel' },
552
+ // (canceling a pending top-up is the gap here: ../manifest.ts unmodeled)
757
553
  // to the Issuing balance alike: "Your top-ups can take up to 5 business days to become available"
758
554
  // (docs.stripe.com/issuing/funding/balance)
759
555
  by('vendor', ['pending'], 'succeeded', 'https://docs.stripe.com/connect/top-ups'),
@@ -768,17 +564,13 @@ export const states = {
768
564
  notState: ['is_default'],
769
565
  },
770
566
  'treasury.outbound_payment': {
771
- state: { status: flowCancel('PostTreasuryOutboundPaymentsIdCancel', 'outbound_payment_not_cancelable', 'OutboundPayment', outbound('https://docs.stripe.com/treasury/moving-money/financial-accounts/out-of/outbound-payments')) },
567
+ state: { status: flow },
772
568
  },
773
569
  'treasury.outbound_transfer': {
774
- state: { status: flowCancel('PostTreasuryOutboundTransfersOutboundTransferCancel', 'outbound_transfer_not_cancelable', 'OutboundTransfer', outbound('https://docs.stripe.com/treasury/moving-money/financial-accounts/out-of/outbound-transfers')) },
570
+ state: { status: flow },
775
571
  },
776
572
  'treasury.inbound_transfer': {
777
- // "An InboundTransfer is processing if it is created and pending. The status changes to succeeded once the funds have
778
- // been "confirmed""; in test mode the succeed helper confirms them (docs.stripe.com/api/treasury/inbound_transfers)
779
- state: { status: flowCancel('PostTreasuryInboundTransfersInboundTransferCancel', 'inbound_transfer_not_cancelable', 'InboundTransfer', [
780
- { 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' },
781
- ]) },
573
+ state: { status: flow },
782
574
  },
783
575
  'treasury.received_credit': {
784
576
  state: { status: { initial: 'succeeded', transitions: [] } },
@@ -790,15 +582,9 @@ export const states = {
790
582
  },
791
583
  'treasury.transaction': {
792
584
  notState: ['flow_type'],
793
- // void: "The Transaction never impacted the balance. For example, a Transaction would enter this state if an
794
- // OutboundPayment was initiated but then canceled" (docs.stripe.com/api/treasury/transactions/object); canceling an
795
- // outbound flow voids its open transaction (semantics/treasury.ts). (Stripe also posts an open transaction as its
796
- // flow settles; the twin's outbound flows stay processing)
797
- state: { status: { initial: 'open', transitions: [
798
- ...['PostTreasuryOutboundPaymentsIdCancel', 'PostTreasuryOutboundTransfersOutboundTransferCancel', 'PostTreasuryInboundTransfersInboundTransferCancel'].map((operation) => ({ operation, from: ['open'], to: 'void', source: 'https://docs.stripe.com/api/treasury/transactions/object' })),
799
- // an inbound transfer's transaction posts when it succeeds ("a transaction is created and posted")
800
- { operation: 'PostTestHelpersTreasuryInboundTransfersIdSucceed', from: ['open'], to: 'posted', source: 'https://docs.stripe.com/api/treasury/inbound_transfers' },
801
- ] } },
585
+ // a transaction posts as its flow settles and is voided when its flow is canceled, both Stripe's (the flows are the
586
+ // gap here: ../manifest.ts unmodeled)
587
+ state: { status: { initial: 'open', transitions: [] } },
802
588
  },
803
589
  'treasury.transaction_entry': {
804
590
  notState: ['flow_type', 'type'],
@@ -809,11 +595,9 @@ export const states = {
809
595
  // an order is created confirmed: the create page's example answers "status": "confirmed" with confirmed_at set
810
596
  // (docs.stripe.com/api/climate/order/create)
811
597
  initial: 'confirmed',
812
- transitions: [
813
- { operation: 'PostClimateOrdersOrderCancel', from: ['awaiting_funds', 'confirmed', 'open'], to: 'canceled', refusal: { status: 400, code: 'climate_order_not_cancelable', message: 'This order can no longer be canceled because it is {from}.' }, source: 'https://docs.stripe.com/api/climate/order/cancel' },
814
- // funded, confirmed by the supplier, then delivered
815
- // (Stripe funds, confirms and delivers an order over months; the twin's orders stay where they are made)
816
- ],
598
+ // (Stripe funds, confirms and delivers an order over months, and cancels one asked to; orders are the gap here,
599
+ // ../manifest.ts unmodeled, read back from a vendor-backed root)
600
+ transitions: [],
817
601
  },
818
602
  },
819
603
  notState: ['cancellation_reason'],
@@ -822,10 +606,9 @@ export const states = {
822
606
  state: {
823
607
  status: {
824
608
  initial: 'active',
825
- transitions: [
826
- { operation: 'PostFinancialConnectionsAccountsAccountDisconnect', from: '*', to: 'disconnected', source: 'https://docs.stripe.com/api/financial_connections/accounts/disconnect' },
827
- // (Stripe marks an account inactive when the bank link breaks, and active again; the twin's links hold)
828
- ],
609
+ // (disconnecting one is the gap here, ../manifest.ts unmodeled; Stripe marks an account inactive when the bank
610
+ // link breaks, and active again)
611
+ transitions: [],
829
612
  },
830
613
  },
831
614
  notState: ['category', 'subcategory'],
@@ -836,17 +619,16 @@ export const states = {
836
619
  },
837
620
  'reporting.report_run': {
838
621
  // "When first created, the object appears with status="pending""; "When the run completes, Stripe updates the
839
- // object, and it has a status of succeeded" (docs.stripe.com/reports/api; ../engine/platform.ts finishReportRuns).
840
- // (Stripe may also fail a run; the twin's runs succeed)
841
- state: { status: { initial: 'pending', transitions: [by('vendor', ['pending'], 'succeeded', 'https://docs.stripe.com/reports/api')] } },
622
+ // object, and it has a status of succeeded" (docs.stripe.com/reports/api). Report runs are the gap here
623
+ // (../manifest.ts unmodeled), read back from a vendor-backed root, so Stripe's move is not the twin's.
624
+ state: { status: { initial: 'pending', transitions: [] } },
842
625
  },
843
626
  product: {
844
- state: { active: { initial: true, transitions: flag('PostProductsId', 'https://docs.stripe.com/api/products/update#update_product-active') } },
627
+ state: { active: { initial: true, transitions: [] } },
845
628
  },
846
629
  price: {
847
630
  notState: ['billing_scheme', 'tax_behavior', 'tiers_mode', 'type'],
848
- // deleting a plan leaves its price for its subscribers, inactive: "Deleting plans means new subscribers can’t be added.
849
- // Existing subscribers aren’t affected." (docs.stripe.com/api/plans/delete; semantics/plans.ts)
850
- 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' }] } },
631
+ // (deleting a plan, which leaves its price inactive, is the gap here: ../manifest.ts unmodeled)
632
+ state: { active: { initial: true, transitions: [] } },
851
633
  },
852
634
  };