Skip to content

Customer Package Payments

Complete guide to package payment processing, payment link generation, and payment status tracking in the Reserva platform.


Overview

The customer package payments system provides comprehensive payment management for package purchases with support for:

  • Manual Payment Recording - Record offline payments (cash, POS, bank transfer)
  • Payment Link Generation - Create Paper.id payment links for online payments
  • Payment Status Tracking - Monitor payment progress and history
  • Automatic Credit Activation - Credits activated upon payment confirmation
  • Multi-Channel Delivery - Send payment links via email, WhatsApp, or SMS
  • Webhook Integration - Automatic payment confirmation via Paper.id webhooks

Key Concepts:

  • Manual Payment = Cash, POS terminal, or bank transfer at venue (immediate activation)
  • Payment Link = Online payment via Paper.id (webhook-triggered activation)
  • Credit Activation = Package credits become available after payment confirmation
  • Platform Fees = Package purchases are fee-exempt across all tiers

Portal: Shared (Staff and Customer)

Access Level: - Staff: TENANT_ADMIN, OUTLET_MANAGER, RECEPTIONIST, SUPER_ADMIN - Customer: Own packages only


Available Endpoints

Endpoint Method Purpose Access
/customer-packages/{id}/record-payment POST Record manual offline payment Staff
/customer-packages/{id}/create-payment-link POST Create Paper.id payment link Staff
/customer-packages/{id}/payment-status GET Check payment status Staff/Customer

Record Manual Payment

Record offline payment for package purchase with automatic credit activation.

Endpoint

POST /api/v1/customer-packages/{customer_package_id}/record-payment

Authentication: Required (Staff JWT - TENANT_ADMIN, OUTLET_MANAGER, RECEPTIONIST, SUPER_ADMIN)

Path Parameters

Parameter Type Required Description
customer_package_id string Yes Customer package ID

Request Body

{
  "amount": 500000,
  "payment_method": "cash",
  "notes": "Paid in cash for package purchase",
  "receipt_number": "PKG-RCPT-2025-001"
}

Parameters

Field Type Required Description
amount decimal Yes Payment amount (must match package price exactly)
payment_method string Yes Payment method: cash, pos_terminal, bank_transfer
notes string No Payment notes (max 500 chars)
receipt_number string No Receipt or reference number (max 100 chars)

Supported Payment Methods

Method Description Use Case
cash Cash payment Customer pays cash at location
pos_terminal Credit/debit card via POS Card swipe at venue terminal
bank_transfer Direct bank transfer Customer shows transfer confirmation

Response

{
  "status": "success",
  "message": "Payment recorded successfully - package credits activated",
  "payment": {
    "id": "507f1f77bcf86cd799439011",
    "amount": 500000.0,
    "method": "cash",
    "status": "completed",
    "recorded_by": "Jane Smith",
    "recorded_at": "2025-01-20T10:30:00",
    "receipt_number": "PKG-RCPT-2025-001",
    "reference_id": "PAY-PKG-20250120103000"
  },
  "package": {
    "id": "507f1f77bcf86cd799439012",
    "status": "active",
    "payment_status": "paid",
    "expires_at": "2025-04-20T10:30:00",
    "total_credits": 10
  }
}

Response Fields

Field Type Description
payment.id string Payment record ID
payment.amount decimal Payment amount
payment.method string Payment method used
payment.status string Payment status (completed)
payment.recorded_by string Staff member who recorded payment
payment.recorded_at datetime When payment was recorded
payment.receipt_number string Receipt or reference number
payment.reference_id string Internal reference ID
package.id string Customer package ID
package.status string Package status (active)
package.payment_status string Payment status (paid)
package.expires_at datetime Package expiry date
package.total_credits integer Total credits allocated

Business Rules

  • Amount must match exactly - Payment amount must equal package price
  • Package must be pending - Package must be in pending_payment status
  • Offline methods only - Only cash, pos_terminal, bank_transfer accepted
  • Immediate activation - Credits activated immediately upon recording
  • Audit trail - Staff member who recorded payment is tracked
  • Tenant isolation - Package must belong to staff's tenant

Process Flow

graph TD
    A[Staff records payment] --> B{Validate package}
    B -->|Not found| C[404 Not Found]
    B -->|Found| D{Check payment status}
    D -->|Already paid| E[409 Conflict]
    D -->|Pending| F{Validate method}
    F -->|Online method| G[400 - Use payment link]
    F -->|Offline method| H{Validate amount}
    H -->|Mismatch| I[400 - Amount mismatch]
    H -->|Match| J[Create payment record]
    J --> K[Update package - PAID]
    K --> L[Activate credits]
    L --> M[Return success]

Error Responses

Package Not Found:

{
  "detail": "Customer package not found"
}

Package Already Paid:

{
  "detail": "Package is already fully paid"
}

Invalid Payment Method:

{
  "detail": "Manual payments only support: ['cash', 'pos_terminal', 'bank_transfer']. Use create-payment-link endpoint for online payments."
}

Amount Mismatch:

{
  "detail": "Payment amount (450000) must match package price (500000)"
}

Generate Paper.id payment link for package purchase with automatic credit activation on payment.

Endpoint

POST /api/v1/customer-packages/{customer_package_id}/create-payment-link

Authentication: Required (Staff JWT)

Path Parameters

Parameter Type Required Description
customer_package_id string Yes Customer package ID

Request Body

{
  "send_email": true,
  "send_whatsapp": true,
  "send_sms": false,
  "notes": "Payment link for package purchase",
  "due_date": "2025-01-25"
}

Parameters

Field Type Required Default Description
send_email boolean No true Send payment link via email
send_whatsapp boolean No false Send payment link via WhatsApp
send_sms boolean No false Send payment link via SMS
notes string No null Invoice notes (max 500 chars)
due_date date No 3 days Payment due date

Customer Contact Requirements

Customer Has Email WhatsApp SMS
Email only Yes No No
Phone only No Yes Yes
Both Yes Yes Yes
Neither Error Error Error

Note: At least one delivery method must be enabled. If customer has no email, send_email is automatically disabled.

Response

{
  "status": "payment_link_created",
  "message": "Payment link sent to customer via email, whatsapp",
  "invoice": {
    "id": "507f1f77bcf86cd799439011",
    "invoice_number": "INV-PKG-20250120-103000",
    "amount": 500000.0,
    "subtotal": 500000.0,
    "platform_fee": 0.0,
    "platform_fee_percentage": 0.0,
    "currency": "IDR",
    "due_date": "2025-01-25",
    "paper_invoice_id": "paper_inv_abc123",
    "invoice_url": "https://paper.id/invoice/abc123"
  },
  "payment": {
    "id": "507f1f77bcf86cd799439012",
    "reference_id": "PKG-507f1f77bcf86cd799439011-20250120103000",
    "status": "pending",
    "amount": 500000.0
  },
  "customer": {
    "name": "John Doe",
    "email": "john.doe@example.com",
    "phone": "+628123456789"
  },
  "delivery_methods": ["email", "whatsapp"],
  "webhook_url": "/webhooks/paper-invoice/tenant/507f1f77bcf86cd799439010"
}

Response Fields

Field Type Description
status string Creation status
message string Success message with delivery methods
invoice.id string Internal invoice ID
invoice.invoice_number string Invoice number for reference
invoice.amount decimal Total invoice amount
invoice.subtotal decimal Subtotal before fees
invoice.platform_fee decimal Platform fee (always 0 for packages)
invoice.platform_fee_percentage float Fee percentage (always 0)
invoice.currency string Currency code (IDR)
invoice.due_date date Payment due date
invoice.paper_invoice_id string Paper.id invoice ID
invoice.invoice_url string Paper.id payment URL
payment.id string Payment record ID
payment.reference_id string Internal reference ID
payment.status string Payment status (pending)
payment.amount decimal Payment amount
customer.name string Customer name
customer.email string Customer email (if available)
customer.phone string Customer phone (if available)
delivery_methods array Methods used to send payment link
webhook_url string Webhook URL for payment confirmation

Platform Fees

Plan Platform Fee
FREE 0%
PRO 0%
ENTERPRISE 0%

Note: Package purchases are fee-exempt across all subscription tiers. This is different from appointment payments which may have platform fees.

Business Rules

  • Package must be pending - Package must be in pending_payment status
  • Customer assigned - Package must have a customer assigned
  • Contact required - Customer must have email OR phone number
  • Delivery method required - At least one delivery method must be enabled
  • Tenant Paper.id configured - Tenant must have Paper.id integration enabled
  • Default due date - 3 days from creation if not specified
  • No platform fees - Package purchases don't incur platform fees

Payment Flow

graph TD
    A[Staff creates payment link] --> B{Validate package}
    B -->|Not found| C[404 Not Found]
    B -->|Found| D{Check payment status}
    D -->|Already paid| E[409 Conflict]
    D -->|Pending| F{Check customer contact}
    F -->|No contact| G[400 - Missing contact]
    F -->|Has contact| H{Check Paper.id config}
    H -->|Not configured| I[400 - Gateway not configured]
    H -->|Configured| J[Create internal invoice]
    J --> K[Generate Paper.id invoice]
    K --> L[Create payment record - PENDING]
    L --> M[Send via selected channels]
    M --> N[Return payment link details]

    N --> O[Customer receives link]
    O --> P[Customer completes payment]
    P --> Q[Paper.id webhook received]
    Q --> R[Credits automatically activated]
    R --> S[Package status: ACTIVE]

Webhook Payment Confirmation

When customer completes payment on Paper.id, a webhook is sent to:

POST /api/v1/webhooks/paper-invoice/tenant/{tenant_id}

The webhook handler:

  1. Validates invoice belongs to tenant
  2. Updates payment record to COMPLETED
  3. Updates customer package payment_status to PAID
  4. Activates package credits
  5. Sets package status to ACTIVE

See Webhook Integration for detailed webhook handling.

Error Responses

Package Not Found:

{
  "detail": "Customer package not found"
}

Package Already Paid:

{
  "detail": "Package already paid. Cannot create payment link."
}

Missing Customer Contact:

{
  "detail": "Customer must have either email or phone number to receive invoice"
}

No Delivery Method:

{
  "detail": "At least one delivery method must be enabled (email, WhatsApp, or SMS)"
}

Gateway Not Configured:

{
  "detail": "Payment gateway not configured for this tenant. Please configure Paper.id credentials in tenant settings."
}

Paper.id Error:

{
  "detail": "Failed to create payment link with Paper.id: [error details]"
}

Get Payment Status

Retrieve comprehensive payment status and history for a package purchase.

Endpoint

GET /api/v1/customer-packages/{customer_package_id}/payment-status

Authentication: Required (Staff or Customer JWT)

Path Parameters

Parameter Type Required Description
customer_package_id string Yes Customer package ID

Response

{
  "customer_package_id": "507f1f77bcf86cd799439011",
  "package_name": "Premium Hair Package - 10 Sessions",
  "package_price": 500000.0,
  "payment_status": "paid",
  "package_status": "active",
  "total_paid": 500000.0,
  "remaining_balance": 0.0,
  "is_paid": true,
  "credits_activated": true,
  "payments": [
    {
      "id": "507f1f77bcf86cd799439012",
      "amount": 500000.0,
      "method": "cash",
      "provider": "manual",
      "status": "completed",
      "reference_id": "PAY-PKG-20250120103000",
      "created_at": "2025-01-20T10:30:00",
      "paid_at": "2025-01-20T10:30:00"
    }
  ]
}

Response Fields

Field Type Description
customer_package_id string Customer package ID
package_name string Package display name
package_price decimal Total package price
payment_status string Current payment status
package_status string Current package status
total_paid decimal Sum of completed payments
remaining_balance decimal Outstanding amount (should be 0 if paid)
is_paid boolean Whether package is fully paid
credits_activated boolean Whether credits are active and usable
payments array Payment history records
payments[].id string Payment record ID
payments[].amount decimal Payment amount
payments[].method string Payment method
payments[].provider string Payment provider (manual, paper_id)
payments[].status string Payment status
payments[].reference_id string Reference ID for tracking
payments[].created_at datetime When payment was created
payments[].paid_at datetime When payment was completed

Payment Statuses

Status Description
pending Payment awaiting completion
paid Payment completed, credits activated
failed Payment failed
refunded Payment refunded

Package Statuses

Status Description
pending_payment Awaiting payment
active Paid and credits available
partially_used Some credits redeemed
depleted All credits used
expired Credits expired

Business Rules

  • Tenant isolation - Package must belong to user's tenant
  • Customer access - Customers can only view their own packages
  • Staff access - Staff can view any package in their tenant
  • Payment history - Includes all payment attempts (successful and failed)
  • Total calculation - Total paid calculated from COMPLETED payments only

Error Responses

Package Not Found:

{
  "detail": "Customer package not found"
}

Access Denied:

{
  "detail": "Access denied - package belongs to different tenant"
}

Payment Lifecycle

Overview

Package payments follow a defined lifecycle from creation to activation:

graph TD
    A[Package Purchase Created] --> B{Payment Method?}

    B -->|Manual On-Spot| C[Payment: COMPLETED]
    C --> D[Package: ACTIVE]
    D --> E[Credits: ALLOCATED]

    B -->|Digital Payment| F[Payment: PENDING]
    F --> G[Invoice Created]
    G --> H[Payment Link Sent]
    H --> I{Customer Pays?}
    I -->|Yes| J[Webhook Received]
    J --> K[Payment: COMPLETED]
    K --> D
    I -->|No/Expired| L[Payment: PENDING/FAILED]

    B -->|Bank Transfer| M[Payment: PENDING]
    M --> N[Customer Transfers]
    N --> O{Staff Confirms?}
    O -->|Yes| P[Record Manual Payment]
    P --> C
    O -->|No| Q[Payment: PENDING]

Payment States

State Package Status Payment Status Credits
Just Created (Manual) active paid Allocated
Just Created (Digital) pending_payment pending Not allocated
Payment Completed active paid Allocated
Payment Failed pending_payment failed Not allocated
Credits Used active/depleted paid Partially/Fully used
Expired expired paid Expired

Integration with Package System

System Endpoint Purpose
Customer Self-Service POST /customer/packages/purchase Customer initiates purchase
Staff Manual Purchase POST /staff/customer-packages Staff creates purchase for customer
Payment Processing POST /customer-packages/{id}/record-payment Record offline payment
Payment Processing POST /customer-packages/{id}/create-payment-link Create online payment link
Payment Processing GET /customer-packages/{id}/payment-status Check payment status
Credit Redemption POST /staff/customer-packages/credits/redeem Use credits for service

Payment Flow Examples

Scenario 1: Walk-In Cash Payment

# 1. Staff creates package purchase for walk-in customer
curl -X POST https://api.example.com/api/v1/staff/customer-packages \
  -H "Authorization: Bearer STAFF_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_id": "507f1f77bcf86cd799439011",
    "package_id": "507f1f77bcf86cd799439012",
    "outlet_id": "507f1f77bcf86cd799439013",
    "payment_method": "manual_onspot",
    "amount_paid": 500000
  }'

# Response: Credits already activated (manual_onspot auto-confirms)

Scenario 2: Customer Online Purchase

# 1. Customer purchases package online
curl -X POST https://api.example.com/api/v1/customer/packages/purchase \
  -H "Authorization: Bearer CUSTOMER_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "package_id": "507f1f77bcf86cd799439012",
    "outlet_id": "507f1f77bcf86cd799439013",
    "payment_method": "paper_digital",
    "send_email": true
  }'

# Response includes payment_url - customer completes payment
# Webhook automatically activates credits

# 2. Check payment status
curl -X GET "https://api.example.com/api/v1/customer-packages/507f1f77bcf86cd799439020/payment-status" \
  -H "Authorization: Bearer CUSTOMER_JWT_TOKEN"

Scenario 3: Bank Transfer with Staff Confirmation

# 1. Customer purchases with bank_transfer
curl -X POST https://api.example.com/api/v1/customer/packages/purchase \
  -H "Authorization: Bearer CUSTOMER_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "package_id": "507f1f77bcf86cd799439012",
    "outlet_id": "507f1f77bcf86cd799439013",
    "payment_method": "bank_transfer"
  }'

# 2. Customer makes bank transfer

# 3. Staff records manual payment
curl -X POST https://api.example.com/api/v1/customer-packages/507f1f77bcf86cd799439020/record-payment \
  -H "Authorization: Bearer STAFF_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 500000,
    "payment_method": "bank_transfer",
    "notes": "Bank transfer confirmed - REF: TRF123456",
    "receipt_number": "TRF123456"
  }'
# 1. Staff creates package purchase (pending)
curl -X POST https://api.example.com/api/v1/staff/customer-packages \
  -H "Authorization: Bearer STAFF_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_id": "507f1f77bcf86cd799439011",
    "package_id": "507f1f77bcf86cd799439012",
    "outlet_id": "507f1f77bcf86cd799439013",
    "payment_method": "paper_digital",
    "amount_paid": 500000
  }'

# 2. Staff creates payment link
curl -X POST https://api.example.com/api/v1/customer-packages/507f1f77bcf86cd799439020/create-payment-link \
  -H "Authorization: Bearer STAFF_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "send_email": true,
    "send_whatsapp": true,
    "notes": "Package payment for John Doe",
    "due_date": "2025-01-25"
  }'

# Customer receives payment link and completes payment
# Webhook activates credits automatically

Subscription Plan Considerations

Package Feature Availability

Package features depend on subscription plan:

Plan Packages Feature Max Packages Max Items/Package
FREE Yes 1 3
PRO Yes 10 10
ENTERPRISE Yes 100 20

Note: Plan limits apply to package creation, not to payment processing. Staff can process payments for any existing package regardless of plan tier.

Platform Fee Exemption

Package purchases are fee-exempt across all tiers:

Payment Type FREE Tier PRO Tier ENTERPRISE Tier
Appointment Payments Variable Variable Variable
Package Payments 0% 0% 0%
Subscription Payments Variable Variable Variable

See Package Management - Subscription Plan Limits for package creation limits.


Webhook Integration

Package Payment Webhook

Package payment webhooks are routed to the tenant-specific endpoint:

POST /api/v1/webhooks/paper-invoice/tenant/{tenant_id}

Invoice Metadata Structure

When creating a payment link, the invoice includes:

{
  "invoice_type": "PACKAGE",
  "metadata": {
    "reference_id": "PKG-507f1f77bcf86cd799439011-20250120103000",
    "customer_package_id": "507f1f77bcf86cd799439011",
    "platform_fee": 0.0,
    "platform_fee_percentage": 0.0
  }
}

Webhook Handler

When payment is confirmed, the webhook handler:

  1. Validates invoice type is PACKAGE
  2. Finds invoice by Paper.id invoice ID
  3. Updates payment record to COMPLETED
  4. Updates customer package payment_status to PAID
  5. Calls activate_package_after_payment()
  6. Creates credits for each package item
  7. Sets package status to ACTIVE

See Webhook Integration for complete webhook documentation.


Best Practices

Recording Manual Payments

DO:

  • Verify customer identity before recording payment
  • Match payment amount exactly to package price
  • Add descriptive notes for audit trail
  • Include receipt/reference numbers when available
  • Confirm package is in pending_payment status first

DON'T:

  • Record payment without verifying customer
  • Use online payment methods (use create-payment-link instead)
  • Forget to add notes for unusual transactions
  • Assume payment is recorded if error occurs

DO:

  • Verify customer has valid contact information
  • Set appropriate due dates (not too short, not too long)
  • Enable multiple delivery channels when possible
  • Add helpful notes to the invoice
  • Confirm tenant has Paper.id configured

DON'T:

  • Create payment links for already paid packages
  • Set due dates too far in the future (>7 days)
  • Skip delivery method selection
  • Ignore Paper.id configuration errors

Checking Payment Status

DO:

  • Check status before processing related operations
  • Use payment status to guide customer service
  • Monitor for pending payments nearing due dates
  • Review payment history for troubleshooting

DON'T:

  • Assume payment is complete without checking
  • Ignore failed payment records
  • Provide inaccurate payment information to customers

Error Handling

Common Errors

Error Code Cause Solution
400 Bad Request Invalid input or validation failure Check request parameters
401 Unauthorized Missing/invalid token Verify JWT token
403 Forbidden Insufficient permissions or wrong tenant Check user role and tenant
404 Not Found Package not found Verify package ID
409 Conflict Package already paid Cannot process duplicate payment
422 Validation Error Invalid data format Check field types and formats
500 Internal Server Error Server or gateway error Contact support

Gateway Errors

Paper.id Connection Error:

{
  "detail": "Failed to create payment link with Paper.id: Connection timeout"
}

Solution: Check Paper.id credentials and network connectivity.

Paper.id API Error:

{
  "detail": "Failed to create payment link with Paper.id: Invalid customer data"
}

Solution: Verify customer has valid phone number format (E.164).


Package System

Appointment Integration

  • Appointment Management - Complete appointment booking guide
  • Appointment Credit Redemption - Using package credits for appointments
  • Once package is paid, credits are activated
  • Credits can be used at appointment booking time
  • FIFO ordering ensures oldest credits used first

Supporting Documentation


API Reference Summary

Endpoint Method Purpose Access
/customer-packages/{id}/record-payment POST Record offline payment Staff
/customer-packages/{id}/create-payment-link POST Create payment link Staff
/customer-packages/{id}/payment-status GET Check payment status Staff/Customer

Next Steps:

  1. Check package payment status: GET /customer-packages/{id}/payment-status
  2. For offline payments: POST /customer-packages/{id}/record-payment
  3. For online payments: POST /customer-packages/{id}/create-payment-link
  4. Monitor webhook deliveries for payment confirmation
  5. Verify credits activated: GET /staff/customer-packages/{customer_id}/credits

For complete API testing, see Swagger UI or ReDoc.


Frontend UI Suggestions

This section provides UI/UX recommendations for frontend developers implementing package payment features.

Use Case 1: Customer Payment Status View

Customer-facing view showing package payment status and history.

Wireframe:

┌─────────────────────────────────────────────────────────────────────────┐
│ My Packages                                              Customer Portal │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│ ┌─ Premium Hair Package ────────────────────────────────────────────┐   │
│ │                                                                   │   │
│ │  ┌────────────────────────────────────────────────────────────┐   │   │
│ │  │                                                            │   │   │
│ │  │  ✅ PAID                                                   │   │   │
│ │  │  Payment confirmed on Jan 20, 2025                         │   │   │
│ │  │                                                            │   │   │
│ │  └────────────────────────────────────────────────────────────┘   │   │
│ │                                                                   │   │
│ │  Package Price:                              Rp 500,000           │   │
│ │  Total Paid:                                 Rp 500,000           │   │
│ │  Balance:                                    Rp 0                 │   │
│ │                                                                   │   │
│ │  ┌─ Package Status ───────────────────────────────────────────┐   │   │
│ │  │ Status: Active                                             │   │   │
│ │  │ Credits: 10 available                                      │   │   │
│ │  │ Expires: Apr 20, 2025 (90 days)                            │   │   │
│ │  └────────────────────────────────────────────────────────────┘   │   │
│ │                                                                   │   │
│ │  ┌─ Payment History ──────────────────────────────────────────┐   │   │
│ │  │                                                            │   │   │
│ │  │ Jan 20, 2025  Cash Payment    Rp 500,000    ✅ Completed   │   │   │
│ │  │ Receipt: PKG-RCPT-2025-001                                 │   │   │
│ │  │                                                            │   │   │
│ │  └────────────────────────────────────────────────────────────┘   │   │
│ │                                                                   │   │
│ │                                    [View Credits] [Book Service]  │   │
│ │                                                                   │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│ ┌─ Spa Relaxation Bundle ───────────────────────────────────────────┐   │
│ │                                                                   │   │
│ │  ┌────────────────────────────────────────────────────────────┐   │   │
│ │  │                                                            │   │   │
│ │  │  ⏳ PENDING PAYMENT                                        │   │   │
│ │  │  Waiting for payment confirmation                          │   │   │
│ │  │                                                            │   │   │
│ │  └────────────────────────────────────────────────────────────┘   │   │
│ │                                                                   │   │
│ │  Package Price:                              Rp 450,000           │   │
│ │  Total Paid:                                 Rp 0                 │   │
│ │  Balance Due:                                Rp 450,000           │   │
│ │                                                                   │   │
│ │  ⚠️ Credits will be activated after payment is confirmed         │   │
│ │                                                                   │   │
│ │                                              [Complete Payment →] │   │
│ │                                                                   │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Key UI Elements:

  • Status Badge: Color-coded (green=paid, yellow=pending, red=failed)
  • Balance Summary: Clear display of amounts paid and due
  • Credits Info: Show activation status
  • Payment History: Chronological list of payment records
  • Action Button: Context-aware (Pay Now vs View Credits)

Use Case 2: Staff Record Manual Payment

Staff interface for recording offline cash/POS payments.

Wireframe:

┌─────────────────────────────────────────────────────────────────────────┐
│ Record Payment - Premium Hair Package                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│ ┌─ Package Details ─────────────────────────────────────────────────┐   │
│ │                                                                   │   │
│ │ Customer:        John Smith                                       │   │
│ │ Package:         Premium Hair Package - 10 Sessions               │   │
│ │ Package Price:   Rp 500,000                                       │   │
│ │ Status:          Pending Payment                                  │   │
│ │                                                                   │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│ Payment Method *                                                        │
│ ┌───────────────────────────────────────────────────────────────────┐   │
│ │ ● Cash                                                            │   │
│ │   Customer paid cash at location                                  │   │
│ │                                                                   │   │
│ │ ○ POS Terminal                                                    │   │
│ │   Credit/debit card via POS machine                               │   │
│ │                                                                   │   │
│ │ ○ Bank Transfer                                                   │   │
│ │   Customer showed bank transfer confirmation                      │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│ Payment Amount *                                                        │
│ ┌───────────────────────────────────────────────────────────────────┐   │
│ │ Rp  500,000                                           [= Price]   │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│ ℹ️ Amount must match package price exactly                              │
│                                                                         │
│ Receipt/Reference Number                                                │
│ ┌───────────────────────────────────────────────────────────────────┐   │
│ │ PKG-RCPT-2025-001                                                 │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│ Notes                                                                   │
│ ┌───────────────────────────────────────────────────────────────────┐   │
│ │ Walk-in customer, paid cash at front desk                         │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│ ┌─ After Recording ─────────────────────────────────────────────────┐   │
│ │                                                                   │   │
│ │ ✓ Package status will change to ACTIVE                            │   │
│ │ ✓ 10 credits will be activated immediately                        │   │
│ │ ✓ Customer can start using credits right away                     │   │
│ │                                                                   │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│                                         [Cancel]   [Record Payment]     │
└─────────────────────────────────────────────────────────────────────────┘

Success Confirmation:

┌─────────────────────────────────────────────────────────────────────────┐
│ ✅ Payment Recorded Successfully                                        │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│ Package:         Premium Hair Package - 10 Sessions                     │
│ Customer:        John Smith                                             │
│ Amount:          Rp 500,000                                             │
│ Method:          Cash                                                   │
│ Reference:       PAY-PKG-20250120103000                                 │
│                                                                         │
│ ─────────────────────────────────────────────────────────────────────   │
│                                                                         │
│ Package Status:  ACTIVE ✓                                               │
│ Credits:         10 credits activated ✓                                 │
│ Expires:         Apr 20, 2025                                           │
│                                                                         │
│ Recorded by:     Sarah Thompson                                         │
│ Time:            Jan 20, 2025 10:30 AM                                  │
│                                                                         │
│                    [Print Receipt]   [View Customer]   [Done]           │
└─────────────────────────────────────────────────────────────────────────┘

Key UI Elements:

  • Package Summary: Show context before processing
  • Method Radio: Clear description of each payment type
  • Amount Auto-fill: Button to fill package price
  • Validation Feedback: Inline error if amount mismatch
  • Post-Payment Info: Explain what happens after recording

Staff interface for generating and sending payment links.

Wireframe:

┌─────────────────────────────────────────────────────────────────────────┐
│ Create Payment Link - Spa Relaxation Bundle                             │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│ ┌─ Package Details ─────────────────────────────────────────────────┐   │
│ │                                                                   │   │
│ │ Customer:        Jane Doe                                         │   │
│ │ Package:         Spa Relaxation Bundle                            │   │
│ │ Package Price:   Rp 450,000                                       │   │
│ │ Platform Fee:    Rp 0 (Fee-exempt)                                │   │
│ │ Total Amount:    Rp 450,000                                       │   │
│ │                                                                   │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│ ┌─ Customer Contact ────────────────────────────────────────────────┐   │
│ │                                                                   │   │
│ │ ✉️  Email: jane.doe@example.com                      ✓ Available   │   │
│ │ 📱 Phone: +628123456789                              ✓ Available   │   │
│ │                                                                   │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│ Send Payment Link Via *                                                 │
│ ┌───────────────────────────────────────────────────────────────────┐   │
│ │ ☑ Email                                                           │   │
│ │   Send invoice to jane.doe@example.com                            │   │
│ │                                                                   │   │
│ │ ☑ WhatsApp                                                        │   │
│ │   Send via WhatsApp to +628123456789                              │   │
│ │                                                                   │   │
│ │ ☐ SMS                                                             │   │
│ │   Send via SMS (additional charges may apply)                     │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│ Payment Due Date                                                        │
│ ┌───────────────────────────────────────────────────────────────────┐   │
│ │ Jan 25, 2025                                           [📅]       │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│ Default: 3 days from today                                              │
│                                                                         │
│ Invoice Notes (optional)                                                │
│ ┌───────────────────────────────────────────────────────────────────┐   │
│ │ Thank you for your purchase! Credits will be activated upon       │   │
│ │ payment completion.                                               │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│                                   [Cancel]   [Create & Send Link]       │
└─────────────────────────────────────────────────────────────────────────┘

Success Confirmation:

┌─────────────────────────────────────────────────────────────────────────┐
│ ✅ Payment Link Created & Sent                                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│ Invoice Number:  INV-PKG-20250120-103000                                │
│ Amount:          Rp 450,000                                             │
│ Due Date:        Jan 25, 2025                                           │
│                                                                         │
│ ─────────────────────────────────────────────────────────────────────   │
│                                                                         │
│ Sent To:                                                                │
│ ✓ Email: jane.doe@example.com                                           │
│ ✓ WhatsApp: +628123456789                                               │
│                                                                         │
│ ─────────────────────────────────────────────────────────────────────   │
│                                                                         │
│ Payment Link:                                                           │
│ ┌───────────────────────────────────────────────────────────────────┐   │
│ │ https://pay.paper.id/invoice/abc123                    [Copy]     │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│ ℹ️ Credits will be activated automatically when payment is completed    │
│                                                                         │
│                          [Resend Link]   [View Package]   [Done]        │
└─────────────────────────────────────────────────────────────────────────┘

Key UI Elements:

  • Fee Transparency: Show platform fee is 0
  • Contact Availability: Indicate which channels are available
  • Multi-Channel Selection: Allow multiple delivery methods
  • Copyable Link: Easy link sharing
  • Auto-Activation Note: Reassure about credit activation

Use Case 4: Pending Payments Dashboard

Staff view for managing all pending package payments.

Wireframe:

┌─────────────────────────────────────────────────────────────────────────┐
│ Pending Package Payments                                  Staff Portal  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│ Filter: [All Methods ▼] [Last 7 Days ▼] [All Outlets ▼]  Search: [___] │
│                                                                         │
│ ┌─ Summary ─────────────────────────────────────────────────────────┐   │
│ │                                                                   │   │
│ │  ┌───────────────┐  ┌───────────────┐  ┌───────────────┐          │   │
│ │  │      5        │  │  Rp 2.5M      │  │      2        │          │   │
│ │  │   Pending     │  │   Total       │  │   Due Today   │          │   │
│ │  │   Payments    │  │   Amount      │  │               │          │   │
│ │  └───────────────┘  └───────────────┘  └───────────────┘          │   │
│ │                                                                   │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│ ┌───────────────────────────────────────────────────────────────────┐   │
│ │ ⚠️ DUE TODAY                                                      │   │
│ │                                                                   │   │
│ │ Jane Doe                           Spa Relaxation Bundle          │   │
│ │ Rp 450,000                         Paper.id Payment Link          │   │
│ │ Due: Jan 20, 2025 (Today)                                         │   │
│ │                                                                   │   │
│ │ 📧 Email sent ✓    📱 WhatsApp sent ✓                             │   │
│ │                                                                   │   │
│ │ [Copy Link]  [Resend]  [Record Payment]  [View Details]           │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│ ┌───────────────────────────────────────────────────────────────────┐   │
│ │ ⏳ PENDING                                                        │   │
│ │                                                                   │   │
│ │ John Smith                         Premium Hair Package           │   │
│ │ Rp 500,000                         Bank Transfer                  │   │
│ │ Created: Jan 18, 2025              Due: Jan 21, 2025              │   │
│ │                                                                   │   │
│ │ Notes: Waiting for customer to complete bank transfer             │   │
│ │                                                                   │   │
│ │ [Record Payment]  [Create Payment Link]  [View Details]           │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│ ┌───────────────────────────────────────────────────────────────────┐   │
│ │ 🔴 OVERDUE                                                        │   │
│ │                                                                   │   │
│ │ Mike Johnson                       Wellness Package               │   │
│ │ Rp 800,000                         Paper.id Payment Link          │   │
│ │ Due: Jan 15, 2025 (5 days overdue)                                │   │
│ │                                                                   │   │
│ │ [Resend Link]  [Call Customer]  [Cancel Package]                  │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│ Showing 3 of 5 pending payments                    [<] [1] [2] [>]      │
└─────────────────────────────────────────────────────────────────────────┘

Key UI Elements:

  • Summary Cards: Quick overview of pending amounts
  • Priority Indicators: Due today and overdue badges
  • Delivery Status: Show which channels were used
  • Quick Actions: Context-appropriate buttons
  • Filters: Filter by method, date, outlet

Use Case 5: Customer Payment Flow

Customer-facing payment completion interface.

Wireframe - Payment Link Landing:

┌─────────────────────────────────────────────────────────────────────────┐
│ Complete Your Payment                                     [Brand Logo]  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │                                                                     │ │
│ │                    Spa Relaxation Bundle                            │ │
│ │                                                                     │ │
│ │  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━    │ │
│ │                                                                     │ │
│ │  Package Includes:                                                  │ │
│ │  • Full Body Massage × 3                                            │ │
│ │  • Facial Treatment × 3                                             │ │
│ │                                                                     │ │
│ │  Valid for: 90 days after purchase                                  │ │
│ │                                                                     │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│                                                                         │
│ ┌─ Payment Summary ─────────────────────────────────────────────────┐   │
│ │                                                                   │   │
│ │ Package Price                                     Rp 450,000      │   │
│ │ Platform Fee                                      Rp 0            │   │
│ │ ───────────────────────────────────────────────────────────────   │   │
│ │ Total                                             Rp 450,000      │   │
│ │                                                                   │   │
│ └───────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│ Invoice: INV-PKG-20250120-103000                                        │
│ Due: Jan 25, 2025                                                       │
│                                                                         │
│                    ┌─────────────────────────────────┐                  │
│                    │      Complete Payment →         │                  │
│                    └─────────────────────────────────┘                  │
│                                                                         │
│ ℹ️ You will be redirected to Paper.id secure payment page               │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Post-Payment Success:

┌─────────────────────────────────────────────────────────────────────────┐
│ Payment Successful!                                       [Brand Logo]  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│                              ✅                                         │
│                                                                         │
│                   Thank you for your purchase!                          │
│                                                                         │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │                                                                     │ │
│ │ Your credits have been activated!                                   │ │
│ │                                                                     │ │
│ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━     │ │
│ │                                                                     │ │
│ │ Package:          Spa Relaxation Bundle                             │ │
│ │ Credits:          6 credits now available                           │ │
│ │ Valid Until:      Apr 20, 2025                                      │ │
│ │                                                                     │ │
│ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━     │ │
│ │                                                                     │ │
│ │ Amount Paid:      Rp 450,000                                        │ │
│ │ Reference:        PAY-PKG-20250120-ABC123                           │ │
│ │                                                                     │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│                                                                         │
│          [View My Credits]   [Book Appointment]   [Download Receipt]    │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Key UI Elements:

  • Package Preview: Show what customer is paying for
  • Fee Transparency: Clear breakdown showing no hidden fees
  • Secure Payment Indicator: Trust signals
  • Post-Payment CTA: Guide to next steps

UI Component Library Suggestions

React/JSX Component Examples:

// PaymentStatusBadge.jsx - Payment status indicator
const PaymentStatusBadge = ({ status, dueDate }) => {
  const isOverdue = dueDate && new Date(dueDate) < new Date() && status === 'pending';
  const isDueToday = dueDate && isToday(new Date(dueDate)) && status === 'pending';

  const variants = {
    paid: { color: 'green', icon: CheckIcon, label: 'Paid' },
    pending: {
      color: isOverdue ? 'red' : isDueToday ? 'orange' : 'yellow',
      icon: isOverdue ? AlertIcon : ClockIcon,
      label: isOverdue ? 'Overdue' : isDueToday ? 'Due Today' : 'Pending'
    },
    failed: { color: 'red', icon: XIcon, label: 'Failed' },
    refunded: { color: 'gray', icon: RefundIcon, label: 'Refunded' }
  };

  const variant = variants[status] || variants.pending;

  return (
    <div className={`status-badge status-${variant.color}`}>
      <variant.icon />
      <span>{variant.label}</span>
    </div>
  );
};

// RecordPaymentForm.jsx - Staff manual payment form
const RecordPaymentForm = ({ customerPackage, onSuccess }) => {
  const [method, setMethod] = useState('cash');
  const [amount, setAmount] = useState(customerPackage.package_price);
  const [receiptNumber, setReceiptNumber] = useState('');
  const [notes, setNotes] = useState('');

  const { mutate: recordPayment, isLoading } = useRecordPayment();

  const handleSubmit = () => {
    recordPayment({
      customer_package_id: customerPackage.id,
      amount: parseFloat(amount),
      payment_method: method,
      receipt_number: receiptNumber,
      notes: notes
    }, {
      onSuccess: (data) => {
        toast.success('Payment recorded - credits activated!');
        onSuccess?.(data);
      }
    });
  };

  const isAmountValid = parseFloat(amount) === customerPackage.package_price;

  return (
    <form onSubmit={handleSubmit}>
      <PackageSummary package={customerPackage} />

      <RadioGroup
        label="Payment Method"
        value={method}
        onChange={setMethod}
        options={[
          { value: 'cash', label: 'Cash', description: 'Customer paid cash' },
          { value: 'pos_terminal', label: 'POS Terminal', description: 'Card via POS' },
          { value: 'bank_transfer', label: 'Bank Transfer', description: 'Transfer confirmed' }
        ]}
      />

      <CurrencyInput
        label="Payment Amount"
        value={amount}
        onChange={setAmount}
        error={!isAmountValid && 'Amount must match package price exactly'}
        helperText={`Package price: ${formatCurrency(customerPackage.package_price)}`}
        endAdornment={
          <Button
            variant="text"
            size="small"
            onClick={() => setAmount(customerPackage.package_price)}
          >
            = Price
          </Button>
        }
      />

      <TextField
        label="Receipt/Reference Number"
        value={receiptNumber}
        onChange={(e) => setReceiptNumber(e.target.value)}
        placeholder="PKG-RCPT-2025-001"
      />

      <TextArea
        label="Notes"
        value={notes}
        onChange={(e) => setNotes(e.target.value)}
        placeholder="Payment notes for audit trail"
      />

      <InfoBox variant="success">
        <strong>After recording:</strong>
        <ul>
          <li>Package status will change to ACTIVE</li>
          <li>{customerPackage.total_credits} credits will be activated</li>
          <li>Customer can start using credits immediately</li>
        </ul>
      </InfoBox>

      <Button
        type="submit"
        loading={isLoading}
        disabled={!isAmountValid}
      >
        Record Payment
      </Button>
    </form>
  );
};

// CreatePaymentLinkForm.jsx - Generate payment link
const CreatePaymentLinkForm = ({ customerPackage, customer, onSuccess }) => {
  const [sendEmail, setSendEmail] = useState(!!customer.email);
  const [sendWhatsApp, setSendWhatsApp] = useState(!!customer.phone);
  const [sendSms, setSendSms] = useState(false);
  const [dueDate, setDueDate] = useState(addDays(new Date(), 3));
  const [notes, setNotes] = useState('');

  const { mutate: createPaymentLink, isLoading } = useCreatePaymentLink();

  const hasDeliveryMethod = sendEmail || sendWhatsApp || sendSms;

  const handleSubmit = () => {
    createPaymentLink({
      customer_package_id: customerPackage.id,
      send_email: sendEmail,
      send_whatsapp: sendWhatsApp,
      send_sms: sendSms,
      due_date: format(dueDate, 'yyyy-MM-dd'),
      notes: notes
    }, {
      onSuccess: (data) => {
        toast.success('Payment link sent successfully!');
        onSuccess?.(data);
      }
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <PackageSummary package={customerPackage} showFees />

      <CustomerContactCard customer={customer} />

      <CheckboxGroup label="Send Payment Link Via">
        <Checkbox
          checked={sendEmail}
          onChange={(e) => setSendEmail(e.target.checked)}
          disabled={!customer.email}
          label={`Email${customer.email ? `: ${customer.email}` : ' (not available)'}`}
        />
        <Checkbox
          checked={sendWhatsApp}
          onChange={(e) => setSendWhatsApp(e.target.checked)}
          disabled={!customer.phone}
          label={`WhatsApp${customer.phone ? `: ${customer.phone}` : ' (not available)'}`}
        />
        <Checkbox
          checked={sendSms}
          onChange={(e) => setSendSms(e.target.checked)}
          disabled={!customer.phone}
          label="SMS (additional charges may apply)"
        />
      </CheckboxGroup>

      {!hasDeliveryMethod && (
        <Alert variant="error">
          At least one delivery method must be selected
        </Alert>
      )}

      <DatePicker
        label="Payment Due Date"
        value={dueDate}
        onChange={setDueDate}
        minDate={new Date()}
        helperText="Default: 3 days from today"
      />

      <TextArea
        label="Invoice Notes (optional)"
        value={notes}
        onChange={(e) => setNotes(e.target.value)}
        placeholder="Notes to include on the invoice"
      />

      <Button
        type="submit"
        loading={isLoading}
        disabled={!hasDeliveryMethod}
      >
        Create & Send Link
      </Button>
    </form>
  );
};

// PaymentHistoryList.jsx - Payment records display
const PaymentHistoryList = ({ payments }) => (
  <div className="payment-history">
    {payments.length === 0 ? (
      <EmptyState message="No payment records yet" />
    ) : (
      payments.map(payment => (
        <div key={payment.id} className="payment-record">
          <div className="payment-date">
            {formatDate(payment.paid_at || payment.created_at)}
          </div>
          <div className="payment-method">
            <PaymentMethodIcon method={payment.method} />
            {PAYMENT_METHOD_LABELS[payment.method]}
          </div>
          <div className="payment-amount">
            {formatCurrency(payment.amount)}
          </div>
          <PaymentStatusBadge status={payment.status} />
          {payment.receipt_number && (
            <div className="payment-reference">
              Ref: {payment.receipt_number}
            </div>
          )}
        </div>
      ))
    )}
  </div>
);

State Management Recommendations

TypeScript Interface for Payment State:

// types/packagePayment.ts
interface PackagePaymentState {
  // Current package
  currentPackage: CustomerPackage | null;
  paymentStatus: PaymentStatusResponse | null;

  // Record payment form
  recordPaymentForm: {
    method: PaymentMethod;
    amount: number;
    receiptNumber: string;
    notes: string;
    isSubmitting: boolean;
  };

  // Create payment link form
  paymentLinkForm: {
    sendEmail: boolean;
    sendWhatsApp: boolean;
    sendSms: boolean;
    dueDate: Date;
    notes: string;
    isSubmitting: boolean;
  };

  // Pending payments dashboard
  pendingPayments: PendingPayment[];
  pendingFilters: {
    method: PaymentMethod | 'all';
    dateRange: DateRange;
    outletId: string | null;
  };

  // UI state
  isLoading: boolean;
  error: string | null;
}

interface PaymentStatusResponse {
  customer_package_id: string;
  package_name: string;
  package_price: number;
  payment_status: 'pending' | 'paid' | 'failed' | 'refunded';
  package_status: string;
  total_paid: number;
  remaining_balance: number;
  is_paid: boolean;
  credits_activated: boolean;
  payments: PaymentRecord[];
}

interface PaymentRecord {
  id: string;
  amount: number;
  method: PaymentMethod;
  provider: 'manual' | 'paper_id';
  status: 'pending' | 'completed' | 'failed';
  reference_id: string;
  created_at: string;
  paid_at: string | null;
}

type PaymentMethod = 'cash' | 'pos_terminal' | 'bank_transfer' | 'paper_digital';

// Payment method labels
const PAYMENT_METHOD_LABELS: Record<PaymentMethod, string> = {
  cash: 'Cash',
  pos_terminal: 'POS Terminal',
  bank_transfer: 'Bank Transfer',
  paper_digital: 'Digital Payment'
};

API Integration Patterns

React Query Hooks:

// hooks/usePackagePayments.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// Get payment status
export const usePaymentStatus = (customerPackageId: string) => {
  return useQuery({
    queryKey: ['package-payment', 'status', customerPackageId],
    queryFn: () => api.get(
      `/api/v1/customer-packages/${customerPackageId}/payment-status`
    ),
    enabled: !!customerPackageId,
    refetchInterval: (data) => {
      // Poll more frequently for pending payments
      return data?.payment_status === 'pending' ? 30000 : false;
    }
  });
};

// Record manual payment
export const useRecordPayment = () => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: {
      customer_package_id: string;
      amount: number;
      payment_method: string;
      notes?: string;
      receipt_number?: string;
    }) => api.post(
      `/api/v1/customer-packages/${data.customer_package_id}/record-payment`,
      data
    ),
    onSuccess: (_, variables) => {
      // Invalidate payment status
      queryClient.invalidateQueries({
        queryKey: ['package-payment', 'status', variables.customer_package_id]
      });
      // Invalidate customer credits (they're now activated)
      queryClient.invalidateQueries({
        queryKey: ['customer-credits']
      });
      // Invalidate pending payments list
      queryClient.invalidateQueries({
        queryKey: ['pending-payments']
      });
    },
    onError: (error) => {
      if (error.response?.status === 409) {
        toast.error('Package is already paid');
      } else if (error.response?.status === 400) {
        toast.error(error.response.data.detail);
      }
    }
  });
};

// Create payment link
export const useCreatePaymentLink = () => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: {
      customer_package_id: string;
      send_email?: boolean;
      send_whatsapp?: boolean;
      send_sms?: boolean;
      due_date?: string;
      notes?: string;
    }) => api.post(
      `/api/v1/customer-packages/${data.customer_package_id}/create-payment-link`,
      data
    ),
    onSuccess: (_, variables) => {
      queryClient.invalidateQueries({
        queryKey: ['package-payment', 'status', variables.customer_package_id]
      });
    }
  });
};

// Get pending payments (staff dashboard)
export const usePendingPayments = (filters: PaymentFilters) => {
  return useQuery({
    queryKey: ['pending-payments', filters],
    queryFn: () => api.get('/api/v1/staff/pending-payments', { params: filters }),
    refetchInterval: 60000 // Refresh every minute
  });
};

Error Handling UI Patterns

Common Error Scenarios:

// Error handling for payment operations
const handlePaymentError = (error) => {
  const status = error.response?.status;
  const detail = error.response?.data?.detail || '';

  switch (status) {
    case 400:
      if (detail.includes('amount')) {
        showModal({
          type: 'error',
          title: 'Amount Mismatch',
          message: detail,
          actions: [{ label: 'Fix Amount', onClick: () => focusAmountInput() }]
        });
      } else if (detail.includes('payment method')) {
        toast.error('Invalid payment method. Use payment link for online payments.');
      } else if (detail.includes('contact')) {
        showModal({
          type: 'error',
          title: 'Missing Contact Information',
          message: 'Customer must have email or phone to receive payment link.',
          actions: [
            { label: 'Update Customer', onClick: () => editCustomer(), primary: true }
          ]
        });
      }
      break;

    case 409:
      showModal({
        type: 'info',
        title: 'Already Paid',
        message: 'This package has already been paid.',
        actions: [
          { label: 'View Credits', onClick: () => viewCredits(), primary: true }
        ]
      });
      break;

    case 500:
      if (detail.includes('Paper.id')) {
        toast.error('Payment gateway error. Please try again or use manual payment.');
      } else {
        toast.error('Server error. Please try again.');
      }
      break;

    default:
      toast.error('An error occurred. Please try again.');
  }
};

// Amount validation component
const PaymentAmountInput = ({ value, onChange, packagePrice }) => {
  const isValid = parseFloat(value) === packagePrice;

  return (
    <div className="amount-input-wrapper">
      <CurrencyInput
        value={value}
        onChange={onChange}
        error={value && !isValid}
      />

      {value && !isValid && (
        <div className="error-message">
          Payment amount ({formatCurrency(value)}) must match package price
          ({formatCurrency(packagePrice)})
        </div>
      )}

      <Button
        variant="text"
        size="small"
        onClick={() => onChange(packagePrice.toString())}
      >
        Set to package price
      </Button>
    </div>
  );
};

Accessibility Considerations

  1. Form Validation: Announce errors with aria-live regions
  2. Status Changes: Use aria-live="polite" for payment status updates
  3. Radio Groups: Proper role="radiogroup" with descriptions
  4. Loading States: Announce "Processing payment..." during submission
  5. Currency Values: Use aria-label with full amount spoken
  6. Due Date Indicators: Color should not be sole indicator (use icons/text)

Mobile Responsive Guidelines

/* Payment form responsive */
.payment-form {
  @media (max-width: 768px) {
    .payment-summary {
      padding: 1rem;
    }

    .payment-methods {
      .method-option {
        padding: 1rem;
        margin-bottom: 0.5rem;
      }
    }

    .amount-input-wrapper {
      flex-direction: column;

      .set-price-button {
        width: 100%;
        margin-top: 0.5rem;
      }
    }
  }
}

/* Pending payments dashboard responsive */
.pending-payments {
  @media (max-width: 768px) {
    .summary-cards {
      grid-template-columns: 1fr;
    }

    .payment-card {
      .payment-actions {
        flex-direction: column;
        gap: 0.5rem;

        button {
          width: 100%;
        }
      }
    }

    .filters {
      flex-direction: column;

      select {
        width: 100%;
      }
    }
  }
}

/* Customer payment page responsive */
.customer-payment-page {
  @media (max-width: 768px) {
    .package-preview {
      padding: 1rem;
    }

    .payment-summary {
      .summary-row {
        flex-direction: column;
        text-align: center;
      }
    }

    .complete-payment-button {
      width: 100%;
      padding: 1rem;
      font-size: 1.1rem;
    }
  }
}