Skip to content

feat(payments): PaymentProcessor interface + Stripe adapter#1

Open
jfuginay wants to merge 1 commit into
mainfrom
feat/payment-processor-interface
Open

feat(payments): PaymentProcessor interface + Stripe adapter#1
jfuginay wants to merge 1 commit into
mainfrom
feat/payment-processor-interface

Conversation

@jfuginay
Copy link
Copy Markdown
Contributor

@jfuginay jfuginay commented May 7, 2026

Summary

Lay the rail for v0.2 payment work — Square as alternate processor and InventoryComponent deductions — by routing all payment-side calls through a single server-side interface instead of importing getStripe() directly in four places.

  • New: apps/web/lib/payments/PaymentProcessor interface, shared types, ProcessorRef, ProcessorNotConfiguredError, and a Stripe adapter that wraps the existing flows.
  • Refactored: the three POS server actions (pos/[slug]/actions.ts, pos/[slug]/checkout/[orderId]/actions.ts, pos/[slug]/receipt/[orderId]/actions.ts) now call getProcessor() instead of touching the Stripe SDK.
  • Intentionally untouched: apps/web/app/api/stripe/connection-token/route.ts. Client-SDK bootstrap (Stripe Terminal connection tokens, Square Reader OAuth) differs too much per processor to abstract cleanly — those live in processor-specific routes.

Behavior is identical to before: same Stripe API calls, same DB writes, same Payment row shape (Stripe-specific columns kept for v0.1).

Why this matters for v0.2

The reason the abstraction is the first PR in this sequence is that the next two PRs both hang off it:

  1. Schema migration — add Payment.processor + a generic processorRef column so a Square payment can be persisted without bolting squarePaymentId onto the schema.
  2. Square adapterapps/web/lib/payments/square-adapter.ts implementing the same interface. getProcessor() will dispatch on a per-Business setting (business.cardProcessor) when this lands.
  3. OrderClosed event + inventory deductionsOrder.close() becomes a single domain entry point. The InventoryComponent deduction reads OrderLine snapshots and decrements stock when the event fires — regardless of which processor handled the card. There's a placeholder comment in captureCardPayment marking the plug-in point.

This sequencing avoids the trap of writing stripe.paymentIntent.succeeded → decrement and then later writing square.payment.completed → decrement as two separate webhook handlers — two code paths, two test surfaces, two ways to leak inventory on retries.

What's still Stripe-flavored

The DB schema. Payment.stripePaymentIntentId and stripeChargeId columns remain. The adapter writes its ProcessorRef.intentId to stripePaymentIntentId for now. Migrating those columns to processor + processorRef is the next PR — it requires a Prisma migration and a backfill, which would have made this PR much louder.

Test plan

  • bun run typecheck clean across all workspaces
  • Playwright e2e — 19/19 green, including refund a cash sale → status flips to refunded
  • Manual card path — needs a real terminal device + Stripe test mode; couldn't run in this PR, but the call sites are 1:1 wraps of the prior code
  • Vercel preview deploy — verify the build still completes

Non-goals (deliberately deferred)

  • Schema migration to a generic processorRef
  • Square adapter
  • Webhook handling (parseWebhook is not on the interface yet — Stripe webhooks aren't wired in v0.1, so adding that now would be designing for nothing)
  • OrderClosed event emission and the inventory subscriber

🤖 Generated with Claude Code

Introduce a server-side PaymentProcessor interface so the v0.2 Square
adapter and the v0.2 inventory-deduction work can land on a single
abstraction instead of two parallel processor-specific code paths.

- apps/web/lib/payments/ — interface, types, errors, Stripe adapter
- POS, checkout, and receipt server actions now go through getProcessor()
  instead of importing Stripe directly
- Connection-token route stays Stripe-specific (intentional: client-SDK
  bootstrap differs too much per processor to abstract cleanly)
- Behavior unchanged: same Stripe calls, same DB writes, same Payment row
  shape (Stripe-specific columns kept for v0.1)

Verification: bun typecheck clean; 19/19 Playwright e2e green including
the cash-refund happy path.

Next PRs:
1. Schema migration: add Payment.processor + generic processorRef
2. Square adapter implementing PaymentProcessor
3. OrderClosed event + InventoryComponent deductions hung off it

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dearpos Error Error May 7, 2026 2:53am

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant