Landing Page → Payment Step → Payment Gateway → Payment Success → OAuth → Report
(1) (2) (3) (4) (5) (6)
PaymentStep.vue)Three pricing tiers displayed as .plan-card elements:
| Plan | Price | CSS Class |
|---|---|---|
| Basic Report | €19 | .plan-card |
| Pro Report | €29 | .plan-card.plan-card-popular (has "POPULAR" badge) |
| Expert Report | from €29.99 | .plan-card |
Selecting a plan reveals the payment method section.
Four methods available as .selection-option buttons:
| Method | Provider | Status |
|---|---|---|
| Credit Card (Visa/MC) | Stripe Checkout | Active |
| PayPal | PayPal Orders API | Active |
| Google Pay | Stripe Checkout | Active (via Stripe) |
| Apple Pay | Stripe Checkout | Active (via Stripe) |
A checkbox (.terms-checkbox) must be checked before the Pay button becomes enabled. Links to the Service Agreement modal.
Disabled until: plan selected + payment method selected + terms agreed. Triggers handlePay() which calls the API and redirects to the payment provider.
stores/app.ts)Payment state is persisted to localStorage to survive redirects to Stripe/PayPal:
| State Key | localStorage Key | Purpose |
|---|---|---|
currentStep |
cpt-current-step |
Current app step |
paymentId |
cpt-payment-id |
Payment/session ID from provider |
selectedPlan |
cpt-selected-plan |
basic / pro / expert |
paymentMethod |
cpt-payment-method |
stripe / paypal / googlepay / applepay |
selectedBrandId |
cpt-selected-brand-id |
Vehicle brand |
selectedModelId |
cpt-selected-model-id |
Vehicle model |
stripeClientSecret is NOT persisted (sensitive, short-lived).
services/api.ts)Base URL from VITE_API_BASE_URL env var (defaults to /api/v1).
// Create payment intent (plan, amount in cents, method)
createPaymentIntent(plan, amount, method)
→ POST /api/v1/payment/create-intent
→ Returns: { clientSecret, paymentIntentId, redirectUrl }
// Verify payment status
verifyPayment(paymentId)
→ POST /api/v1/payment/verify
→ Returns: { status, plan, amount }
// Google Pay (routes through Stripe)
initiateGooglePay(plan, amount)
→ POST /api/v1/payment/create-intent (method: googlepay)
// Apple Pay (routes through Stripe)
initiateApplePay(plan, amount)
→ POST /api/v1/payment/create-intent (method: applepay)
JWT token from localStorage['access_token'] is sent as Authorization: Bearer <token> header on all API calls.
PaymentStep calls createPaymentIntent(plan, amount, 'stripe')redirectUrl (Stripe Checkout hosted page)window.location.href = redirectUrl/payment/return?session_id=...App.vue onMounted() detects the Stripe return URLhandleStripeReturn() calls verifyPayment(sessionId)payment-success stepPaymentStep calls createPaymentIntent(plan, amount, 'paypal')redirectUrl (PayPal approval page)/payment/paypal/{token}?token=...&PayerID=...App.vue onMounted() detects the PayPal callback URLhandlePayPalCallback() captures order via POST /api/v1/payment/capture/paypal/{token}verifyPayment(token) to confirmpayment-success stepBoth route through Stripe Checkout, so the flow is identical to Stripe after the initial API call.
App.vue)onMounted() checks window.location.pathname and URL params:
/payment/return?session_id=... → handleStripeReturn()
/payment/paypal/{token}?token=... → handlePayPalCallback()
After processing, the URL is cleaned with window.history.replaceState().
store.reset() clears all payment state and localStorage keyslocalStorage['access_token']VITE_API_BASE_URL=/api/v1
In local dev, Vite proxies /api to http://localhost:8100 (configured in vite.config.ts).
Plan prices are defined in both frontend and backend:
Frontend (PaymentStep.vue):
const planPrices = {
basic: 1900, // €19.00 in cents
pro: 2900, // €29.00
expert: 2999 // €29.99 (Phase 2: CPQ tier-based pricing)
}
Backend (config.py): Server-side validation uses its own PAYMENT_PLANS dict. The client-provided amount is ignored for security.
.alert.alert-error div (bound to store.error)store.isProcessingcancel flag or token without PayerID)verification.status response| Selector | Element |
|---|---|
.plan-card |
Plan selection cards (3) |
.plan-card-selected |
Currently selected plan |
.plan-card-popular |
Pro plan card (with badge) |
.plan-badge |
"POPULAR" badge on Pro plan |
.plan-title |
Plan name text |
.plan-price |
Plan price text |
.payment-methods-grid |
Payment method buttons container |
.selection-option |
Individual payment method button |
.selection-option-active |
Selected payment method |
.terms-checkbox |
Terms agreement checkbox |
.security-note |
Security notice text |
.alert-error |
Error message display |