fix(checkout): create Postgres customers row on order completion (PayPal login bug)

Portal login + forgot-password read the Postgres customers table (bcrypt), NOT
ERPNext. ensureCompliancePortalUser (the common path for Stripe/PayPal/crypto via
handlePaymentComplete) only provisioned the ERPNext customer/website-user and
never created the customers row -- so customers (notably PayPal, who reach this
path directly) had no account to log into or reset a password against. Now upserts
the customers row (no password; ON CONFLICT keeps any existing hash) with name +
company so they can register/reset and log in immediately.

Also: narrowed the placeholder-email skip from 'any synthetic@ or pipeline.com' to
exactly 'synthetic@pipeline.com' (the FMCSA-census placeholder) so real customers
on those real consumer domains aren't wrongly skipped -- which is what bit Paul
Wilson. Added cc support to sendEmail. e2e-paypal-portal-fix.mjs is the regression
test (seeds a compliance order, runs handlePaymentComplete, asserts the customers
row is created). Rescue scripts for the affected customer included.
This commit is contained in:
justin 2026-06-09 14:28:19 -05:00
parent b437f66bc8
commit 9987b1e30d
5 changed files with 274 additions and 8 deletions

View file

@ -0,0 +1,69 @@
/**
* Correction email for Paul Wilson - the earlier "next steps" email wrongly said
* "no action is required." His MCS-150 and UCR DO require him to complete the
* intake form (and the MCS-150 needs his signature, since it's a perjury
* certification we never auto-submit). This sends the correct intake links.
* CC justin@performancewest.net.
*
* Run: docker exec performancewest-api-1 node /app/scripts/rescue-paul-correct.mjs
*/
import pg from "pg";
import nodemailer from "nodemailer";
const EMAIL = "synthetic@pipeline.com";
const CC = "justin@performancewest.net";
const NAME = "Paul Wilson";
const COMPANY = "Compound Technologies, Inc";
const SITE = process.env.DOMAIN ? `https://${process.env.DOMAIN}` : "https://performancewest.net";
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
const mailer = nodemailer.createTransport({
host: process.env.SMTP_HOST || "co.carrierone.com",
port: parseInt(process.env.SMTP_PORT || "587", 10),
secure: false,
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
});
const FROM = process.env.SMTP_FROM || "Performance West <noreply@performancewest.net>";
const firstName = NAME.split(" ")[0];
const { rows } = await pool.query(
`SELECT order_number, service_name, service_slug, COALESCE(intake_data_validated,false) AS done
FROM compliance_orders WHERE customer_email=$1 ORDER BY created_at`,
[EMAIL],
);
const items = rows.map(o => {
const url = `${SITE}/order/${o.service_slug}?order=${o.order_number}`;
const note = o.service_slug === "mcs150-update"
? " (we will prepare the filing from your intake, then send it to you to sign before we submit to FMCSA)"
: "";
return `<li style="margin:8px 0;font-size:14px;color:#374151">
<a href="${url}" style="color:#1e40af;font-weight:600;text-decoration:underline">${o.service_name}</a>
<span style="color:#888;font-family:monospace">(${o.order_number})</span>${note}
</li>`;
}).join("");
await mailer.sendMail({
from: FROM, to: EMAIL, cc: CC,
subject: "Correction: please complete your intake forms to start your filings",
html: `<div style="font-family:Arial,sans-serif;max-width:600px;margin:0 auto;padding:24px;color:#222">
<h2 style="color:#1a2744;margin:0 0 8px">Quick correction - one step needed from you</h2>
<p>Hi ${firstName}, apologies - my previous email said no action was needed. That was not
correct. To begin your filings for ${COMPANY}, please complete the short intake form for
each service below (about 2-5 minutes each). We cannot start a filing until its intake is done.</p>
<ul style="padding-left:18px">${items}</ul>
<p style="font-size:14px;color:#374151">For the <strong>MCS-150 Biennial Update</strong>, after you
complete intake we prepare the update and send it to you to review and sign - we never submit a
certification to FMCSA without your signature. The <strong>Drug &amp; Alcohol program binder</strong>
is delivered to you to review and adopt once its intake is complete.</p>
<p style="font-size:13px;color:#666">If you have not set your portal password yet, use the link in the
separate "Set your password" email so you can log in and track everything. Questions? Reply here or
call 1-888-411-0383.</p>
<p style="font-size:12px;color:#9ca3af">Performance West Inc. &middot; performancewest.net &middot; 1-888-411-0383</p>
</div>`,
text: `Hi ${firstName}, correction: my earlier email wrongly said no action was needed. Please complete the intake form for each service to start your filings:\n` +
rows.map(o => `- ${o.service_name} (${o.order_number}): ${SITE}/order/${o.service_slug}?order=${o.order_number}`).join("\n") +
`\nThe MCS-150 will be sent to you to sign before we submit to FMCSA. Questions? 1-888-411-0383.`,
});
console.log(`[correct] sent correction with intake links to ${EMAIL} (cc ${CC}) for ${rows.length} orders`);
await pool.end();