← back to AbramsEgo
scripts/pull-cj-commissions.mjs
84 lines
#!/usr/bin/env node
// pull-cj-commissions.mjs — pull publisher commissions from CJ (Commission Junction)
// and emit an importer-ready CSV, so the loop is: CJ API → CSV → import-affiliate-commissions.mjs.
//
// Reads CJ_TOKEN (personal access token) + CJ_COMPANY_ID (publisher CID) from the
// ENVIRONMENT only — never prints, logs, or writes the token anywhere.
//
// USAGE:
// CJ_TOKEN=… CJ_COMPANY_ID=… node scripts/pull-cj-commissions.mjs [--since 2026-01-01] [--before 2026-08-04] [--out cj.csv]
// (default window: Jan 1 this-year → tomorrow UTC; default: CSV to stdout)
const TOKEN = process.env.CJ_TOKEN;
const CID = process.env.CJ_COMPANY_ID;
if (!TOKEN || !CID) {
console.error('✗ CJ_TOKEN and CJ_COMPANY_ID must be set in the environment.');
process.exit(2);
}
const arg = (n, d) => { const i = process.argv.indexOf(n); return i >= 0 ? process.argv[i + 1] : d; };
const since = `${arg('--since', '2026-01-01')}T00:00:00Z`;
const before = `${arg('--before', new Date(Date.now() + 864e5).toISOString().slice(0, 10))}T00:00:00Z`;
const outFile = arg('--out', null);
// CJ caps each query at 31 days, so page the range in ≤30-day windows.
const q = (s, b) => `{
publisherCommissions(forPublishers:["${CID}"], sincePostingDate:"${s}", beforePostingDate:"${b}") {
count
payloadComplete
records { advertiserName postingDate pubCommissionAmountUsd saleAmountUsd actionStatus actionType commissionId orderId }
}
}`;
async function queryWindow(s, b) {
const res = await fetch('https://commissions.api.cj.com/query', {
method: 'POST',
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ query: q(s, b) }),
}).catch((e) => { console.error('✗ network error:', e.message); process.exit(1); });
const text = await res.text();
if (!res.ok) { console.error(`✗ CJ API HTTP ${res.status} (${s.slice(0,10)}→${b.slice(0,10)}):`, text.slice(0, 300)); process.exit(1); }
let json; try { json = JSON.parse(text); } catch { console.error('✗ non-JSON:', text.slice(0, 300)); process.exit(1); }
if (json.errors) { console.error('✗ CJ GraphQL errors:', JSON.stringify(json.errors)); process.exit(1); }
return (json.data && json.data.publisherCommissions) || { count: 0, records: [] };
}
const DAY = 864e5, WIN = 30 * DAY;
const records = [];
let cursor = Date.parse(since); const end = Date.parse(before);
while (cursor < end) {
const wEnd = Math.min(cursor + WIN, end);
const pc = await queryWindow(new Date(cursor).toISOString(), new Date(wEnd).toISOString());
records.push(...(pc.records || []));
console.error(` ${new Date(cursor).toISOString().slice(0,10)} → ${new Date(wEnd).toISOString().slice(0,10)}: ${(pc.records||[]).length} record(s)`);
cursor = wEnd;
}
console.error(`CJ commissions ${since.slice(0,10)} → ${before.slice(0,10)}: ${records.length} record(s) total`);
if (!records.length) {
console.error('(no commission records in window — nothing to book)');
if (outFile) { const fs = await import('fs'); fs.writeFileSync(outFile, 'Event Date,Advertiser Name,Publisher Commission,Action Status,Commission ID,Order ID\n'); console.error(`✓ wrote header-only CSV → ${outFile}`); }
process.exit(0);
}
// emit CSV the importer's cj preset reads (Event Date / Advertiser Name / Publisher Commission / Commission ID)
const esc = (v) => { const s = String(v ?? ''); return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s; };
const header = 'Event Date,Advertiser Name,Publisher Commission,Action Status,Commission ID,Order ID';
const lines = records.map((r) => [
(r.postingDate || '').slice(0, 10),
esc(r.advertiserName),
r.pubCommissionAmountUsd ?? '0',
esc(r.actionStatus),
esc(r.commissionId),
esc(r.orderId),
].join(','));
const csv = [header, ...lines].join('\n') + '\n';
if (outFile) { const fs = await import('fs'); fs.writeFileSync(outFile, csv); console.error(`✓ wrote ${records.length} row(s) → ${outFile}`); }
else process.stdout.write(csv);
// quick totals to stderr (visible even when CSV goes to stdout)
const byStatus = {};
let total = 0;
for (const r of records) { const a = Number(r.pubCommissionAmountUsd) || 0; total += a; byStatus[r.actionStatus] = (byStatus[r.actionStatus] || 0) + a; }
console.error(`total pub commission: $${total.toFixed(2)} ${JSON.stringify(byStatus)}`);