04-Integrations / 04.03.Payment-Integration-WUI

04.03.Payment Integration WUI

04.03. Payment Integration WUI

Payment Flow Overview

Landing Page → Payment Step → Payment Gateway → Payment Success → OAuth → Report
     (1)           (2)             (3)              (4)            (5)     (6)
  1. Landing: User selects vehicle brand and model
  2. Payment Step: User selects plan (Basic/Pro/Expert), payment method, agrees to terms
  3. Payment Gateway: Shows processing state while redirecting to provider
  4. Payment Success: Confirms payment and shows receipt details
  5. OAuth: Connect Tesla account
  6. Report: Display generated PDF report

Payment Step Component (PaymentStep.vue)

Plan Selection

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.

Payment Methods

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)

Terms Agreement

A checkbox (.terms-checkbox) must be checked before the Pay button becomes enabled. Links to the Service Agreement modal.

Pay Button

Disabled until: plan selected + payment method selected + terms agreed. Triggers handlePay() which calls the API and redirects to the payment provider.

Pinia Store (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).

API Service (services/api.ts)

Base URL from VITE_API_BASE_URL env var (defaults to /api/v1).

Payment Functions

// 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)

Authentication

JWT token from localStorage['access_token'] is sent as Authorization: Bearer <token> header on all API calls.

Provider-Specific Flows

Stripe Flow

  1. PaymentStep calls createPaymentIntent(plan, amount, 'stripe')
  2. API returns redirectUrl (Stripe Checkout hosted page)
  3. User is redirected to window.location.href = redirectUrl
  4. After payment, Stripe redirects back to /payment/return?session_id=...
  5. App.vue onMounted() detects the Stripe return URL
  6. handleStripeReturn() calls verifyPayment(sessionId)
  7. On success → navigate to payment-success step

PayPal Flow

  1. PaymentStep calls createPaymentIntent(plan, amount, 'paypal')
  2. API returns redirectUrl (PayPal approval page)
  3. User is redirected to PayPal
  4. After approval, PayPal redirects back to /payment/paypal/{token}?token=...&PayerID=...
  5. App.vue onMounted() detects the PayPal callback URL
  6. handlePayPalCallback() captures order via POST /api/v1/payment/capture/paypal/{token}
  7. Calls verifyPayment(token) to confirm
  8. On success → navigate to payment-success step

Google Pay / Apple Pay

Both route through Stripe Checkout, so the flow is identical to Stripe after the initial API call.

Callback Handling (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().

Security

Environment Variables

VITE_API_BASE_URL=/api/v1

In local dev, Vite proxies /api to http://localhost:8100 (configured in vite.config.ts).

Pricing Configuration

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.

Error Handling

CSS Selectors (for UI Testing)

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