Payment History¶
Complete guide to accessing payment history with dual authentication support for both staff and customers in the Reserva platform.
Overview¶
The payment history system provides comprehensive transaction tracking with support for:
- Dual Authentication - Access for both staff/admin and customers with RBAC
- Platform Fee Breakdown - Display base amount + platform fee for transparency
- Flexible Filtering - Filter by payment type, status, date range, and customer
- Pagination - Efficient data retrieval with customizable page size
- Tenant Isolation - Automatic data isolation per tenant
- Transaction Details - Complete payment information with reference IDs
Key Features:
- Staff Access: View all payments within their tenant with optional customer filtering
- Customer Access: View only their own payment history automatically
- Fee Transparency: Shows base_amount and platform_fee when available
- RBAC Enforcement: Role-based access control with SUPER_ADMIN privileges
- Comprehensive Filtering: Multiple filter options for precise data retrieval
Get Payment History¶
Retrieve payment history with comprehensive filtering options and dual authentication support.
Endpoint¶
Authentication: Required (JWT token - Staff or Customer)
Access Control¶
| User Type | Access Level | Filtering |
|---|---|---|
| Customer | Own payments only | Automatic customer_id filter applied |
| Staff | All tenant payments | Can filter by specific customer_id |
| SUPER_ADMIN | All payments across tenants | Full access to all data |
Query Parameters¶
payment_type(optional) - Filter by payment type:APPOINTMENT,WALLET_TOPUPstatus(optional) - Filter by payment status:COMPLETED,PENDING,FAILED,CANCELLEDdate_from(optional) - Filter payments from this date (ISO 8601 format)date_to(optional) - Filter payments until this date (ISO 8601 format)customer_id(optional) - Filter by customer ID (staff only, ignored for customers)skip(optional) - Number of records to skip (default: 0, min: 0)limit(optional) - Maximum records to return (default: 20, min: 1, max: 100)
Example Requests¶
Customer Request (View Own Payments)¶
curl -X GET "https://api.myreserva.id/api/v1/customer-payments/history?limit=20&skip=0" \
-H "Authorization: Bearer CUSTOMER_JWT_TOKEN"
Staff Request (View All Tenant Payments)¶
curl -X GET "https://api.myreserva.id/api/v1/customer-payments/history?limit=20&skip=0" \
-H "Authorization: Bearer STAFF_JWT_TOKEN"
Staff Request (Filter by Specific Customer)¶
curl -X GET "https://api.myreserva.id/api/v1/customer-payments/history?customer_id=507f1f77bcf86cd799439011&limit=20" \
-H "Authorization: Bearer STAFF_JWT_TOKEN"
Filter by Payment Type and Status¶
curl -X GET "https://api.myreserva.id/api/v1/customer-payments/history?payment_type=APPOINTMENT&status=COMPLETED&limit=50" \
-H "Authorization: Bearer JWT_TOKEN"
Filter by Date Range¶
curl -X GET "https://api.myreserva.id/api/v1/customer-payments/history?date_from=2025-01-01T00:00:00Z&date_to=2025-01-31T23:59:59Z" \
-H "Authorization: Bearer JWT_TOKEN"
Response¶
Success Response (200 OK)¶
{
"data": [
{
"_id": "507f1f77bcf86cd799439020",
"appointment_id": "507f1f77bcf86cd799439015",
"payment_type": "APPOINTMENT",
"amount": 108000.00,
"base_amount": 100000.00,
"platform_fee": 8000.00,
"payment_method": "QRIS",
"status": "COMPLETED",
"created_at": "2025-01-15T10:30:00Z",
"completed_at": "2025-01-15T10:35:00Z",
"description": "Appointment payment for John Doe",
"reference_id": "APT-507f1f77bcf86cd799439015-20250115103000"
},
{
"_id": "507f1f77bcf86cd799439021",
"appointment_id": null,
"payment_type": "WALLET_TOPUP",
"amount": 54000.00,
"base_amount": 50000.00,
"platform_fee": 4000.00,
"payment_method": "VIRTUAL_ACCOUNT",
"status": "COMPLETED",
"created_at": "2025-01-14T14:20:00Z",
"completed_at": "2025-01-14T14:25:00Z",
"description": "Wallet top-up for customer@example.com",
"reference_id": "WALLET-507f1f77bcf86cd799439012-20250114142000"
},
{
"_id": "507f1f77bcf86cd799439022",
"appointment_id": "507f1f77bcf86cd799439016",
"payment_type": "APPOINTMENT",
"amount": 216000.00,
"base_amount": 200000.00,
"platform_fee": 16000.00,
"payment_method": "CREDIT_CARD",
"status": "PENDING",
"created_at": "2025-01-13T09:15:00Z",
"completed_at": null,
"description": "Appointment payment for Jane Smith",
"reference_id": "APT-507f1f77bcf86cd799439016-20250113091500"
}
],
"total": 45,
"skip": 0,
"limit": 20,
"has_more": true
}
Response Fields¶
| Field | Type | Description |
|---|---|---|
_id |
string | Unique payment record ID |
appointment_id |
string|null | Associated appointment ID (null for wallet top-ups) |
payment_type |
enum | Payment type: APPOINTMENT, WALLET_TOPUP |
amount |
decimal | Total amount paid (base + platform fee) |
base_amount |
decimal|null | Base amount before platform fee |
platform_fee |
decimal|null | Platform fee charged |
payment_method |
enum|null | Payment method used (see Payment Methods) |
status |
enum | Payment status (see Payment Statuses) |
created_at |
datetime | Payment creation timestamp |
completed_at |
datetime|null | Payment completion timestamp |
description |
string|null | Payment description |
reference_id |
string|null | Unique reference ID for tracking |
Payment Types¶
APPOINTMENT- Payment for booking appointment servicesWALLET_TOPUP- Customer wallet balance top-up
Payment Statuses¶
COMPLETED- Payment successfully processed and confirmedPENDING- Payment initiated, awaiting completionPROCESSING- Payment being processed by gatewayFAILED- Payment attempt failedCANCELLED- Payment cancelled by user or systemREFUNDED- Payment refunded to customer
Payment Methods¶
BANK_TRANSFER- Bank transfer via Indonesian banksVIRTUAL_ACCOUNT- Virtual account paymentE_WALLET- E-wallet (OVO, GoPay, Dana, LinkAja)QRIS- QR code paymentCREDIT_CARD- Credit/debit card payment
Platform Fee Calculation¶
The platform fee is automatically calculated based on the tenant's subscription plan:
| Subscription Plan | Platform Fee Rate | Example (Base: IDR 100,000) |
|---|---|---|
| FREE | 8% | Fee: IDR 8,000, Total: IDR 108,000 |
| PRO | 5% | Fee: IDR 5,000, Total: IDR 105,000 |
| ENTERPRISE | 3% | Fee: IDR 3,000, Total: IDR 103,000 |
Note: The fee is displayed when the payment was created using the PaymentProcessor service, which automatically includes fee calculation.
Business Rules¶
Access Control Rules¶
-
Customer Authentication:
-
Can only view their own payment history
- Automatic
customer_idfilter applied customer_idquery parameter ignored-
Restricted to their tenant's data only
-
Staff Authentication:
-
Can view all payments within their tenant
- Can filter by specific
customer_id - Cannot access other tenants' data (unless SUPER_ADMIN)
-
Must belong to the tenant being queried
-
SUPER_ADMIN Access:
-
Can view payments across all tenants
- No tenant restriction applied
- Full access to all historical data
Data Isolation¶
-
Tenant Isolation:
-
Payments automatically filtered by tenant_id
- Staff cannot access other tenants' payments
-
Customers cannot access other tenants' payments
-
Soft Delete Exclusion:
-
Soft-deleted payments (
is_deleted: true) excluded from results -
Only active payment records returned
-
Pagination Limits:
-
Maximum 100 records per page
- Default page size: 20 records
- Efficient data retrieval for large datasets
Filtering Rules¶
-
Date Range:
-
date_fromanddate_tosupport timezone-aware timestamps - Inclusive filtering (includes boundary dates)
-
Based on payment
created_attimestamp -
Status Filtering:
-
Only valid PaymentStatus enum values accepted
-
Multiple statuses require separate API calls
-
Customer Filtering (Staff Only):
-
Valid ObjectId format required
- Customer must belong to the same tenant
- Invalid customer_id returns 400 Bad Request
Error Responses¶
401 Unauthorized¶
Scenario: Invalid or expired JWT token
403 Forbidden¶
Scenario: Staff attempting to access another tenant's payments
400 Bad Request¶
Scenario: Invalid customer_id format
422 Unprocessable Entity¶
Scenario: Invalid query parameters
{
"detail": [
{
"loc": ["query", "limit"],
"msg": "ensure this value is less than or equal to 100",
"type": "value_error.number.not_le"
}
]
}
Use Cases¶
Use Case 1: Customer Views Their Payment History¶
Scenario: Customer wants to see all their past payments
Request:
curl -X GET "https://api.myreserva.id/api/v1/customer-payments/history" \
-H "Authorization: Bearer CUSTOMER_JWT_TOKEN"
Result:
- Returns only payments belonging to the authenticated customer
- Automatically filtered by customer_id
- Includes all payment types (appointments, wallet top-ups)
Use Case 2: Staff Views All Tenant Payments¶
Scenario: Staff member wants to see all payments for their salon
Request:
curl -X GET "https://api.myreserva.id/api/v1/customer-payments/history?limit=50" \
-H "Authorization: Bearer STAFF_JWT_TOKEN"
Result:
- Returns all payments within the staff's tenant
- Sorted by most recent first
- Includes fee breakdown for transparency
Use Case 3: Staff Views Specific Customer's Payments¶
Scenario: Staff member wants to review a specific customer's payment history
Request:
curl -X GET "https://api.myreserva.id/api/v1/customer-payments/history?customer_id=507f1f77bcf86cd799439011" \
-H "Authorization: Bearer STAFF_JWT_TOKEN"
Result:
- Returns only payments for the specified customer
- Must be within the same tenant
- Useful for customer support and reconciliation
Use Case 4: Filter Completed Appointment Payments¶
Scenario: View only successful appointment payments
Request:
curl -X GET "https://api.myreserva.id/api/v1/customer-payments/history?payment_type=APPOINTMENT&status=COMPLETED" \
-H "Authorization: Bearer JWT_TOKEN"
Result:
- Returns only completed appointment payments
- Excludes pending, failed, or cancelled payments
- Useful for revenue reporting
Use Case 5: Monthly Payment Report¶
Scenario: Generate payment report for January 2025
Request:
curl -X GET "https://api.myreserva.id/api/v1/customer-payments/history?date_from=2025-01-01T00:00:00Z&date_to=2025-01-31T23:59:59Z&limit=100" \
-H "Authorization: Bearer STAFF_JWT_TOKEN"
Result:
- Returns all payments within the specified date range
- Includes fee breakdown for accounting
- Useful for monthly reconciliation and reporting
Integration Examples¶
Frontend Integration (React)¶
import axios from 'axios';
interface PaymentHistoryItem {
_id: string;
appointment_id?: string;
payment_type: 'APPOINTMENT' | 'WALLET_TOPUP';
amount: number;
base_amount?: number;
platform_fee?: number;
payment_method?: string;
status: string;
created_at: string;
completed_at?: string;
description?: string;
reference_id?: string;
}
interface PaymentHistoryResponse {
data: PaymentHistoryItem[];
total: number;
skip: number;
limit: number;
has_more: boolean;
}
// Customer viewing their own payments
async function getMyPaymentHistory(
page: number = 0,
pageSize: number = 20
): Promise<PaymentHistoryResponse> {
const response = await axios.get(
'https://api.myreserva.id/api/v1/customer-payments/history',
{
params: {
skip: page * pageSize,
limit: pageSize
},
headers: {
Authorization: `Bearer ${customerToken}`
}
}
);
return response.data;
}
// Staff viewing tenant payments with filters
async function getTenantPaymentHistory(
filters: {
customerId?: string;
paymentType?: string;
status?: string;
dateFrom?: string;
dateTo?: string;
page?: number;
pageSize?: number;
}
): Promise<PaymentHistoryResponse> {
const response = await axios.get(
'https://api.myreserva.id/api/v1/customer-payments/history',
{
params: {
customer_id: filters.customerId,
payment_type: filters.paymentType,
status: filters.status,
date_from: filters.dateFrom,
date_to: filters.dateTo,
skip: (filters.page || 0) * (filters.pageSize || 20),
limit: filters.pageSize || 20
},
headers: {
Authorization: `Bearer ${staffToken}`
}
}
);
return response.data;
}
// Display payment with fee breakdown
function PaymentItem({ payment }: { payment: PaymentHistoryItem }) {
return (
<div className="payment-item">
<div className="payment-header">
<span className="payment-type">{payment.payment_type}</span>
<span className={`status ${payment.status.toLowerCase()}`}>
{payment.status}
</span>
</div>
<div className="payment-details">
<p>Reference: {payment.reference_id}</p>
<p>Date: {new Date(payment.created_at).toLocaleDateString()}</p>
{payment.appointment_id && (
<p>Appointment: {payment.appointment_id}</p>
)}
</div>
<div className="payment-amount">
{payment.base_amount && payment.platform_fee ? (
<>
<div>Base Amount: IDR {payment.base_amount.toLocaleString()}</div>
<div>Platform Fee: IDR {payment.platform_fee.toLocaleString()}</div>
<div className="total">
Total: IDR {payment.amount.toLocaleString()}
</div>
</>
) : (
<div className="total">
Amount: IDR {payment.amount.toLocaleString()}
</div>
)}
</div>
</div>
);
}
Best Practices¶
For Customers¶
✅ DO:
- Check payment history regularly for completed bookings
- Verify payment amounts match appointment totals
- Keep reference IDs for customer support
- Review platform fees for cost transparency
❌ DON'T:
- Share payment history with unauthorized parties
- Assume pending payments are completed
- Ignore failed payment notifications
For Staff¶
✅ DO:
- Use customer_id filter for customer support inquiries
- Apply date range filters for monthly reconciliation
- Export payment data regularly for accounting
- Monitor pending payments for follow-up
- Review platform fee breakdown for revenue calculation
❌ DON'T:
- Access payments from other tenants (unless SUPER_ADMIN)
- Share customer payment details without authorization
- Modify payment records directly (use refund process)
- Ignore failed payment patterns
For Developers¶
✅ DO:
- Implement proper pagination for large datasets
- Cache payment history for better performance
- Handle all error responses gracefully
- Display platform fee breakdown transparently
- Use appropriate authentication type (staff vs customer)
- Implement retry logic for network failures
❌ DON'T:
- Load entire payment history without pagination
- Skip error handling for 403 Forbidden responses
- Hard-code customer_id filters
- Ignore tenant isolation rules
- Mix staff and customer authentication flows
Performance Considerations¶
Pagination¶
- Default Page Size: 20 records
- Maximum Page Size: 100 records per request
- Recommended: Use smaller page sizes (20-50) for better performance
- Large Datasets: Implement infinite scroll or "Load More" pattern
Indexing¶
The following database indexes optimize query performance:
// MongoDB Indexes (for reference)
db.payments.createIndex({ "tenant_id": 1, "customer_id": 1, "created_at": -1 })
db.payments.createIndex({ "tenant_id": 1, "payment_type": 1, "status": 1 })
db.payments.createIndex({ "tenant_id": 1, "created_at": -1 })
db.payments.createIndex({ "is_deleted": 1 })
Caching Strategy¶
// Example: Client-side caching with 5-minute TTL
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
const paymentCache = new Map();
async function getCachedPaymentHistory(
filters: any
): Promise<PaymentHistoryResponse> {
const cacheKey = JSON.stringify(filters);
const cached = paymentCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}
const data = await getTenantPaymentHistory(filters);
paymentCache.set(cacheKey, { data, timestamp: Date.now() });
return data;
}
Troubleshooting¶
Issue: Staff Cannot See Customer Payments¶
Symptoms: Staff receives empty array despite customer having payments
Checks:
- Verify staff belongs to the same tenant as customer
- Check if customer_id is valid ObjectId format
- Confirm staff has appropriate role (not restricted by RBAC)
- Verify payments are not soft-deleted
Solution:
# Verify tenant association
curl -X GET "https://api.myreserva.id/api/v1/customer-payments/history" \
-H "Authorization: Bearer STAFF_JWT_TOKEN"
# If empty, check customer_id filter
curl -X GET "https://api.myreserva.id/api/v1/customer-payments/history?customer_id=VALID_CUSTOMER_ID" \
-H "Authorization: Bearer STAFF_JWT_TOKEN"
Issue: Platform Fee Not Displayed¶
Symptoms: base_amount and platform_fee are null in response
Possible Causes:
- Payment was created before PaymentProcessor integration
- Legacy payment record without fee breakdown
- Manual payment entry without fee calculation
Solution:
- Platform fee breakdown only available for payments created via PaymentProcessor
- Legacy payments show total
amountonly - Contact support for historical fee data if needed
Issue: Date Range Returns No Results¶
Symptoms: Date filter returns empty array despite having payments
Checks:
- Verify date format is ISO 8601 (e.g.,
2025-01-01T00:00:00Z) - Check timezone offset (use UTC for consistency)
- Confirm date range includes payment timestamps
Solution:
# Correct format with UTC timezone
curl -X GET "https://api.myreserva.id/api/v1/customer-payments/history?date_from=2025-01-01T00:00:00Z&date_to=2025-01-31T23:59:59Z" \
-H "Authorization: Bearer JWT_TOKEN"
API Reference Summary¶
| Endpoint | Method | Authentication | Access Level |
|---|---|---|---|
/customer-payments/history |
GET | Required (Staff or Customer) | RBAC-based |
Query Parameters Summary¶
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
payment_type |
enum | No | - | Filter by payment type |
status |
enum | No | - | Filter by payment status |
date_from |
datetime | No | - | Start date filter |
date_to |
datetime | No | - | End date filter |
customer_id |
string | No | - | Customer filter (staff only) |
skip |
integer | No | 0 | Pagination offset |
limit |
integer | No | 20 | Page size (max: 100) |
Related Documentation¶
- Subscription Management - Platform fee calculation based on subscription plans
- Invoice Management - Invoice generation and tracking
- Appointment Management - Appointment booking and payment flow
- Customer Management - Customer profile and authentication
Next Steps:
- Implement payment history display in your application
- Add filters for payment type and status
- Implement pagination for better UX
- Display platform fee breakdown for transparency
- Set up proper error handling for all scenarios
For webhook integration and payment confirmation flow, refer to the Webhook Integration documentation.