thevoidforge 21.0.10 → 21.0.12
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/dist/.claude/commands/ai.md +69 -0
- package/dist/.claude/commands/architect.md +121 -0
- package/dist/.claude/commands/assemble.md +201 -0
- package/dist/.claude/commands/assess.md +75 -0
- package/dist/.claude/commands/blueprint.md +135 -0
- package/dist/.claude/commands/build.md +116 -0
- package/dist/.claude/commands/campaign.md +201 -0
- package/dist/.claude/commands/cultivation.md +166 -0
- package/dist/.claude/commands/current.md +128 -0
- package/dist/.claude/commands/dangerroom.md +74 -0
- package/dist/.claude/commands/debrief.md +178 -0
- package/dist/.claude/commands/deploy.md +99 -0
- package/dist/.claude/commands/devops.md +143 -0
- package/dist/.claude/commands/gauntlet.md +140 -0
- package/dist/.claude/commands/git.md +104 -0
- package/dist/.claude/commands/grow.md +146 -0
- package/dist/.claude/commands/imagine.md +126 -0
- package/dist/.claude/commands/portfolio.md +50 -0
- package/dist/.claude/commands/prd.md +113 -0
- package/dist/.claude/commands/qa.md +107 -0
- package/dist/.claude/commands/review.md +151 -0
- package/dist/.claude/commands/security.md +100 -0
- package/dist/.claude/commands/test.md +96 -0
- package/dist/.claude/commands/thumper.md +116 -0
- package/dist/.claude/commands/treasury.md +100 -0
- package/dist/.claude/commands/ux.md +118 -0
- package/dist/.claude/commands/vault.md +189 -0
- package/dist/.claude/commands/void.md +108 -0
- package/dist/CHANGELOG.md +1918 -0
- package/dist/CLAUDE.md +250 -0
- package/dist/HOLOCRON.md +856 -0
- package/dist/VERSION.md +123 -0
- package/dist/docs/NAMING_REGISTRY.md +478 -0
- package/dist/docs/methods/AI_INTELLIGENCE.md +276 -0
- package/dist/docs/methods/ASSEMBLER.md +142 -0
- package/dist/docs/methods/BACKEND_ENGINEER.md +165 -0
- package/dist/docs/methods/BUILD_JOURNAL.md +185 -0
- package/dist/docs/methods/BUILD_PROTOCOL.md +426 -0
- package/dist/docs/methods/CAMPAIGN.md +568 -0
- package/dist/docs/methods/CONTEXT_MANAGEMENT.md +189 -0
- package/dist/docs/methods/DEEP_CURRENT.md +184 -0
- package/dist/docs/methods/DEVOPS_ENGINEER.md +295 -0
- package/dist/docs/methods/FIELD_MEDIC.md +261 -0
- package/dist/docs/methods/FORGE_ARTIST.md +108 -0
- package/dist/docs/methods/FORGE_KEEPER.md +268 -0
- package/dist/docs/methods/GAUNTLET.md +344 -0
- package/dist/docs/methods/GROWTH_STRATEGIST.md +466 -0
- package/dist/docs/methods/HEARTBEAT.md +168 -0
- package/dist/docs/methods/MCP_INTEGRATION.md +139 -0
- package/dist/docs/methods/MUSTER.md +148 -0
- package/dist/docs/methods/PRD_GENERATOR.md +186 -0
- package/dist/docs/methods/PRODUCT_DESIGN_FRONTEND.md +250 -0
- package/dist/docs/methods/QA_ENGINEER.md +337 -0
- package/dist/docs/methods/RELEASE_MANAGER.md +145 -0
- package/dist/docs/methods/SECURITY_AUDITOR.md +320 -0
- package/dist/docs/methods/SUB_AGENTS.md +335 -0
- package/dist/docs/methods/SYSTEMS_ARCHITECT.md +171 -0
- package/dist/docs/methods/TESTING.md +359 -0
- package/dist/docs/methods/THUMPER.md +175 -0
- package/dist/docs/methods/TIME_VAULT.md +120 -0
- package/dist/docs/methods/TREASURY.md +184 -0
- package/dist/docs/methods/TROUBLESHOOTING.md +265 -0
- package/dist/docs/patterns/README.md +52 -0
- package/dist/docs/patterns/ad-billing-adapter.ts +537 -0
- package/dist/docs/patterns/ad-platform-adapter.ts +421 -0
- package/dist/docs/patterns/ai-classifier.ts +195 -0
- package/dist/docs/patterns/ai-eval.ts +272 -0
- package/dist/docs/patterns/ai-orchestrator.ts +341 -0
- package/dist/docs/patterns/ai-router.ts +194 -0
- package/dist/docs/patterns/ai-tool-schema.ts +237 -0
- package/dist/docs/patterns/api-route.ts +241 -0
- package/dist/docs/patterns/backtest-engine.ts +499 -0
- package/dist/docs/patterns/browser-review.ts +292 -0
- package/dist/docs/patterns/combobox.tsx +300 -0
- package/dist/docs/patterns/component.tsx +262 -0
- package/dist/docs/patterns/daemon-process.ts +338 -0
- package/dist/docs/patterns/data-pipeline.ts +297 -0
- package/dist/docs/patterns/database-migration.ts +466 -0
- package/dist/docs/patterns/e2e-test.ts +629 -0
- package/dist/docs/patterns/error-handling.ts +312 -0
- package/dist/docs/patterns/execution-safety.ts +601 -0
- package/dist/docs/patterns/financial-transaction.ts +342 -0
- package/dist/docs/patterns/funding-plan.ts +462 -0
- package/dist/docs/patterns/game-entity.ts +137 -0
- package/dist/docs/patterns/game-loop.ts +113 -0
- package/dist/docs/patterns/game-state.ts +143 -0
- package/dist/docs/patterns/job-queue.ts +225 -0
- package/dist/docs/patterns/kongo-integration.ts +164 -0
- package/dist/docs/patterns/middleware.ts +363 -0
- package/dist/docs/patterns/mobile-screen.tsx +139 -0
- package/dist/docs/patterns/mobile-service.ts +167 -0
- package/dist/docs/patterns/multi-tenant.ts +382 -0
- package/dist/docs/patterns/oauth-token-lifecycle.ts +223 -0
- package/dist/docs/patterns/outbound-rate-limiter.ts +260 -0
- package/dist/docs/patterns/prompt-template.ts +195 -0
- package/dist/docs/patterns/revenue-source-adapter.ts +311 -0
- package/dist/docs/patterns/service.ts +224 -0
- package/dist/docs/patterns/sse-endpoint.ts +118 -0
- package/dist/docs/patterns/stablecoin-adapter.ts +511 -0
- package/dist/docs/patterns/third-party-script.ts +68 -0
- package/dist/scripts/thumper/gom-jabbar.sh +241 -0
- package/dist/scripts/thumper/relay.sh +610 -0
- package/dist/scripts/thumper/scan.sh +359 -0
- package/dist/scripts/thumper/thumper.sh +190 -0
- package/dist/scripts/thumper/water-rings.sh +76 -0
- package/dist/scripts/voidforge.js +1 -1
- package/package.json +1 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern: Ad Billing Adapter (Split Interface)
|
|
3
|
+
*
|
|
4
|
+
* Key principles:
|
|
5
|
+
* - Billing is SEPARATE from campaign CRUD. This adapter handles how ad platforms
|
|
6
|
+
* get paid (invoices, direct debits, settlements), not how campaigns are managed.
|
|
7
|
+
* Campaign operations live in ad-platform-adapter.ts.
|
|
8
|
+
* - Three capability states determine what Treasury can automate:
|
|
9
|
+
* FULLY_FUNDABLE — Treasury manages settlement lifecycle end-to-end
|
|
10
|
+
* MONITORED_ONLY — spend and billing are observed, but funding is not automated
|
|
11
|
+
* UNSUPPORTED — billing configuration blocks any automation
|
|
12
|
+
* - Split interface: AdBillingSetup (interactive) + AdBillingAdapter (runtime)
|
|
13
|
+
* - Single-writer: only Heartbeat daemon calls the runtime adapter
|
|
14
|
+
* - All amounts in branded integer cents (Cents type from financial-transaction.ts)
|
|
15
|
+
*
|
|
16
|
+
* Agents: Dockson (treasury), Wax (paid ads), Heartbeat daemon
|
|
17
|
+
*
|
|
18
|
+
* PRD Reference: §10.2, §11.1B, §12.3
|
|
19
|
+
*
|
|
20
|
+
* Google Ads Billing API reference:
|
|
21
|
+
* Billing setup: managed via Google Ads UI — no API for enabling monthly invoicing.
|
|
22
|
+
* Read-only API paths (Ads API v16+):
|
|
23
|
+
* BillingSetup resource → billingSetup.status, paymentsAccount, paymentsProfile
|
|
24
|
+
* Invoice resource → GET /customers/{id}/invoices (monthly invoicing accounts only)
|
|
25
|
+
* Fields: invoice.id, invoice.type, invoice.due_date, invoice.total_amount_micros
|
|
26
|
+
* Note: amounts in micros (1/1,000,000 of currency unit). Divide by 10,000 for cents.
|
|
27
|
+
* payments_account_id identifies the payments profile linked to billing.
|
|
28
|
+
* monthly_invoicing must be enabled by Google — not programmatically toggleable.
|
|
29
|
+
*
|
|
30
|
+
* Meta Ads Billing reference:
|
|
31
|
+
* No first-party invoice API. Billing is managed via:
|
|
32
|
+
* GET /act_{id}?fields=funding_source,funding_source_details → funding method classification
|
|
33
|
+
* funding_source_details.type: 1=credit_card, 2=debit_card, 4=direct_debit,
|
|
34
|
+
* 5=paypal, 8=extended_credit, 11=invoice
|
|
35
|
+
* Direct debit: Meta pulls from linked bank. No settlement instruction needed.
|
|
36
|
+
* Extended credit / invoicing: available to qualifying business accounts.
|
|
37
|
+
* For direct debit accounts, Treasury maintains a buffer; for invoiced accounts,
|
|
38
|
+
* Treasury tracks due dates and settlement instructions.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
// ── Branded Financial Types (from financial-transaction.ts) ──
|
|
42
|
+
|
|
43
|
+
type Cents = number & { readonly __brand: 'Cents' };
|
|
44
|
+
|
|
45
|
+
function toCents(dollars: number): Cents {
|
|
46
|
+
return Math.round(dollars * 100) as Cents;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── Capability and Billing Types ─────────────────────
|
|
50
|
+
|
|
51
|
+
type CapabilityState = 'FULLY_FUNDABLE' | 'MONITORED_ONLY' | 'UNSUPPORTED';
|
|
52
|
+
|
|
53
|
+
type BillingMode =
|
|
54
|
+
| 'monthly_invoicing' // Google: approved monthly invoicing
|
|
55
|
+
| 'direct_debit' // Meta: bank-backed autopay
|
|
56
|
+
| 'extended_credit' // Meta: extended credit / invoice path
|
|
57
|
+
| 'manual_bank_transfer' // manual wire/ACH with reference numbers
|
|
58
|
+
| 'card_only' // credit/debit card — not automatable
|
|
59
|
+
| 'unknown'; // could not determine billing mode
|
|
60
|
+
|
|
61
|
+
type AdPlatform = 'google' | 'meta' | 'tiktok' | 'linkedin' | 'twitter' | 'reddit' | 'snap';
|
|
62
|
+
|
|
63
|
+
// ── Invoice and Debit Types ─────────────────────────
|
|
64
|
+
|
|
65
|
+
interface Invoice {
|
|
66
|
+
id: string; // platform-specific invoice ID
|
|
67
|
+
platform: AdPlatform;
|
|
68
|
+
externalAccountId: string;
|
|
69
|
+
amountCents: Cents;
|
|
70
|
+
currency: 'USD';
|
|
71
|
+
issueDate: string; // ISO 8601
|
|
72
|
+
dueDate: string; // ISO 8601
|
|
73
|
+
status: 'pending' | 'paid' | 'overdue' | 'cancelled';
|
|
74
|
+
lineItems?: InvoiceLineItem[];
|
|
75
|
+
paymentReference?: string; // bank reference for settlement matching
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface InvoiceLineItem {
|
|
79
|
+
description: string;
|
|
80
|
+
amountCents: Cents;
|
|
81
|
+
campaignId?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface ExpectedDebit {
|
|
85
|
+
id: string; // internal tracking ID
|
|
86
|
+
platform: AdPlatform;
|
|
87
|
+
externalAccountId: string;
|
|
88
|
+
estimatedAmountCents: Cents; // estimated debit based on recent spend
|
|
89
|
+
currency: 'USD';
|
|
90
|
+
expectedDate: string; // ISO 8601 — estimated debit date
|
|
91
|
+
status: 'expected' | 'detected' | 'confirmed' | 'failed';
|
|
92
|
+
bankTransactionId?: string; // set once debit is detected in bank
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Settlement Types ────────────────────────────────
|
|
96
|
+
|
|
97
|
+
interface SettlementInstruction {
|
|
98
|
+
invoiceId: string;
|
|
99
|
+
platform: AdPlatform;
|
|
100
|
+
payeeName: string; // e.g., "Google Ads" or "Meta Platforms Inc"
|
|
101
|
+
paymentMethod: 'wire' | 'ach' | 'direct_debit';
|
|
102
|
+
amountCents: Cents;
|
|
103
|
+
currency: 'USD';
|
|
104
|
+
dueDate: string; // ISO 8601
|
|
105
|
+
bankReference?: string; // reference number for wire/ACH
|
|
106
|
+
routingNumber?: string; // destination routing (for wire/ACH to platform)
|
|
107
|
+
accountNumber?: string; // destination account (for wire/ACH to platform)
|
|
108
|
+
notes: string; // human-readable settlement summary
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── Platform Billing Profile (PRD §12.3) ────────────
|
|
112
|
+
|
|
113
|
+
interface PlatformBillingProfile {
|
|
114
|
+
platform: AdPlatform;
|
|
115
|
+
capabilityState: CapabilityState;
|
|
116
|
+
billingMode: BillingMode;
|
|
117
|
+
externalAccountId: string; // Google: customer ID, Meta: ad account ID
|
|
118
|
+
billingSetupId?: string; // Google: billing setup resource name
|
|
119
|
+
invoiceGroupId?: string; // Google: invoice group for consolidated billing
|
|
120
|
+
paymentProfileId?: string; // Google: payments profile ID
|
|
121
|
+
fundingSourceId?: string; // Meta: funding source ID
|
|
122
|
+
currency: 'USD';
|
|
123
|
+
nextDueDate?: string; // ISO 8601
|
|
124
|
+
status: 'active' | 'degraded' | 'suspended' | 'unconfigured';
|
|
125
|
+
lastVerifiedAt: string; // ISO 8601
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Billing Configuration ───────────────────────────
|
|
129
|
+
|
|
130
|
+
interface BillingConfiguration {
|
|
131
|
+
billingMode: BillingMode;
|
|
132
|
+
accountIds: {
|
|
133
|
+
externalAccountId: string;
|
|
134
|
+
billingSetupId?: string;
|
|
135
|
+
invoiceGroupId?: string;
|
|
136
|
+
paymentProfileId?: string;
|
|
137
|
+
fundingSourceId?: string;
|
|
138
|
+
};
|
|
139
|
+
nextDueDate?: string;
|
|
140
|
+
estimatedMonthlySpendCents?: Cents;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Normalized Funding State ────────────────────────
|
|
144
|
+
|
|
145
|
+
interface NormalizedFundingState {
|
|
146
|
+
platform: AdPlatform;
|
|
147
|
+
capabilityState: CapabilityState;
|
|
148
|
+
billingMode: BillingMode;
|
|
149
|
+
outstandingCents: Cents; // unpaid invoices or expected debits
|
|
150
|
+
nextPaymentDueDate?: string;
|
|
151
|
+
daysUntilNextPayment?: number;
|
|
152
|
+
fundingHealthy: boolean; // true if no overdue invoices and capability is not degraded
|
|
153
|
+
warnings: string[];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ── Date Range ──────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
interface DateRange {
|
|
159
|
+
start: string; // ISO 8601
|
|
160
|
+
end: string; // ISO 8601
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── Interactive Setup Interface ─────────────────────
|
|
164
|
+
// Runs in CLI or Danger Room during `/grow --setup`.
|
|
165
|
+
// Classifies each platform's billing capability.
|
|
166
|
+
|
|
167
|
+
interface AdBillingSetup {
|
|
168
|
+
/** Determine what level of funding automation this platform supports */
|
|
169
|
+
verifyBillingCapability(
|
|
170
|
+
platform: AdPlatform,
|
|
171
|
+
externalAccountId: string,
|
|
172
|
+
tokens: { accessToken: string }
|
|
173
|
+
): Promise<CapabilityState>;
|
|
174
|
+
|
|
175
|
+
/** Read the billing configuration details for storage in vault */
|
|
176
|
+
readBillingConfiguration(
|
|
177
|
+
platform: AdPlatform,
|
|
178
|
+
externalAccountId: string,
|
|
179
|
+
tokens: { accessToken: string }
|
|
180
|
+
): Promise<BillingConfiguration>;
|
|
181
|
+
|
|
182
|
+
/** Auto-detect the billing mode from platform API responses */
|
|
183
|
+
detectBillingMode(
|
|
184
|
+
platform: AdPlatform,
|
|
185
|
+
externalAccountId: string,
|
|
186
|
+
tokens: { accessToken: string }
|
|
187
|
+
): Promise<BillingMode>;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── Runtime Adapter Interface ───────────────────────
|
|
191
|
+
// Runs in heartbeat daemon ONLY — non-interactive, autonomous.
|
|
192
|
+
// Single-writer: no other process may call these methods.
|
|
193
|
+
|
|
194
|
+
interface AdBillingAdapter {
|
|
195
|
+
/** Current capability state — may degrade at runtime if billing config changes */
|
|
196
|
+
getCapabilityState(platform: AdPlatform): Promise<CapabilityState>;
|
|
197
|
+
|
|
198
|
+
/** List pending and paid invoices in date range (Google monthly invoicing) */
|
|
199
|
+
readInvoices(platform: AdPlatform, dateRange: DateRange): Promise<Invoice[]>;
|
|
200
|
+
|
|
201
|
+
/** List expected bank debits in date range (Meta direct debit) */
|
|
202
|
+
readExpectedDebits(platform: AdPlatform, dateRange: DateRange): Promise<ExpectedDebit[]>;
|
|
203
|
+
|
|
204
|
+
/** Generate a settlement instruction for a specific invoice */
|
|
205
|
+
generateSettlementInstructions(invoice: Invoice): Promise<SettlementInstruction>;
|
|
206
|
+
|
|
207
|
+
/** Confirm that an invoice was settled by linking to a bank transaction */
|
|
208
|
+
confirmSettlement(invoiceId: string, bankTransactionId: string): Promise<{
|
|
209
|
+
confirmed: boolean;
|
|
210
|
+
reconciledAmountCents: Cents;
|
|
211
|
+
varianceCents: Cents;
|
|
212
|
+
}>;
|
|
213
|
+
|
|
214
|
+
/** Unified funding view across all connected platforms */
|
|
215
|
+
normalizeFundingState(): Promise<NormalizedFundingState[]>;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ── Reference Implementation: Google Billing ─────────
|
|
219
|
+
// Production implementation would live in wizard/lib/financial/billing/google-billing.ts
|
|
220
|
+
|
|
221
|
+
class GoogleBillingAdapter implements AdBillingSetup, AdBillingAdapter {
|
|
222
|
+
private readonly adsApiUrl = 'https://googleads.googleapis.com/v16';
|
|
223
|
+
private profiles: Map<string, PlatformBillingProfile> = new Map();
|
|
224
|
+
|
|
225
|
+
// ── Setup (interactive) ──────────
|
|
226
|
+
|
|
227
|
+
async verifyBillingCapability(
|
|
228
|
+
_platform: AdPlatform,
|
|
229
|
+
externalAccountId: string,
|
|
230
|
+
tokens: { accessToken: string }
|
|
231
|
+
): Promise<CapabilityState> {
|
|
232
|
+
const mode = await this.detectBillingMode('google', externalAccountId, tokens);
|
|
233
|
+
if (mode === 'monthly_invoicing') return 'FULLY_FUNDABLE';
|
|
234
|
+
if (mode === 'manual_bank_transfer') return 'MONITORED_ONLY';
|
|
235
|
+
if (mode === 'unknown' || mode === 'card_only') return 'UNSUPPORTED';
|
|
236
|
+
return 'MONITORED_ONLY';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async readBillingConfiguration(
|
|
240
|
+
_platform: AdPlatform,
|
|
241
|
+
externalAccountId: string,
|
|
242
|
+
tokens: { accessToken: string }
|
|
243
|
+
): Promise<BillingConfiguration> {
|
|
244
|
+
// Google Ads API: query billing_setup resource
|
|
245
|
+
// SELECT billing_setup.id, billing_setup.status, billing_setup.payments_account,
|
|
246
|
+
// billing_setup.payments_profile
|
|
247
|
+
// FROM billing_setup
|
|
248
|
+
const billingSetup = await this.queryBillingSetup(externalAccountId, tokens);
|
|
249
|
+
const mode = this.classifyGoogleBillingMode(billingSetup);
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
billingMode: mode,
|
|
253
|
+
accountIds: {
|
|
254
|
+
externalAccountId,
|
|
255
|
+
billingSetupId: billingSetup.id as string | undefined,
|
|
256
|
+
paymentProfileId: billingSetup.paymentsProfile as string | undefined,
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async detectBillingMode(
|
|
262
|
+
_platform: AdPlatform,
|
|
263
|
+
externalAccountId: string,
|
|
264
|
+
tokens: { accessToken: string }
|
|
265
|
+
): Promise<BillingMode> {
|
|
266
|
+
const billingSetup = await this.queryBillingSetup(externalAccountId, tokens);
|
|
267
|
+
return this.classifyGoogleBillingMode(billingSetup);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ── Runtime (daemon) ──────────
|
|
271
|
+
|
|
272
|
+
async getCapabilityState(_platform: AdPlatform): Promise<CapabilityState> {
|
|
273
|
+
// Re-verify from cached profile — full re-check happens on scheduled interval
|
|
274
|
+
const profile = this.profiles.get('google');
|
|
275
|
+
return profile?.capabilityState ?? 'UNSUPPORTED';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async readInvoices(_platform: AdPlatform, dateRange: DateRange): Promise<Invoice[]> {
|
|
279
|
+
// Google Ads API: query invoice resource
|
|
280
|
+
// SELECT invoice.id, invoice.type, invoice.due_date,
|
|
281
|
+
// invoice.service_date_range, invoice.total_amount_micros,
|
|
282
|
+
// invoice.payments_account_id
|
|
283
|
+
// FROM invoice
|
|
284
|
+
// WHERE invoice.issue_date >= '{dateRange.start}'
|
|
285
|
+
// AND invoice.issue_date <= '{dateRange.end}'
|
|
286
|
+
//
|
|
287
|
+
// Amounts are in micros: divide by 10,000 to get cents
|
|
288
|
+
// e.g., 1,500,000,000 micros = $1,500.00 = 150,000 cents
|
|
289
|
+
throw new Error('HTTP implementation — use node:https with Google Ads REST API');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async readExpectedDebits(_platform: AdPlatform, _dateRange: DateRange): Promise<ExpectedDebit[]> {
|
|
293
|
+
// Google does not use direct debit in the monthly invoicing flow.
|
|
294
|
+
// This method returns empty for Google — debits are a Meta concept.
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async generateSettlementInstructions(invoice: Invoice): Promise<SettlementInstruction> {
|
|
299
|
+
// Google monthly invoicing: payment via wire/ACH to Google's bank account
|
|
300
|
+
// Payment instructions are on the invoice itself.
|
|
301
|
+
// Reference number must be included for matching.
|
|
302
|
+
return {
|
|
303
|
+
invoiceId: invoice.id,
|
|
304
|
+
platform: 'google',
|
|
305
|
+
payeeName: 'Google Ads',
|
|
306
|
+
paymentMethod: 'wire',
|
|
307
|
+
amountCents: invoice.amountCents,
|
|
308
|
+
currency: 'USD',
|
|
309
|
+
dueDate: invoice.dueDate,
|
|
310
|
+
bankReference: invoice.paymentReference,
|
|
311
|
+
notes: `Google monthly invoice ${invoice.id} — wire payment with reference ${invoice.paymentReference ?? 'see invoice'}`,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async confirmSettlement(invoiceId: string, bankTransactionId: string): Promise<{
|
|
316
|
+
confirmed: boolean; reconciledAmountCents: Cents; varianceCents: Cents;
|
|
317
|
+
}> {
|
|
318
|
+
// Match bank transaction amount against invoice amount.
|
|
319
|
+
// In production: read from bank adapter + invoice store.
|
|
320
|
+
// Variance threshold: configurable, default 0 cents for exact match.
|
|
321
|
+
throw new Error('Implementation requires invoice store + bank adapter integration');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async normalizeFundingState(): Promise<NormalizedFundingState[]> {
|
|
325
|
+
const profile = this.profiles.get('google');
|
|
326
|
+
if (!profile) return [];
|
|
327
|
+
|
|
328
|
+
const invoices = await this.readInvoices('google', {
|
|
329
|
+
start: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString(),
|
|
330
|
+
end: new Date().toISOString(),
|
|
331
|
+
});
|
|
332
|
+
const pending = invoices.filter(i => i.status === 'pending' || i.status === 'overdue');
|
|
333
|
+
const outstandingCents = pending.reduce(
|
|
334
|
+
(sum, i) => (sum + i.amountCents) as Cents, 0 as Cents
|
|
335
|
+
);
|
|
336
|
+
const overdue = pending.some(i => i.status === 'overdue');
|
|
337
|
+
|
|
338
|
+
return [{
|
|
339
|
+
platform: 'google',
|
|
340
|
+
capabilityState: profile.capabilityState,
|
|
341
|
+
billingMode: profile.billingMode,
|
|
342
|
+
outstandingCents,
|
|
343
|
+
nextPaymentDueDate: profile.nextDueDate,
|
|
344
|
+
daysUntilNextPayment: profile.nextDueDate
|
|
345
|
+
? Math.ceil((new Date(profile.nextDueDate).getTime() - Date.now()) / (24 * 60 * 60 * 1000))
|
|
346
|
+
: undefined,
|
|
347
|
+
fundingHealthy: !overdue && profile.status === 'active',
|
|
348
|
+
warnings: overdue ? ['Overdue Google Ads invoice — settlement required'] : [],
|
|
349
|
+
}];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ── Private helpers ──────────
|
|
353
|
+
|
|
354
|
+
private async queryBillingSetup(
|
|
355
|
+
_externalAccountId: string, _tokens: { accessToken: string }
|
|
356
|
+
): Promise<Record<string, unknown>> {
|
|
357
|
+
// POST {adsApiUrl}/customers/{id}/googleAds:searchStream
|
|
358
|
+
// Body: { query: "SELECT billing_setup.id, ..." }
|
|
359
|
+
// Headers: Authorization: Bearer {accessToken}, developer-token: {devToken}
|
|
360
|
+
throw new Error('HTTP implementation — use node:https with Google Ads REST API');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private classifyGoogleBillingMode(billingSetup: Record<string, unknown>): BillingMode {
|
|
364
|
+
const status = billingSetup.status as string | undefined;
|
|
365
|
+
if (status === 'APPROVED') return 'monthly_invoicing';
|
|
366
|
+
if (status === 'PENDING') return 'unknown';
|
|
367
|
+
if (status === 'CANCELLED') return 'unknown';
|
|
368
|
+
return 'unknown';
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ── Reference Implementation: Meta Billing ───────────
|
|
373
|
+
// Production implementation would live in wizard/lib/financial/billing/meta-billing.ts
|
|
374
|
+
|
|
375
|
+
class MetaBillingAdapter implements AdBillingSetup, AdBillingAdapter {
|
|
376
|
+
private readonly baseUrl = 'https://graph.facebook.com/v19.0';
|
|
377
|
+
private profiles: Map<string, PlatformBillingProfile> = new Map();
|
|
378
|
+
|
|
379
|
+
// ── Setup (interactive) ──────────
|
|
380
|
+
|
|
381
|
+
async verifyBillingCapability(
|
|
382
|
+
_platform: AdPlatform,
|
|
383
|
+
externalAccountId: string,
|
|
384
|
+
tokens: { accessToken: string }
|
|
385
|
+
): Promise<CapabilityState> {
|
|
386
|
+
const mode = await this.detectBillingMode('meta', externalAccountId, tokens);
|
|
387
|
+
if (mode === 'direct_debit' || mode === 'extended_credit') return 'FULLY_FUNDABLE';
|
|
388
|
+
if (mode === 'card_only') return 'UNSUPPORTED';
|
|
389
|
+
return 'MONITORED_ONLY';
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async readBillingConfiguration(
|
|
393
|
+
_platform: AdPlatform,
|
|
394
|
+
externalAccountId: string,
|
|
395
|
+
tokens: { accessToken: string }
|
|
396
|
+
): Promise<BillingConfiguration> {
|
|
397
|
+
const mode = await this.detectBillingMode('meta', externalAccountId, tokens);
|
|
398
|
+
return {
|
|
399
|
+
billingMode: mode,
|
|
400
|
+
accountIds: {
|
|
401
|
+
externalAccountId,
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async detectBillingMode(
|
|
407
|
+
_platform: AdPlatform,
|
|
408
|
+
externalAccountId: string,
|
|
409
|
+
tokens: { accessToken: string }
|
|
410
|
+
): Promise<BillingMode> {
|
|
411
|
+
// GET /act_{id}?fields=funding_source_details&access_token={token}
|
|
412
|
+
// Response: { funding_source_details: { id, type, display_string } }
|
|
413
|
+
// type values: 1=credit_card, 2=debit_card, 4=direct_debit,
|
|
414
|
+
// 5=paypal, 8=extended_credit, 11=invoice
|
|
415
|
+
const res = await this.apiCall(
|
|
416
|
+
'GET',
|
|
417
|
+
`/act_${externalAccountId}`,
|
|
418
|
+
tokens.accessToken,
|
|
419
|
+
{ fields: 'funding_source_details' }
|
|
420
|
+
);
|
|
421
|
+
const details = res.funding_source_details as Record<string, unknown> | undefined;
|
|
422
|
+
const fundingType = details?.type as number | undefined;
|
|
423
|
+
|
|
424
|
+
switch (fundingType) {
|
|
425
|
+
case 4: return 'direct_debit';
|
|
426
|
+
case 8: return 'extended_credit';
|
|
427
|
+
case 11: return 'monthly_invoicing';
|
|
428
|
+
case 1:
|
|
429
|
+
case 2: return 'card_only';
|
|
430
|
+
default: return 'unknown';
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ── Runtime (daemon) ──────────
|
|
435
|
+
|
|
436
|
+
async getCapabilityState(_platform: AdPlatform): Promise<CapabilityState> {
|
|
437
|
+
const profile = this.profiles.get('meta');
|
|
438
|
+
return profile?.capabilityState ?? 'UNSUPPORTED';
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async readInvoices(_platform: AdPlatform, _dateRange: DateRange): Promise<Invoice[]> {
|
|
442
|
+
// Meta does not expose a first-party invoice API for most account types.
|
|
443
|
+
// For extended_credit accounts: invoices may be available via Business Manager.
|
|
444
|
+
// In V1: return empty — Meta billing is tracked via expected debits.
|
|
445
|
+
return [];
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async readExpectedDebits(_platform: AdPlatform, dateRange: DateRange): Promise<ExpectedDebit[]> {
|
|
449
|
+
// For direct debit accounts: estimate upcoming debits from recent spend.
|
|
450
|
+
// Meta typically debits when spend threshold is reached or on billing date.
|
|
451
|
+
// This is a forecast based on spend velocity, not a platform API call.
|
|
452
|
+
// The actual debit is detected when it appears in the bank account.
|
|
453
|
+
throw new Error('Implementation requires spend history + bank transaction detection');
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async generateSettlementInstructions(invoice: Invoice): Promise<SettlementInstruction> {
|
|
457
|
+
// Meta direct debit: no manual settlement needed — Meta pulls from bank.
|
|
458
|
+
// Meta extended credit / invoicing: payment instructions on invoice.
|
|
459
|
+
return {
|
|
460
|
+
invoiceId: invoice.id,
|
|
461
|
+
platform: 'meta',
|
|
462
|
+
payeeName: 'Meta Platforms Inc',
|
|
463
|
+
paymentMethod: 'direct_debit',
|
|
464
|
+
amountCents: invoice.amountCents,
|
|
465
|
+
currency: 'USD',
|
|
466
|
+
dueDate: invoice.dueDate,
|
|
467
|
+
notes: 'Meta direct debit — ensure sufficient bank balance before debit date',
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async confirmSettlement(invoiceId: string, bankTransactionId: string): Promise<{
|
|
472
|
+
confirmed: boolean; reconciledAmountCents: Cents; varianceCents: Cents;
|
|
473
|
+
}> {
|
|
474
|
+
throw new Error('Implementation requires invoice store + bank adapter integration');
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
async normalizeFundingState(): Promise<NormalizedFundingState[]> {
|
|
478
|
+
const profile = this.profiles.get('meta');
|
|
479
|
+
if (!profile) return [];
|
|
480
|
+
|
|
481
|
+
return [{
|
|
482
|
+
platform: 'meta',
|
|
483
|
+
capabilityState: profile.capabilityState,
|
|
484
|
+
billingMode: profile.billingMode,
|
|
485
|
+
outstandingCents: 0 as Cents, // Meta direct debit is pulled, not pushed
|
|
486
|
+
nextPaymentDueDate: profile.nextDueDate,
|
|
487
|
+
daysUntilNextPayment: profile.nextDueDate
|
|
488
|
+
? Math.ceil((new Date(profile.nextDueDate).getTime() - Date.now()) / (24 * 60 * 60 * 1000))
|
|
489
|
+
: undefined,
|
|
490
|
+
fundingHealthy: profile.status === 'active',
|
|
491
|
+
warnings: profile.status === 'degraded'
|
|
492
|
+
? ['Meta billing degraded — check funding source status']
|
|
493
|
+
: [],
|
|
494
|
+
}];
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// ── Private helpers ──────────
|
|
498
|
+
|
|
499
|
+
private async apiCall(
|
|
500
|
+
method: string,
|
|
501
|
+
path: string,
|
|
502
|
+
accessToken: string,
|
|
503
|
+
params: Record<string, unknown>
|
|
504
|
+
): Promise<Record<string, unknown>> {
|
|
505
|
+
// Implementation: raw HTTPS (no SDK — zero dependency principle)
|
|
506
|
+
// Headers: Authorization: Bearer {accessToken}
|
|
507
|
+
// Sanitize response strings per §9.19.16 before returning
|
|
508
|
+
throw new Error('HTTP implementation — use node:https, no SDK dependencies');
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ── Framework Adaptation Notes ──────────────────────
|
|
513
|
+
//
|
|
514
|
+
// Express/Node.js:
|
|
515
|
+
// - Setup routes in /api/grow/billing/* (interactive, session-authed)
|
|
516
|
+
// - Runtime methods called by Heartbeat scheduler only (daemon-process.ts)
|
|
517
|
+
// - Billing adapter is a SEPARATE service from campaign adapter
|
|
518
|
+
//
|
|
519
|
+
// Django/FastAPI:
|
|
520
|
+
// - AdBillingSetup maps to a ViewSet with session auth
|
|
521
|
+
// - AdBillingAdapter methods become Celery/ARQ tasks (single-worker queue)
|
|
522
|
+
// - Store PlatformBillingProfile in encrypted JSONField
|
|
523
|
+
//
|
|
524
|
+
// Next.js:
|
|
525
|
+
// - Setup API routes under /api/grow/billing/*
|
|
526
|
+
// - Server actions for interactive billing verification
|
|
527
|
+
// - Runtime adapter runs in separate worker, NOT in Next.js server
|
|
528
|
+
|
|
529
|
+
export type {
|
|
530
|
+
AdBillingSetup, AdBillingAdapter,
|
|
531
|
+
CapabilityState, BillingMode, AdPlatform,
|
|
532
|
+
Invoice, InvoiceLineItem, ExpectedDebit,
|
|
533
|
+
SettlementInstruction, PlatformBillingProfile,
|
|
534
|
+
BillingConfiguration, NormalizedFundingState, DateRange,
|
|
535
|
+
Cents,
|
|
536
|
+
};
|
|
537
|
+
export { toCents, GoogleBillingAdapter, MetaBillingAdapter };
|