@whooshbang/contract-mock 2.0.0-rc.5 → 2.0.0-rc.7
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/CHANGELOG.md +12 -0
- package/README.md +69 -3
- package/dist/billing.d.ts +78 -0
- package/dist/billing.js +871 -0
- package/dist/billing.js.map +1 -0
- package/dist/core.js +150 -6
- package/dist/core.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +28 -2
- package/package.json +3 -3
- package/scenarios/v1/manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ recorded here.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 2.0.0-rc.7
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Deterministic in-app card, credit purchase, receipt address, invoice history and Team and Agency subscription-change scenarios through the rc.7 contract, including undoing a scheduled change (FXO-2941).
|
|
13
|
+
|
|
14
|
+
## 2.0.0-rc.6
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Deterministic prepaid billing, recurring entitlement and quoted subscription-change scenarios, including insufficient funding and paid attribution enforcement through the rc.6 contract (FXO-2275, FXO-2276, FXO-2814).
|
|
19
|
+
|
|
8
20
|
## 2.0.0-rc.5
|
|
9
21
|
|
|
10
22
|
### Changed
|
package/README.md
CHANGED
|
@@ -31,9 +31,8 @@ const response = await mock.fetch(
|
|
|
31
31
|
);
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
publication; the tenant rename has no old-route or old-field aliases.
|
|
34
|
+
Install contracts, SDK and mock from the same published candidate batch.
|
|
35
|
+
The tenant rename has no old-route or old-field aliases.
|
|
37
36
|
|
|
38
37
|
The mock also executes the organization-scoped administration contract. Use
|
|
39
38
|
`humanSession` (or `otherHumanSession` for isolation tests) with
|
|
@@ -41,6 +40,73 @@ The mock also executes the organization-scoped administration contract. Use
|
|
|
41
40
|
`GET /v1/projects/{project_id}`. Project creation atomically returns synthetic
|
|
42
41
|
`test` and `live` environments and one `Default` notifier for each.
|
|
43
42
|
|
|
43
|
+
## Billing consumer tests
|
|
44
|
+
|
|
45
|
+
The mock executes the public wallet, recurring Checkout, billing portal,
|
|
46
|
+
automatic top-up settings/card setup, project slot and attribution routes. Organization administrators may use a
|
|
47
|
+
human session; OAuth grants also need explicit `billing:read` or
|
|
48
|
+
`billing:write`. Project, organization API and machine credentials cannot
|
|
49
|
+
perform financial operations. Attribution uses the organization-level
|
|
50
|
+
`projects:read` and `projects:write` scopes, with administrator membership for
|
|
51
|
+
changes. The MCP tools retain their own scope and action-confirmation checks.
|
|
52
|
+
|
|
53
|
+
Each account starts with the once-only $2 starter credit and one project slot.
|
|
54
|
+
The fixture project already occupies that slot. Multi-project consumer tests
|
|
55
|
+
must purchase extra slots through recurring Checkout or release the old slot
|
|
56
|
+
with `assignBillingProjectSlots` before creating its replacement. Old projects
|
|
57
|
+
retain their data and recorded test access; new live sends require an active
|
|
58
|
+
slot. The default recurring catalog contains the $5 attribution and project
|
|
59
|
+
add-ons. Team/Agency prices and allowances are not invented: pass synthetic
|
|
60
|
+
offers through `billing.offers` when a consumer test needs a base plan.
|
|
61
|
+
|
|
62
|
+
Wallet purchases use the approved $5/$10/$25/$50/$100 packs and
|
|
63
|
+
$5/$10/$30/$65/$150 total core credit. Purchased and bonus funds remain
|
|
64
|
+
separate. One accepted live notification debits $0.003 for core delivery;
|
|
65
|
+
shared email adds $0.001 from purchased or starter credit. Paid allowances are
|
|
66
|
+
used first. Tests, provider retries, later failures and answers do not create
|
|
67
|
+
extra charges. Payment replay cannot fund the same Checkout twice.
|
|
68
|
+
|
|
69
|
+
These are synthetic payments. Creating Checkout returns a local fixture URL;
|
|
70
|
+
reconciliation applies the fixture's payment outcome, not a query parameter
|
|
71
|
+
or caller-supplied `paid` flag. No Stripe request, card charge or webhook proof
|
|
72
|
+
occurs. Set `billing.checkoutOutcome` to a callback returning `paid`, `unpaid`
|
|
73
|
+
or `expired` to exercise recovery. `billing.accounts` may seed exact decimal
|
|
74
|
+
balances for boundary tests. State resets with `control.reset()`.
|
|
75
|
+
|
|
76
|
+
Automatic top-up starts disabled and requires a confirmed pack, threshold and
|
|
77
|
+
gross monthly cap covering that pack. The mock's card setup is synthetic:
|
|
78
|
+
`billing.autoTopUpSetupOutcome` returns `ready`, `unpaid` or `expired`. It models
|
|
79
|
+
owned setup recovery and rejects stale completion after settings change; it
|
|
80
|
+
does not run a bank charge or a scheduled automatic-purchase engine. Saving a
|
|
81
|
+
card does not grant delivery credit. Actual automatic payment capture, pending
|
|
82
|
+
reservations and gross monthly cap enforcement are tested against the owned
|
|
83
|
+
storage and Stripe SDK boundaries.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const mock = createWhooshBangContractMock({
|
|
87
|
+
billing: {
|
|
88
|
+
checkoutOutcome: () => "unpaid",
|
|
89
|
+
accounts: [{
|
|
90
|
+
organizationId: "organization_synthetic",
|
|
91
|
+
starterCreditUsd: "0.003000",
|
|
92
|
+
}],
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Paid add-on reconciliation enables the server-enforced attribution preference
|
|
98
|
+
until its paid boundary. Restoring the preference restores visible attribution;
|
|
99
|
+
expiry preserves the preference but removes its paid effect. Paid wallet
|
|
100
|
+
purchases or current recurring periods select 90-day content retention;
|
|
101
|
+
starter-only accounts select seven days. The mock projects that entitlement;
|
|
102
|
+
it does not simulate the storage purge jobs or prove real recipient rendering.
|
|
103
|
+
|
|
104
|
+
Each project has one webhook endpoint across its environments. Paused and
|
|
105
|
+
unverified endpoints still occupy it; disabling one releases it. Only a
|
|
106
|
+
terminal failed automatic cycle can receive the one included manual replay.
|
|
107
|
+
`control.runPending()` executes that cycle using the same eight-attempt bound.
|
|
108
|
+
The original cycle and manual recovery together cannot exceed 16 attempts.
|
|
109
|
+
|
|
44
110
|
The same human session can execute the environment credential lifecycle:
|
|
45
111
|
`GET/POST /v1/projects/{project_id}/environments/{environment_id}/credentials`
|
|
46
112
|
and the nested `rotate` and `revoke` actions. The mock emits a deterministic
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { AutoTopUpSettings, UpdateAutoTopUpSettingsRequest, BillingAccount, BillingEntitlements, BillingInvoiceCollection, BillingProjectSlots, CardSetup, ContractValidator, CreditPurchase, Problem, ProblemCode, Project, ProjectAttribution, SubscriptionChange, SubscriptionSelection } from "@whooshbang/contracts";
|
|
2
|
+
import type { Hono } from "hono";
|
|
3
|
+
import type { ContractMockBillingOptions, OrganizationScope } from "./types.js";
|
|
4
|
+
interface ChangeState {
|
|
5
|
+
organizationId: string;
|
|
6
|
+
resource: SubscriptionChange;
|
|
7
|
+
selectedOffers: BillingEntitlements["offers"];
|
|
8
|
+
}
|
|
9
|
+
/** Local, isolated financial fixtures. All state resets with the mock. The
|
|
10
|
+
* production service independently verifies Stripe objects and signatures. */
|
|
11
|
+
export declare class MockBillingState {
|
|
12
|
+
private readonly options;
|
|
13
|
+
readonly cardSetups: Map<string, {
|
|
14
|
+
organizationId: string;
|
|
15
|
+
resource: CardSetup;
|
|
16
|
+
}>;
|
|
17
|
+
readonly purchases: Map<string, {
|
|
18
|
+
organizationId: string;
|
|
19
|
+
resource: CreditPurchase;
|
|
20
|
+
createdAt: string;
|
|
21
|
+
}>;
|
|
22
|
+
private readonly automaticSettings;
|
|
23
|
+
private readonly accounts;
|
|
24
|
+
readonly changes: Map<string, ChangeState>;
|
|
25
|
+
private readonly invoices;
|
|
26
|
+
readonly attribution: Map<string, boolean>;
|
|
27
|
+
readonly replays: Map<string, {
|
|
28
|
+
fingerprint: string;
|
|
29
|
+
status: number;
|
|
30
|
+
body: unknown;
|
|
31
|
+
diagnosticId: string;
|
|
32
|
+
}>;
|
|
33
|
+
readonly offers: BillingEntitlements["offers"];
|
|
34
|
+
private sequence;
|
|
35
|
+
constructor(options?: ContractMockBillingOptions);
|
|
36
|
+
private id;
|
|
37
|
+
private account;
|
|
38
|
+
private record;
|
|
39
|
+
private outcome;
|
|
40
|
+
readAccount(organizationId: string, now?: string): BillingAccount;
|
|
41
|
+
setReceiptEmail(organizationId: string, email: string | null): void;
|
|
42
|
+
createCardSetup(organizationId: string): CardSetup;
|
|
43
|
+
reconcileCardSetup(organizationId: string, id: string): CardSetup | null;
|
|
44
|
+
createCreditPurchase(organizationId: string, purchaseCents: number, now: string): CreditPurchase | "card_required";
|
|
45
|
+
settlePurchase(organizationId: string, id: string): CreditPurchase | null;
|
|
46
|
+
listInvoices(organizationId: string, page: number, limit: number): BillingInvoiceCollection;
|
|
47
|
+
readAutoTopUp(organizationId: string): AutoTopUpSettings;
|
|
48
|
+
updateAutoTopUp(organizationId: string, input: UpdateAutoTopUpSettingsRequest): AutoTopUpSettings;
|
|
49
|
+
private changeOffers;
|
|
50
|
+
private selectionTotal;
|
|
51
|
+
createChange(organizationId: string, selection: SubscriptionSelection, now: string): SubscriptionChange | null;
|
|
52
|
+
private apply;
|
|
53
|
+
settleChange(organizationId: string, id: string, now: string): SubscriptionChange | null;
|
|
54
|
+
confirmChange(organizationId: string, id: string, charge: string, now: string): SubscriptionChange | "card_required" | null;
|
|
55
|
+
readEntitlements(organizationId: string, now: string): BillingEntitlements;
|
|
56
|
+
readAttribution(organizationId: string, projectId: string, now: string): ProjectAttribution;
|
|
57
|
+
readSlots(organizationId: string, projects: readonly Project[], now: string): BillingProjectSlots;
|
|
58
|
+
assignSlots(organizationId: string, ids: readonly string[]): void;
|
|
59
|
+
addProject(organizationId: string, id: string): void;
|
|
60
|
+
/** Call once at acceptance, after validation/replay/routing. Provider work and
|
|
61
|
+
* answers never call this function, so retries and later failures do not debit. */
|
|
62
|
+
hasCoreFunding(organizationId: string, now: string): boolean;
|
|
63
|
+
fundLive(organizationId: string, sharedEmail: boolean, now: string): "wallet_insufficient" | "email_credit_insufficient" | null;
|
|
64
|
+
}
|
|
65
|
+
interface BillingHooks {
|
|
66
|
+
state(): MockBillingState;
|
|
67
|
+
now(): string;
|
|
68
|
+
projects(): readonly Project[];
|
|
69
|
+
fingerprint(value: unknown): string;
|
|
70
|
+
authorize(request: Request, scope: OrganizationScope, admin: boolean): string | Response;
|
|
71
|
+
fail(code: ProblemCode, status: Problem["status"], fieldCode?: string, detail?: string): Response;
|
|
72
|
+
reply<T>(body: T, validator: ContractValidator<T>, status?: number): Response;
|
|
73
|
+
read<T>(request: Request, validator: ContractValidator<T>): Promise<T | Response>;
|
|
74
|
+
}
|
|
75
|
+
/** Register only published public resources. Synthetic card completion and
|
|
76
|
+
* charge outcomes are programmatic fixtures, never caller-supplied flags. */
|
|
77
|
+
export declare function addMockBillingRoutes(app: Hono, hooks: BillingHooks): void;
|
|
78
|
+
export {};
|