{ "info": { "name": "CBSnexora — Login OTP (Mobile + Email)", "description": "Send and verify login OTPs via SMS (LincPay) and email. The `Send OTP` requests automatically capture the returned `otpId` into a collection variable so the matching `Verify OTP` request works in a single click.\n\n## Setup\n\n1. Open the collection's **Variables** tab and set:\n - `baseUrl` — e.g. `https://localhost:8443/fineract-provider/api/v1`\n - `tenantId` — e.g. `default`\n - `username` — the user whose registered mobile/email should receive the OTP\n2. Run **Send Mobile OTP** (or **Send Email OTP**), check the SMS/email for the 6-digit code, paste it into the matching **Verify** request's body, and run.\n\n## Endpoints\n\n- `POST /v1/login/otp/generate` — sends OTP via LincPay SMS\n- `POST /v1/login/otp/verify` — verifies the SMS OTP\n- `POST /v1/login/otp/email/generate` — sends OTP via SMTP email (CBSnexora-branded)\n- `POST /v1/login/otp/email/verify` — verifies the email OTP\n\nAll four endpoints are public (no auth header needed). Tenant header is still required.", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ { "name": "Mobile OTP (LincPay SMS)", "item": [ { "name": "Send Mobile OTP", "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "// Capture otpId so the matching Verify request works without copy/paste", "pm.test('Status 200', function () { pm.response.to.have.status(200); });", "var json = pm.response.json();", "if (json && json.otpId) {", " pm.collectionVariables.set('mobileOtpId', json.otpId);", " console.log('Saved mobileOtpId =', json.otpId);", "}", "pm.test('success flag is true', function () {", " pm.expect(json.success).to.eql(true, json.message || 'OTP send failed');", "});" ] } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "Fineract-Platform-TenantId", "value": "{{tenantId}}" } ], "body": { "mode": "raw", "raw": "{\n \"username\": \"{{username}}\"\n}" }, "url": { "raw": "{{baseUrl}}/login/otp/generate", "host": ["{{baseUrl}}"], "path": ["login", "otp", "generate"] }, "description": "Sends a 6-digit OTP via LincPay SMS to the user's registered mobile.\n\nMobile is fetched server-side from `m_appuser.mobile_no` based on the username — the UI does not need to pass it.\n\nResponse stores `otpId` and `otpReference` in `m_otp_log`. The `otpId` is auto-saved to the `mobileOtpId` collection variable for the verify step." } }, { "name": "Verify Mobile OTP", "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "pm.test('Status 200', function () { pm.response.to.have.status(200); });", "var json = pm.response.json();", "pm.test('verified flag is true', function () {", " pm.expect(json.verified).to.eql(true, json.message || 'verification failed');", "});" ] } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "Fineract-Platform-TenantId", "value": "{{tenantId}}" } ], "body": { "mode": "raw", "raw": "{\n \"otpId\": {{mobileOtpId}},\n \"otp\": \"123456\"\n}" }, "url": { "raw": "{{baseUrl}}/login/otp/verify", "host": ["{{baseUrl}}"], "path": ["login", "otp", "verify"] }, "description": "Verifies the SMS OTP delivered by LincPay. Replace `123456` with the OTP you received on your phone before sending.\n\nThe `otpId` is auto-populated from the previous `Send Mobile OTP` request via the `mobileOtpId` collection variable.\n\nVerification is delegated to LincPay's `/verify-mobile-otp` endpoint; the audit row in `m_otp_log` is updated with the result." } } ] }, { "name": "Email OTP (SMTP)", "item": [ { "name": "Send Email OTP", "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "pm.test('Status 200', function () { pm.response.to.have.status(200); });", "var json = pm.response.json();", "if (json && json.otpId) {", " pm.collectionVariables.set('emailOtpId', json.otpId);", " console.log('Saved emailOtpId =', json.otpId);", "}", "pm.test('success flag is true', function () {", " pm.expect(json.success).to.eql(true, json.message || 'OTP send failed');", "});" ] } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "Fineract-Platform-TenantId", "value": "{{tenantId}}" } ], "body": { "mode": "raw", "raw": "{\n \"username\": \"{{username}}\"\n}" }, "url": { "raw": "{{baseUrl}}/login/otp/email/generate", "host": ["{{baseUrl}}"], "path": ["login", "otp", "email", "generate"] }, "description": "Generates a 6-digit OTP, stores its SHA-256 hash in `m_otp_log`, and emails the plaintext OTP to the user's registered email (`m_appuser.email`) using the CBSnexora-branded HTML template.\n\nThe `otpId` is auto-saved to the `emailOtpId` collection variable for the verify step.\n\n**Pre-flight**: ensure `SPRING_MAIL_HOST`, `SPRING_MAIL_USERNAME`, `SPRING_MAIL_PASSWORD` env vars are populated. If SMTP is not configured, the response will be `{\"success\": false, \"message\": \"OTP send failed\"}` and the audit row marked `FAILED`." } }, { "name": "Verify Email OTP", "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "pm.test('Status 200', function () { pm.response.to.have.status(200); });", "var json = pm.response.json();", "pm.test('verified flag is true', function () {", " pm.expect(json.verified).to.eql(true, json.message || 'verification failed');", "});" ] } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "Fineract-Platform-TenantId", "value": "{{tenantId}}" } ], "body": { "mode": "raw", "raw": "{\n \"otpId\": {{emailOtpId}},\n \"otp\": \"123456\"\n}" }, "url": { "raw": "{{baseUrl}}/login/otp/email/verify", "host": ["{{baseUrl}}"], "path": ["login", "otp", "email", "verify"] }, "description": "Verifies the email OTP. Replace `123456` with the code from the email before sending.\n\nVerification is local: the submitted OTP is SHA-256 hashed and compared against the stored hash. On success the audit row is marked `VERIFIED`; failures increment `attempts` and lock at `max-attempts` (default 5)." } } ] } ], "variable": [ { "key": "baseUrl", "value": "https://localhost:8443/fineract-provider/api/v1", "type": "string", "description": "Fineract API base URL including the version segment (e.g. /api/v1)." }, { "key": "tenantId", "value": "default", "type": "string", "description": "Tenant identifier sent in the `Fineract-Platform-TenantId` header." }, { "key": "username", "value": "alice", "type": "string", "description": "Username of the AppUser whose registered mobile / email will receive the OTP." }, { "key": "mobileOtpId", "value": "", "type": "string", "description": "Auto-populated from `Send Mobile OTP` response — used by the verify request." }, { "key": "emailOtpId", "value": "", "type": "string", "description": "Auto-populated from `Send Email OTP` response — used by the verify request." } ] }