Add GET /payments/:id/receipt — resolves the Stripe-hosted receipt URL
from the payment intent's latest charge (ownership-scoped; returns EFT
receipt directly when present, null otherwise). Wire a "View receipt"
action on completed Stripe rows that fetches the URL on demand and opens
it, with a toast when none is available.
Closes the last billing-audit item (#8).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refund (Stripe API)
- StripeService.refundPayment({ paymentId, amount?, reason, founderId })
is a new public method that wraps stripe.refunds.create:
* Requires the payment to have a stripePaymentIntentId (post-Iyzico).
* Refuses payments not in completed/partially_refunded status.
* Partial refund: amount must be in 1..payment.amount (kuruş).
* Sends panel_* metadata to Stripe for the founder/reason audit trail.
* Flips payments.status to refunded / partially_refunded.
* Appends a dated reason line to payments.admin_note.
* Captures a `payment_refunded` PostHog event (via:'super_panel').
* Does NOT cancel the subscription — that's a separate decision.
- New endpoint POST /internal/admin/payments/:id/refund behind the
InternalTokenGuard, body { amount?, reason, founderId }.
- Wired through PaymentsAdminController in InternalAdminModule;
StripeModule imported.
Extend (goodwill / bonus time)
- BillingService.extendTrial now accepts both trial AND active
subscriptions (was trial-only). Same end-date semantics
(base = max(now, current endDate)). Response now also returns
subscriptionStatus so the panel can surface the right copy.
- Endpoint URL kept as /trial/extend for backward compatibility; the
panel decides the user-facing label ("Trial uzat" vs "Bonus süre
ekle / Goodwill") based on current status.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Removes the EFT (havale) payment surface across API, web, i18n, and docs.
Card payment via Stripe is now the only checkout path.
API (apps/api/src):
- Delete payments/bank-accounts/ module (controller + service + module)
- payments.service: drop EFT methods (createEftPayment, uploadEftReceipt,
approveEft, rejectEft, getActiveBankInfo, getPendingEftPayments) and the
BankAccountsService dependency
- payments.controller: drop /payments/eft, /payments/eft/:id/{receipt,approve,reject},
/payments/bank-info, /payments/pending; keep /payments/me
- payments.module: drop BankAccountsModule import
- admin.service: drop pending EFT counter from getDashboardStats; drop
getPendingPayments (admin EFT approval list)
- admin.controller: drop /admin/payments/pending endpoint
- admin.service.spec: drop the getPendingPayments + pendingPayments assertions
Web (apps/web/src):
- Delete components/payment/bank-transfer-card.tsx
- Delete routes/dashboard/admin/payments.tsx (EFT approval page); regenerate
routeTree.gen.ts
- subscription/index.tsx: drop EFT tab from PaymentMethodSection, drop the
paymentMethod state + handleEftCompleted, simplify ConfirmationCard to the
Stripe-only path, drop unused imports (Tabs, BankTransferCard, Building2)
- billing.tsx: drop "eft" from method filter chips; keep methodLabels.eft and
the legacy receipt download so historical EFT records still display
- dashboard.tsx + admin/index.tsx: drop /dashboard/admin/payments from the
sidebar + admin quick-link grid; trim unused icon imports; KEYS_6 → KEYS_5
for the now-5-card admin stats skeleton
- messages/{tr,en}.json: strip every EFT-only key under payment.* (eftTransfer,
eftConfirmationDescription, eftPaid, eftStatus, bank.*, uploadReceipt* etc.)
Schema (apps/api/src/database/schema):
- bankAccounts table + payments.bankAccountId column are kept as-is: legacy
EFT payments remain visible on the billing page and the schema preserves
historical records.
Docs:
- README.md: "Stripe (kart) + EFT" → "Stripe (kart)"
- CLAUDE.md: stack table + PaymentsModule row + payments table note + route
list updated; admin EFT approval route removed
Verification:
- pnpm typecheck: green (api + web + shared + config + ui)
- pnpm lint: green
- pnpm test: 20 web tests pass, 173 api tests pass
Subscription/checkout flow rewritten end-to-end. The plan-card "Devam Et"
button silently wiped brand selection on re-click and the actual proceed
button lived offscreen — fixed by collapsing plan/brands/payment/confirm
into a single vertical stepper with one sticky CTA.
Backend
- Stripe Hosted Checkout (`/payments/stripe/checkout`) + webhook
(`/payments/stripe/webhook`, raw body) replacing the stubbed Iyzico
module. Webhook activates subscription on `checkout.session.completed`,
expires the pending subscription on cancel/expire so users can retry.
- New `bank_accounts` table — multiple rows, single `is_active` enforced
by a partial unique index. Admin CRUD under `/admin/bank-accounts`
with multipart QR upload to MinIO; atomic `/activate` swap in a
transaction; `GET /payments/bank-info` returns the active row.
- `payments` gains `stripe_session_id`, `stripe_payment_intent_id`,
`bank_account_id`. EFT flow now reads the active bank account at
payment time and stores the FK for reconciliation.
- Env: `IYZICO_*` removed, `STRIPE_*` added (validated by zod schema).
- `main.ts` `rawBody: true` for Stripe signature verification.
- Drizzle 0003 snapshot id collision fixed (VIEW-only migration shared
prevId with 0002, blocking new generates).
Frontend
- `/dashboard/subscription` rewritten as a 4-step vertical stepper with
step-aware sticky bottom CTA; plan re-selection is idempotent and
preserves brand state. `/dashboard/subscription/pay` deleted; Stripe
returns to the same page via `?stripe=success|cancelled` and the UI
polls `/subscriptions/me` until the webhook activates the row.
- New components: `bank-transfer-card.tsx` (DB-driven IBAN + Kolay Adres
+ uploaded QR image + receipt upload) and `stripe-checkout-button.tsx`.
- Active subscription view, trial onboarding/urgency banner, downgrade
and cancel dialogs preserved.
- TR/EN i18n: new `subscription.steps.*`, `subscription.stickyCta.*`,
`payment.stripe.*`, `payment.bank.*`; provider label updated.
PostHog: `method: "iyzico"` → `"stripe"`; new events `iban_copied`,
`kolay_adres_copied`, `qr_viewed`, `eft_initiated`,
`stripe_redirect_returned`.
Deploy runs `db:migrate` which applies 0004_hot_quicksilver
(additive: new table + nullable columns; safe to apply on prod).
Operator must add `STRIPE_SECRET_KEY`, `STRIPE_PUBLISHABLE_KEY`,
`STRIPE_WEBHOOK_SECRET` to env and create the first
`bank_accounts` row via the admin endpoint before the bank tab works.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Move trial creation from auth hook to dedicated /subscriptions/trial endpoint
with eligibility checks (3-day Full Paket trial)
- Add animated onboarding progress (Remotion) with confetti on completion
- Register redirects to subscription page with welcome flow
- Add Google social login/signup support with config injection
- Improve hotspot overlay visibility in dark mode
- Show "Panele Git" on landing page when authenticated
- Turkish translations for API error messages
- Add toast utility wrapper, UUID generation for auth IDs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 11 new test files covering roles guard, brand-access guard, users,
brands, plans, payments, vehicles, categories, parts, referrals, and
admin services. Total test count increases from ~52 to 164, all passing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>