From 549a849bc02f80767d174382ef0fde1f128ae8d1 Mon Sep 17 00:00:00 2001 From: Jennie Robinson Faber Date: Sat, 18 Apr 2026 20:58:17 +0100 Subject: [PATCH] fix(scripts): helcim plan-create payload shape + empty-GET handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verified against the live Helcim v2 API during the deploy migration: - POST /payment-plans requires { paymentPlans: [plan] } wrapper (mirrors the POST /subscriptions shape), and response is { data: [plan] }. - taxType 'customer' rejects as ERR_VALIDATION_FAILED; must be 'no_tax' with taxCalculation 'country_province'. - termLength:1 rejects when termType:'forever' — drop the field. - GET /subscriptions returns an empty body (not JSON) when no subs exist; tolerate that instead of failing with 'Unexpected end of JSON input'. Plans created in the Helcim account: Monthly=50302, Annual=50303. --- scripts/helcim-plan-consolidation.js | 29 ++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/scripts/helcim-plan-consolidation.js b/scripts/helcim-plan-consolidation.js index 2a3a5f1..aaa0cc4 100644 --- a/scripts/helcim-plan-consolidation.js +++ b/scripts/helcim-plan-consolidation.js @@ -44,8 +44,12 @@ const ANNUAL_PLAN = { billingPeriod: 'yearly', } +// Shape mirrors the pre-migration legacy plans (verified via backup dump). +// taxType:'customer' + missing taxCalculation/termLength caused ERR_VALIDATION_FAILED. const HELCIM_PLAN_PAYLOAD_BASE = { - description: 'Ghost Guild membership', + businessName: '', + businessEmail: '', + description: '', type: 'subscription', status: 'active', currency: 'CAD', @@ -54,8 +58,10 @@ const HELCIM_PLAN_PAYLOAD_BASE = { dateBilling: 'Sign-up', termType: 'forever', freeTrialPeriod: 0, - taxType: 'customer', + taxType: 'no_tax', + taxCalculation: 'country_province', paymentMethod: 'card', + isProrated: 'no', } // --------------------------------------------------------------------------- @@ -87,11 +93,12 @@ async function helcimGet(path) { const res = await fetch(`${HELCIM_API_BASE}${path}`, { headers: helcimHeaders(), }) + const body = await res.text() if (!res.ok) { - const body = await res.text() throw new Error(`GET ${path} failed (${res.status}): ${body}`) } - return res.json() + if (!body.trim()) return [] + try { return JSON.parse(body) } catch { return [] } } async function helcimDelete(path) { @@ -280,9 +287,10 @@ async function main() { console.log(` [DRY RUN] Would POST /payment-plans with:`) console.log(' ', JSON.stringify(monthlyPayload, null, 4).split('\n').join('\n ')) } else { + const wrapped = { paymentPlans: [monthlyPayload] } console.log(` POSTing /payment-plans with:`) - console.log(' ', JSON.stringify(monthlyPayload, null, 4).split('\n').join('\n ')) - const result = await helcimPost('/payment-plans', monthlyPayload) + console.log(' ', JSON.stringify(wrapped, null, 4).split('\n').join('\n ')) + const result = await helcimPost('/payment-plans', wrapped) if (!result.ok) { console.error(` [ERROR] POST failed (${result.status}):`) console.error(' ', JSON.stringify(result.body, null, 2)) @@ -290,7 +298,7 @@ async function main() { await mongoose.disconnect() process.exit(1) } - monthlyPlanId = result.body.id + monthlyPlanId = result.body.data?.[0]?.id ?? result.body.paymentPlans?.[0]?.id ?? result.body.id console.log(` [OK] Created Monthly Membership plan id: ${monthlyPlanId}`) } console.log() @@ -313,9 +321,10 @@ async function main() { console.log(' Note: billingPeriod="yearly" is Helcim v2 convention.') console.log(' If 4xx on --confirm, check the Helcim API docs for the correct field value.') } else { + const wrapped = { paymentPlans: [annualPayload] } console.log(` POSTing /payment-plans with:`) - console.log(' ', JSON.stringify(annualPayload, null, 4).split('\n').join('\n ')) - const result = await helcimPost('/payment-plans', annualPayload) + console.log(' ', JSON.stringify(wrapped, null, 4).split('\n').join('\n ')) + const result = await helcimPost('/payment-plans', wrapped) if (!result.ok) { console.error(` [ERROR] POST failed (${result.status}):`) console.error(' ', JSON.stringify(result.body, null, 2)) @@ -324,7 +333,7 @@ async function main() { await mongoose.disconnect() process.exit(1) } - annualPlanId = result.body.id + annualPlanId = result.body.data?.[0]?.id ?? result.body.paymentPlans?.[0]?.id ?? result.body.id console.log(` [OK] Created Annual Membership plan id: ${annualPlanId}`) } console.log()