# Payment Workflow ## Overview - **Provider**: Stripe - **Commission**: Platform 15% | Sheikh 85% - **Currency**: USD ## Payment Status Lifecycle ``` ┌─────────┐ │ PENDING │ ← PaymentIntent created └────┬────┘ │ ┌──────────┴──────────┐ │ Stripe succeeds │ Stripe fails / expires ▼ ▼ ┌──────┐ ┌────────┐ │ PAID │ │ FAILED │ → Can retry └───┬──┘ └────────┘ │ │ Session COMPLETED │ Sheikh payout triggered ▼ ┌────────────┐ │ PAYOUT_DONE│ └────────────┘ PAID/PENDING → REFUNDED (session MISSED or CANCELLED) ``` --- ## Phase 1 — Initiate Payment **Trigger**: Student clicks "Pay Now" on a TOPAY session **Component**: `StripePaymentModal.tsx` ``` POST /api/payments/initiate/card { "sessionId": 123, "studentId": 45, "sheikhId": 67, "amount": 50.00 } ``` **Backend logic** (`PaymentServiceStripe.java`): ``` Receive request │ ▼ Check existing Payment record for this session │ ├── PAID / PAYOUT_DONE → Reject (already paid) ├── PENDING → Return existing PaymentIntent (idempotent) ├── FAILED / REFUNDED → Delete old record, create new └── No record → Create new │ ▼ Create Stripe PaymentIntent amount = 5000 cents (USD) │ ▼ Save Payment record: amount: $50.00 platformCommission: $7.50 (15%) sheikhPayout: $42.50 (85%) paymentStatus: PENDING transactionId: pi_xxxxx │ ▼ Return to frontend: { clientSecret, publishableKey, paymentIntentId } ``` --- ## Phase 2 — Frontend Stripe Checkout **Component**: `StripePaymentModal.tsx` ``` Load Stripe.js with publishableKey │ ▼ Mount Stripe CardElement (secure iframe) │ ▼ User enters card details │ ▼ stripe.confirmCardPayment(clientSecret, { payment_method: { card: cardElement } }) │ ├── success → proceed to Phase 3 └── error → show error to user (can retry) ``` --- ## Phase 3 — Confirm Payment (Backend) ``` POST /api/payments/confirm-payment/{paymentIntentId} │ ▼ Backend retrieves PaymentIntent from Stripe API │ ├── status: "succeeded" / "requires_capture" │ │ │ ▼ │ Payment record → PAID │ Session status → SCHEDULED │ Return success to frontend │ ├── status: "processing" │ │ │ ▼ │ Keep PENDING (Stripe webhook handles later) │ └── other status │ ▼ Payment record → FAILED Throw error ``` --- ## Phase 4 — Payout to Sheikh **Trigger**: Sheikh calls `POST /api/sheikh/sessions/{id}/end` **Automatic — no manual action needed** ``` Session ends → status: COMPLETED │ ▼ paymentServiceStripe.payoutSheikh(sessionId) │ ▼ Validate payment is PAID │ ▼ Validate sheikh has Stripe connected account (sheikhProfile.stripeAccountId) │ ▼ Create Stripe Transfer: amount: $42.50 (85% of $50.00) destination: sheikh's Stripe account ID │ ▼ Payment record → PAYOUT_DONE Transfer ID saved │ ▼ Funds arrive in sheikh's bank within 1–2 business days > If transfer fails: error is logged, session stays COMPLETED > Sheikh contacts support for manual payout ``` --- ## Refund Scenarios | Scenario | Trigger | Result | |---|---|---| | Session MISSED — no-show | Auto, 10 min after start | Full refund to student | | Session auto-cancelled (unpaid) | Auto, 5 min before start | No charge (PaymentIntent voided) | | Sheikh manually rejects | Sheikh clicks Reject | Full refund if already paid | **Refund Flow**: ``` POST /api/payments/refund/{sessionId} │ ▼ Check payment status │ ├── PENDING (not captured yet) │ │ │ └── Mark REFUNDED locally (no Stripe call needed) │ └── PAID │ ▼ Create Stripe Refund amount: full original charge │ ▼ Payment record → REFUNDED Refund timestamp saved ``` --- ## Commission Split Diagram ``` Student pays $50.00 │ ▼ ┌───────────────────────────────────┐ │ Platform Account │ │ │ │ $7.50 (15%) Platform fee │ │ $42.50 (85%) → Sheikh payout │ └──────────────────┬────────────────┘ │ Stripe Transfer (on session complete) ▼ Sheikh's Stripe Account $42.50 ``` > Commission percentage is configurable via `platform.commission.percentage` in `application.yaml` (default: 15%) --- ## API Endpoints Reference | Method | Endpoint | Description | |---|---|---| | POST | `/api/payments/initiate/card` | Create PaymentIntent, return clientSecret | | POST | `/api/payments/confirm-payment/{paymentIntentId}` | Confirm payment after Stripe.js success | | POST | `/api/payments/confirm-session/{sessionId}` | Sheikh accepts — verify payment is PAID | | POST | `/api/payments/payout/{sessionId}` | Payout sheikh (called automatically on session end) | | POST | `/api/payments/refund/{sessionId}` | Refund student | --- ## Key Notes - **Idempotent**: Calling initiate twice for the same session returns the existing PaymentIntent — no duplicate charges. - **No double-pay**: Backend checks for existing PAID/PAYOUT_DONE records and rejects the request. - **Auto-retry safe**: FAILED/REFUNDED records are deleted and a fresh PaymentIntent is created on retry. - **Payout is non-blocking**: A failed Stripe Transfer does not roll back the COMPLETED session status.