Wallet & Payments
This guide covers the customer wallet system, payment methods, auto top-up functionality, and payment processing. Understanding these systems is essential for operators troubleshooting billing issues and for customers managing their payment preferences.
Overview
The payment system consists of three key components:
- Customer Wallet - A stored-value account for ride payments
- Payment Methods - Saved credit/debit cards linked to Stripe
- Auto Top-Up - Automatic wallet funding when balance is low
Rides are paid from the wallet balance. When the balance is insufficient, the system can automatically charge the customer's default payment method.
Customer Wallet
How the Wallet Works
Each customer has a wallet balance that:
- Stores credit in USD
- Is used to pay for rides
- Can be funded manually or automatically
- Receives credits from promo codes, referrals, and loyalty redemption
Wallet Balance Flow:
Credits IN:
• Manual top-up (customer adds funds)
• Auto top-up (automatic when low)
• Promo code redemption
• Referral rewards
• Loyalty points redemption
• Customer service credits
Debits OUT:
• Ride charges
• Subscription purchases
• Package purchases
Wallet Balance Display
Customers see their wallet balance:
- In the mobile app home screen
- Before starting a ride
- On ride receipts
- In account settings
Format: Always displayed in dollars with 2 decimal places (e.g., "$25.00")
Minimum Balance Requirements
To start a ride, customers must have:
- Positive wallet balance, OR
- Valid payment method for auto top-up, OR
- Active subscription/package that covers the ride
Important
If a customer's wallet is empty and auto top-up is disabled, they cannot start a ride.
Manual Top-Up
Customer Top-Up Flow
- Customer opens wallet/payment section
- Selects or enters top-up amount
- Chooses payment method
- Confirms payment
- Wallet balance updates instantly
Top-Up Amounts
Operators can configure preset amounts:
| Setting | Description | Example |
|---|---|---|
| Preset amounts | Quick-select options | $10, $25, $50, $100 |
| Custom amount | Allow any amount | $15.00 (min $5, max $500) |
| Minimum top-up | Lowest allowed amount | $5.00 |
| Maximum top-up | Highest allowed amount | $500.00 |
Top-Up Processing
Top-ups are processed via Stripe:
- Create PaymentIntent with amount
- Confirm with customer's payment method
- On success: Increment wallet balance
- Create wallet transaction record
- Send confirmation (email/push)
Auto Top-Up
How Auto Top-Up Works
Auto top-up automatically charges the customer when their wallet balance falls below a threshold:
If wallet_balance ≤ threshold:
Charge default payment method for top-up amount
Add funds to wallet
When It Triggers:
- Before ride starts (if balance insufficient)
- After ride ends (if balance went negative)
- During ride (for real-time billing)
Configuration Requirements
Auto top-up requires BOTH:
- Customer opt-in - Customer enables auto top-up in their settings
- Subaccount enabled - Operator enables auto top-up for the location
| Level | Setting | Who Controls |
|---|---|---|
| Customer | auto_topup_enabled | Customer in app |
| Subaccount | auto_topup_enabled | Operator in dashboard |
| Subaccount | auto_topup_amount_cents | Operator in dashboard |
| Subaccount | auto_topup_threshold_cents | Operator in dashboard |
Configuration Options
Subaccount Settings:
| Setting | Description | Default | Example |
|---|---|---|---|
auto_topup_enabled | Enable for this location | false | true |
auto_topup_amount_cents | Amount to charge | 1500 | 2000 ($20) |
auto_topup_threshold_cents | Trigger threshold | 500 | 1000 ($10) |
Example Configuration:
- Threshold: $5.00 (500 cents)
- Top-up amount: $15.00 (1500 cents)
When wallet drops to $5 or below, the system automatically charges $15.
Auto Top-Up Flow
1. Check if customer allows auto top-up (customer.auto_topup_enabled)
2. Check if subaccount allows auto top-up (subaccount.auto_topup_enabled)
3. Get threshold and amount from subaccount settings
4. If wallet_balance ≤ threshold:
a. Acquire lock (prevent duplicate charges)
b. Get customer's default payment method
c. Create Stripe PaymentIntent
d. Confirm payment (off_session)
e. Increment wallet balance
f. Record transaction
g. Release lock
Concurrency Protection
The system prevents duplicate charges using a database lock:
| Field | Description |
|---|---|
auto_topup_lock_id | Unique lock identifier |
auto_topup_lock_expires_at | Lock expiration time |
Lock Flow:
- Attempt to acquire lock (2-minute TTL)
- If lock acquired, proceed with charge
- If lock exists (another process is charging), wait and check for wallet increase
- Release lock when done
Error Handling
| Error | Cause | Resolution |
|---|---|---|
| Card declined | Insufficient funds, expired card | Notify customer to update payment |
| Authentication required | 3DS challenge needed | Prompt customer for authentication |
| No payment method | No card on file | Prompt customer to add card |
| Lock conflict | Another charge in progress | Wait for other charge to complete |
Payment Methods
Storing Payment Methods
Payment methods are stored via Stripe:
- Customer enters card details
- Card tokenized by Stripe.js (PCI compliant)
- PaymentMethod attached to Stripe Customer
- Reference saved to local database
Payment Method Table
| Field | Description |
|---|---|
stripe_payment_method_id | Stripe PM identifier |
last_four | Last 4 digits of card |
brand | Card brand (Visa, Mastercard, etc.) |
exp_month | Expiration month |
exp_year | Expiration year |
is_default | Default for charges |
Default Payment Method
One payment method is marked as default:
- Used for auto top-up
- Used for manual top-up (unless another selected)
- Used for subscription charges
Setting Default:
- Customer selects "Make Default" in app
- System updates
is_defaultflag - Syncs with Stripe's
invoice_settings.default_payment_method
Managing Payment Methods
Adding a Card:
- Customer taps "Add Payment Method"
- Stripe Elements collects card details
- Card validated and tokenized
- PaymentMethod created and saved
Removing a Card:
- Customer selects card to remove
- System checks it's not the only card (if auto top-up enabled)
- Detaches PaymentMethod from Stripe Customer
- Deletes local record
Updating a Card: Cards cannot be updated - customer must add new card and remove old one.
Wallet Transactions
Transaction Types
| Type | Description | Amount Sign |
|---|---|---|
credit | Funds added to wallet | Positive |
debit | Funds deducted from wallet | Negative |
topup | Manual top-up | Positive |
auto_topup | Automatic top-up | Positive |
ride | Ride charge | Negative |
refund | Charge refund | Positive |
promo | Promo code redemption | Positive |
referral | Referral reward | Positive |
loyalty | Loyalty points redemption | Positive |
adjustment | Manual adjustment | Either |
Transaction Record
| Field | Description |
|---|---|
customer_id | Customer reference |
amount | Amount in dollars |
type | Transaction type |
description | Human-readable description |
reference_type | Source type (ride, promo, etc.) |
reference_id | Source identifier |
balance_after | Wallet balance after transaction |
stripe_payment_intent_id | Stripe PI (for payments) |
created_at | Timestamp |
Viewing Transaction History
Customers can view their transaction history:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
WALLET HISTORY
Today
─────
Ride completed -$7.35
Balance: $17.65
Yesterday
─────────
Auto top-up +$15.00
Balance: $25.00
Ride completed -$8.50
Balance: $10.00
Dec 23
─────────
Promo code HOLIDAY10 +$10.00
Balance: $18.50
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Ride Payment Flow
Start-of-Ride Balance Check
When a customer starts a ride:
- Check wallet balance - Is it above $0?
- Check auto top-up eligibility - Is it enabled and has payment method?
- Check coverage - Does subscription/package cover the ride?
If none of the above are met, ride cannot start.
End-of-Ride Payment
When a ride ends:
1. Calculate final ride charge
2. Check current wallet balance
3. If balance >= charge:
- Deduct from wallet
- Record transaction
4. If balance < charge AND auto top-up enabled:
- Process auto top-up first
- Then deduct ride charge
5. If balance < charge AND no auto top-up:
- Deduct available balance
- Record outstanding amount
- Schedule payment retry
Payment Retry System
When a customer has an outstanding balance:
- Schedule retry - Add to background retry queue
- Retry attempts - Try to collect over time
- Notification - Alert customer of failed payment
- Account restriction - Block new rides until balance cleared
Retry Schedule:
| Attempt | Delay | Action |
|---|---|---|
| 1 | Immediate | First charge attempt |
| 2 | 1 hour | Retry with notification |
| 3 | 24 hours | Retry with warning |
| 4 | 72 hours | Final attempt |
| 5+ | Manual | Requires operator intervention |
Operator Dashboard
Viewing Customer Wallet
In customer detail view:
| Field | Description |
|---|---|
| Wallet Balance | Current balance in dollars |
| Auto Top-Up | Enabled/Disabled status |
| Payment Methods | List of saved cards |
| Transaction History | Recent wallet activity |
Manual Wallet Adjustments
Operators can adjust wallet balances:
Adding Credit:
- Customer service gestures
- Refunds for service issues
- Promotional credits
Removing Credit:
- Fraud corrections
- Error corrections
All adjustments require:
- Amount
- Reason/description
- Operator authentication
Transaction Export
Export wallet transactions for:
- Accounting reconciliation
- Customer disputes
- Audit purposes
Stripe Integration
Test vs Live Mode
The system supports both Stripe modes:
| Mode | Key Type | Use Case |
|---|---|---|
| Live | sk_live_* | Production transactions |
| Test | sk_test_* | Internal testing |
Test Mode Selection:
@levyelectric.comemails use test mode- All other customers use live mode
Stripe Objects Used
| Object | Purpose |
|---|---|
| Customer | Links local customer to Stripe |
| PaymentMethod | Stored card details |
| PaymentIntent | Individual payment |
| SetupIntent | Card setup without charge |
Webhooks
Stripe webhooks update local records:
| Event | Action |
|---|---|
payment_intent.succeeded | Confirm wallet credit |
payment_intent.failed | Log failure, notify customer |
payment_method.attached | Save payment method locally |
payment_method.detached | Remove payment method locally |
Security Considerations
PCI Compliance
- Card numbers never touch our servers
- All card collection via Stripe.js/Elements
- Only store tokenized references
Data Protection
- Wallet balances encrypted at rest
- Transaction history access controlled
- Payment method access restricted
Fraud Prevention
- Velocity limits on top-ups
- Unusual activity detection
- Manual review thresholds
Technical Reference
Database Tables
customers (wallet fields)
| Column | Type | Description |
|---|---|---|
wallet_balance | Decimal | Current balance in dollars |
stripe_customer_id | Text | Stripe Customer ID |
auto_topup_enabled | Boolean | Customer's auto top-up preference |
auto_topup_lock_id | UUID | Concurrency lock ID |
auto_topup_lock_expires_at | Timestamp | Lock expiration |
payment_methods
| Column | Type | Description |
|---|---|---|
customer_uuid | UUID | Reference to customer |
stripe_payment_method_id | Text | Stripe PM ID |
last_four | Text | Last 4 card digits |
brand | Text | Card brand |
exp_month | Integer | Expiration month |
exp_year | Integer | Expiration year |
is_default | Boolean | Default payment method |
wallet_transactions
| Column | Type | Description |
|---|---|---|
customer_id | UUID | Reference to customer |
amount | Decimal | Amount in dollars |
type | Text | Transaction type |
description | Text | Description |
reference_type | Text | Source type |
reference_id | UUID | Source ID |
balance_after | Decimal | Balance after transaction |
stripe_payment_intent_id | Text | Stripe PI ID |
subaccounts (auto top-up fields)
| Column | Type | Description |
|---|---|---|
auto_topup_enabled | Boolean | Enable for location |
auto_topup_amount_cents | Integer | Amount to charge |
auto_topup_threshold_cents | Integer | Trigger threshold |
Core Functions
| Function | Location | Purpose |
|---|---|---|
processAutoTopup | src/lib/auto-topup.ts | Execute auto top-up charge |
canProcessAutoTopup | src/lib/auto-topup.ts | Check if auto top-up possible |
incrementWalletCents | RPC function | Atomically update wallet |
acquireAutoTopupLock | RPC function | Get concurrency lock |
releaseAutoTopupLock | RPC function | Release concurrency lock |
API Endpoints
| Method | Endpoint | Action |
|---|---|---|
| GET | /api/mobile/payment | Get wallet & payment methods |
| POST | /api/mobile/payment/topup | Manual top-up |
| POST | /api/mobile/payment/methods | Add payment method |
| DELETE | /api/mobile/payment/methods/:id | Remove payment method |
| PUT | /api/mobile/payment/methods/:id/default | Set default |
Troubleshooting
Auto Top-Up Not Working
- Check customer setting - Is
auto_topup_enabledtrue? - Check subaccount setting - Is auto top-up enabled for location?
- Verify payment method - Is there a default card?
- Check Stripe customer - Is
stripe_customer_idvalid? - Review lock status - Is there a stale lock blocking?
Payment Failed
- Check Stripe error - Card declined? Expired? 3DS required?
- Verify payment method - Is it still valid in Stripe?
- Review account status - Is Stripe account active?
- Check idempotency - Was the same charge attempted twice?
Wallet Balance Incorrect
- Review transaction history - What transactions occurred?
- Check for pending charges - Any in-flight payments?
- Verify ride charges - Were rides billed correctly?
- Check for duplicates - Any duplicate transactions?
Cannot Start Ride
- Check wallet balance - Is it $0 or negative?
- Verify auto top-up - Is it enabled and functional?
- Check payment method - Is there a valid card?
- Review outstanding balance - Any unpaid charges?
Need Help?
For wallet and payment assistance, contact support@levyelectric.com.