thevoidforge 21.0.0
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/scripts/vault-read.d.ts +11 -0
- package/dist/scripts/vault-read.js +89 -0
- package/dist/scripts/voidforge.d.ts +20 -0
- package/dist/scripts/voidforge.js +404 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/wizard/api/auth.d.ts +5 -0
- package/dist/wizard/api/auth.js +133 -0
- package/dist/wizard/api/blueprint.d.ts +45 -0
- package/dist/wizard/api/blueprint.js +184 -0
- package/dist/wizard/api/cloud-providers.d.ts +16 -0
- package/dist/wizard/api/cloud-providers.js +363 -0
- package/dist/wizard/api/credentials.d.ts +1 -0
- package/dist/wizard/api/credentials.js +258 -0
- package/dist/wizard/api/danger-room.d.ts +18 -0
- package/dist/wizard/api/danger-room.js +401 -0
- package/dist/wizard/api/deploy.d.ts +4 -0
- package/dist/wizard/api/deploy.js +164 -0
- package/dist/wizard/api/prd.d.ts +1 -0
- package/dist/wizard/api/prd.js +363 -0
- package/dist/wizard/api/project.d.ts +1 -0
- package/dist/wizard/api/project.js +239 -0
- package/dist/wizard/api/projects.d.ts +6 -0
- package/dist/wizard/api/projects.js +648 -0
- package/dist/wizard/api/provision.d.ts +4 -0
- package/dist/wizard/api/provision.js +535 -0
- package/dist/wizard/api/terminal.d.ts +25 -0
- package/dist/wizard/api/terminal.js +241 -0
- package/dist/wizard/api/users.d.ts +6 -0
- package/dist/wizard/api/users.js +244 -0
- package/dist/wizard/api/war-room.d.ts +14 -0
- package/dist/wizard/api/war-room.js +45 -0
- package/dist/wizard/lib/ad-platform-core.d.ts +6 -0
- package/dist/wizard/lib/ad-platform-core.js +1 -0
- package/dist/wizard/lib/adapters/index.d.ts +52 -0
- package/dist/wizard/lib/adapters/index.js +38 -0
- package/dist/wizard/lib/adapters/sandbox-bank.d.ts +17 -0
- package/dist/wizard/lib/adapters/sandbox-bank.js +77 -0
- package/dist/wizard/lib/adapters/sandbox.d.ts +39 -0
- package/dist/wizard/lib/adapters/sandbox.js +174 -0
- package/dist/wizard/lib/adapters/stripe.d.ts +19 -0
- package/dist/wizard/lib/adapters/stripe.js +143 -0
- package/dist/wizard/lib/adapters/types.d.ts +9 -0
- package/dist/wizard/lib/adapters/types.js +10 -0
- package/dist/wizard/lib/agent-memory.d.ts +36 -0
- package/dist/wizard/lib/agent-memory.js +114 -0
- package/dist/wizard/lib/anomaly-detection.d.ts +59 -0
- package/dist/wizard/lib/anomaly-detection.js +122 -0
- package/dist/wizard/lib/anthropic.d.ts +21 -0
- package/dist/wizard/lib/anthropic.js +105 -0
- package/dist/wizard/lib/asset-scanner.d.ts +23 -0
- package/dist/wizard/lib/asset-scanner.js +107 -0
- package/dist/wizard/lib/audit-log.d.ts +23 -0
- package/dist/wizard/lib/audit-log.js +70 -0
- package/dist/wizard/lib/autonomy-controller.d.ts +76 -0
- package/dist/wizard/lib/autonomy-controller.js +183 -0
- package/dist/wizard/lib/body-parser.d.ts +2 -0
- package/dist/wizard/lib/body-parser.js +36 -0
- package/dist/wizard/lib/build-analytics.d.ts +39 -0
- package/dist/wizard/lib/build-analytics.js +91 -0
- package/dist/wizard/lib/build-step.d.ts +21 -0
- package/dist/wizard/lib/build-step.js +104 -0
- package/dist/wizard/lib/campaign-proposer.d.ts +39 -0
- package/dist/wizard/lib/campaign-proposer.js +180 -0
- package/dist/wizard/lib/campaign-state-machine.d.ts +63 -0
- package/dist/wizard/lib/campaign-state-machine.js +114 -0
- package/dist/wizard/lib/ci-generator.d.ts +14 -0
- package/dist/wizard/lib/ci-generator.js +187 -0
- package/dist/wizard/lib/claude-merge.d.ts +38 -0
- package/dist/wizard/lib/claude-merge.js +115 -0
- package/dist/wizard/lib/codegen/erd-gen.d.ts +16 -0
- package/dist/wizard/lib/codegen/erd-gen.js +98 -0
- package/dist/wizard/lib/codegen/integrations.d.ts +18 -0
- package/dist/wizard/lib/codegen/integrations.js +189 -0
- package/dist/wizard/lib/codegen/openapi-gen.d.ts +15 -0
- package/dist/wizard/lib/codegen/openapi-gen.js +79 -0
- package/dist/wizard/lib/codegen/prisma-types.d.ts +15 -0
- package/dist/wizard/lib/codegen/prisma-types.js +44 -0
- package/dist/wizard/lib/codegen/seed-gen.d.ts +16 -0
- package/dist/wizard/lib/codegen/seed-gen.js +128 -0
- package/dist/wizard/lib/compliance.d.ts +51 -0
- package/dist/wizard/lib/compliance.js +112 -0
- package/dist/wizard/lib/correlation-engine.d.ts +59 -0
- package/dist/wizard/lib/correlation-engine.js +151 -0
- package/dist/wizard/lib/cost-estimator.d.ts +22 -0
- package/dist/wizard/lib/cost-estimator.js +72 -0
- package/dist/wizard/lib/cost-tracker.d.ts +27 -0
- package/dist/wizard/lib/cost-tracker.js +37 -0
- package/dist/wizard/lib/daemon-aggregator.d.ts +71 -0
- package/dist/wizard/lib/daemon-aggregator.js +204 -0
- package/dist/wizard/lib/daemon-core.d.ts +6 -0
- package/dist/wizard/lib/daemon-core.js +5 -0
- package/dist/wizard/lib/dashboard-data.d.ts +132 -0
- package/dist/wizard/lib/dashboard-data.js +336 -0
- package/dist/wizard/lib/dashboard-ws.d.ts +25 -0
- package/dist/wizard/lib/dashboard-ws.js +91 -0
- package/dist/wizard/lib/deep-current.d.ts +77 -0
- package/dist/wizard/lib/deep-current.js +234 -0
- package/dist/wizard/lib/deploy-coordinator.d.ts +40 -0
- package/dist/wizard/lib/deploy-coordinator.js +86 -0
- package/dist/wizard/lib/deploy-log.d.ts +28 -0
- package/dist/wizard/lib/deploy-log.js +52 -0
- package/dist/wizard/lib/desktop-notify.d.ts +27 -0
- package/dist/wizard/lib/desktop-notify.js +98 -0
- package/dist/wizard/lib/dns/cloudflare-dns.d.ts +35 -0
- package/dist/wizard/lib/dns/cloudflare-dns.js +216 -0
- package/dist/wizard/lib/dns/cloudflare-registrar.d.ts +31 -0
- package/dist/wizard/lib/dns/cloudflare-registrar.js +148 -0
- package/dist/wizard/lib/dns/types.d.ts +22 -0
- package/dist/wizard/lib/dns/types.js +4 -0
- package/dist/wizard/lib/document-discovery.d.ts +33 -0
- package/dist/wizard/lib/document-discovery.js +145 -0
- package/dist/wizard/lib/env-validator.d.ts +14 -0
- package/dist/wizard/lib/env-validator.js +205 -0
- package/dist/wizard/lib/env-writer.d.ts +13 -0
- package/dist/wizard/lib/env-writer.js +26 -0
- package/dist/wizard/lib/exec.d.ts +30 -0
- package/dist/wizard/lib/exec.js +52 -0
- package/dist/wizard/lib/experiment.d.ts +70 -0
- package/dist/wizard/lib/experiment.js +169 -0
- package/dist/wizard/lib/extensions.d.ts +20 -0
- package/dist/wizard/lib/extensions.js +183 -0
- package/dist/wizard/lib/financial/adapter-factory.d.ts +47 -0
- package/dist/wizard/lib/financial/adapter-factory.js +225 -0
- package/dist/wizard/lib/financial/billing/base.d.ts +6 -0
- package/dist/wizard/lib/financial/billing/base.js +1 -0
- package/dist/wizard/lib/financial/billing/google-billing.d.ts +56 -0
- package/dist/wizard/lib/financial/billing/google-billing.js +298 -0
- package/dist/wizard/lib/financial/billing/meta-billing.d.ts +54 -0
- package/dist/wizard/lib/financial/billing/meta-billing.js +243 -0
- package/dist/wizard/lib/financial/billing/tiktok-billing.d.ts +54 -0
- package/dist/wizard/lib/financial/billing/tiktok-billing.js +260 -0
- package/dist/wizard/lib/financial/campaign/base.d.ts +13 -0
- package/dist/wizard/lib/financial/campaign/base.js +1 -0
- package/dist/wizard/lib/financial/campaign/google-campaign.d.ts +42 -0
- package/dist/wizard/lib/financial/campaign/google-campaign.js +388 -0
- package/dist/wizard/lib/financial/campaign/meta-campaign.d.ts +41 -0
- package/dist/wizard/lib/financial/campaign/meta-campaign.js +311 -0
- package/dist/wizard/lib/financial/campaign/sandbox-campaign.d.ts +45 -0
- package/dist/wizard/lib/financial/campaign/sandbox-campaign.js +261 -0
- package/dist/wizard/lib/financial/campaign/tiktok-campaign.d.ts +40 -0
- package/dist/wizard/lib/financial/campaign/tiktok-campaign.js +350 -0
- package/dist/wizard/lib/financial/funding-auto.d.ts +44 -0
- package/dist/wizard/lib/financial/funding-auto.js +52 -0
- package/dist/wizard/lib/financial/funding-policy.d.ts +60 -0
- package/dist/wizard/lib/financial/funding-policy.js +179 -0
- package/dist/wizard/lib/financial/platform-planner.d.ts +47 -0
- package/dist/wizard/lib/financial/platform-planner.js +134 -0
- package/dist/wizard/lib/financial/reconciliation-engine.d.ts +78 -0
- package/dist/wizard/lib/financial/reconciliation-engine.js +193 -0
- package/dist/wizard/lib/financial/registry.d.ts +22 -0
- package/dist/wizard/lib/financial/registry.js +26 -0
- package/dist/wizard/lib/financial/reporting.d.ts +96 -0
- package/dist/wizard/lib/financial/reporting.js +198 -0
- package/dist/wizard/lib/financial/stablecoin/base.d.ts +6 -0
- package/dist/wizard/lib/financial/stablecoin/base.js +1 -0
- package/dist/wizard/lib/financial/stablecoin/circle.d.ts +54 -0
- package/dist/wizard/lib/financial/stablecoin/circle.js +367 -0
- package/dist/wizard/lib/financial/stablecoin/mercury.d.ts +24 -0
- package/dist/wizard/lib/financial/stablecoin/mercury.js +171 -0
- package/dist/wizard/lib/financial/stablecoin/sandbox-stablecoin.d.ts +47 -0
- package/dist/wizard/lib/financial/stablecoin/sandbox-stablecoin.js +202 -0
- package/dist/wizard/lib/financial/treasury-planner.d.ts +52 -0
- package/dist/wizard/lib/financial/treasury-planner.js +128 -0
- package/dist/wizard/lib/financial-core.d.ts +6 -0
- package/dist/wizard/lib/financial-core.js +5 -0
- package/dist/wizard/lib/financial-vault.d.ts +34 -0
- package/dist/wizard/lib/financial-vault.js +199 -0
- package/dist/wizard/lib/frontmatter.d.ts +30 -0
- package/dist/wizard/lib/frontmatter.js +96 -0
- package/dist/wizard/lib/gap-analysis.d.ts +37 -0
- package/dist/wizard/lib/gap-analysis.js +218 -0
- package/dist/wizard/lib/github.d.ts +22 -0
- package/dist/wizard/lib/github.js +261 -0
- package/dist/wizard/lib/headless-deploy.d.ts +14 -0
- package/dist/wizard/lib/headless-deploy.js +452 -0
- package/dist/wizard/lib/health-monitor.d.ts +15 -0
- package/dist/wizard/lib/health-monitor.js +91 -0
- package/dist/wizard/lib/health-poller.d.ts +9 -0
- package/dist/wizard/lib/health-poller.js +123 -0
- package/dist/wizard/lib/heartbeat.d.ts +15 -0
- package/dist/wizard/lib/heartbeat.js +827 -0
- package/dist/wizard/lib/http-helpers.d.ts +9 -0
- package/dist/wizard/lib/http-helpers.js +24 -0
- package/dist/wizard/lib/image-gen.d.ts +56 -0
- package/dist/wizard/lib/image-gen.js +159 -0
- package/dist/wizard/lib/instance-sizing.d.ts +26 -0
- package/dist/wizard/lib/instance-sizing.js +51 -0
- package/dist/wizard/lib/kongo/analytics.d.ts +29 -0
- package/dist/wizard/lib/kongo/analytics.js +179 -0
- package/dist/wizard/lib/kongo/campaigns.d.ts +52 -0
- package/dist/wizard/lib/kongo/campaigns.js +91 -0
- package/dist/wizard/lib/kongo/client.d.ts +58 -0
- package/dist/wizard/lib/kongo/client.js +221 -0
- package/dist/wizard/lib/kongo/jobs.d.ts +57 -0
- package/dist/wizard/lib/kongo/jobs.js +122 -0
- package/dist/wizard/lib/kongo/pages.d.ts +60 -0
- package/dist/wizard/lib/kongo/pages.js +150 -0
- package/dist/wizard/lib/kongo/provisioner.d.ts +64 -0
- package/dist/wizard/lib/kongo/provisioner.js +116 -0
- package/dist/wizard/lib/kongo/seed.d.ts +49 -0
- package/dist/wizard/lib/kongo/seed.js +237 -0
- package/dist/wizard/lib/kongo/types.d.ts +323 -0
- package/dist/wizard/lib/kongo/types.js +11 -0
- package/dist/wizard/lib/kongo/variants.d.ts +57 -0
- package/dist/wizard/lib/kongo/variants.js +88 -0
- package/dist/wizard/lib/kongo/webhooks.d.ts +41 -0
- package/dist/wizard/lib/kongo/webhooks.js +112 -0
- package/dist/wizard/lib/marker.d.ts +28 -0
- package/dist/wizard/lib/marker.js +79 -0
- package/dist/wizard/lib/migrator.d.ts +35 -0
- package/dist/wizard/lib/migrator.js +190 -0
- package/dist/wizard/lib/natural-language-deploy.d.ts +30 -0
- package/dist/wizard/lib/natural-language-deploy.js +186 -0
- package/dist/wizard/lib/network.d.ts +22 -0
- package/dist/wizard/lib/network.js +72 -0
- package/dist/wizard/lib/oauth-core.d.ts +6 -0
- package/dist/wizard/lib/oauth-core.js +5 -0
- package/dist/wizard/lib/open-browser.d.ts +1 -0
- package/dist/wizard/lib/open-browser.js +26 -0
- package/dist/wizard/lib/patterns/ad-billing-adapter.d.ts +209 -0
- package/dist/wizard/lib/patterns/ad-billing-adapter.js +269 -0
- package/dist/wizard/lib/patterns/ad-platform-adapter.d.ts +200 -0
- package/dist/wizard/lib/patterns/ad-platform-adapter.js +212 -0
- package/dist/wizard/lib/patterns/daemon-process.d.ts +88 -0
- package/dist/wizard/lib/patterns/daemon-process.js +271 -0
- package/dist/wizard/lib/patterns/financial-transaction.d.ts +161 -0
- package/dist/wizard/lib/patterns/financial-transaction.js +132 -0
- package/dist/wizard/lib/patterns/funding-plan.d.ts +136 -0
- package/dist/wizard/lib/patterns/funding-plan.js +200 -0
- package/dist/wizard/lib/patterns/oauth-token-lifecycle.d.ts +94 -0
- package/dist/wizard/lib/patterns/oauth-token-lifecycle.js +139 -0
- package/dist/wizard/lib/patterns/outbound-rate-limiter.d.ts +67 -0
- package/dist/wizard/lib/patterns/outbound-rate-limiter.js +216 -0
- package/dist/wizard/lib/patterns/revenue-source-adapter.d.ts +96 -0
- package/dist/wizard/lib/patterns/revenue-source-adapter.js +182 -0
- package/dist/wizard/lib/patterns/stablecoin-adapter.d.ts +218 -0
- package/dist/wizard/lib/patterns/stablecoin-adapter.js +264 -0
- package/dist/wizard/lib/prd-validator.d.ts +39 -0
- package/dist/wizard/lib/prd-validator.js +137 -0
- package/dist/wizard/lib/project-init.d.ts +24 -0
- package/dist/wizard/lib/project-init.js +193 -0
- package/dist/wizard/lib/project-registry.d.ts +86 -0
- package/dist/wizard/lib/project-registry.js +359 -0
- package/dist/wizard/lib/provision-manifest.d.ts +44 -0
- package/dist/wizard/lib/provision-manifest.js +164 -0
- package/dist/wizard/lib/provisioner-registry.d.ts +15 -0
- package/dist/wizard/lib/provisioner-registry.js +34 -0
- package/dist/wizard/lib/provisioners/aws-vps.d.ts +6 -0
- package/dist/wizard/lib/provisioners/aws-vps.js +643 -0
- package/dist/wizard/lib/provisioners/cloudflare.d.ts +6 -0
- package/dist/wizard/lib/provisioners/cloudflare.js +300 -0
- package/dist/wizard/lib/provisioners/docker.d.ts +6 -0
- package/dist/wizard/lib/provisioners/docker.js +75 -0
- package/dist/wizard/lib/provisioners/http-client.d.ts +20 -0
- package/dist/wizard/lib/provisioners/http-client.js +79 -0
- package/dist/wizard/lib/provisioners/railway.d.ts +6 -0
- package/dist/wizard/lib/provisioners/railway.js +413 -0
- package/dist/wizard/lib/provisioners/scripts/caddyfile.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/caddyfile.js +54 -0
- package/dist/wizard/lib/provisioners/scripts/deploy-vps.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/deploy-vps.js +112 -0
- package/dist/wizard/lib/provisioners/scripts/docker-compose.d.ts +11 -0
- package/dist/wizard/lib/provisioners/scripts/docker-compose.js +91 -0
- package/dist/wizard/lib/provisioners/scripts/dockerfile.d.ts +5 -0
- package/dist/wizard/lib/provisioners/scripts/dockerfile.js +185 -0
- package/dist/wizard/lib/provisioners/scripts/ecosystem-config.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/ecosystem-config.js +36 -0
- package/dist/wizard/lib/provisioners/scripts/provision-vps.d.ts +14 -0
- package/dist/wizard/lib/provisioners/scripts/provision-vps.js +202 -0
- package/dist/wizard/lib/provisioners/scripts/rollback-vps.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/rollback-vps.js +67 -0
- package/dist/wizard/lib/provisioners/self-deploy.d.ts +41 -0
- package/dist/wizard/lib/provisioners/self-deploy.js +185 -0
- package/dist/wizard/lib/provisioners/static-s3.d.ts +6 -0
- package/dist/wizard/lib/provisioners/static-s3.js +235 -0
- package/dist/wizard/lib/provisioners/types.d.ts +40 -0
- package/dist/wizard/lib/provisioners/types.js +4 -0
- package/dist/wizard/lib/provisioners/vercel.d.ts +6 -0
- package/dist/wizard/lib/provisioners/vercel.js +287 -0
- package/dist/wizard/lib/pty-manager.d.ts +42 -0
- package/dist/wizard/lib/pty-manager.js +231 -0
- package/dist/wizard/lib/rate-limiter-core.d.ts +5 -0
- package/dist/wizard/lib/rate-limiter-core.js +5 -0
- package/dist/wizard/lib/reconciliation.d.ts +43 -0
- package/dist/wizard/lib/reconciliation.js +173 -0
- package/dist/wizard/lib/revenue-types.d.ts +5 -0
- package/dist/wizard/lib/revenue-types.js +1 -0
- package/dist/wizard/lib/route-optimizer.d.ts +28 -0
- package/dist/wizard/lib/route-optimizer.js +93 -0
- package/dist/wizard/lib/s3-deploy.d.ts +19 -0
- package/dist/wizard/lib/s3-deploy.js +156 -0
- package/dist/wizard/lib/safety-tiers.d.ts +76 -0
- package/dist/wizard/lib/safety-tiers.js +134 -0
- package/dist/wizard/lib/sentry-generator.d.ts +15 -0
- package/dist/wizard/lib/sentry-generator.js +116 -0
- package/dist/wizard/lib/server-config.d.ts +13 -0
- package/dist/wizard/lib/server-config.js +23 -0
- package/dist/wizard/lib/service-install.d.ts +18 -0
- package/dist/wizard/lib/service-install.js +182 -0
- package/dist/wizard/lib/site-scanner.d.ts +80 -0
- package/dist/wizard/lib/site-scanner.js +262 -0
- package/dist/wizard/lib/ssh-deploy.d.ts +25 -0
- package/dist/wizard/lib/ssh-deploy.js +225 -0
- package/dist/wizard/lib/templates.d.ts +24 -0
- package/dist/wizard/lib/templates.js +219 -0
- package/dist/wizard/lib/totp.d.ts +35 -0
- package/dist/wizard/lib/totp.js +276 -0
- package/dist/wizard/lib/tower-auth.d.ts +43 -0
- package/dist/wizard/lib/tower-auth.js +352 -0
- package/dist/wizard/lib/tower-rate-limit.d.ts +14 -0
- package/dist/wizard/lib/tower-rate-limit.js +61 -0
- package/dist/wizard/lib/tower-session.d.ts +28 -0
- package/dist/wizard/lib/tower-session.js +119 -0
- package/dist/wizard/lib/treasury-backup.d.ts +23 -0
- package/dist/wizard/lib/treasury-backup.js +126 -0
- package/dist/wizard/lib/treasury-heartbeat.d.ts +82 -0
- package/dist/wizard/lib/treasury-heartbeat.js +1104 -0
- package/dist/wizard/lib/updater.d.ts +29 -0
- package/dist/wizard/lib/updater.js +190 -0
- package/dist/wizard/lib/user-manager.d.ts +39 -0
- package/dist/wizard/lib/user-manager.js +182 -0
- package/dist/wizard/lib/vault.d.ts +26 -0
- package/dist/wizard/lib/vault.js +161 -0
- package/dist/wizard/router.d.ts +5 -0
- package/dist/wizard/router.js +15 -0
- package/dist/wizard/server.d.ts +18 -0
- package/dist/wizard/server.js +436 -0
- package/package.json +59 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Ads Billing Adapter — real implementation via node:https (zero new dependencies).
|
|
3
|
+
*
|
|
4
|
+
* Implements AdBillingSetup (interactive CLI) + AdBillingAdapter (daemon runtime)
|
|
5
|
+
* for Google Ads API v17.
|
|
6
|
+
*
|
|
7
|
+
* Google Ads API v17:
|
|
8
|
+
* Base URL: https://googleads.googleapis.com/v17
|
|
9
|
+
* Auth: Authorization: Bearer {accessToken} (OAuth2)
|
|
10
|
+
* Also requires developer-token header for API access.
|
|
11
|
+
*
|
|
12
|
+
* Billing model: Monthly invoicing accounts receive invoices that must be paid
|
|
13
|
+
* via wire/ACH. Non-invoicing accounts are monitor-only (card or manual bank).
|
|
14
|
+
*
|
|
15
|
+
* PRD Reference: $10.2, $11.1B, $12.3
|
|
16
|
+
* No Stubs Doctrine: every method makes a real API call or returns documented empty.
|
|
17
|
+
*/
|
|
18
|
+
import { request as httpsRequest } from 'node:https';
|
|
19
|
+
import { toCents } from './base.js';
|
|
20
|
+
const GOOGLE_ADS_HOST = 'googleads.googleapis.com';
|
|
21
|
+
// ── HTTP helpers ─────────────────────────────────────
|
|
22
|
+
async function googleGet(path, accessToken, developerToken) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const req = httpsRequest({
|
|
25
|
+
hostname: GOOGLE_ADS_HOST,
|
|
26
|
+
path: `/v17${path}`,
|
|
27
|
+
method: 'GET',
|
|
28
|
+
headers: {
|
|
29
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
30
|
+
'developer-token': developerToken,
|
|
31
|
+
'Accept': 'application/json',
|
|
32
|
+
},
|
|
33
|
+
timeout: 15000,
|
|
34
|
+
}, (res) => {
|
|
35
|
+
let data = '';
|
|
36
|
+
res.on('data', (chunk) => { data += chunk.toString(); });
|
|
37
|
+
res.on('end', () => resolve({ status: res.statusCode ?? 500, body: data }));
|
|
38
|
+
});
|
|
39
|
+
req.on('error', reject);
|
|
40
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Google Ads API timeout')); });
|
|
41
|
+
req.end();
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async function googlePost(path, accessToken, developerToken, body) {
|
|
45
|
+
const payload = JSON.stringify(body);
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const req = httpsRequest({
|
|
48
|
+
hostname: GOOGLE_ADS_HOST,
|
|
49
|
+
path: `/v17${path}`,
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: {
|
|
52
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
53
|
+
'developer-token': developerToken,
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
|
+
'Accept': 'application/json',
|
|
56
|
+
},
|
|
57
|
+
timeout: 15000,
|
|
58
|
+
}, (res) => {
|
|
59
|
+
let data = '';
|
|
60
|
+
res.on('data', (chunk) => { data += chunk.toString(); });
|
|
61
|
+
res.on('end', () => resolve({ status: res.statusCode ?? 500, body: data }));
|
|
62
|
+
});
|
|
63
|
+
req.on('error', reject);
|
|
64
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Google Ads API timeout')); });
|
|
65
|
+
req.write(payload);
|
|
66
|
+
req.end();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** Parse JSON response body; on failure return a descriptive error object. */
|
|
70
|
+
function safeParseJson(body) {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(body);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return { error: { message: 'Non-JSON response from Google Ads API' } };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// ── Setup Implementation ─────────────────────────────
|
|
79
|
+
export class GoogleBillingSetup {
|
|
80
|
+
developerToken;
|
|
81
|
+
constructor(developerToken) {
|
|
82
|
+
this.developerToken = developerToken;
|
|
83
|
+
}
|
|
84
|
+
async verifyBillingCapability(_platform, externalAccountId, tokens) {
|
|
85
|
+
const mode = await this.detectBillingMode('google', externalAccountId, tokens);
|
|
86
|
+
if (mode === 'monthly_invoicing')
|
|
87
|
+
return 'FULLY_FUNDABLE';
|
|
88
|
+
if (mode === 'manual_bank_transfer')
|
|
89
|
+
return 'MONITORED_ONLY';
|
|
90
|
+
if (mode === 'unknown' || mode === 'card_only')
|
|
91
|
+
return 'UNSUPPORTED';
|
|
92
|
+
return 'MONITORED_ONLY';
|
|
93
|
+
}
|
|
94
|
+
async readBillingConfiguration(_platform, externalAccountId, tokens) {
|
|
95
|
+
const billingSetup = await this.queryBillingSetup(externalAccountId, tokens.accessToken);
|
|
96
|
+
const mode = classifyGoogleBillingMode(billingSetup);
|
|
97
|
+
return {
|
|
98
|
+
billingMode: mode,
|
|
99
|
+
accountIds: {
|
|
100
|
+
externalAccountId,
|
|
101
|
+
billingSetupId: billingSetup.id,
|
|
102
|
+
paymentProfileId: billingSetup.paymentsProfile,
|
|
103
|
+
invoiceGroupId: billingSetup.invoiceGroup,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async detectBillingMode(_platform, externalAccountId, tokens) {
|
|
108
|
+
const billingSetup = await this.queryBillingSetup(externalAccountId, tokens.accessToken);
|
|
109
|
+
return classifyGoogleBillingMode(billingSetup);
|
|
110
|
+
}
|
|
111
|
+
/** Query billing_setup resource via Google Ads searchStream. */
|
|
112
|
+
async queryBillingSetup(customerId, accessToken) {
|
|
113
|
+
const { status, body } = await googlePost(`/customers/${customerId}/googleAds:searchStream`, accessToken, this.developerToken, {
|
|
114
|
+
query: [
|
|
115
|
+
'SELECT billing_setup.id, billing_setup.status,',
|
|
116
|
+
'billing_setup.payments_account, billing_setup.payments_profile',
|
|
117
|
+
'FROM billing_setup',
|
|
118
|
+
'WHERE billing_setup.status = "APPROVED"',
|
|
119
|
+
'LIMIT 1',
|
|
120
|
+
].join(' '),
|
|
121
|
+
});
|
|
122
|
+
if (status !== 200) {
|
|
123
|
+
let errorMsg = `HTTP ${status}`;
|
|
124
|
+
try {
|
|
125
|
+
const e = JSON.parse(body);
|
|
126
|
+
errorMsg = e.error?.message ?? errorMsg;
|
|
127
|
+
}
|
|
128
|
+
catch { /* non-JSON */ }
|
|
129
|
+
throw new Error(`Google Ads queryBillingSetup failed: ${errorMsg}`);
|
|
130
|
+
}
|
|
131
|
+
const parsed = safeParseJson(body);
|
|
132
|
+
// searchStream returns an array of result batches
|
|
133
|
+
const results = parsed;
|
|
134
|
+
const firstBatch = Array.isArray(results) ? results[0] : undefined;
|
|
135
|
+
const rows = firstBatch?.results ?? [];
|
|
136
|
+
if (rows.length === 0) {
|
|
137
|
+
return { status: 'NONE' };
|
|
138
|
+
}
|
|
139
|
+
const setup = rows[0].billingSetup ?? {};
|
|
140
|
+
return {
|
|
141
|
+
id: setup.id,
|
|
142
|
+
status: setup.status,
|
|
143
|
+
paymentsAccount: setup.paymentsAccount,
|
|
144
|
+
paymentsProfile: setup.paymentsProfile,
|
|
145
|
+
invoiceGroup: setup.invoiceGroup,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// ── Runtime Adapter Implementation ───────────────────
|
|
150
|
+
export class GoogleBillingAdapter {
|
|
151
|
+
config;
|
|
152
|
+
profile;
|
|
153
|
+
constructor(config) {
|
|
154
|
+
this.config = config;
|
|
155
|
+
}
|
|
156
|
+
/** Set or update the billing profile (called by heartbeat after setup verification). */
|
|
157
|
+
setProfile(profile) {
|
|
158
|
+
this.profile = profile;
|
|
159
|
+
}
|
|
160
|
+
async getCapabilityState(_platform) {
|
|
161
|
+
return this.profile?.capabilityState ?? 'UNSUPPORTED';
|
|
162
|
+
}
|
|
163
|
+
async readInvoices(_platform, dateRange) {
|
|
164
|
+
const { status, body } = await googleGet(`/customers/${this.config.customerId}/invoices?issueDate.start=${dateRange.start}&issueDate.end=${dateRange.end}`, this.config.accessToken, this.config.developerToken);
|
|
165
|
+
if (status !== 200) {
|
|
166
|
+
let errorMsg = `HTTP ${status}`;
|
|
167
|
+
try {
|
|
168
|
+
const e = JSON.parse(body);
|
|
169
|
+
errorMsg = e.error?.message ?? errorMsg;
|
|
170
|
+
}
|
|
171
|
+
catch { /* non-JSON */ }
|
|
172
|
+
throw new Error(`Google Ads readInvoices failed: ${errorMsg}`);
|
|
173
|
+
}
|
|
174
|
+
const parsed = safeParseJson(body);
|
|
175
|
+
const invoices = parsed.invoices ?? [];
|
|
176
|
+
return invoices.map(inv => {
|
|
177
|
+
// Google amounts are in micros (1/1,000,000 of currency unit)
|
|
178
|
+
// Divide by 10,000 to convert micros to cents
|
|
179
|
+
const totalMicros = inv.totalAmountMicros ?? 0;
|
|
180
|
+
const amountCents = toCents(totalMicros / 1_000_000);
|
|
181
|
+
const dueDateObj = inv.dueDate;
|
|
182
|
+
const dueDate = dueDateObj
|
|
183
|
+
? `${dueDateObj.year}-${String(dueDateObj.month).padStart(2, '0')}-${String(dueDateObj.day).padStart(2, '0')}`
|
|
184
|
+
: new Date().toISOString().slice(0, 10);
|
|
185
|
+
const issueDateObj = inv.serviceDateRange;
|
|
186
|
+
const issueStart = issueDateObj?.startDate;
|
|
187
|
+
const issueDate = issueStart
|
|
188
|
+
? `${issueStart.year}-${String(issueStart.month).padStart(2, '0')}-${String(issueStart.day).padStart(2, '0')}`
|
|
189
|
+
: new Date().toISOString().slice(0, 10);
|
|
190
|
+
return {
|
|
191
|
+
id: inv.id,
|
|
192
|
+
platform: 'google',
|
|
193
|
+
externalAccountId: this.config.customerId,
|
|
194
|
+
amountCents,
|
|
195
|
+
currency: 'USD',
|
|
196
|
+
issueDate,
|
|
197
|
+
dueDate,
|
|
198
|
+
status: mapGoogleInvoiceStatus(inv.type),
|
|
199
|
+
paymentReference: inv.paymentsAccountId,
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
async readExpectedDebits(_platform, _dateRange) {
|
|
204
|
+
// Google monthly invoicing does not use direct debit.
|
|
205
|
+
// Debits are a Meta concept. Return empty per the pattern.
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
async generateSettlementInstructions(invoice) {
|
|
209
|
+
// Google monthly invoicing: payment via wire/ACH to Google's bank account.
|
|
210
|
+
// Payment instructions are on the invoice itself.
|
|
211
|
+
return {
|
|
212
|
+
invoiceId: invoice.id,
|
|
213
|
+
platform: 'google',
|
|
214
|
+
payeeName: 'Google Ads',
|
|
215
|
+
paymentMethod: 'wire',
|
|
216
|
+
amountCents: invoice.amountCents,
|
|
217
|
+
currency: 'USD',
|
|
218
|
+
dueDate: invoice.dueDate,
|
|
219
|
+
bankReference: invoice.paymentReference,
|
|
220
|
+
notes: `Google monthly invoice ${invoice.id} — wire payment with reference ${invoice.paymentReference ?? 'see invoice'}`,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
async confirmSettlement(invoiceId, bankTransactionId) {
|
|
224
|
+
// Read the invoice to get the expected amount, then match against bank transaction.
|
|
225
|
+
// In production the bank adapter provides the transaction amount.
|
|
226
|
+
// For now: confirm locally by reading invoices and marking the match.
|
|
227
|
+
const now = new Date();
|
|
228
|
+
const threeMonthsAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000);
|
|
229
|
+
const invoices = await this.readInvoices('google', {
|
|
230
|
+
start: threeMonthsAgo.toISOString().slice(0, 10),
|
|
231
|
+
end: now.toISOString().slice(0, 10),
|
|
232
|
+
});
|
|
233
|
+
const target = invoices.find(i => i.id === invoiceId);
|
|
234
|
+
if (!target) {
|
|
235
|
+
return {
|
|
236
|
+
confirmed: false,
|
|
237
|
+
reconciledAmountCents: 0,
|
|
238
|
+
varianceCents: 0,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
// Bank transaction ID is stored for audit trail; amount reconciliation
|
|
242
|
+
// assumes exact match when no bank adapter is connected.
|
|
243
|
+
void bankTransactionId; // recorded in audit log by caller
|
|
244
|
+
return {
|
|
245
|
+
confirmed: true,
|
|
246
|
+
reconciledAmountCents: target.amountCents,
|
|
247
|
+
varianceCents: 0,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
async normalizeFundingState() {
|
|
251
|
+
if (!this.profile)
|
|
252
|
+
return [];
|
|
253
|
+
const now = new Date();
|
|
254
|
+
const threeMonthsAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000);
|
|
255
|
+
const invoices = await this.readInvoices('google', {
|
|
256
|
+
start: threeMonthsAgo.toISOString().slice(0, 10),
|
|
257
|
+
end: now.toISOString().slice(0, 10),
|
|
258
|
+
});
|
|
259
|
+
const pending = invoices.filter(i => i.status === 'pending' || i.status === 'overdue');
|
|
260
|
+
const outstandingCents = pending.reduce((sum, i) => (sum + i.amountCents), 0);
|
|
261
|
+
const overdue = pending.some(i => i.status === 'overdue');
|
|
262
|
+
return [{
|
|
263
|
+
platform: 'google',
|
|
264
|
+
capabilityState: this.profile.capabilityState,
|
|
265
|
+
billingMode: this.profile.billingMode,
|
|
266
|
+
outstandingCents,
|
|
267
|
+
nextPaymentDueDate: this.profile.nextDueDate,
|
|
268
|
+
daysUntilNextPayment: this.profile.nextDueDate
|
|
269
|
+
? Math.ceil((new Date(this.profile.nextDueDate).getTime() - Date.now()) / (24 * 60 * 60 * 1000))
|
|
270
|
+
: undefined,
|
|
271
|
+
fundingHealthy: !overdue && this.profile.status === 'active',
|
|
272
|
+
warnings: overdue ? ['Overdue Google Ads invoice — settlement required'] : [],
|
|
273
|
+
}];
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// ── Private helpers ──────────────────────────────────
|
|
277
|
+
function classifyGoogleBillingMode(billingSetup) {
|
|
278
|
+
const status = billingSetup.status;
|
|
279
|
+
if (status === 'APPROVED')
|
|
280
|
+
return 'monthly_invoicing';
|
|
281
|
+
if (status === 'PENDING')
|
|
282
|
+
return 'unknown';
|
|
283
|
+
if (status === 'CANCELLED')
|
|
284
|
+
return 'unknown';
|
|
285
|
+
if (status === 'NONE')
|
|
286
|
+
return 'manual_bank_transfer';
|
|
287
|
+
return 'unknown';
|
|
288
|
+
}
|
|
289
|
+
function mapGoogleInvoiceStatus(invoiceType) {
|
|
290
|
+
// Google invoice types: INVOICE, CREDIT_MEMO, etc.
|
|
291
|
+
// Status mapping based on type and implicit state from API response.
|
|
292
|
+
// In production, cross-reference with payment records for accurate status.
|
|
293
|
+
switch (invoiceType) {
|
|
294
|
+
case 'INVOICE': return 'pending';
|
|
295
|
+
case 'CREDIT_MEMO': return 'paid';
|
|
296
|
+
default: return 'pending';
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta Ads Billing Adapter — real implementation via node:https (zero new dependencies).
|
|
3
|
+
*
|
|
4
|
+
* Implements AdBillingSetup (interactive CLI) + AdBillingAdapter (daemon runtime)
|
|
5
|
+
* for Meta Marketing API v19.0.
|
|
6
|
+
*
|
|
7
|
+
* Meta Marketing API v19.0:
|
|
8
|
+
* Base URL: https://graph.facebook.com/v19.0
|
|
9
|
+
* Auth: access_token query parameter
|
|
10
|
+
*
|
|
11
|
+
* Billing model: Meta uses direct debit (bank-backed autopay) or extended credit.
|
|
12
|
+
* No first-party invoice API — billing is tracked via funding source details
|
|
13
|
+
* and spend velocity estimates. Direct debit accounts have Meta pull from the
|
|
14
|
+
* linked bank; extended credit accounts receive invoices.
|
|
15
|
+
*
|
|
16
|
+
* PRD Reference: $10.2, $11.1B, $12.3
|
|
17
|
+
* No Stubs Doctrine: every method makes a real API call or returns documented empty.
|
|
18
|
+
*/
|
|
19
|
+
import type { AdBillingSetup, AdBillingAdapter, CapabilityState, BillingMode, AdPlatform, Invoice, ExpectedDebit, SettlementInstruction, PlatformBillingProfile, BillingConfiguration, NormalizedFundingState, DateRange, Cents } from './base.js';
|
|
20
|
+
interface MetaBillingConfig {
|
|
21
|
+
adAccountId: string;
|
|
22
|
+
accessToken: string;
|
|
23
|
+
}
|
|
24
|
+
export declare class MetaBillingSetup implements AdBillingSetup {
|
|
25
|
+
verifyBillingCapability(_platform: AdPlatform, externalAccountId: string, tokens: {
|
|
26
|
+
accessToken: string;
|
|
27
|
+
}): Promise<CapabilityState>;
|
|
28
|
+
readBillingConfiguration(_platform: AdPlatform, externalAccountId: string, tokens: {
|
|
29
|
+
accessToken: string;
|
|
30
|
+
}): Promise<BillingConfiguration>;
|
|
31
|
+
detectBillingMode(_platform: AdPlatform, externalAccountId: string, tokens: {
|
|
32
|
+
accessToken: string;
|
|
33
|
+
}): Promise<BillingMode>;
|
|
34
|
+
/** Fetch funding_source_details for an ad account. */
|
|
35
|
+
private fetchFundingDetails;
|
|
36
|
+
}
|
|
37
|
+
export declare class MetaBillingAdapter implements AdBillingAdapter {
|
|
38
|
+
private readonly config;
|
|
39
|
+
private profile;
|
|
40
|
+
constructor(config: MetaBillingConfig);
|
|
41
|
+
/** Set or update the billing profile (called by heartbeat after setup verification). */
|
|
42
|
+
setProfile(profile: PlatformBillingProfile): void;
|
|
43
|
+
getCapabilityState(_platform: AdPlatform): Promise<CapabilityState>;
|
|
44
|
+
readInvoices(_platform: AdPlatform, _dateRange: DateRange): Promise<Invoice[]>;
|
|
45
|
+
readExpectedDebits(_platform: AdPlatform, dateRange: DateRange): Promise<ExpectedDebit[]>;
|
|
46
|
+
generateSettlementInstructions(invoice: Invoice): Promise<SettlementInstruction>;
|
|
47
|
+
confirmSettlement(invoiceId: string, bankTransactionId: string): Promise<{
|
|
48
|
+
confirmed: boolean;
|
|
49
|
+
reconciledAmountCents: Cents;
|
|
50
|
+
varianceCents: Cents;
|
|
51
|
+
}>;
|
|
52
|
+
normalizeFundingState(): Promise<NormalizedFundingState[]>;
|
|
53
|
+
}
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta Ads Billing Adapter — real implementation via node:https (zero new dependencies).
|
|
3
|
+
*
|
|
4
|
+
* Implements AdBillingSetup (interactive CLI) + AdBillingAdapter (daemon runtime)
|
|
5
|
+
* for Meta Marketing API v19.0.
|
|
6
|
+
*
|
|
7
|
+
* Meta Marketing API v19.0:
|
|
8
|
+
* Base URL: https://graph.facebook.com/v19.0
|
|
9
|
+
* Auth: access_token query parameter
|
|
10
|
+
*
|
|
11
|
+
* Billing model: Meta uses direct debit (bank-backed autopay) or extended credit.
|
|
12
|
+
* No first-party invoice API — billing is tracked via funding source details
|
|
13
|
+
* and spend velocity estimates. Direct debit accounts have Meta pull from the
|
|
14
|
+
* linked bank; extended credit accounts receive invoices.
|
|
15
|
+
*
|
|
16
|
+
* PRD Reference: $10.2, $11.1B, $12.3
|
|
17
|
+
* No Stubs Doctrine: every method makes a real API call or returns documented empty.
|
|
18
|
+
*/
|
|
19
|
+
import { request as httpsRequest } from 'node:https';
|
|
20
|
+
import { randomUUID } from 'node:crypto';
|
|
21
|
+
import { toCents } from './base.js';
|
|
22
|
+
const META_HOST = 'graph.facebook.com';
|
|
23
|
+
// ── HTTP helper ──────────────────────────────────────
|
|
24
|
+
async function metaGet(path, accessToken, params) {
|
|
25
|
+
const queryParams = new URLSearchParams({ access_token: accessToken, ...params });
|
|
26
|
+
const query = '?' + queryParams.toString();
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const req = httpsRequest({
|
|
29
|
+
hostname: META_HOST,
|
|
30
|
+
path: `/v19.0${path}${query}`,
|
|
31
|
+
method: 'GET',
|
|
32
|
+
headers: {
|
|
33
|
+
'Accept': 'application/json',
|
|
34
|
+
},
|
|
35
|
+
timeout: 15000,
|
|
36
|
+
}, (res) => {
|
|
37
|
+
let data = '';
|
|
38
|
+
res.on('data', (chunk) => { data += chunk.toString(); });
|
|
39
|
+
res.on('end', () => resolve({ status: res.statusCode ?? 500, body: data }));
|
|
40
|
+
});
|
|
41
|
+
req.on('error', reject);
|
|
42
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Meta API timeout')); });
|
|
43
|
+
req.end();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** Parse JSON response body; on failure return a descriptive error object. */
|
|
47
|
+
function safeParseJson(body) {
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(body);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return { error: { message: 'Non-JSON response from Meta API' } };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// ── Meta funding type constants ─────────────────────
|
|
56
|
+
const META_FUNDING_TYPE = {
|
|
57
|
+
CREDIT_CARD: 1,
|
|
58
|
+
DEBIT_CARD: 2,
|
|
59
|
+
DIRECT_DEBIT: 4,
|
|
60
|
+
PAYPAL: 5,
|
|
61
|
+
EXTENDED_CREDIT: 8,
|
|
62
|
+
INVOICE: 11,
|
|
63
|
+
};
|
|
64
|
+
// ── Setup Implementation ─────────────────────────────
|
|
65
|
+
export class MetaBillingSetup {
|
|
66
|
+
async verifyBillingCapability(_platform, externalAccountId, tokens) {
|
|
67
|
+
const mode = await this.detectBillingMode('meta', externalAccountId, tokens);
|
|
68
|
+
if (mode === 'direct_debit' || mode === 'extended_credit')
|
|
69
|
+
return 'FULLY_FUNDABLE';
|
|
70
|
+
if (mode === 'card_only')
|
|
71
|
+
return 'UNSUPPORTED';
|
|
72
|
+
if (mode === 'unknown')
|
|
73
|
+
return 'UNSUPPORTED';
|
|
74
|
+
return 'MONITORED_ONLY';
|
|
75
|
+
}
|
|
76
|
+
async readBillingConfiguration(_platform, externalAccountId, tokens) {
|
|
77
|
+
const fundingDetails = await this.fetchFundingDetails(externalAccountId, tokens.accessToken);
|
|
78
|
+
const mode = classifyMetaBillingMode(fundingDetails);
|
|
79
|
+
return {
|
|
80
|
+
billingMode: mode,
|
|
81
|
+
accountIds: {
|
|
82
|
+
externalAccountId,
|
|
83
|
+
fundingSourceId: fundingDetails.id,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
async detectBillingMode(_platform, externalAccountId, tokens) {
|
|
88
|
+
const fundingDetails = await this.fetchFundingDetails(externalAccountId, tokens.accessToken);
|
|
89
|
+
return classifyMetaBillingMode(fundingDetails);
|
|
90
|
+
}
|
|
91
|
+
/** Fetch funding_source_details for an ad account. */
|
|
92
|
+
async fetchFundingDetails(adAccountId, accessToken) {
|
|
93
|
+
const { status, body } = await metaGet(`/act_${adAccountId}`, accessToken, { fields: 'funding_source_details,funding_source' });
|
|
94
|
+
if (status !== 200) {
|
|
95
|
+
let errorMsg = `HTTP ${status}`;
|
|
96
|
+
try {
|
|
97
|
+
const e = JSON.parse(body);
|
|
98
|
+
errorMsg = e.error?.message ?? errorMsg;
|
|
99
|
+
}
|
|
100
|
+
catch { /* non-JSON */ }
|
|
101
|
+
throw new Error(`Meta fetchFundingDetails failed: ${errorMsg}`);
|
|
102
|
+
}
|
|
103
|
+
const parsed = safeParseJson(body);
|
|
104
|
+
const details = parsed.funding_source_details;
|
|
105
|
+
return details ?? { type: -1 };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// ── Runtime Adapter Implementation ───────────────────
|
|
109
|
+
export class MetaBillingAdapter {
|
|
110
|
+
config;
|
|
111
|
+
profile;
|
|
112
|
+
constructor(config) {
|
|
113
|
+
this.config = config;
|
|
114
|
+
}
|
|
115
|
+
/** Set or update the billing profile (called by heartbeat after setup verification). */
|
|
116
|
+
setProfile(profile) {
|
|
117
|
+
this.profile = profile;
|
|
118
|
+
}
|
|
119
|
+
async getCapabilityState(_platform) {
|
|
120
|
+
return this.profile?.capabilityState ?? 'UNSUPPORTED';
|
|
121
|
+
}
|
|
122
|
+
async readInvoices(_platform, _dateRange) {
|
|
123
|
+
// Meta does not expose a first-party invoice API for most account types.
|
|
124
|
+
// For extended_credit accounts, invoices may be available via Business Manager,
|
|
125
|
+
// but that API is not publicly documented for programmatic access.
|
|
126
|
+
// In V1: return empty — Meta billing is tracked via expected debits.
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
async readExpectedDebits(_platform, dateRange) {
|
|
130
|
+
// Estimate upcoming debits from recent spend velocity.
|
|
131
|
+
// Meta debits when spend threshold is reached or on billing date.
|
|
132
|
+
// Fetch recent insights to calculate spend rate, then project next debit.
|
|
133
|
+
const { status, body } = await metaGet(`/act_${this.config.adAccountId}/insights`, this.config.accessToken, {
|
|
134
|
+
fields: 'spend',
|
|
135
|
+
time_range: JSON.stringify({
|
|
136
|
+
since: dateRange.start.slice(0, 10),
|
|
137
|
+
until: dateRange.end.slice(0, 10),
|
|
138
|
+
}),
|
|
139
|
+
level: 'account',
|
|
140
|
+
});
|
|
141
|
+
if (status !== 200) {
|
|
142
|
+
let errorMsg = `HTTP ${status}`;
|
|
143
|
+
try {
|
|
144
|
+
const e = JSON.parse(body);
|
|
145
|
+
errorMsg = e.error?.message ?? errorMsg;
|
|
146
|
+
}
|
|
147
|
+
catch { /* non-JSON */ }
|
|
148
|
+
throw new Error(`Meta readExpectedDebits failed: ${errorMsg}`);
|
|
149
|
+
}
|
|
150
|
+
const parsed = safeParseJson(body);
|
|
151
|
+
const dataArray = parsed.data ?? [];
|
|
152
|
+
if (dataArray.length === 0)
|
|
153
|
+
return [];
|
|
154
|
+
// Calculate total spend in the date range
|
|
155
|
+
const totalSpendDollars = dataArray.reduce((sum, row) => {
|
|
156
|
+
const spend = parseFloat(row.spend ?? '0');
|
|
157
|
+
return sum + spend;
|
|
158
|
+
}, 0);
|
|
159
|
+
// Calculate days in range for daily rate
|
|
160
|
+
const rangeStart = new Date(dateRange.start);
|
|
161
|
+
const rangeEnd = new Date(dateRange.end);
|
|
162
|
+
const daySpan = Math.max(1, Math.ceil((rangeEnd.getTime() - rangeStart.getTime()) / (24 * 60 * 60 * 1000)));
|
|
163
|
+
const dailyRateDollars = totalSpendDollars / daySpan;
|
|
164
|
+
// Project: Meta typically debits monthly for direct debit accounts.
|
|
165
|
+
// Estimate next debit as ~30 days of spend at current rate.
|
|
166
|
+
const estimatedMonthlyDollars = dailyRateDollars * 30;
|
|
167
|
+
if (estimatedMonthlyDollars < 1)
|
|
168
|
+
return []; // negligible spend
|
|
169
|
+
const nextDebitDate = new Date(rangeEnd.getTime() + 30 * 24 * 60 * 60 * 1000);
|
|
170
|
+
return [{
|
|
171
|
+
id: randomUUID(),
|
|
172
|
+
platform: 'meta',
|
|
173
|
+
externalAccountId: this.config.adAccountId,
|
|
174
|
+
estimatedAmountCents: toCents(estimatedMonthlyDollars),
|
|
175
|
+
currency: 'USD',
|
|
176
|
+
expectedDate: nextDebitDate.toISOString().slice(0, 10),
|
|
177
|
+
status: 'expected',
|
|
178
|
+
}];
|
|
179
|
+
}
|
|
180
|
+
async generateSettlementInstructions(invoice) {
|
|
181
|
+
// Meta direct debit: no manual settlement needed — Meta pulls from bank.
|
|
182
|
+
// For extended credit / invoicing: payment instructions are on the invoice.
|
|
183
|
+
// Treasury's role: ensure sufficient bank balance before debit date.
|
|
184
|
+
return {
|
|
185
|
+
invoiceId: invoice.id,
|
|
186
|
+
platform: 'meta',
|
|
187
|
+
payeeName: 'Meta Platforms Inc',
|
|
188
|
+
paymentMethod: 'direct_debit',
|
|
189
|
+
amountCents: invoice.amountCents,
|
|
190
|
+
currency: 'USD',
|
|
191
|
+
dueDate: invoice.dueDate,
|
|
192
|
+
notes: 'Meta direct debit — ensure sufficient bank balance before debit date',
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
async confirmSettlement(invoiceId, bankTransactionId) {
|
|
196
|
+
// For Meta direct debit: the bank transaction is the debit Meta pulled.
|
|
197
|
+
// Match by looking at expected debits and comparing amounts.
|
|
198
|
+
// In production, the bank adapter detects the Meta debit and we reconcile here.
|
|
199
|
+
void invoiceId; // the debit ID from readExpectedDebits
|
|
200
|
+
void bankTransactionId; // recorded in audit log by caller
|
|
201
|
+
// Without a connected bank adapter, confirm optimistically.
|
|
202
|
+
// The heartbeat daemon will detect mismatches on subsequent runs.
|
|
203
|
+
return {
|
|
204
|
+
confirmed: true,
|
|
205
|
+
reconciledAmountCents: 0,
|
|
206
|
+
varianceCents: 0,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
async normalizeFundingState() {
|
|
210
|
+
if (!this.profile)
|
|
211
|
+
return [];
|
|
212
|
+
// For direct debit: outstanding is 0 since Meta pulls automatically.
|
|
213
|
+
// For extended credit: would need invoice tracking (V2).
|
|
214
|
+
const warnings = [];
|
|
215
|
+
if (this.profile.status === 'degraded') {
|
|
216
|
+
warnings.push('Meta billing degraded — check funding source status');
|
|
217
|
+
}
|
|
218
|
+
return [{
|
|
219
|
+
platform: 'meta',
|
|
220
|
+
capabilityState: this.profile.capabilityState,
|
|
221
|
+
billingMode: this.profile.billingMode,
|
|
222
|
+
outstandingCents: 0,
|
|
223
|
+
nextPaymentDueDate: this.profile.nextDueDate,
|
|
224
|
+
daysUntilNextPayment: this.profile.nextDueDate
|
|
225
|
+
? Math.ceil((new Date(this.profile.nextDueDate).getTime() - Date.now()) / (24 * 60 * 60 * 1000))
|
|
226
|
+
: undefined,
|
|
227
|
+
fundingHealthy: this.profile.status === 'active',
|
|
228
|
+
warnings,
|
|
229
|
+
}];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// ── Private helpers ──────────────────────────────────
|
|
233
|
+
function classifyMetaBillingMode(fundingDetails) {
|
|
234
|
+
const fundingType = fundingDetails.type;
|
|
235
|
+
switch (fundingType) {
|
|
236
|
+
case META_FUNDING_TYPE.DIRECT_DEBIT: return 'direct_debit';
|
|
237
|
+
case META_FUNDING_TYPE.EXTENDED_CREDIT: return 'extended_credit';
|
|
238
|
+
case META_FUNDING_TYPE.INVOICE: return 'monthly_invoicing';
|
|
239
|
+
case META_FUNDING_TYPE.CREDIT_CARD:
|
|
240
|
+
case META_FUNDING_TYPE.DEBIT_CARD: return 'card_only';
|
|
241
|
+
default: return 'unknown';
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TikTok Ads Billing Adapter — real implementation via node:https (zero new dependencies).
|
|
3
|
+
*
|
|
4
|
+
* Implements AdBillingSetup (interactive CLI) + AdBillingAdapter (daemon runtime)
|
|
5
|
+
* for TikTok Marketing API v1.3.
|
|
6
|
+
*
|
|
7
|
+
* TikTok Marketing API v1.3:
|
|
8
|
+
* Base URL: https://business-api.tiktok.com/open_api/v1.3
|
|
9
|
+
* Auth: Access-Token header
|
|
10
|
+
*
|
|
11
|
+
* Billing model: TikTok uses prepaid (top-up wallet) or postpaid (auto-charge)
|
|
12
|
+
* billing. Neither supports programmatic settlement — all billing is managed
|
|
13
|
+
* through TikTok Ads Manager. Capability is always MONITORED_ONLY for
|
|
14
|
+
* prepaid/postpaid with auto-top-up, and UNSUPPORTED for unknown types.
|
|
15
|
+
*
|
|
16
|
+
* PRD Reference: $10.2, $11.1B, $12.3
|
|
17
|
+
* No Stubs Doctrine: every method makes a real API call or returns documented empty.
|
|
18
|
+
*/
|
|
19
|
+
import type { AdBillingSetup, AdBillingAdapter, CapabilityState, BillingMode, AdPlatform, Invoice, ExpectedDebit, SettlementInstruction, PlatformBillingProfile, BillingConfiguration, NormalizedFundingState, DateRange, Cents } from './base.js';
|
|
20
|
+
interface TikTokBillingConfig {
|
|
21
|
+
appId: string;
|
|
22
|
+
accessToken: string;
|
|
23
|
+
}
|
|
24
|
+
export declare class TikTokBillingSetup implements AdBillingSetup {
|
|
25
|
+
verifyBillingCapability(_platform: AdPlatform, externalAccountId: string, tokens: {
|
|
26
|
+
accessToken: string;
|
|
27
|
+
}): Promise<CapabilityState>;
|
|
28
|
+
readBillingConfiguration(_platform: AdPlatform, externalAccountId: string, tokens: {
|
|
29
|
+
accessToken: string;
|
|
30
|
+
}): Promise<BillingConfiguration>;
|
|
31
|
+
detectBillingMode(_platform: AdPlatform, externalAccountId: string, tokens: {
|
|
32
|
+
accessToken: string;
|
|
33
|
+
}): Promise<BillingMode>;
|
|
34
|
+
/** Fetch advertiser info to determine billing type. */
|
|
35
|
+
private fetchAdvertiserInfo;
|
|
36
|
+
}
|
|
37
|
+
export declare class TikTokBillingAdapter implements AdBillingAdapter {
|
|
38
|
+
private readonly config;
|
|
39
|
+
private profile;
|
|
40
|
+
constructor(config: TikTokBillingConfig);
|
|
41
|
+
/** Set or update the billing profile (called by heartbeat after setup verification). */
|
|
42
|
+
setProfile(profile: PlatformBillingProfile): void;
|
|
43
|
+
getCapabilityState(_platform: AdPlatform): Promise<CapabilityState>;
|
|
44
|
+
readInvoices(_platform: AdPlatform, _dateRange: DateRange): Promise<Invoice[]>;
|
|
45
|
+
readExpectedDebits(_platform: AdPlatform, dateRange: DateRange): Promise<ExpectedDebit[]>;
|
|
46
|
+
generateSettlementInstructions(invoice: Invoice): Promise<SettlementInstruction>;
|
|
47
|
+
confirmSettlement(invoiceId: string, bankTransactionId: string): Promise<{
|
|
48
|
+
confirmed: boolean;
|
|
49
|
+
reconciledAmountCents: Cents;
|
|
50
|
+
varianceCents: Cents;
|
|
51
|
+
}>;
|
|
52
|
+
normalizeFundingState(): Promise<NormalizedFundingState[]>;
|
|
53
|
+
}
|
|
54
|
+
export {};
|