Files
property-management-network/scripts/paypal-setup-plans.mjs
T
Leon SerfatyandClaude Opus 4.8 c9968531e4 Consolidate audit-fixes branch: webhooks, integrations, and deploy hardening
Batch commit of the pending working tree on security/audit-fixes-2026-07.
Major areas:
- Outbound webhooks / Zapier: schema + signed delivery with retries, public
  v1 API (REST-hook subscribe/unsubscribe), settings UI, cron drain.
- Deploy hardening: email via SMTP2GO (Resend fully removed), verified DB TLS
  (DATABASE_SSL=require + DATABASE_CA), storage fails loud in production when
  Spaces is unconfigured instead of silently using ephemeral disk.
- Integrations & features (concurrent work): accounting (QuickBooks/Xero),
  e-signature (DocuSign/Dropbox Sign), PayPal, geocoding/maps, onboarding,
  expanded legal pages.
- DB migrations 0006–0009.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 13:42:34 -04:00

103 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// One-time PayPal setup: creates the product + the recurring billing plans and
// prints the plan IDs to paste into your environment.
//
// 1. Set PAYPAL_CLIENT_ID / PAYPAL_SECRET (and PAYPAL_ENVIRONMENT) in .env.local
// 2. node scripts/paypal-setup-plans.mjs
// 3. Copy the printed PAYPAL_*_PLAN_ID lines into .env.local / production env
//
// Amounts mirror the app's pricing (Pro $29/mo, Landlord $59/mo); yearly is
// billed at 10× monthly (~2 months free). Adjust in the PayPal dashboard if you
// want different annual pricing. Safe to re-run (it creates fresh plans).
import { config } from "dotenv"
config({ path: ".env.local", quiet: true })
const ENV = process.env.PAYPAL_ENVIRONMENT === "live" ? "live" : "sandbox"
const BASE = ENV === "live" ? "https://api-m.paypal.com" : "https://api-m.sandbox.paypal.com"
const id = process.env.PAYPAL_CLIENT_ID
const secret = process.env.PAYPAL_SECRET
if (!id || !secret) {
console.error("[paypal-setup] Set PAYPAL_CLIENT_ID and PAYPAL_SECRET in .env.local first.")
process.exit(1)
}
async function getToken() {
const r = await fetch(`${BASE}/v1/oauth2/token`, {
method: "POST",
headers: {
Authorization: `Basic ${Buffer.from(`${id}:${secret}`).toString("base64")}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: "grant_type=client_credentials",
})
if (!r.ok) throw new Error(`auth ${r.status}: ${await r.text()}`)
return (await r.json()).access_token
}
const token = await getToken()
const post = (path, body) =>
fetch(`${BASE}${path}`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
Prefer: "return=representation",
},
body: JSON.stringify(body),
})
console.log(`[paypal-setup] Environment: ${ENV}`)
const prodRes = await post("/v1/catalogs/products", {
name: "Property Management Network",
description: "Property Management Network subscription",
type: "SERVICE",
category: "SOFTWARE",
})
if (!prodRes.ok) {
console.error("[paypal-setup] product creation failed:", await prodRes.text())
process.exit(1)
}
const product = await prodRes.json()
console.log(`[paypal-setup] Product: ${product.id}`)
const AMOUNTS = { pro: 29, landlord: 59 }
const envLines = []
for (const plan of ["pro", "landlord"]) {
for (const interval of ["month", "year"]) {
const amount = interval === "month" ? AMOUNTS[plan] : AMOUNTS[plan] * 10
const res = await post("/v1/billing/plans", {
product_id: product.id,
name: `${plan[0].toUpperCase()}${plan.slice(1)} ${interval === "month" ? "Monthly" : "Yearly"}`,
status: "ACTIVE",
billing_cycles: [
{
frequency: { interval_unit: interval === "month" ? "MONTH" : "YEAR", interval_count: 1 },
tenure_type: "REGULAR",
sequence: 1,
total_cycles: 0,
pricing_scheme: { fixed_price: { value: amount.toFixed(2), currency_code: "USD" } },
},
],
payment_preferences: {
auto_bill_outstanding: true,
setup_fee_failure_action: "CONTINUE",
payment_failure_threshold: 2,
},
})
if (!res.ok) {
console.error(`[paypal-setup] plan ${plan}/${interval} failed:`, await res.text())
continue
}
const p = await res.json()
const key = `PAYPAL_${plan.toUpperCase()}_${interval === "month" ? "MONTHLY" : "YEARLY"}_PLAN_ID`
console.log(` ${plan}/${interval} $${amount}${p.id}`)
envLines.push(`${key}=${p.id}`)
}
}
console.log("\n[paypal-setup] Add these to your environment:\n")
console.log(envLines.join("\n"))