SUCCEEDED/FAILED (not "APPROVED"/"REJECTED"); live Paynamics flows are untestable now (no prod credentials, events currently free).
URLs in this section were NOT migrated to the staging-subdomain form (
stg-rcme.membervu.com) in the 2026-06-10 env pass — they will be corrected during the rewrite.
Reference:
_bmad-output/test-artifacts/test-reviews/test-guide-html-staleness-audit-2026-06-10.md
Test Cases: Credits System
Module: Payments - Credits Management
Priority: P2
Phase: 5
Backend Endpoints:
GET /api/billing/admin/creditsPOST /api/billing/admin/creditsGET /api/billing/credits/balancePOST /api/billing/credits/:creditId/applyDELETE /api/billing/admin/credits/:creditId
Frontend Page: mmp/frontend/pwa-app/src/pages/admin/AdminCreditsPage.tsx
Member Page: mmp/frontend/pwa-app/src/pages/member/MemberCreditsPage.tsx
Prerequisites
- User logged in with ADMIN or FINANCE_MANAGER role for admin tests.
- User logged in as MEMBER for member-facing tests.
- Test data from
00_MASTER_TEST_DATA.mdis seeded. - At least one invoice exists for credit application testing.
Test Data
| Credit ID | Member | Amount | Reason | Source Payment | Status | Expires |
|---|---|---|---|---|---|---|
| CRD-2024-0001 | Antonio Dela Cruz | PHP 500 | Overpayment on INV-2024-0001 | PAY-2024-0001 | UNUSED | 2025-12-31 |
| CRD-2024-0002 | Maria Cruz | PHP 1,000 | Promotional credit | - | UNUSED | 2024-06-30 |
| CRD-2024-0003 | Antonio Dela Cruz | PHP 250 | Partial refund | PAY-2024-0002 | USED | - |
Data Model Reference
Based on BILLING.md (lines 287-301):
model Credit {
id String @id @default(cuid())
tenantId String
memberId String
amountCents Int
reason String?
sourcePaymentId String?
usedAt DateTime?
usedOnInvoiceId String?
expiresAt DateTime?
}
Test Cases
TC-PAY-070: Credit Created from Overpayment
| Step | Action | Expected Result |
|---|---|---|
| 1 | Login as Ana Reyes (FINANCE_MANAGER). | Dashboard displayed. |
| 2 | Navigate to Admin > Payments. | Payment list displayed. |
| 3 | Approve payment PAY-2024-XXXX for PHP 15,500 on invoice of PHP 15,000. | Payment approved. |
| 4 | Verify overpayment handling. | System creates credit of PHP 500 for excess amount. |
| 5 | Navigate to Admin > Credits. | Credit list displayed. |
| 6 | Verify new credit exists. | Credit CRD-XXXX with amount PHP 500, reason "Overpayment on INV-XXXX". |
| 7 | Verify credit linked to source payment. | sourcePaymentId references the approved payment. |
| 8 | Verify member notification. | Member receives email about credit added to account. |
TC-PAY-071: Manually Create Credit (Admin)
| Step | Action | Expected Result |
|---|---|---|
| 1 | Login as ADMIN. | Dashboard displayed. |
| 2 | Navigate to Admin > Credits. | Credit list displayed. |
| 3 | Click "Create Credit" button. | Credit creation form opens. |
| 4 | Select member: Antonio Dela Cruz. | Member selected. |
| 5 | Enter amount: PHP 1,000. | Amount entered. |
| 6 | Enter reason: "Loyalty reward". | Reason entered. |
| 7 | Set expiration date: 2025-12-31. | Expiration set. |
| 8 | Submit form. | API POST /api/billing/admin/credits called. |
| 9 | Verify success message. | "Credit created successfully" displayed. |
| 10 | Verify credit in list. | New credit appears in credit list. |
TC-PAY-072: List All Credits (Admin)
| Step | Action | Expected Result |
|---|---|---|
| 1 | Login as Ana Reyes (FINANCE_MANAGER). | Dashboard displayed. |
| 2 | Navigate to Admin > Credits. | Credit list displayed. |
| 3 | Verify list columns. | Credit ID, Member, Amount, Reason, Source, Status, Expiry Date, Actions. |
| 4 | Verify CRD-2024-0001 visible. | Antonio Dela Cruz's credit of PHP 500 listed. |
| 5 | Verify CRD-2024-0002 visible. | Maria Cruz's promotional credit listed. |
| 6 | Verify pagination works. | Can navigate between pages if more than page size. |
TC-PAY-073: Filter Credits by Status (Admin)
| Step | Action | Expected Result |
|---|---|---|
| 1 | Navigate to Admin > Credits. | Full list displayed. |
| 2 | Filter by status: UNUSED. | Only unused credits shown. |
| 3 | Filter by status: USED. | Only used credits shown (CRD-2024-0003). |
| 4 | Filter by status: EXPIRED. | Only expired credits shown. |
| 5 | Filter by status: All. | All credits displayed. |
TC-PAY-074: Filter Credits by Member (Admin)
| Step | Action | Expected Result |
|---|---|---|
| 1 | Navigate to Admin > Credits. | Credit list displayed. |
| 2 | Search/filter by member: Antonio Dela Cruz. | Only Roberto's credits shown. |
| 3 | Verify results. | CRD-2024-0001 and CRD-2024-0003 visible. |
| 4 | Clear filter. | All credits displayed again. |
TC-PAY-075: Apply Credit to Invoice
| Step | Action | Expected Result |
|---|---|---|
| 1 | Login as FINANCE_MANAGER. | Dashboard displayed. |
| 2 | Navigate to Admin > Invoices. | Invoice list displayed. |
| 3 | Open pending invoice INV-2024-XXXX for Antonio Dela Cruz (PHP 15,000). | Invoice details displayed. |
| 4 | Click "Apply Credit" button. | Available credits modal opens. |
| 5 | Verify Roberto's available credits listed. | CRD-2024-0001 (PHP 500) shown. |
| 6 | Select CRD-2024-0001 to apply. | Credit selected. |
| 7 | Confirm application. | API POST /api/billing/credits/:id/apply called. |
| 8 | Verify success message. | "Credit applied successfully" displayed. |
| 9 | Verify invoice balance reduced. | Invoice balance now PHP 14,500. |
| 10 | Verify credit status. | CRD-2024-0001 now marked as USED. |
| 11 | Verify credit usedAt timestamp. | Current timestamp recorded. |
| 12 | Verify credit usedOnInvoiceId. | Links to INV-2024-XXXX. |
TC-PAY-076: Apply Credit During Payment Submission
| Step | Action | Expected Result |
|---|---|---|
| 1 | Login as Antonio Dela Cruz (MEMBER). | Member dashboard displayed. |
| 2 | Navigate to My Invoices. | Invoice list displayed. |
| 3 | Select pending invoice of PHP 15,000. | Invoice details displayed. |
| 4 | Click "Pay Now". | Payment form opens. |
| 5 | Verify credit option displayed. | "Available Credit: PHP 500" shown. |
| 6 | Check "Apply available credit". | Credit checkbox selected. |
| 7 | Verify amount adjustment. | Amount to pay shows PHP 14,500. |
| 8 | Complete payment for PHP 14,500. | Payment submitted. |
| 9 | Verify credit applied. | Credit marked as used. |
| 10 | Verify invoice marked paid. | Invoice status changes to PAID. |
TC-PAY-077: Partial Credit Usage
| Step | Action | Expected Result |
|---|---|---|
| 1 | Login as FINANCE_MANAGER. | Dashboard displayed. |
| 2 | Member has credit of PHP 1,000. | Credit CRD-2024-0002 for Maria Cruz. |
| 3 | Apply credit to invoice of PHP 600. | Credit application form opens. |
| 4 | Enter amount to apply: PHP 600. | Partial amount entered. |
| 5 | Confirm application. | Credit partially applied. |
| 6 | Verify original credit updated. | CRD-2024-0002 amount reduced to PHP 400 OR new credit created for remainder. |
| 7 | Verify invoice fully paid. | Invoice status PAID. |
| 8 | Verify remaining credit available. | PHP 400 credit still available for Maria Cruz. |
TC-PAY-078: Credit Balance Display for Member
| Step | Action | Expected Result |
|---|---|---|
| 1 | Login as Antonio Dela Cruz (MEMBER). | Member dashboard displayed. |
| 2 | Check dashboard widgets. | "Available Credit: PHP 500" displayed. |
| 3 | Navigate to My Credits page. | Credit history displayed. |
| 4 | Verify credit list columns. | Amount, Reason, Date Created, Expiry Date, Status. |
| 5 | Verify CRD-2024-0001 visible. | PHP 500 credit with expiry date shown. |
| 6 | Verify CRD-2024-0003 visible. | PHP 250 shown as USED with usage date. |
| 7 | Verify total available credit. | Summary shows PHP 500 available. |
TC-PAY-079: Credit Expiration Warning
| Step | Action | Expected Result |
|---|---|---|
| 1 | Login as Maria Cruz (MEMBER) with credit expiring soon. | Dashboard displayed. |
| 2 | Check dashboard notifications. | "Credit expiring soon" warning displayed. |
| 3 | Navigate to My Credits. | Credit list displayed. |
| 4 | Verify expiring credit highlighted. | CRD-2024-0002 shows warning indicator. |
| 5 | Verify days until expiry shown. | "Expires in X days" displayed. |
TC-PAY-080: Credit Expiration Handling
| Step | Action | Expected Result |
|---|---|---|
| 1 | Scheduled job runs for credit expiration. | System checks credit expiry dates. |
| 2 | Credit CRD-2024-0002 past expiry date. | Credit identified as expired. |
| 3 | Verify credit status changed. | Status set to EXPIRED. |
| 4 | Verify credit no longer usable. | Cannot apply expired credit to invoices. |
| 5 | Verify member notification sent. | Member receives "Credit expired" notification. |
TC-PAY-081: Attempt to Apply Expired Credit
| Step | Action | Expected Result |
|---|---|---|
| 1 | Login as FINANCE_MANAGER. | Dashboard displayed. |
| 2 | Navigate to member's invoice. | Invoice displayed. |
| 3 | Attempt to apply expired credit. | Credit selection shown. |
| 4 | Select expired credit CRD-2024-0002. | - |
| 5 | Attempt to apply. | Error: "Credit has expired and cannot be applied". |
| 6 | Verify invoice unchanged. | Invoice balance unchanged. |
TC-PAY-082: Attempt to Apply Already Used Credit
| Step | Action | Expected Result |
|---|---|---|
| 1 | Login as FINANCE_MANAGER. | Dashboard displayed. |
| 2 | Attempt to apply CRD-2024-0003 (already used). | - |
| 3 | Verify credit not in available list. | Used credits not shown in selection. |
| 4 | Attempt via direct API call. | Status 400 Bad Request - "Credit already used". |
TC-PAY-083: Credit Cannot Exceed Invoice Balance
| Step | Action | Expected Result |
|---|---|---|
| 1 | Member has credit of PHP 1,000. | Credit available. |
| 2 | Apply credit to invoice of PHP 500. | Credit application form opens. |
| 3 | Verify maximum applicable amount. | System limits to PHP 500 (invoice balance). |
| 4 | Apply PHP 500 from credit. | Credit partially used. |
| 5 | Verify invoice fully paid. | Invoice status PAID. |
| 6 | Verify remaining credit. | PHP 500 credit remains available. |
TC-PAY-084: Delete/Void Credit (Admin)
| Step | Action | Expected Result |
|---|---|---|
| 1 | Login as ADMIN. | Dashboard displayed. |
| 2 | Navigate to Admin > Credits. | Credit list displayed. |
| 3 | Find unused credit CRD-2024-0001. | Credit displayed. |
| 4 | Click "Delete" or "Void" action. | Confirmation dialog appears. |
| 5 | Enter reason: "Issued in error". | Reason entered (required). |
| 6 | Confirm deletion. | API DELETE /api/billing/admin/credits/:id called. |
| 7 | Verify success message. | "Credit voided successfully" displayed. |
| 8 | Verify credit removed from available list. | Credit no longer available for use. |
| 9 | Verify audit trail. | Deletion recorded with reason and admin who voided. |
TC-PAY-085: Cannot Delete Used Credit
| Step | Action | Expected Result |
|---|---|---|
| 1 | Login as ADMIN. | Dashboard displayed. |
| 2 | Find used credit CRD-2024-0003. | Credit displayed with USED status. |
| 3 | Look for delete action. | Delete button disabled or hidden. |
| 4 | Attempt via direct API call. | Status 400 Bad Request - "Cannot delete used credit". |
TC-PAY-086: Member Cannot Create Credits
| Step | Action | Expected Result |
|---|---|---|
| 1 | Login as Maria Cruz (MEMBER). | Member dashboard displayed. |
| 2 | Attempt to access Admin > Credits. | Access denied - no menu option visible. |
| 3 | Attempt direct navigation to admin credits URL. | Redirect to member dashboard or 403 error. |
| 4 | Attempt API POST /api/billing/admin/credits. | Status 403 Forbidden. |
TC-PAY-087: Credit with No Expiration
| Step | Action | Expected Result |
|---|---|---|
| 1 | Login as ADMIN. | Dashboard displayed. |
| 2 | Create credit without expiration date. | Leave expiration field empty. |
| 3 | Submit form. | Credit created successfully. |
| 4 | Verify credit in list. | Expiry shows "Never" or "-". |
| 5 | Verify credit remains valid indefinitely. | No expiration processing applies. |
TC-PAY-088: Multiple Credits Applied to Single Invoice
| Step | Action | Expected Result |
|---|---|---|
| 1 | Member has two credits: PHP 300 and PHP 200. | Both credits available. |
| 2 | Open invoice of PHP 1,000. | Invoice displayed. |
| 3 | Apply first credit (PHP 300). | Balance reduced to PHP 700. |
| 4 | Apply second credit (PHP 200). | Balance reduced to PHP 500. |
| 5 | Both credits marked as used. | Both credits show USED status. |
| 6 | Invoice balance now PHP 500. | Remaining balance displayed. |
| 7 | Member pays remaining PHP 500. | Invoice marked as PAID. |
API Test Cases
TC-PAY-API-020: GET /api/billing/admin/credits
Request: GET /api/billing/admin/credits?status=UNUSED
Expected Response: 200 OK
{
"credits": [
{
"id": "CRD-2024-0001",
"memberId": "member-roberto",
"member": { "id": "member-roberto", "name": "Antonio Dela Cruz" },
"amountCents": 50000,
"amount": 500,
"reason": "Overpayment on INV-2024-0001",
"sourcePaymentId": "PAY-2024-0001",
"status": "UNUSED",
"usedAt": null,
"usedOnInvoiceId": null,
"expiresAt": "2025-12-31T23:59:59Z",
"createdAt": "2024-01-15T10:30:00Z"
}
],
"pagination": { "page": 1, "total": 5 }
}
TC-PAY-API-021: POST /api/billing/admin/credits
Request:
{
"memberId": "member-roberto",
"amountCents": 100000,
"reason": "Loyalty reward",
"expiresAt": "2025-12-31T23:59:59Z"
}
Expected Response: 201 Created
{
"credit": {
"id": "CRD-2024-XXXX",
"memberId": "member-roberto",
"amountCents": 100000,
"reason": "Loyalty reward",
"sourcePaymentId": null,
"status": "UNUSED",
"usedAt": null,
"usedOnInvoiceId": null,
"expiresAt": "2025-12-31T23:59:59Z",
"createdAt": "timestamp"
}
}
TC-PAY-API-022: GET /api/billing/credits/balance
Request: GET /api/billing/credits/balance (authenticated as member)
Expected Response: 200 OK
{
"totalAvailableCents": 50000,
"totalAvailable": 500,
"credits": [
{
"id": "CRD-2024-0001",
"amountCents": 50000,
"amount": 500,
"reason": "Overpayment on INV-2024-0001",
"expiresAt": "2025-12-31T23:59:59Z",
"expiresInDays": 340
}
]
}
TC-PAY-API-023: POST /api/billing/credits/:creditId/apply
Request: POST /api/billing/credits/CRD-2024-0001/apply
{
"invoiceId": "INV-2024-0005",
"amountCents": 50000
}
Expected Response: 200 OK
{
"credit": {
"id": "CRD-2024-0001",
"status": "USED",
"usedAt": "timestamp",
"usedOnInvoiceId": "INV-2024-0005"
},
"invoice": {
"id": "INV-2024-0005",
"previousBalanceCents": 1500000,
"newBalanceCents": 1450000,
"creditAppliedCents": 50000
}
}
TC-PAY-API-024: POST /api/billing/credits/:creditId/apply - Expired Credit
Request: POST /api/billing/credits/CRD-2024-0002/apply (expired credit)
{
"invoiceId": "INV-2024-0005",
"amountCents": 100000
}
Expected Response: 400 Bad Request
{
"error": "CREDIT_EXPIRED",
"message": "Credit has expired and cannot be applied"
}
TC-PAY-API-025: POST /api/billing/credits/:creditId/apply - Already Used
Request: POST /api/billing/credits/CRD-2024-0003/apply (used credit)
{
"invoiceId": "INV-2024-0005",
"amountCents": 25000
}
Expected Response: 400 Bad Request
{
"error": "CREDIT_ALREADY_USED",
"message": "Credit has already been applied to another invoice"
}
TC-PAY-API-026: DELETE /api/billing/admin/credits/:creditId
Request: DELETE /api/billing/admin/credits/CRD-2024-0001
{
"reason": "Issued in error"
}
Expected Response: 200 OK
{
"success": true,
"message": "Credit voided successfully",
"credit": {
"id": "CRD-2024-0001",
"status": "VOIDED",
"voidedAt": "timestamp",
"voidedBy": "admin@rcme.membervu.com",
"voidReason": "Issued in error"
}
}
TC-PAY-API-027: DELETE /api/billing/admin/credits/:creditId - Used Credit
Request: DELETE /api/billing/admin/credits/CRD-2024-0003 (used credit)
Expected Response: 400 Bad Request
{
"error": "CANNOT_DELETE_USED_CREDIT",
"message": "Cannot delete credit that has already been applied to an invoice"
}
TC-PAY-API-028: POST /api/billing/admin/credits - Unauthorized
Request: POST /api/billing/admin/credits (as MEMBER role)
{
"memberId": "member-self",
"amountCents": 100000,
"reason": "Self-issued credit"
}
Expected Response: 403 Forbidden
{
"error": "FORBIDDEN",
"message": "Insufficient permissions to create credits"
}
Edge Cases
TC-PAY-089: Credit Amount Validation
| Step | Action | Expected Result |
|---|---|---|
| 1 | Attempt to create credit with amount 0. | Validation error: "Amount must be greater than 0". |
| 2 | Attempt to create credit with negative amount. | Validation error: "Amount must be greater than 0". |
| 3 | Attempt to create credit with very large amount. | System handles or shows max limit warning. |
TC-PAY-090: Credit for Inactive Member
| Step | Action | Expected Result |
|---|---|---|
| 1 | Attempt to create credit for inactive/suspended member. | Warning displayed: "Member is inactive". |
| 2 | Confirm creation. | Credit created (or blocked per business rules). |
| 3 | Verify credit usability. | Define expected behavior for inactive member credits. |
TC-PAY-091: Concurrent Credit Application
| Step | Action | Expected Result |
|---|---|---|
| 1 | Open two browser tabs with same invoice. | Same invoice displayed in both. |
| 2 | Apply same credit from both tabs simultaneously. | One succeeds, one fails with "Credit already used". |
| 3 | Verify credit applied only once. | Invoice balance reduced once, credit marked used once. |
Notes
- Credits stored in cents (
amountCents) to avoid floating-point issues. - Currency is assumed PHP based on existing test data.
- Credit expiration should be checked by a scheduled job running daily.
- Notification emails should be sent for: credit creation, credit applied, credit expiring soon, credit expired.
- Audit trail should record all credit operations with timestamps and user who performed the action.