🎸 JMI go-live plan

jonesmusicalinstruments.com, on the new site, taking real money Β· written 2026‑09‑16

Those three commits shipped on Sep 16 in trc-beta-apps v242 per memory; live jmi-server equals HEAD (inference).

8 steps. Step 2 is the only gate, and it now has three ways through.
πŸ”‘ A Darryl opens his own Stripe account Β· B Greg opens it for him and hands ownership over Β· C TRC takes the money as merchant of record, as a stopgap. Everything after step 2 is the same work either way: ~2 hours of Greg-and-Claude, plus two DNS records.
8
steps
~2 h
Greg + Claude work
3
ways through step 2
3
fixes awaiting deploy
$1
live test, refunded

πŸ“‹ The whole sequence, one table

#StepOwnerTimeWaits onStatus
1Darryl reviews and approves the siteDarryl20 minnothingready now
2Stripe account exists and is activated Β· branch A, B or CDarryl Greg30 min – 3 hstep 1needs Darryl's pick
3Live account configured: tax, branding, key, webhook, secrets, deploy 3 fixesClaude45 minstep 2blocked
4$1 live purchase, refund, abandoned-hold checkGreg Claude20 minstep 3blocked
5Domain cutover: two Cloudflare recordsDarryl15 min + 1 h DNSstep 4needs his Cloudflare
6Campaign #1 sends to 101 peopleGreg10 minstep 5drafted, never sent
7Darryl fills the three console data gapsDarryl60–90 minnothingopen since 9/3
8App Store: transfer to Darryl's account, or leave on TRC'sDarryl Gregdecision + 1–2 wknothingin review now
πŸ‘€ Darryl's site-check page πŸ’³ Darryl's Stripe page πŸ›  Staff console 🌐 The new site

🚦 Read this before step 1

βœ… Already done, contrary to the 9/3 notes jonesmusicalinstruments.com and www are already attached and verified on the jones-instruments Vercel project. No project move needed. Step 5 is DNS only.
βœ… Fixed today, 2026‑09‑16 A restricted live key (rk_live_) was going to be labelled "test" in the console while charging real cards. Widened to /^(sk|rk)_live_/, commit df717fc. Full suite green: 1,303 checks.
πŸ›‘ Fixed today, would have silently failed the cutover The deploy doc and the runbook said fly secrets set STRIPE_SECRET_KEY=…. The real names are JMI_STRIPE_SECRET_KEY / JMI_STRIPE_WEBHOOK_SECRET. The bare names set variables nothing reads, and the shop keeps taking money on the sandbox key with no error. Commit 4b5e182.
⚠️ Greg cannot do step 5 himself jonesmusicalinstruments.com sits in Darryl's Cloudflare account. Greg's token sees only therecording.club and tinyroom.com. Either Darryl pastes two records, or he adds Greg to that Cloudflare account first.
πŸ›‘ Found and fixed today: jones.gregspero.com had a dead shop That hostname now serves the new site, but the server's CORS allowlist did not include it, so every shop page on the link Darryl already has would have failed silently. Commit 7b0b9e6. It stays broken until step 3 deploys.
⚠️ Three commits are on main but not deployed df717fc, 4b5e182 and 7b0b9e6 are committed, not shipped. The trc-beta-apps working tree is dirty with another agent's in-progress server.js edit, so deploying now would ship it too. Deploy during step 3, on a clean tree, with ./bin/deploy.

1️⃣ Darryl reviews and approves the site

πŸ‘€ 20 minutes, his phone is fine

Darryl Β· prerequisite: nothing Β· rollback: n/a
Does
Walks the site-check page, which lists every page with a thumb and a yes/no.
Flags wrong photos, wrong prices, wrong copy, missing instruments.
Verify
His written "approved" on the checklist, or a list of changes.
Why first
Everything downstream points customers at this site. Copy fixes after the domain moves are public edits.

2️⃣ Whose Stripe account takes the money

πŸ”€ Three ways to clear this gate. Darryl picks one.

The rest of the plan is identical whichever he picks. Only this step changes.
BranchWho does the accountOwnerTimeWhat Darryl must supplyShips when
A Darryl opens and activates his own account, invites Greg as Developer Darryl 30 min + KYC Everything, himself: entity type, EIN or SSN, address, bank account, ID. Nothing passes through Greg. Same day he sits down
B Greg opens it for him, activates it, then hands ownership over Greg 45 min + KYC Business details by email. SSN, date of birth, bank numbers and photo ID on a call or in person in Tokyo, never by email or text. As soon as they get 20 minutes together
C Run the shop on The Recording Club's existing live account for now Greg 2–3 h Nothing to start. A W‑9 before the first payout. Today, if Greg accepts being merchant of record
Recommend
A if Darryl will sit down this week. B if he will not. C only if the shop has to be open before either can happen, and only as a stopgap with a written end date.
Common to all
Whoever holds the account still owes the same things in step 3: tax, branding, a restricted key, the six-event webhook. None of that is skipped by picking C.
Darryl sees
All three branches are written up for him on his Stripe page.

2️⃣ A Β· Darryl opens his own account

πŸ”‘ The cleanest version Β· 30 minutes plus Stripe's KYC wait

Darryl Β· prerequisite: step 1 Β· rollback: none needed, nothing is live yet
Does
1. Registers at dashboard.stripe.com/register with his own email.
2. Completes activation himself: business entity, EIN or SSN, business address, his bank account.
3. Turns on a passkey at account security. Not SMS.
4. Invites info@therecording.club at Team with the Developer role.
Why Developer
Greg can rotate keys, read logs, refund a buyer and edit tax and branding. He cannot move the bank account, change the payout schedule, or add team members. The money is Darryl's and stays Darryl's.
Also supplies
Entity type: sole proprietor with an SSN, or LLC with an EIN.
Whether he holds a California seller's permit. If he does not, that is the long pole in the whole plan and it is his accountant's call.
Verify
Greg sees Darryl's account in his Stripe account switcher, and the dashboard says payments and payouts are enabled.
Rollback
Darryl removes the team invite. Nothing is connected yet.

2️⃣ B Β· Greg opens it, then hands it over

🀝 45 minutes, then a 5-minute ownership transfer · account is Darryl's from day one on paper

Greg Β· prerequisite: step 1 + Darryl's details Β· rollback: Stripe closes an unactivated account on request
By email
Legal business name, entity type, business address, phone, website, what he sells, expected monthly volume, average order value.
Never by email
πŸ”’ SSN or EIN, date of birth, bank routing and account numbers, photo ID. These go over a call with screen share, or in person in Tokyo. Greg types them into Stripe while Darryl reads them out, and keeps no copy.
Does
1. Registers the account in Darryl's legal name, with an email Darryl controls, or a Greg-held email swapped at handover.
2. Completes activation and KYC with the details above. The bank account is Darryl's.
3. Runs all of step 3 and step 4: tax, branding, key, webhook, $1 test, refund.
4. At Team, transfers Account Owner to Darryl's own login, then drops himself to Developer.
Darryl still does
Sets his own password and passkey, confirms the bank account is his, and accepts the owner transfer. Stripe requires the new owner to have a login; ownership cannot be handed to an email nobody has claimed.
Verify
The Team page shows Darryl as Owner and Greg as Developer.
Payouts land in Darryl's bank, confirmed on the first real payout.
Honest risk
Greg attests to another person's identity during activation. Do this only with Darryl on the line agreeing to each field, and keep no record of the SSN or bank numbers anywhere afterwards.

2️⃣ C Β· TRC is merchant of record, as a stopgap

πŸ› Fastest to open, most expensive to unwind Β· 2–3 hours

Greg Β· prerequisite: Greg accepts being merchant of record Β· rollback: re-point two Fly secrets at whichever account replaces it
Shape
Money lands in TRC's Stripe account acct_1HJ2lhChrL42COq4, "The Recording Club by Tiny Room Studios". Charges and payouts are enabled today.
TRC collects and files California sales tax under TRC's own permit. Greg forwards net proceeds to Darryl by bank transfer on the Mercury rail the payroll app already uses, with a monthly statement, and issues a 1099‑NEC at year end.
Darryl supplies
A W‑9 before the first payout, and his bank details for the transfer. Nothing on day one.
Long-term
His own account (branch A or B), or a Stripe Connect Express sub-account under TRC so payouts go straight to him and TRC never holds his money. TRC has zero connected accounts today, so Express would be built from scratch. Set an end date before opening the shop this way.

πŸ–₯ Branch C Β· exactly what changes on the server

Checked against TRC's live account 2026‑09‑16, read-only calls
Which key
πŸ†• Mint a new restricted key on TRC's live account named jmi-server, write on Checkout Sessions only, and set it as JMI_STRIPE_SECRET_KEY.
πŸ›‘ Do not reuse TRCTAX_STRIPE_SECRET_KEY from ~/.env. It is a full sk_live on TRC's account; handing it to the JMI server gives a bass shop unrestricted access to every dollar TRC takes.
πŸ›‘ STRIPE_READ_KEY cannot be used either. It is read-only and cannot create a checkout session.
Stripe Tax
πŸ›‘ Not configured on TRC's account, contrary to assumption. tax/settings returns status: pending, no head office, no default tax code or behaviour, and zero tax registrations.
So branch C saves nothing here: head office, preset code, exclusive behaviour and the California registration all have to be set up, same as A and B. The difference is the registration rides on TRC's existing seller's permit instead of waiting on Darryl's.
⚠️ Turning Stripe Tax on for the account affects TRC's other checkouts too, which have been running without it.
Branding
TRC's account currently carries TRC's icon and logo with both colours set to #000000, and the statement descriptor THE RECORDING CLUB.
⚠️ Branding is per-account, so there is no version of this where both look right. Either a bass buyer sees a recording-studio logo at checkout and THE RECORDING CLUB on their card statement, or TRC's own checkouts get re-branded as Jones Musical Instruments. The card-statement half is the bigger problem: an unrecognised descriptor is the commonest first-sale chargeback.
Webhook
βœ… Yes, an endpoint can be added alongside the existing ones. TRC has 8 endpoints, 3 enabled, against a live-mode ceiling of 16. Each endpoint carries its own signing secret, so JMI_STRIPE_WEBHOOK_SECRET does not collide with anything.
⚠️ Two enabled TRC endpoints already subscribe to checkout.session.completed, so every bass sale gets delivered to them as well: the TRC app at app.therecording.club (245 events, also takes expired, refunded and both async events) and interval-club.fly.dev.
The TRC app handles this safely, verified in its source: a session that is not a night ticket returns handled:false and it logs "nothing to do". It does write the event id into its stripe_webhook_events table, which is noise only.
❓ interval-club.fly.dev was not checked. Read its handler or narrow that endpoint's events before the first live sale.
SITE_ORIGIN
βœ… Unchanged by the account choice. It is the site's hostname, not Stripe's: daryljones.gregspero.com until step 5, jonesmusicalinstruments.com after.
Buyers return to ${SITE_ORIGIN}/order?session_id=… and cancel to ${SITE_ORIGIN}/bag. The Stripe account never appears in either URL.
Everything else
No code change. The server reads one key and one webhook secret and does not care whose account is behind them. Orders, instruments and the registry stay in SQLite on Fly either way.
Rollback
Disable the JMI endpoint on TRC's account, then re-point the two JMI_ secrets. Sales already taken stay on TRC's account and are TRC's to refund and to report.

3️⃣ Claude configures the live account and points the server at it

βš™οΈ 45 minutes, all of it on Greg's side

Claude Β· prerequisite: step 2 on any branch Β· rollback: one command, below
Same on A, B, C
Every row below is owed on whichever account step 2 produced. Branch C skips none of it.
Tax
Head office address, preset code txcd_99999999, default behaviour exclusive.
California registration, plus any other nexus state. No registration means Stripe charges zero tax and the sale goes out untaxed.
Branding
Icon, logo, #0c0b09, #b0813f, prefer logo. Re-uploaded, because branding is per-account.
Public
Name, website, support email, statement descriptor JONESMUSICALINSTRUMENTS.COM. An unrecognised descriptor is the commonest cause of a first-sale chargeback.
Key
Restricted key named jmi-server, write on Checkout Sessions and nothing else. That is the entire Stripe surface this server uses.
Webhook
New endpoint at https://jmi.gregspero.com/api/shop/stripe/webhook with six events, not five:
checkout.session.completed Β· checkout.session.async_payment_succeeded Β· checkout.session.async_payment_failed Β· checkout.session.expired Β· charge.refunded Β· payment_intent.payment_failed
Deploy
On a clean tree: cd ~/trc-beta-apps-deploy && ./bin/deploy. Ships df717fc, 4b5e182 and 7b0b9e6. Never a bare fly deploy.
Secrets
Deploy first, then one command, so the machine restarts once on the right code.
fly secrets set -a trc-beta-apps \
  JMI_STRIPE_SECRET_KEY=rk_live_... \
  JMI_STRIPE_WEBHOOK_SECRET=whsec_...
Verify
curl -s -o /dev/null -w "%{http_code}" https://jmi.gregspero.com/api/shop/products returns 200.
The console order list reports mode live. If it still says test, the deploy did not land.
STRIPE_MOCK is not set on the machine.
Rollback
Disable the live webhook endpoint on Darryl's account first, then re-set the two sandbox values from ~/.env. That is why the sandbox key is not deleted until the very end.

4️⃣ One real dollar, spent and refunded

πŸ’΅ 20 minutes Β· a live account declines test cards

Greg Claude Β· prerequisite: step 3 Β· rollback: refund, which is half the test anyway
Does
Buys the cheapest real accessory with a real card, California shipping address.
Refunds it in full from Darryl's dashboard.
Starts a second checkout and abandons it.
Verify
βœ… Stripe's hosted page shows the JMI logo on the dark ground.
βœ… Sales tax appears on the total.
βœ… The order lands Paid in the console with the shipping address and no "tax was not configured" note.
βœ… The webhook shows checkout.session.completed delivered 200. A 400 means the signing secret is wrong.
βœ… Within a minute of the refund, the console shows it. That proves charge.refunded is subscribed.
βœ… The abandoned checkout releases its instrument back to the site. That proves checkout.session.expired.
Do not skip
The refund and the abandoned hold are the only two checks that prove the new webhook secret and the full event list. A successful purchase alone proves neither.

5️⃣ Domain cutover: two Cloudflare records

🌐 15 minutes of typing, up to an hour of DNS

Darryl (Greg has no access today) Β· prerequisite: step 4 green Β· rollback: put the old A records back
Already done
The domain and its www are attached and verified on the jones-instruments Vercel project. Verified 2026‑09‑16 against the Vercel API. Nothing to move.
Change
@ Β· A Β· 216.198.79.1 Β· proxy off (grey cloud)
@ Β· A Β· 64.29.17.1 Β· proxy off
www Β· CNAME Β· 2083afedd08121ec.vercel-dns-017.com Β· proxy off
Delete the old A records 199.16.172.169 and 199.16.173.146.
Do not touch
πŸ“§ MX stays: jonesmusicalinstruments-com.mail.protection.outlook.com. Email is untouched by this change.
πŸ…¦ darryljones.com stays on the old WordPress host, including /wp-admin. It is a separate zone and a separate A record.
Then
Claude sets SITE_ORIGIN=https://jonesmusicalinstruments.com on Fly, or buyers land back on daryljones.gregspero.com after paying. One more restart.
Verify
dig +short jonesmusicalinstruments.com returns the two Vercel addresses.
The apex and www both serve the new site over HTTPS with a valid certificate.
A fresh checkout returns to jonesmusicalinstruments.com/order?session_id=….
Rollback
Restore the two old A records. The old WordPress site is still there, untouched. Then revert SITE_ORIGIN.

6️⃣ Campaign #1 goes out

πŸ“£ 10 minutes Β· 101 recipients Β· never sent, not once

Greg Β· prerequisite: step 5, so the links point at the real domain Β· rollback: none, a send is final
Ready
SendGrid domain authentication verified 2026‑09‑03. The signed event webhook is installed; opens, bounces and unsubscribes flow back into the console.
Does
Console β†’ Campaigns β†’ "The JMI app is here. You're invited first."
Test send to himself β†’ reads it on a phone β†’ Send.
Verify
The campaign shows 101 delivered and the opens counter starts moving within the hour.
Credentials
Console login is admin; the password lives in ~/.jmi-creds/UPDATES.md on Greg's Mac.

7️⃣ Darryl's three data gaps

πŸ“ 60–90 minutes Β· open since 2026‑09‑03 Β· runs in parallel with everything

Darryl Β· prerequisite: nothing Β· rollback: n/a, these are edits
1
137 option prices. Until he fills them the configurator says "Options are priced in the quote." Console β†’ Build Requests β†’ Spec.
2
13 sold-instrument dates. The registry shows them sold with no date.
3
The Chatham Matte Black duplicate. Two rows, one bass. He picks which stays.
Verify
The configurator quotes a real number, the registry shows a date on every sold instrument, and one Chatham Matte Black row remains.
Blocks?
No. The shop sells without these. They make it look finished rather than half-built when 101 people arrive in step 6.

8️⃣ The App Store decision

πŸ“± A decision now, then one to two weeks of Apple

Darryl Greg Β· prerequisite: nothing Β· rollback: a transfer can be reversed, slowly
Where it is
JMI Backstage build 20 is in App Store review on TRC's developer account, submitted 2026‑09‑16. TestFlight is approved and open.
Option A
Leave it on TRC's account. Nothing to do. Ships fastest. Darryl does not control the listing and cannot pull it himself.
Option B
Transfer to Darryl's own developer account. He enrols ($99/year), then Apple's app transfer moves the listing, its reviews and its users. Requires the app to be already released and to have no pending contracts.
Recommendation
Ship on TRC's account now, transfer later. A transfer needs a released app anyway, so option A is a prerequisite for option B, not a competing choice.
Verify
The app appears on the public App Store under the account the two of them chose.

πŸ“Ž The documents behind this

Runbook
runbook.html β€” the verified Stripe cutover, corrected today for the secret names and the fixed regex.
For Darryl
stripe-steps-for-darryl.html β€” the plain-language version of step 2.
Site approval
site-approval-checklist.html β€” what step 1 walks through.
9/3 gaps
go-live-gaps-2026-09-03.html β€” the earlier state of play.
Deploy doc
~/trc-beta-apps-deploy/apps/jmi-server/DEPLOY-SHOP.md, corrected in 4b5e182.