@volter/twin-stripe 0.1.2 → 2.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 +96 -27
- package/client/dashboard-api.ts +286 -0
- package/client/stripe-mirror.css +272 -159
- package/client/stripe-mirror.tsx +1384 -541
- package/dist/client/dashboard-api.d.ts +107 -0
- package/dist/client/dashboard-api.js +238 -0
- package/dist/client/dashboard-api.ts +286 -0
- package/dist/client/stripe-mirror.bundle.js +236 -0
- package/dist/client/stripe-mirror.css +275 -0
- package/dist/client/stripe-mirror.d.ts +134 -0
- package/dist/client/stripe-mirror.js +823 -0
- package/dist/client/stripe-mirror.tsx +1534 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +39 -0
- package/dist/src/generated/events.gen.json +1 -0
- package/dist/src/generated/surface.gen.json +1 -0
- package/dist/src/generated/ui.gen.json +1 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.js +75 -0
- package/dist/src/manifest.d.ts +2 -0
- package/dist/src/manifest.js +1070 -0
- package/dist/src/screens/checkout.d.ts +31 -0
- package/dist/src/screens/checkout.js +255 -0
- package/dist/src/screens/connect-oauth.d.ts +27 -0
- package/dist/src/screens/connect-oauth.js +414 -0
- package/dist/src/screens/connect-settings.d.ts +22 -0
- package/dist/src/screens/connect-settings.js +103 -0
- package/dist/src/screens/consent-skin.d.ts +4 -0
- package/dist/src/screens/consent-skin.js +18 -0
- package/dist/src/screens/financial-connections.d.ts +5 -0
- package/dist/src/screens/financial-connections.js +90 -0
- package/dist/src/screens/identity.d.ts +5 -0
- package/dist/src/screens/identity.js +86 -0
- package/dist/src/screens/industries.d.ts +1 -0
- package/dist/src/screens/industries.js +267 -0
- package/dist/src/screens/onboarding.d.ts +13 -0
- package/dist/src/screens/onboarding.js +225 -0
- package/dist/src/screens/portal.d.ts +5 -0
- package/dist/src/screens/portal.js +216 -0
- package/dist/src/screens/public-details.d.ts +5 -0
- package/dist/src/screens/public-details.js +90 -0
- package/dist/src/semantics/after-payment.d.ts +22 -0
- package/dist/src/semantics/after-payment.js +99 -0
- package/dist/src/semantics/apps-secrets.d.ts +2 -0
- package/dist/src/semantics/apps-secrets.js +54 -0
- package/dist/src/semantics/balance.d.ts +11 -0
- package/dist/src/semantics/balance.js +195 -0
- package/dist/src/semantics/billing.d.ts +2 -0
- package/dist/src/semantics/billing.js +220 -0
- package/dist/src/semantics/charges.d.ts +28 -0
- package/dist/src/semantics/charges.js +209 -0
- package/dist/src/semantics/checkout.d.ts +15 -0
- package/dist/src/semantics/checkout.js +316 -0
- package/dist/src/semantics/connect.d.ts +5 -0
- package/dist/src/semantics/connect.js +493 -0
- package/dist/src/semantics/coupons.d.ts +6 -0
- package/dist/src/semantics/coupons.js +92 -0
- package/dist/src/semantics/credit-notes.d.ts +2 -0
- package/dist/src/semantics/credit-notes.js +172 -0
- package/dist/src/semantics/customers.d.ts +6 -0
- package/dist/src/semantics/customers.js +429 -0
- package/dist/src/semantics/disputes.d.ts +2 -0
- package/dist/src/semantics/disputes.js +51 -0
- package/dist/src/semantics/entitlements.d.ts +2 -0
- package/dist/src/semantics/entitlements.js +95 -0
- package/dist/src/semantics/ephemeral-keys.d.ts +2 -0
- package/dist/src/semantics/ephemeral-keys.js +34 -0
- package/dist/src/semantics/files.d.ts +2 -0
- package/dist/src/semantics/files.js +125 -0
- package/dist/src/semantics/invoices.d.ts +18 -0
- package/dist/src/semantics/invoices.js +545 -0
- package/dist/src/semantics/issuing.d.ts +13 -0
- package/dist/src/semantics/issuing.js +575 -0
- package/dist/src/semantics/ledger.d.ts +59 -0
- package/dist/src/semantics/ledger.js +200 -0
- package/dist/src/semantics/payment-intents.d.ts +18 -0
- package/dist/src/semantics/payment-intents.js +404 -0
- package/dist/src/semantics/payment-links.d.ts +2 -0
- package/dist/src/semantics/payment-links.js +133 -0
- package/dist/src/semantics/payment-methods.d.ts +20 -0
- package/dist/src/semantics/payment-methods.js +140 -0
- package/dist/src/semantics/plans.d.ts +5 -0
- package/dist/src/semantics/plans.js +121 -0
- package/dist/src/semantics/platform.d.ts +9 -0
- package/dist/src/semantics/platform.js +206 -0
- package/dist/src/semantics/products.d.ts +2 -0
- package/dist/src/semantics/products.js +140 -0
- package/dist/src/semantics/radar.d.ts +2 -0
- package/dist/src/semantics/radar.js +83 -0
- package/dist/src/semantics/refunds.d.ts +9 -0
- package/dist/src/semantics/refunds.js +195 -0
- package/dist/src/semantics/renewals.d.ts +47 -0
- package/dist/src/semantics/renewals.js +251 -0
- package/dist/src/semantics/setup-intents.d.ts +2 -0
- package/dist/src/semantics/setup-intents.js +84 -0
- package/dist/src/semantics/shared.d.ts +82 -0
- package/dist/src/semantics/shared.js +203 -0
- package/dist/src/semantics/subscription-schedules.d.ts +2 -0
- package/dist/src/semantics/subscription-schedules.js +119 -0
- package/dist/src/semantics/subscriptions.d.ts +11 -0
- package/dist/src/semantics/subscriptions.js +605 -0
- package/dist/src/semantics/tax.d.ts +2 -0
- package/dist/src/semantics/tax.js +197 -0
- package/dist/src/semantics/terminal.d.ts +5 -0
- package/dist/src/semantics/terminal.js +182 -0
- package/dist/src/semantics/test-cards.d.ts +4 -0
- package/dist/src/semantics/test-cards.js +7 -0
- package/dist/src/semantics/test-clocks.d.ts +6 -0
- package/dist/src/semantics/test-clocks.js +73 -0
- package/dist/src/semantics/tokens.d.ts +4 -0
- package/dist/src/semantics/tokens.js +44 -0
- package/dist/src/semantics/transfers.d.ts +2 -0
- package/dist/src/semantics/transfers.js +154 -0
- package/dist/src/semantics/treasury.d.ts +2 -0
- package/dist/src/semantics/treasury.js +377 -0
- package/dist/src/semantics/webhook-endpoints.d.ts +3 -0
- package/dist/src/semantics/webhook-endpoints.js +85 -0
- package/dist/src/stripe-budget.d.ts +55 -0
- package/dist/src/stripe-budget.js +155 -0
- package/dist/src/stripe-capabilities.d.ts +3 -0
- package/dist/src/stripe-capabilities.js +5695 -0
- package/dist/src/stripe-conformance.d.ts +43 -0
- package/dist/src/stripe-conformance.js +105 -0
- package/dist/src/stripe-connector.d.ts +161 -0
- package/dist/src/stripe-connector.js +414 -0
- package/dist/src/stripe-emit.d.ts +2 -0
- package/dist/src/stripe-emit.js +145 -0
- package/dist/src/stripe-events.d.ts +93 -0
- package/dist/src/stripe-events.js +392 -0
- package/dist/src/stripe-js.d.ts +4 -0
- package/dist/src/stripe-js.js +70 -0
- package/dist/src/stripe-mirror-ui.d.ts +15 -0
- package/dist/src/stripe-mirror-ui.js +87 -0
- package/dist/src/stripe-params.d.ts +3 -0
- package/dist/src/stripe-params.js +43 -0
- package/dist/src/stripe-perform-harness.d.ts +9 -0
- package/dist/src/stripe-perform-harness.js +26 -0
- package/dist/src/stripe-server.d.ts +33 -0
- package/dist/src/stripe-server.js +393 -0
- package/dist/src/stripe-shared.d.ts +109 -0
- package/dist/src/stripe-shared.js +276 -0
- package/dist/src/stripe-twin.d.ts +155 -0
- package/dist/src/stripe-twin.js +1232 -0
- package/dist/src/stripe-ui-conformance.d.ts +5 -0
- package/dist/src/stripe-ui-conformance.js +79 -0
- package/dist/src/stripe-ui-structure.d.ts +3 -0
- package/dist/src/stripe-ui-structure.js +168 -0
- package/dist/src/stripe-version.d.ts +12 -0
- package/dist/src/stripe-version.js +287 -0
- package/dist/test-fixtures/stripe-known-deviations.json +110 -0
- package/dist/test-fixtures/stripe-openapi-operations.SOURCE.md +14 -0
- package/dist/test-fixtures/stripe-openapi-operations.json +4717 -0
- package/dist/test-fixtures/stripe-schemas.SOURCE.md +35 -0
- package/dist/test-fixtures/stripe-schemas.json +3813 -0
- package/package.json +18 -10
- package/src/cli.ts +7 -7
- package/src/generated/events.gen.json +1 -0
- package/src/generated/surface.gen.json +1 -0
- package/src/generated/ui.gen.json +1 -0
- package/src/index.ts +34 -10
- package/src/manifest.ts +1102 -0
- package/src/screens/checkout.tsx +267 -0
- package/src/screens/connect-oauth.tsx +400 -0
- package/src/screens/connect-settings.tsx +121 -0
- package/src/screens/consent-skin.ts +20 -0
- package/src/screens/financial-connections.tsx +101 -0
- package/src/screens/identity.tsx +96 -0
- package/src/screens/industries.ts +267 -0
- package/src/screens/onboarding.tsx +243 -0
- package/src/screens/portal.tsx +220 -0
- package/src/screens/public-details.tsx +105 -0
- package/src/semantics/after-payment.ts +118 -0
- package/src/semantics/apps-secrets.ts +58 -0
- package/src/semantics/balance.ts +209 -0
- package/src/semantics/billing.ts +216 -0
- package/src/semantics/charges.ts +220 -0
- package/src/semantics/checkout.ts +310 -0
- package/src/semantics/connect.ts +487 -0
- package/src/semantics/coupons.ts +97 -0
- package/src/semantics/credit-notes.ts +168 -0
- package/src/semantics/customers.ts +432 -0
- package/src/semantics/disputes.ts +62 -0
- package/src/semantics/entitlements.ts +94 -0
- package/src/semantics/ephemeral-keys.ts +34 -0
- package/src/semantics/files.ts +143 -0
- package/src/semantics/invoices.ts +545 -0
- package/src/semantics/issuing.ts +590 -0
- package/src/semantics/ledger.ts +253 -0
- package/src/semantics/payment-intents.ts +420 -0
- package/src/semantics/payment-links.ts +148 -0
- package/src/semantics/payment-methods.ts +145 -0
- package/src/semantics/plans.ts +131 -0
- package/src/semantics/platform.ts +220 -0
- package/src/semantics/products.ts +154 -0
- package/src/semantics/radar.ts +85 -0
- package/src/semantics/refunds.ts +218 -0
- package/src/semantics/renewals.ts +274 -0
- package/src/semantics/setup-intents.ts +87 -0
- package/src/semantics/shared.ts +226 -0
- package/src/semantics/subscription-schedules.ts +129 -0
- package/src/semantics/subscriptions.ts +610 -0
- package/src/semantics/tax.ts +220 -0
- package/src/semantics/terminal.ts +195 -0
- package/src/semantics/test-cards.ts +7 -0
- package/src/semantics/test-clocks.ts +77 -0
- package/src/semantics/tokens.ts +52 -0
- package/src/semantics/transfers.ts +174 -0
- package/src/semantics/treasury.ts +383 -0
- package/src/semantics/webhook-endpoints.ts +87 -0
- package/src/stripe-budget.ts +4 -4
- package/src/stripe-capabilities.ts +2258 -380
- package/src/stripe-conformance.ts +19 -7
- package/src/stripe-connector.ts +68 -40
- package/src/stripe-emit.ts +15 -8
- package/src/stripe-events.ts +102 -40
- package/src/stripe-js.ts +70 -0
- package/src/stripe-mirror-ui.ts +28 -298
- package/src/stripe-params.ts +44 -0
- package/src/stripe-perform-harness.ts +29 -0
- package/src/stripe-server.ts +318 -38
- package/src/stripe-shared.ts +297 -0
- package/src/stripe-twin.ts +434 -5325
- package/src/stripe-ui-conformance.ts +70 -107
- package/src/stripe-ui-structure.ts +124 -348
- package/src/stripe-version.ts +281 -0
- package/test-fixtures/stripe-known-deviations.json +8 -8
- package/test-fixtures/stripe-openapi-operations.json +1188 -2855
- package/test-fixtures/stripe-schemas.json +85 -12
- package/src/stripe-form.ts +0 -35
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// Stripe's balance ledger (docs.stripe.com/api/balance_transactions): every movement of the account's money writes
|
|
2
|
+
// a balance transaction, and the balance is their sum. A captured charge adds its amount less Stripe's fee, pending
|
|
3
|
+
// until its available_on; a refund takes its amount back at once; a payout takes its amount out of what is available
|
|
4
|
+
// and is refused beyond it (balance_insufficient). A transaction's status is what the World clock says of its
|
|
5
|
+
// available_on (the machine's time move, pending → available), so a `wait` in a story settles funds as time does.
|
|
6
|
+
//
|
|
7
|
+
// Where the documentation stops and the twin decides: the fee is Stripe's standard US card pricing, 2.9% + 30¢
|
|
8
|
+
// (stripe.com/pricing), for every charge; funds become available two days after capture (Stripe's standard US
|
|
9
|
+
// payout schedule counts business days).
|
|
10
|
+
//
|
|
11
|
+
// Test mode (the twin serves livemode: false), as docs.stripe.com/testing documents it:
|
|
12
|
+
// - cards keep that delay but two: "Other test cards send funds from a successful payment to your pending balance",
|
|
13
|
+
// while 4000000000000077 and 4000003720000278 (and their test names pm_card_bypassPending,
|
|
14
|
+
// pm_card_bypassPendingInternational, tok_bypassPending, tok_bypassPendingInternational) "succeed. Funds are added
|
|
15
|
+
// directly to your available balance, bypassing your pending balance" (#available-balance; test-cards.ts). A
|
|
16
|
+
// PaymentMethod made from one keeps that (payment-methods.ts, after-payment.ts), so a saved card charged later by
|
|
17
|
+
// id bypasses too;
|
|
18
|
+
// - a US bank account debit: "Test transactions settle instantly and are added to your available test balance. This
|
|
19
|
+
// behavior differs from live mode" (ACH Direct Debit, "Test settlement behavior");
|
|
20
|
+
// - a source_transaction transfer "takes on the pending status of the associated charge"
|
|
21
|
+
// (docs.stripe.com/connect/separate-charges-and-transfers), so it is available at once when its charge is;
|
|
22
|
+
// - "Test payouts simulate a live payout but aren't processed with the bank" (docs.stripe.com/payouts#test-payouts):
|
|
23
|
+
// the payout clock (semantics/balance.ts) is live's.
|
|
24
|
+
// No other test-mode speed-up is documented, so 4242 4242 4242 4242 funds sit pending the two days.
|
|
25
|
+
//
|
|
26
|
+
// Where the documentation stops and the twin decides: every credit written available at once (a bypass or bank-debit
|
|
27
|
+
// charge, a transfer, a transfer from such a charge, an application fee, a top-up, an Issuing top-up included, a reversal) is marked not yet
|
|
28
|
+
// sent, and the drain sends its account balance.available for it ("Occurs whenever your Stripe balance has been
|
|
29
|
+
// updated (e.g., when a charge is available to be paid out). ... This event is not fired for negative
|
|
30
|
+
// transactions", docs.stripe.com/api/events/types), as it does for funds that came due after the delay. The Balance
|
|
31
|
+
// it carries is the account's at the drain: a debit or an automatic payout landing first shows in it.
|
|
32
|
+
//
|
|
33
|
+
// Each connected account keeps its own balance (docs.stripe.com/connect/account-balances): a transaction belongs to
|
|
34
|
+
// the account the request acts for (the Stripe-Account header), else to the platform. A transfer takes its amount out
|
|
35
|
+
// of the platform's available balance and adds it to the destination's; a destination charge transfers its amount
|
|
36
|
+
// less the application fee, available when the charge's funds are; a direct charge's application fee moves from the
|
|
37
|
+
// connected account to the platform.
|
|
38
|
+
import type { Semantics, SemanticsContext } from '@volter/world-core';
|
|
39
|
+
import { asOf, at, created, fail, inRange, kept, list, newest, where, type Row } from './shared.ts';
|
|
40
|
+
import { BYPASS_PENDING_CARDS } from './test-cards.ts';
|
|
41
|
+
|
|
42
|
+
const BT = 'balance_transaction';
|
|
43
|
+
const DAY = 86_400;
|
|
44
|
+
/** A ledger entry's mark: available at once, its balance.available not yet sent (settleDueEntries). */
|
|
45
|
+
const UNSENT = '_availableUnsent';
|
|
46
|
+
const BYPASS_NUMBERS = new Set(Object.values(BYPASS_PENDING_CARDS).map((c) => c.number));
|
|
47
|
+
|
|
48
|
+
/** Whether a charge's funds go straight to the available balance: a bypass card as a raw number, a test name (pm_card_*
|
|
49
|
+
* or tok_*), or a stored PaymentMethod made from one (which records it as what follows its success, after-payment.ts);
|
|
50
|
+
* or a US bank account, stored or one of Stripe's test bank accounts named as one (pm_usBankAccount_*). */
|
|
51
|
+
export function bypassesPending(ctx: SemanticsContext, card: string | undefined): boolean {
|
|
52
|
+
if (!card) return false;
|
|
53
|
+
const stored = ctx.row('payment_method', card);
|
|
54
|
+
// the row carries what the method recorded; its type is the vendor object's (ctx.get), not the row's envelope
|
|
55
|
+
if (stored) return stored._afterSuccess === 'available' || ctx.get('payment_method', card)?.type === 'us_bank_account';
|
|
56
|
+
if (/^pm_us_?bank_?account/i.test(card)) return true;
|
|
57
|
+
return BYPASS_NUMBERS.has(card.replace(/\D/g, '')) || card.replace(/^tok_/, 'pm_card_') in BYPASS_PENDING_CARDS;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const feeOf = (amount: number): number => (amount > 0 ? Math.round(amount * 0.029) + 30 : 0);
|
|
61
|
+
|
|
62
|
+
/** The connected account a request acts for, or undefined for the platform. */
|
|
63
|
+
export const actingAccount = (ctx: SemanticsContext): string | undefined => ctx.call.request.headers.get('stripe-account') ?? undefined;
|
|
64
|
+
|
|
65
|
+
/** A ledger entry on an account's balance: the acting account's unless one is named (null names the platform). */
|
|
66
|
+
async function write(ctx: SemanticsContext, fields: Row, account: string | null | undefined = actingAccount(ctx)): Promise<string> {
|
|
67
|
+
// a credit available at once waits for the drain's balance.available (settleDueEntries)
|
|
68
|
+
const unsent = (fields.status ?? 'available') === 'available' && Number(fields.net) > 0 ? { [UNSENT]: true } : {};
|
|
69
|
+
const bt = await created(ctx, BT, {}, { status: 'available', fee_details: [], description: null, exchange_rate: null, balance_type: 'payments', ...fields, ...unsent, ...(account ? { _account: account } : {}) });
|
|
70
|
+
return String(bt.id);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Whether a stored ledger entry is on this account's balance (undefined: the platform's). */
|
|
74
|
+
const onAccount = (t: Row, account: string | undefined): boolean => (typeof t._account === 'string' ? t._account : undefined) === account;
|
|
75
|
+
|
|
76
|
+
/** A captured charge's credit: its amount less the fee, pending two days unless the card bypasses the pending
|
|
77
|
+
* balance (bypassesPending), when it is due at once. The
|
|
78
|
+
* caller mints the charge's id first and stores the returned id as the charge's balance_transaction. */
|
|
79
|
+
export async function settleCharge(ctx: SemanticsContext, chargeId: string, amount: number, currency: string, card?: string): Promise<string> {
|
|
80
|
+
const now = Number(ctx.now());
|
|
81
|
+
const fee = feeOf(amount);
|
|
82
|
+
const atOnce = bypassesPending(ctx, card);
|
|
83
|
+
const id = await write(ctx, {
|
|
84
|
+
amount, currency, fee, net: amount - fee, type: 'charge', reporting_category: 'charge', source: chargeId,
|
|
85
|
+
...(atOnce ? { status: 'available', available_on: now } : { status: 'pending', available_on: now + 2 * DAY }),
|
|
86
|
+
fee_details: fee ? [{ amount: fee, application: null, currency, description: 'Stripe processing fees', type: 'stripe_fee' }] : [],
|
|
87
|
+
});
|
|
88
|
+
return id;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** A refund's debit, at once, on the acting account's balance unless one is named (null names the platform). */
|
|
92
|
+
export async function settleRefund(ctx: SemanticsContext, refundId: string, amount: number, currency: string, account: string | null | undefined = actingAccount(ctx)): Promise<string> {
|
|
93
|
+
return write(ctx, { amount: -amount, currency, fee: 0, net: -amount, type: 'refund', reporting_category: 'refund', source: refundId, available_on: Number(ctx.now()) }, account);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** What an account had available in a currency at a moment: every entry of its payments balance whose funds had come
|
|
97
|
+
* due by then. */
|
|
98
|
+
export function availableAt(ctx: SemanticsContext, account: string | undefined, currency: string, at: number): number {
|
|
99
|
+
let sum = 0;
|
|
100
|
+
for (const t of ctx.rowsRaw(BT)) {
|
|
101
|
+
if (!onAccount(t, account) || t.balance_type === 'issuing' || String(t.currency ?? 'usd') !== currency) continue;
|
|
102
|
+
const due = Number(t.available_on ?? t.created);
|
|
103
|
+
if (Number.isFinite(due) && due <= at) sum += Number(t.net ?? 0);
|
|
104
|
+
}
|
|
105
|
+
return sum;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The moments after `from` and up to `until` when funds of an account came due, in order: when a held refund can
|
|
109
|
+
* next be covered. */
|
|
110
|
+
export function fundsDueBetween(ctx: SemanticsContext, account: string | undefined, currency: string, from: number, until: number): number[] {
|
|
111
|
+
const times = ctx.rowsRaw(BT).filter((t) => onAccount(t, account) && String(t.currency ?? 'usd') === currency).map((t) => Number(t.available_on ?? t.created)).filter((d) => Number.isFinite(d) && d > from && d <= until);
|
|
112
|
+
return [...new Set(times)].sort((a, b) => a - b);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** A dispute's debit, at once: the disputed amount and the dispute fee (the twin's 1500 cents, Stripe's US $15). */
|
|
116
|
+
export async function settleDispute(ctx: SemanticsContext, disputeId: string, amount: number, currency: string): Promise<string> {
|
|
117
|
+
const fee = 1500;
|
|
118
|
+
return write(ctx, {
|
|
119
|
+
amount: -amount, currency, fee, net: -amount - fee, type: 'adjustment', reporting_category: 'dispute', source: disputeId, available_on: Number(ctx.now()),
|
|
120
|
+
fee_details: [{ amount: fee, application: null, currency, description: 'Dispute fee', type: 'stripe_fee' }],
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** A won dispute's credit, at once: the disputed amount returned (docs.stripe.com/disputes/how-disputes-work). Where the
|
|
125
|
+
* documentation stops and the twin decides: the dispute fee is not returned. */
|
|
126
|
+
export async function settleDisputeWon(ctx: SemanticsContext, disputeId: string, amount: number, currency: string): Promise<string> {
|
|
127
|
+
return write(ctx, { amount, currency, fee: 0, net: amount, type: 'adjustment', reporting_category: 'dispute_reversal', source: disputeId, available_on: Number(ctx.now()) });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** An account's ledger entries an automatic payout has not yet paid out: every entry on its payments balance that no
|
|
131
|
+
* automatic payout took (the automatic payouts' own debits excluded), as stored. */
|
|
132
|
+
export function unpaidEntries(ctx: SemanticsContext, account: string | undefined): Row[] {
|
|
133
|
+
const automatic = new Set(ctx.rowsRaw('payout').filter((p) => p.automatic === true).map((p) => p.id));
|
|
134
|
+
return ctx.rowsRaw(BT).filter((t) => onAccount(t, account) && t.balance_type !== 'issuing' && !t._payout && !automatic.has(t.source));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** An automatic payout's debit at `at`, and the entries it pays out marked with it, so the ledger lists them under
|
|
138
|
+
* the payout (docs.stripe.com/api/balance_transactions/list#balance_transaction_list-payout). */
|
|
139
|
+
export async function settleAutomaticPayout(ctx: SemanticsContext, payoutId: string, amount: number, currency: string, account: string | undefined, at: number, entries: Row[]): Promise<string> {
|
|
140
|
+
for (const t of entries) await ctx.write(BT, String(t.id), { _payout: payoutId }, 'balance_transaction.paid_out');
|
|
141
|
+
return write(ctx, { amount: -amount, currency, fee: 0, net: -amount, type: 'payout', reporting_category: 'payout', source: payoutId, available_on: at, _payout: payoutId }, account ?? null);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** A payout's debit (negative) or a cancellation's or reversal's credit (positive), at once, on the payout's account. */
|
|
145
|
+
export async function settlePayout(ctx: SemanticsContext, payoutId: string, amount: number, currency: string, account: string | null | undefined = actingAccount(ctx)): Promise<string> {
|
|
146
|
+
const type = amount < 0 ? 'payout' : 'payout_cancel';
|
|
147
|
+
return write(ctx, { amount, currency, fee: 0, net: amount, type, reporting_category: type === 'payout' ? 'payout' : 'payout_reversal', source: payoutId, available_on: Number(ctx.now()) }, account);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** A transfer's two entries: out of the platform's balance at once, into the destination's when `fromCharge` (its
|
|
151
|
+
* charge's funds' availability) comes (a plain transfer, with no `fromCharge`, moves funds available already).
|
|
152
|
+
* Answers the platform's entry. */
|
|
153
|
+
export async function settleTransfer(ctx: SemanticsContext, transferId: string, amount: number, currency: string, destination: string, fromCharge?: number, fromPending = false): Promise<string> {
|
|
154
|
+
const availableOn = fromCharge ?? Number(ctx.now());
|
|
155
|
+
const settled = availableOn <= Number(ctx.now());
|
|
156
|
+
// a transfer from a charge's pending funds (source_transaction) leaves the platform when they arrive, not before
|
|
157
|
+
const platform = await write(ctx, { amount: -amount, currency, fee: 0, net: -amount, type: 'transfer', reporting_category: 'transfer', source: transferId, ...(fromPending ? { status: settled ? 'available' : 'pending', available_on: availableOn } : { available_on: Number(ctx.now()) }) }, null);
|
|
158
|
+
await write(ctx, { amount, currency, fee: 0, net: amount, type: 'payment', reporting_category: 'charge', source: transferId, status: settled ? 'available' : 'pending', available_on: availableOn }, destination);
|
|
159
|
+
return platform;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** A transfer reversal's two entries: back into the platform's balance, out of the destination's. */
|
|
163
|
+
export async function settleTransferReversal(ctx: SemanticsContext, reversalId: string, amount: number, currency: string, destination: string): Promise<string> {
|
|
164
|
+
const platform = await write(ctx, { amount, currency, fee: 0, net: amount, type: 'transfer_refund', reporting_category: 'transfer_reversal', source: reversalId, available_on: Number(ctx.now()) }, null);
|
|
165
|
+
await write(ctx, { amount: -amount, currency, fee: 0, net: -amount, type: 'payment_refund', reporting_category: 'refund', source: reversalId, available_on: Number(ctx.now()) }, destination);
|
|
166
|
+
return platform;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** A direct charge's application fee: out of the connected account's balance, into the platform's. */
|
|
170
|
+
export async function settleApplicationFee(ctx: SemanticsContext, feeId: string, amount: number, currency: string, account: string): Promise<string> {
|
|
171
|
+
await write(ctx, { amount: -amount, currency, fee: 0, net: -amount, type: 'application_fee', reporting_category: 'platform_earning', source: feeId, available_on: Number(ctx.now()) }, account);
|
|
172
|
+
return write(ctx, { amount, currency, fee: 0, net: amount, type: 'application_fee', reporting_category: 'platform_earning', source: feeId, available_on: Number(ctx.now()) }, null);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** A top-up's credit, to the payments balance or, with `destination_balance=issuing`, to Issuing's
|
|
176
|
+
* (docs.stripe.com/issuing/funding/balance). Where the documentation stops and the twin decides: a test-mode top-up
|
|
177
|
+
* is available at once. */
|
|
178
|
+
export async function settleTopup(ctx: SemanticsContext, topupId: string, amount: number, currency: string, destination: string): Promise<string> {
|
|
179
|
+
const issuing = destination === 'issuing';
|
|
180
|
+
return write(ctx, { amount, currency, fee: 0, net: amount, type: 'topup', reporting_category: 'topup', source: topupId, available_on: Number(ctx.now()), balance_type: issuing ? 'issuing' : 'payments' });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Time's settlements, written: each entry on the acting account's balance whose funds came due by the World's clock
|
|
184
|
+
* moves pending → available (the clock's move the machine allows), so a settlement is recorded once. A read already
|
|
185
|
+
* sees it (asOf); the record is what lets Stripe's balance.available be sent once (stripe-server.ts, the drain).
|
|
186
|
+
* Funds that were available at once and not yet sent are taken too, and marked sent. Answers the entries taken. */
|
|
187
|
+
export async function settleDueEntries(ctx: SemanticsContext): Promise<Row[]> {
|
|
188
|
+
const now = Number(ctx.now());
|
|
189
|
+
const account = actingAccount(ctx);
|
|
190
|
+
const moved: Row[] = [];
|
|
191
|
+
for (const t of ctx.rowsRaw(BT)) {
|
|
192
|
+
if (!onAccount(t, account)) continue;
|
|
193
|
+
if (t.status === 'available' && t[UNSENT] === true) {
|
|
194
|
+
await ctx.write(BT, String(t.id), { [UNSENT]: false }, 'balance_transaction.available_sent');
|
|
195
|
+
moved.push(t);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (t.status !== 'pending' || !(Number(t.available_on) <= now)) continue;
|
|
199
|
+
if (ctx.legal(BT, 'status', ctx.call.operation.id, 'pending', 'available', String(t.id), 'time')) continue;
|
|
200
|
+
await ctx.write(BT, String(t.id), { status: 'available' }, 'balance_transaction.available');
|
|
201
|
+
moved.push(t);
|
|
202
|
+
}
|
|
203
|
+
return moved;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** An account's balance, by currency: what the clock has made available, what is still pending, and Issuing's own. */
|
|
207
|
+
export function balanceOf(ctx: SemanticsContext, account: string | undefined = actingAccount(ctx)): { available: Map<string, number>; pending: Map<string, number>; issuing: Map<string, number> } {
|
|
208
|
+
const available = new Map<string, number>();
|
|
209
|
+
const pending = new Map<string, number>();
|
|
210
|
+
const issuing = new Map<string, number>();
|
|
211
|
+
for (const raw of ctx.rowsRaw(BT)) {
|
|
212
|
+
if (!onAccount(raw, account)) continue;
|
|
213
|
+
const t = asOf(ctx, raw);
|
|
214
|
+
const cur = String(t.currency ?? 'usd');
|
|
215
|
+
const net = Number(t.net ?? 0);
|
|
216
|
+
const bucket = t.balance_type === 'issuing' ? issuing : t.status === 'available' ? available : pending;
|
|
217
|
+
bucket.set(cur, (bucket.get(cur) ?? 0) + net);
|
|
218
|
+
}
|
|
219
|
+
return { available, pending, issuing };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** balance_insufficient when a payout or transfer would take more than the account has available in its currency. */
|
|
223
|
+
export function refusePayout(ctx: SemanticsContext, amount: number, currency: string, account: string | undefined = actingAccount(ctx)): Response | undefined {
|
|
224
|
+
const have = balanceOf(ctx, account).available.get(currency) ?? 0;
|
|
225
|
+
if (amount <= have) return undefined;
|
|
226
|
+
return fail(ctx, "The transfer or payout couldn't be completed because the associated account doesn't have a sufficient balance available.", 400, 'balance_insufficient');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** The ids of the ledger entries on the acting account's balance. */
|
|
230
|
+
const mine = (ctx: SemanticsContext): Set<unknown> => new Set(ctx.rowsRaw(BT).filter((t) => onAccount(t, actingAccount(ctx))).map((t) => t.id));
|
|
231
|
+
|
|
232
|
+
const retrieve: Semantics = async (ctx) => {
|
|
233
|
+
const t = ctx.get(BT, at(ctx, 'id'));
|
|
234
|
+
return t && mine(ctx).has(t.id) ? ctx.reply(asOf(ctx, t)) : ctx.notFound(BT, at(ctx, 'id'));
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const listAll: Semantics = async (ctx) => {
|
|
238
|
+
const own = mine(ctx);
|
|
239
|
+
return list(ctx, BT, where(ctx, newest(ctx, BT).filter((t) => own.has(t.id)).map((t) => asOf(ctx, t)), {
|
|
240
|
+
type: (t, v) => t.type === v,
|
|
241
|
+
currency: (t, v) => t.currency === v,
|
|
242
|
+
source: (t, v) => t.source === v,
|
|
243
|
+
// an automatic payout lists the entries it paid out, its own debit among them
|
|
244
|
+
payout: (t, v) => t.source === v || kept(ctx, BT, t, '_payout') === v,
|
|
245
|
+
// a range of creation times (docs.stripe.com/api/balance_transactions/list#balance_transaction_list-created)
|
|
246
|
+
created: (t, v) => inRange(t.created, v),
|
|
247
|
+
}));
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export const ledger: Record<string, Semantics> = {
|
|
251
|
+
GetBalanceTransactions: listAll,
|
|
252
|
+
GetBalanceTransactionsId: retrieve,
|
|
253
|
+
};
|