gigafibre-fsm/apps/client/src/api/payments.js
louispaulb 041df48ac3 security: harden payments/hub auth + remove leaked ERPNext token from source
Auth hardening:
- payments: per-customer JWT authorization on dual-use /payments routes
  (balance/methods/invoice/checkout/setup/portal/toggle-ppa) via authorizeCustomer
  + PAYMENTS_AUTH=enforce; portal retains+sends magic-link JWT (sessionStorage)
- hub: fail-closed Stripe webhook, /accept/doc-pdf IDOR gate, telephony field-name
  guard (SQLi), modem-bridge private-IP guard (SSRF), ALWAYS_ENFORCE expansion

Leaked-credential cleanup (token already rotated in ERPNext):
- de-hardcode ERPNext API token -> env in bulk_submit.py, import_items.py,
  apps/client/deploy.sh; placeholder in apps/ops/infra/nginx.conf (nginx injects)
- ops prod build no longer bakes VITE_ERP_TOKEN (.env.production empty)
- de-hardcode legacy DB password -> env in import_items.py
- gitignore legacy migration PII exports (tsv/json)

Conversations/UI:
- floating (un-docked) conversation panel; full-width mailbox
- per-message real sender from email headers; unified scroll; header spacing
- campaign pre-send review (subject/from/channel), Gmail send channel, clone-as-draft

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 06:17:17 -04:00

59 lines
2.3 KiB
JavaScript

/**
* Payment API — talks to targo-hub /payments/* endpoints
*/
const HUB = location.hostname === 'localhost' ? 'http://localhost:3300' : 'https://msg.gigafibre.ca'
// Attach the customer's magic-link JWT (stored by the customer store) so the hub can
// authorize per-customer access to balance/methods/invoice/portal/toggle-ppa — see
// stores/customer.js TOKEN_KEY (kept in sync).
function authHeaders (base = {}) {
let t = ''
try { t = sessionStorage.getItem('targo_portal_jwt') || '' } catch { t = '' }
return t ? { ...base, Authorization: 'Bearer ' + t } : base
}
async function hubGet (path) {
const r = await fetch(HUB + path, { headers: authHeaders() })
if (!r.ok) {
const data = await r.json().catch(() => ({}))
throw new Error(data.error || `Hub ${r.status}`)
}
return r.json()
}
async function hubPost (path, body) {
const r = await fetch(HUB + path, {
method: 'POST',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify(body),
})
const data = await r.json().catch(() => ({}))
if (!r.ok) throw new Error(data.error || `Hub ${r.status}`)
return data
}
/** Get customer balance + unpaid invoices */
export const getBalance = (customer) => hubGet(`/payments/balance/${encodeURIComponent(customer)}`)
/** Get saved payment methods for customer */
export const getPaymentMethods = (customer) => hubGet(`/payments/methods/${encodeURIComponent(customer)}`)
/** Get invoice payment info */
export const getInvoicePaymentInfo = (invoice) => hubGet(`/payments/invoice/${encodeURIComponent(invoice)}`)
/** Create checkout session for full balance */
export const checkoutBalance = (customer) => hubPost('/payments/checkout', { customer })
/** Create checkout session for specific invoice */
export const checkoutInvoice = (customer, invoice, { save_card = true, payment_method = 'card' } = {}) =>
hubPost('/payments/checkout-invoice', { customer, invoice, save_card, payment_method })
/** Save card (setup mode) */
export const setupCard = (customer) => hubPost('/payments/setup', { customer })
/** Open Stripe billing portal */
export const openBillingPortal = (customer) => hubPost('/payments/portal', { customer })
/** Toggle auto-pay (PPA) */
export const togglePPA = (customer, enabled) => hubPost('/payments/toggle-ppa', { customer, enabled })