{ "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 ' 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." } } ] } ] }