{ "info": { "_postman_id": "c4a8f2e1-9b3d-4c7a-a1e6-chapa-payments-01", "name": "Chapa Subscription Payments", "description": "Postman collection for Yimaru LMS Chapa subscription payment flow.\n\n## Setup\n1. Set `base_url` (default `http://localhost:8080`).\n2. Set `learner_email`, `learner_password`, and `learner_phone`.\n3. Set `chapa_webhook_secret` (same as `CHAPA_WEBHOOK_SECRET` in `.env`).\n4. Run **Customer Login** to populate `access_token`.\n5. Run **List Subscription Plans** to populate `plan_id`.\n6. Run **Subscribe with Payment** — open `payment_url` in a browser and complete Chapa test checkout.\n7. Run **Verify Payment** (uses `tx_ref` saved as `session_id`).\n\n## Notes\n- `session_id` in verify/cancel paths is Chapa `tx_ref` (UUID returned at checkout).\n- Webhook request includes a pre-request script that signs the body with HMAC-SHA256.\n- See `docs/CHAPA_INTEGRATION.md` for dashboard webhook URL configuration.", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "auth": { "type": "bearer", "bearer": [ { "key": "token", "value": "{{access_token}}", "type": "string" } ] }, "variable": [ { "key": "base_url", "value": "http://localhost:8080" }, { "key": "access_token", "value": "" }, { "key": "learner_email", "value": "learner@example.com" }, { "key": "learner_password", "value": "your-password" }, { "key": "learner_phone", "value": "0912345678" }, { "key": "plan_id", "value": "1" }, { "key": "payment_id", "value": "" }, { "key": "tx_ref", "value": "" }, { "key": "payment_url", "value": "" }, { "key": "chapa_webhook_secret", "value": "" }, { "key": "chapa_ref_id", "value": "APqDvYw1okk2" } ], "item": [ { "name": "00 - Auth", "item": [ { "name": "Customer Login", "event": [ { "listen": "test", "script": { "exec": [ "pm.test('Status code is 200', function () {", " pm.response.to.have.status(200);", "});", "const body = pm.response.json();", "if (body.data && body.data.access_token) {", " pm.collectionVariables.set('access_token', body.data.access_token);", " pm.test('Access token saved', function () {", " pm.expect(body.data.access_token).to.be.a('string').and.not.empty;", " });", "}" ], "type": "text/javascript" } } ], "request": { "auth": { "type": "noauth" }, "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"email\": \"{{learner_email}}\",\n \"password\": \"{{learner_password}}\"\n}" }, "url": { "raw": "{{base_url}}/api/v1/auth/customer-login", "host": ["{{base_url}}"], "path": ["api", "v1", "auth", "customer-login"] }, "description": "Authenticates a learner and saves `access_token` for subsequent requests." }, "response": [] } ] }, { "name": "01 - Subscription Plans", "item": [ { "name": "List Subscription Plans", "event": [ { "listen": "test", "script": { "exec": [ "pm.test('Status code is 200', function () {", " pm.response.to.have.status(200);", "});", "const body = pm.response.json();", "if (Array.isArray(body.data) && body.data.length > 0) {", " pm.collectionVariables.set('plan_id', String(body.data[0].id));", "}" ], "type": "text/javascript" } } ], "request": { "auth": { "type": "noauth" }, "method": "GET", "header": [], "url": { "raw": "{{base_url}}/api/v1/subscription-plans?active_only=true", "host": ["{{base_url}}"], "path": ["api", "v1", "subscription-plans"], "query": [ { "key": "active_only", "value": "true" } ] }, "description": "Public list of active plans. Saves first plan `id` to `plan_id`." }, "response": [] }, { "name": "Get Subscription Plan by ID", "request": { "auth": { "type": "noauth" }, "method": "GET", "header": [], "url": { "raw": "{{base_url}}/api/v1/subscription-plans/{{plan_id}}", "host": ["{{base_url}}"], "path": ["api", "v1", "subscription-plans", "{{plan_id}}"] } }, "response": [] } ] }, { "name": "02 - Chapa Payment Flow", "item": [ { "name": "Subscribe with Payment (Checkout)", "event": [ { "listen": "test", "script": { "exec": [ "pm.test('Status code is 200', function () {", " pm.response.to.have.status(200);", "});", "const body = pm.response.json();", "if (body.data) {", " if (body.data.payment_id) {", " pm.collectionVariables.set('payment_id', String(body.data.payment_id));", " }", " if (body.data.session_id) {", " pm.collectionVariables.set('tx_ref', body.data.session_id);", " }", " if (body.data.payment_url) {", " pm.collectionVariables.set('payment_url', body.data.payment_url);", " }", " pm.test('Payment URL returned', function () {", " pm.expect(body.data.payment_url).to.be.a('string').and.not.empty;", " });", "}" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"plan_id\": {{plan_id}},\n \"phone\": \"{{learner_phone}}\",\n \"email\": \"{{learner_email}}\"\n}" }, "url": { "raw": "{{base_url}}/api/v1/subscriptions/checkout", "host": ["{{base_url}}"], "path": ["api", "v1", "subscriptions", "checkout"] }, "description": "Primary learner endpoint. Returns Chapa `payment_url`. Open it in a browser to complete payment. `session_id` in the response is the Chapa `tx_ref`." }, "response": [] }, { "name": "Initiate Subscription Payment", "event": [ { "listen": "test", "script": { "exec": [ "const body = pm.response.json();", "if (body.data) {", " if (body.data.payment_id) pm.collectionVariables.set('payment_id', String(body.data.payment_id));", " if (body.data.session_id) pm.collectionVariables.set('tx_ref', body.data.session_id);", " if (body.data.payment_url) pm.collectionVariables.set('payment_url', body.data.payment_url);", "}" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"plan_id\": {{plan_id}},\n \"phone\": \"{{learner_phone}}\",\n \"email\": \"{{learner_email}}\"\n}" }, "url": { "raw": "{{base_url}}/api/v1/payments/subscribe", "host": ["{{base_url}}"], "path": ["api", "v1", "payments", "subscribe"] }, "description": "Alias of checkout — same Chapa initialize flow." }, "response": [] }, { "name": "Verify Payment", "request": { "method": "GET", "header": [], "url": { "raw": "{{base_url}}/api/v1/payments/verify/{{tx_ref}}", "host": ["{{base_url}}"], "path": ["api", "v1", "payments", "verify", "{{tx_ref}}"] }, "description": "Verifies payment with Chapa using `tx_ref` (path param named `session_id` in the API). Run after completing checkout." }, "response": [] }, { "name": "Get My Payments", "request": { "method": "GET", "header": [], "url": { "raw": "{{base_url}}/api/v1/payments?limit=20&offset=0", "host": ["{{base_url}}"], "path": ["api", "v1", "payments"], "query": [ { "key": "limit", "value": "20" }, { "key": "offset", "value": "0" } ] } }, "response": [] }, { "name": "Get Payment by ID", "request": { "method": "GET", "header": [], "url": { "raw": "{{base_url}}/api/v1/payments/{{payment_id}}", "host": ["{{base_url}}"], "path": ["api", "v1", "payments", "{{payment_id}}"] } }, "response": [] }, { "name": "Cancel Pending Payment", "request": { "method": "POST", "header": [], "url": { "raw": "{{base_url}}/api/v1/payments/{{payment_id}}/cancel", "host": ["{{base_url}}"], "path": ["api", "v1", "payments", "{{payment_id}}", "cancel"] }, "description": "Only works while payment status is PENDING." }, "response": [] }, { "name": "Get Chapa Payment Methods", "request": { "auth": { "type": "noauth" }, "method": "GET", "header": [], "url": { "raw": "{{base_url}}/api/v1/payments/methods", "host": ["{{base_url}}"], "path": ["api", "v1", "payments", "methods"] } }, "response": [] } ] }, { "name": "03 - Subscription Status", "item": [ { "name": "Get My Subscription", "request": { "method": "GET", "header": [], "url": { "raw": "{{base_url}}/api/v1/subscriptions/me", "host": ["{{base_url}}"], "path": ["api", "v1", "subscriptions", "me"] }, "description": "Returns active subscription after successful payment." }, "response": [] }, { "name": "Check Subscription Status", "request": { "method": "GET", "header": [], "url": { "raw": "{{base_url}}/api/v1/subscriptions/status", "host": ["{{base_url}}"], "path": ["api", "v1", "subscriptions", "status"] } }, "response": [] }, { "name": "Get Subscription History", "request": { "method": "GET", "header": [], "url": { "raw": "{{base_url}}/api/v1/subscriptions/history", "host": ["{{base_url}}"], "path": ["api", "v1", "subscriptions", "history"] } }, "response": [] } ] }, { "name": "04 - Chapa Webhooks (no auth)", "item": [ { "name": "Chapa Webhook (charge.success)", "event": [ { "listen": "prerequest", "script": { "exec": [ "const secret = pm.collectionVariables.get('chapa_webhook_secret') || '';", "const body = pm.request.body.raw || '';", "if (!secret) {", " console.warn('Set chapa_webhook_secret collection variable to sign the webhook');", "}", "const signature = CryptoJS.HmacSHA256(body, secret).toString(CryptoJS.enc.Hex);", "pm.request.headers.upsert({ key: 'x-chapa-signature', value: signature });" ], "type": "text/javascript" } } ], "request": { "auth": { "type": "noauth" }, "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"event\": \"charge.success\",\n \"type\": \"API\",\n \"tx_ref\": \"{{tx_ref}}\",\n \"reference\": \"{{chapa_ref_id}}\",\n \"status\": \"success\",\n \"amount\": \"500.00\",\n \"currency\": \"ETB\",\n \"payment_method\": \"telebirr\",\n \"mode\": \"test\"\n}" }, "url": { "raw": "{{base_url}}/api/v1/payments/webhook", "host": ["{{base_url}}"], "path": ["api", "v1", "payments", "webhook"] }, "description": "Simulates Chapa webhook. Requires valid `tx_ref` from a real initialize call. Backend re-verifies with Chapa API before activating subscription. Set `chapa_webhook_secret` to match dashboard / `.env`." }, "response": [] }, { "name": "Chapa Callback (GET)", "request": { "auth": { "type": "noauth" }, "method": "GET", "header": [], "url": { "raw": "{{base_url}}/api/v1/payments/chapa/callback?trx_ref={{tx_ref}}&ref_id={{chapa_ref_id}}&status=success", "host": ["{{base_url}}"], "path": ["api", "v1", "payments", "chapa", "callback"], "query": [ { "key": "trx_ref", "value": "{{tx_ref}}" }, { "key": "ref_id", "value": "{{chapa_ref_id}}" }, { "key": "status", "value": "success" } ] }, "description": "Simulates Chapa redirect to `CHAPA_CALLBACK_URL`. Uses same verify flow as webhook." }, "response": [] } ] }, { "name": "05 - ArifPay Direct (legacy)", "description": "OTP/direct payment flows still use ArifPay. Subscription checkout uses Chapa (folder 02).", "item": [ { "name": "Get Direct Payment Methods", "request": { "auth": { "type": "noauth" }, "method": "GET", "header": [], "url": { "raw": "{{base_url}}/api/v1/payments/direct/methods", "host": ["{{base_url}}"], "path": ["api", "v1", "payments", "direct", "methods"] } }, "response": [] }, { "name": "Initiate Direct Payment", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"plan_id\": {{plan_id}},\n \"phone\": \"{{learner_phone}}\",\n \"email\": \"{{learner_email}}\",\n \"payment_method\": \"TELEBIRR\"\n}" }, "url": { "raw": "{{base_url}}/api/v1/payments/direct", "host": ["{{base_url}}"], "path": ["api", "v1", "payments", "direct"] } }, "response": [] }, { "name": "Verify Direct Payment OTP", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"session_id\": \"{{tx_ref}}\",\n \"otp\": \"123456\"\n}" }, "url": { "raw": "{{base_url}}/api/v1/payments/direct/verify-otp", "host": ["{{base_url}}"], "path": ["api", "v1", "payments", "direct", "verify-otp"] } }, "response": [] } ] } ] }