RevParPro · payment acceptance · review only

Stripe lifecycle works for one payer. Hotel-direct billing does not.

Provider-backed test-mode acceptance of base setup, the first charge, add-on checkout, failure, provider recovery, refund, cancellation, portal access, duplicate events, and responsive customer state.

Test mode onlySynthetic management company3 hotels + 1 target-only corporate serviceSource: 43ca9749No repairs implemented
CONDITIONAL FAIL

Do not call the approved hotel-direct model accepted.

Today’s shared-payer model can activate one base hotel and bill additional monitored hotels on separate subscriptions under the same Stripe customer. It cannot represent three independent hotel payers or a corporate payer. A recovered Hotel B invoice also failed to restore the terminally canceled RPP item.

What is safe to rely on

Zero-dollar card setup, signed activation, a sandbox-only first-issue charge, authenticated $9 add-on checkout, full refund, cancellation isolation, portal creation, and duplicate-event idempotency were provider-proved.E3,E4

7checks passed
2checks failed
2material warnings

Acceptance scorecard

CheckResultEvidence-backed finding
Zero-dollar base setupPASSHosted Checkout completed in setup mode with no payment required; signed setup activation made the account and included hotel active.E3,E4
First base chargePASSThe service-only first-issue path created and verified the $15 standard subscription after one explicit sandbox issue key.E3,E4
Authenticated add-on checkoutPASSTwo signed-in calls reached Stripe Checkout, each at $9 monthly, and activated one hotel-bound add-on item.E3,E4
Hotel C refund + cancelPASSThe $9 charge was fully refunded and the add-on subscription, item, and hotel were canceled while Hotel A and Hotel B remained active.E3,E4,E5
Hotel B failure isolationPASSProvider dunning canceled Hotel B only. Hotel A, the base membership, and the account remained active.E3,E4,E5
Hotel B recovery propagationFAILThe saved valid test card paid the failed invoice, but the signed invoice.paid receipt was ignored as terminal; Hotel B remained canceled.E3,E4,E5
Billing portal + returnsPASSAn authenticated test-mode portal session was created with the Account return route.E3
Duplicate eventsPASSExact signed event replay returned the replay path and did not create a second receipt or mutation.E4
Desktop + mobile statePASSFive lifecycle checkpoints were verified at 1440px and 390px with all expected labels present and zero console errors in the final failure/recovery states.E5
3 independent hotel payers + corporate payerFAILProvider identity exists only on the account-level membership; hotels have no billing identity columns and corporate service is not an allowed item kind.E1,E4
Commercial copy versus renewalsWARNINGThe base subscription renewed for $15 on the provider clock with zero monthly delivery events, while Account copy says the charge follows delivery and never the calendar.E3,E4,E5,E6

Lifecycle evidence

1

Card saved

Hosted setup completed at $0. RPP activated the base membership from a signed provider event.

2

Hotel A healthy

The sandbox-only first-issue action created the $15 base subscription and verified the charge.

3

Hotels B and C added

Authenticated add-on Checkout created two $9 monthly subscriptions under the shared customer.

4

Hotel C refunded

Full refund succeeded, then cancellation propagated only to Hotel C.

5

Hotel B failed

Dunning produced eight processed failures, one out-of-order ignored failure, then terminal cancellation.

6

Provider recovered, RPP did not

The invoice became paid, but the application’s terminal reducer refused restoration.

Provider, application, database, and visible state

TransitionProviderApplicationDatabaseVisible state
Base activationSetup succeeded, $0 dueAccount activeSaved-method and activation receiptActive, $15/month, 1 hotel
Add-ons2 paid $9 subscriptions3 hotels active2 active hotel-bound items3 active
Hotel C refund/cancelRefunded, subscription canceledOnly Hotel C canceledRefund action succeeded2 active, refund shown
Hotel B failureFailed dunning, subscription canceledHotel A stayed activeFailure + delete receipts processed1 active, 2 canceled
Hotel B provider recoveryFailed invoice paidHotel B stayed canceledinvoice.paid ignored as terminalPaid charge shown, hotel still canceled

Final fixture state

Hotel AActive base hotel · provider subscription active
Hotel BInvoice recovered at provider · RPP item canceled
Hotel CFully refunded · subscription and item canceled
CorporateVisible in this target matrix only · no current record type

Cross-hotel isolation passed for failure and cancellation. The base account and Hotel A remained active throughout both add-on terminal paths. Isolation does not equal independent billing identity: all three provider subscriptions still belong to one shared Stripe customer.E3,E4

Impact-ranked gaps

Critical · architecture

No hotel-direct billing identity

There is one provider customer on rpp_memberships. Neither accounts nor account hotels have billing identity fields. Separate payment methods, invoices, tax identity, contacts, and portal ownership per hotel cannot be represented.

Critical · architecture

No corporate service identity or item

Membership items permit only base and monitored_hotel_addon. A company-level service has no product/item kind, payer, invoice stream, or visible account row.

High · recovery

Paid provider invoice cannot reopen terminal add-on

Once dunning cancels an add-on, provider recovery does not restore it. The webhook reducer ignores the paid event as terminal, and authenticated add-on checkout will not restart a hotel in canceled state.

High · billing promise

Renewals are calendar-driven after the first issue

The first charge is delivery-gated, but the resulting Stripe subscription renews on its calendar. The fixture recorded a $15 renewal while the account had zero monthly delivery events. Current customer copy overpromises delivery gating.

Medium · return UX

Add-on returns to the wrong product surface

Both add-on success and cancel URLs point to /app/compsets, even though the paid customer front door is Reports and Account.

Medium · operator noise

Dunning renders repeated failure rows

The single Hotel B renewal produced nine failure receipts and repeated visible payment-failure entries before cancellation. The state is honest, but the history is noisy and hard to reconcile.

Exact target gap matrix

CapabilityCurrent implementationApproved hotel-direct targetGap
Management companyOne RPP accountOne consolidated company viewPRESENT
Hotel payerNo hotel billing identityOne billing profile per hotelMISSING
Provider customerOne on the account membership3 hotel customers + 1 corporate customerMISSING
Hotel lifecycleSeparate add-on subscription and itemIndependent payer, invoice, failure, recovery, refund, cancelPARTIAL
Corporate serviceNo item kind or identityVisible, separately billed company serviceMISSING
Consolidated visibilityOne Account view lists all hotelsCompany view rolls up all billing identitiesPARTIAL
Failure isolationAdd-on failure did not pause the baseOne hotel’s failure cannot affect anotherMECHANICS PASS
RecoveryTerminal item cannot reopenRecovered hotel resumes without manual database repairFAIL

Method, boundary, and limitations

The test used current origin/main code at commit 43ca9749. The deployed production functions were inspected read-only and downloaded for route reconciliation. Because the deployed project correctly holds live Stripe secrets, provider writes were run through the same edge handlers locally with test-mode keys and a loopback-only port option. The production schema, deployed customer-account read model, and production desktop/mobile UI were used for RPP state evidence.E1,E2,E4,E5

BoundaryWhat happened
Provider safetyStripe test mode only. No live card, live Checkout Session, live charge, prospect, or real customer was used.
FixtureOne synthetic management company, three synthetic hotel rows, one synthetic authenticated owner, and one target-only corporate service row in this matrix.
Report deliveryThe first base charge used an explicit sandbox issue identity. No production report-delivery rule was bypassed for a real customer.
SanitizationNo secret, raw provider identifier, authentication token, real customer record, or screenshot containing the synthetic login alias is published here.
ChangesNo product code, billing policy, production function, migration, or customer-facing behavior was changed.

Evidence register

  1. E1 · Source trace. origin/main commit 43ca9749; paid-checkout, stripe-webhook, customer-account, membership-billing, migration 1060, and customer Account UI inspected 2026-09-16 Central.
  2. E2 · Deployment trace. Supabase function inventory and downloaded bundles: paid-checkout v50, stripe-webhook v50, customer-account v24, all active and deployed without gateway JWT verification.
  3. E3 · Provider proof. Stripe test-mode API readback for hosted setup, three subscriptions, paid and failed invoices, full refund, cancellations, portal session, test clock, and provider recovery. Raw provider identifiers withheld.
  4. E4 · Database and application proof. Production Supabase readback for the synthetic fixture: signed webhook receipts, billing action receipts, membership items, account/hotel state, and deployed customer-account response.
  5. E5 · Visible-state proof. Authenticated production Account UI at 1440px and 390px across base activation, three active hotels, Hotel C refund/cancel, Hotel B terminal failure, and provider-paid recovery mismatch.
  6. E6 · Policy authority. RevParPro Pricing.md, especially “How money moves” and “The guarantee,” plus the project Handoff contract dated 2026-09-16.
  7. Regression evidence. npm run test:paid-customer-core passed all six groups; the billing module’s 26 Deno tests passed independently.