177 lines
10 KiB
JSON
177 lines
10 KiB
JSON
{
|
|
"info": {
|
|
"name": "Hexa Fitness - Subscription Renewal Job (Manual Walkthrough)",
|
|
"description": "Walks through the daily SubscriptionRenewalJob approval-gated renewal flow end-to-end against your LOCAL database (hexa_tenant_test1 / Test Gym Alpha). Run folders top to bottom: 0 -> 1 -> 2 -> 3 -> 4. Run '0. Auth > Login' first -- it auto-saves the JWT into {{mobileAuthToken}}, which every other request sends as the 'MobileAuthToken: Bearer <token>' header (same auth JwtMiddleware accepts as a fallback for the httponly cookie).\n\nSeeded test data (already set up via real API calls, not raw SQL -- only RenewalDate was backdated directly in the DB since no endpoint exposes it):\n- Renewal Test Customer (sub 7d7351ed...) -> current invoice PENDING, 3000, RenewalDate backdated to 2026-09-13\n- Renewal Test Customer 2 (sub edbcf90e...) -> current invoice PAID, 6000, RenewalDate backdated to 2026-09-13\n- Renewal Test Customer 3 (sub 39feeb0b...) -> current invoice PENDING, 7500, RenewalDate backdated to 2026-09-13\n- Renewal Test Customer 4 (sub 995a4915...) -> current invoice PARTIAL,4000 (2000 paid / 2000 balance), RenewalDate backdated to 2026-09-13\n\nWhat to expect when you POST '2. Background Job > Trigger Renewal Job Now':\n- Customer/Customer 3 (Pending): old invoice flips to status RolledOver, a NEW invoice is created with IsActive=false, amount = new round + carried-forward unpaid balance (itemized as a separate 'Previous Balance' line).\n- Customer 2 (Paid): a NEW invoice is created with IsActive=false, amount = new round only (no carry-forward), old Paid invoice untouched.\n- Customer 4 (Partial): old invoice flips to RolledOver, NEW invoice carries the 2000 unpaid balance forward alongside the new round.\n- The response also iterates your OTHER local tenant schemas (hexa_tenant_ironcore and any unmigrated ones) -- those are expected to show a Failed/error entry since they don't have this migration applied. That is correct isolation behavior, not a bug.\n\nAfter the trigger, '3. Approvals' will list exactly these 4 newly-created unapproved invoices. Copy an invoiceId from that response into the {{invoiceId}} variable to try Approve / Change Plan / Reject on it. Approve and Change-Plan will make that invoice reappear (IsActive=true) in '4. Verify After > Get Member Invoices (scope=latest)'; Reject will deactivate the subscription instead.",
|
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
|
},
|
|
"variable": [
|
|
{ "key": "baseUrl", "value": "http://localhost:5601" },
|
|
{ "key": "identifier", "value": "testgym1@local.test" },
|
|
{ "key": "password", "value": "TestPass@123" },
|
|
{ "key": "mobileAuthToken", "value": "", "description": "Auto-filled by 0. Auth > Login's test script. Sent as the MobileAuthToken header." },
|
|
{ "key": "invoiceId", "value": "", "description": "Paste an unapproved invoice id here after running the trigger + 'List Unapproved Invoices'." },
|
|
{ "key": "customerId", "value": "08df125d-311f-41eb-8bea-aeddd5df1fc0", "description": "Renewal Test Customer (Pending scenario). Others: cust2=08df125d-7cab-494f-84fc-2bd895f37cb5 (Paid), cust3=08df125d-7ef7-4967-8d98-cfeef523dbef (Pending), cust4=08df125d-811d-4047-8542-263194ab0745 (Partial)." },
|
|
{ "key": "newCustomerPlanId", "value": "72aaff5a-7f2b-4de3-a2f7-996095596a38", "description": "'Renewal Test Plan B' (4500/mo) -- example target plan for the Change-Plan action." }
|
|
],
|
|
"auth": {
|
|
"type": "apikey",
|
|
"apikey": [
|
|
{ "key": "key", "value": "MobileAuthToken", "type": "string" },
|
|
{ "key": "value", "value": "Bearer {{mobileAuthToken}}", "type": "string" },
|
|
{ "key": "in", "value": "header", "type": "string" }
|
|
]
|
|
},
|
|
"item": [
|
|
{
|
|
"name": "0. Auth",
|
|
"item": [
|
|
{
|
|
"name": "Login",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
|
"url": "{{baseUrl}}/api/auth",
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"functionName\": \"LoginUser\",\n \"payload\": {\n \"identifier\": \"{{identifier}}\",\n \"password\": \"{{password}}\"\n },\n \"reference\": \"postman-login\"\n}"
|
|
},
|
|
"auth": { "type": "noauth" },
|
|
"description": "Function-dispatch login (not a REST route). Response.data.accessToken is auto-saved into {{mobileAuthToken}} by the test script below."
|
|
},
|
|
"response": [],
|
|
"event": [
|
|
{
|
|
"listen": "test",
|
|
"script": {
|
|
"exec": [
|
|
"var jsonData = pm.response.json();",
|
|
"if (jsonData && jsonData.data && jsonData.data.accessToken) {",
|
|
" pm.collectionVariables.set('mobileAuthToken', jsonData.data.accessToken);",
|
|
" console.log('Saved accessToken to {{mobileAuthToken}}');",
|
|
"} else {",
|
|
" console.warn('Login response did not contain data.accessToken', jsonData);",
|
|
"}"
|
|
],
|
|
"type": "text/javascript"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "1. Setup Check (before trigger)",
|
|
"item": [
|
|
{
|
|
"name": "List Renewal Approvals (expect empty)",
|
|
"request": {
|
|
"method": "GET",
|
|
"url": "{{baseUrl}}/api/client/renewal-approvals",
|
|
"description": "Should return an empty array before the job has run -- nothing is pending approval yet."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Member Invoices (scope=latest)",
|
|
"request": {
|
|
"method": "GET",
|
|
"url": "{{baseUrl}}/api/client/member-invoices?scope=latest",
|
|
"description": "One row per customer, approved-only. Look for the 4 'Renewal Test Customer*' rows: Pending 3000, Paid 6000, Pending 7500, Partial 4000 (paid 2000 / balance 2000)."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Invoice History For Customer",
|
|
"request": {
|
|
"method": "GET",
|
|
"url": "{{baseUrl}}/api/client/member-invoices/customer/{{customerId}}",
|
|
"description": "Full invoice history for one customer (the 'view previous invoices' action). Useful after the trigger to see the old invoice's status flip to RolledOver alongside the new unapproved one."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "2. Background Job",
|
|
"item": [
|
|
{
|
|
"name": "Trigger Renewal Job Now",
|
|
"request": {
|
|
"method": "POST",
|
|
"url": "{{baseUrl}}/api/admin/background-jobs/renewal/trigger",
|
|
"description": "Runs SubscriptionRenewalRunner immediately across every provisioned tenant schema (same code the 01:00 Sri Lanka time daily job calls) instead of waiting for the real 1am tick. Expect one result entry per tenant: hexa_tenant_test1 (Test Gym Alpha) should show renewed=4, failed=0; the other local tenant(s) are expected to show a Failed entry since they don't have the RenewalDate/IsActive migration applied yet -- that's correct per-tenant isolation, not a bug."
|
|
}
|
|
},
|
|
{
|
|
"name": "Get Renewal Job Run History",
|
|
"request": {
|
|
"method": "GET",
|
|
"url": "{{baseUrl}}/api/admin/background-jobs/renewal/runs?take=20",
|
|
"description": "JobRunHistory entries for 'SubscriptionRenewalJob', most recent first -- confirms the manual trigger (and the real nightly run, once it fires) got recorded."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "3. Approvals (after trigger)",
|
|
"item": [
|
|
{
|
|
"name": "List Unapproved Invoices",
|
|
"request": {
|
|
"method": "GET",
|
|
"url": "{{baseUrl}}/api/client/renewal-approvals",
|
|
"description": "Should now list the 4 new IsActive=false invoices the trigger just created. Copy one customerInvoiceId into the {{invoiceId}} collection variable, then use Approve / Change Plan / Reject below."
|
|
}
|
|
},
|
|
{
|
|
"name": "Approve",
|
|
"request": {
|
|
"method": "POST",
|
|
"url": "{{baseUrl}}/api/client/renewal-approvals/{{invoiceId}}/approve",
|
|
"description": "Sets invoice IsActive=true, builds a fresh installment schedule anchored to now, advances the subscription's RenewalDate by the plan's duration from its OLD RenewalDate. Re-run 'Get Member Invoices (scope=latest)' afterwards to see it reappear as the current invoice."
|
|
}
|
|
},
|
|
{
|
|
"name": "Change Plan",
|
|
"request": {
|
|
"method": "POST",
|
|
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
|
"url": "{{baseUrl}}/api/client/renewal-approvals/{{invoiceId}}/change-plan",
|
|
"body": {
|
|
"mode": "raw",
|
|
"raw": "{\n \"newCustomerPlanId\": \"{{newCustomerPlanId}}\"\n}"
|
|
},
|
|
"description": "Deactivates the old subscription, creates a new one on newCustomerPlanId, repoints the invoice to it and recalculates the new-round amount (any carried-forward 'Previous Balance' line is left untouched), sets IsActive=true."
|
|
}
|
|
},
|
|
{
|
|
"name": "Reject",
|
|
"request": {
|
|
"method": "POST",
|
|
"url": "{{baseUrl}}/api/client/renewal-approvals/{{invoiceId}}/reject",
|
|
"description": "Sets invoice Status=Cancelled and deactivates the subscription entirely. Use a different {{invoiceId}} than the one you Approved/Change-Planned above, since each invoice can only be actioned once."
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "4. Verify After",
|
|
"item": [
|
|
{
|
|
"name": "Get Member Invoices (scope=latest)",
|
|
"request": {
|
|
"method": "GET",
|
|
"url": "{{baseUrl}}/api/client/member-invoices?scope=latest",
|
|
"description": "Approved+Rejected customers show their new state; a still-pending-approval customer is temporarily absent from this list until actioned (its invoice is IsActive=false)."
|
|
}
|
|
},
|
|
{
|
|
"name": "List Renewal Approvals (remaining)",
|
|
"request": {
|
|
"method": "GET",
|
|
"url": "{{baseUrl}}/api/client/renewal-approvals",
|
|
"description": "Shows whatever you haven't actioned yet out of the 4 seeded invoices."
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|