diff --git a/README.md b/README.md index 5b70cfe..2cbe1a2 100644 --- a/README.md +++ b/README.md @@ -1,295 +1,97 @@ # Doom CC Loan on App API -Base path: `/api/v1/cc-loan-on-app/` - -All endpoints accept `POST` and return JSON. Standard response wrapper (defined by `bitbucket.bri.co.id/mod/brimocomp/basic/dto/response/v2`): - -```json -{ - "code": "00", - "refnum": "", - "id": "", - "desc": "success", - "data": { ... endpoint-specific payload ... } -} -``` - -When an error occurs, `data` is wrapped as `{"error": ""}` (see `internal/utils/utils.go:22`). - -## Request Origin: Frontend vs Erangel - -Doom is **not** called directly by the frontend. The call flow is: - -``` -Frontend ──POST──▶ Erangel (API Gateway, CodeIgniter PHP) - └─ Doom library - └─POST──▶ Doom microservice - ├─ Vikendi (user/account/safety-mode) - ├─ Eredar (credit card / CC list) - ├─ Brigate (LOA simulation, submit, status) - ├─ Database (static params, LOA persistence) - └─ Cipher-OTP (OTP send / validate) ◀── only send-otp & submit-otp -``` - -- **Frontend → Erangel**: hits routes like `POST /v3-cc-loan-on-app-onboarding` (mapped in `erangel/application/config/routes.php`). The frontend sends only the user-action fields (see per-endpoint tables). -- **Erangel → Doom**: the `Doom` library (`erangel/application/libraries/microservices/Doom.php`) injects the cross-cutting fields. It also adds HTTP headers: `User-Agent`, `IP-Address`, `Device-Type`, `Device-Id`, `Device-Name`, `Device-Version`. -- **Doom → Cipher-OTP**: triggered only by the `send-otp` and `submit-otp` endpoints (see the "Downstream: Cipher-OTP" subsections). - -Field source legend used below: -- **[FE]** = set by the **frontend** in the body sent to erangel -- **[EG]** = **erangel**-generated/injected when forwarding to doom (not in the frontend body) -- **[EG→DM]** = arrives at doom via erangel-injected field (originated from session/config/timestamp) -- **[FE→EG→DM]** = flows frontend → erangel → doom unchanged -- **[DM]** = **doom** server-side (overwritten inside the handler, or hardcoded) - -### Common Request Fields (all endpoints) - -| Field | Type | Required | Source | Validation | Notes | -|---|---|---|---|---|---| -| `client` | string | yes | [EG] | `oneof=BRIMON` | From erangel config `doom_client` | -| `request_refnum` | string | yes | [EG] | `len=12` | Generated by erangel (`get_reference_number()`); echoed in `refnum` | -| `username` | string | yes | [EG] | `len=10` | From erangel auth session (`get_session_username()`) | -| `timestamp` | string | yes | [EG] | `len=13` | Generated by erangel via `round(microtime(TRUE) * 1000)` | -| `channel_id` | string | yes | [EG] | `oneof=NBMB` | From erangel config `doom_channel_id` | -| `request_id` | string | no | [DM] | – | Not sent by erangel; overwritten in handler with the process ID | - -### Common Response Codes - -| Code | Constant | Description | -|---|---|---| -| `00` | `RCSuccess` | Success | -| `01` | `RC01` | No credit card / empty list | -| `VE` | `RCValidationError` | Invalid request body | -| `EH` | `RCErrorHandling` | Error handling | -| `NF` | `RCNotFound` | Not found | -| `NA` | `RCNotAuthorized` | Not authorized | -| `CNF` | `RCCreditCardNotFound` | Credit card not found | -| `SM` | `RCUserSafetyMode` | Safety mode active | -| `FM` | `RCFM` | Malformed JSON | -| `TV` | `RCTimeoutVikendi` | Vikendi timeout | -| `TB` | `RCTimeoutDB` | Database timeout | -| `TBR` | `RCTimeoutBrigate` | Brigate timeout | -| `TC` | `RCTimeoutMicroOTP` | Cipher/OTP timeout | -| `TE` | `RCTimeoutEredar` | Eredar timeout | -| `SK` | `RCSkipOtp` (cipher) | OTP skipped (disabled in config) — passed through from `send-otp` | -| `NV` | `RCOtpNotValid` (cipher) | Invalid OTP — passed through from `submit-otp` | -| `TMT` | `RCOtpTooManyTries` (cipher) | Too many wrong-OTP attempts | -| `EX` | `RCOtpNotExist` (cipher) | OTP not exist or expired | -| `TR` | `RCTimeoutRedis` (cipher) | Redis timeout (OTP store) | -| `TK` | `RCTimeoutKafka` (cipher) | Kafka timeout (OTP SMS/WA delivery) | -| `THV` | `RCTimeoutHaven` (cipher) | Haven timeout | - -Messages are localized in `id` or `en` (selected via the `language` header / middleware). - ---- - ## 1. `POST /api/v1/cc-loan-on-app/onboarding` -**Erangel route:** `POST /v3-cc-loan-on-app-onboarding` → `erangel/application/controllers/api/v3/loan_on_app/Onboarding.php` -**Doom library call:** `Doom::onboarding($refnum, $username, $device)` → `Doom.php:68` -**Handler:** `loaHandler.Onboarding` → `loauc.Onboarding` (`internal/usecase/loa/onboarding.go`) - -Loads the onboarding screen: scheme/slider text configuration and the user's existing loan applications grouped by status. - -### Request Body - -| Field | Source | Required | -|---|---|---| -| _no body fields_ | – | – | - -The frontend sends **no body fields** to erangel for this endpoint. All common fields below are injected by erangel. +**Erangel route:** `POST /v3-cc-loan-on-app-onboarding`
-Request Body JSON +Request Body ```json { - "client": "BRIMON", // [EG] - "request_refnum": "123456789154", // [EG] - "username": "acctest009", // [EG] - "timestamp": "1652034352767", // [EG] - "channel_id": "NBMB", // [EG] - "request_id": "f6eb58d24d6842f59ddb40f40d2a7992" // [DM] (overwritten) + "card_token": "f104d035a92652987b8c82dd91cb13aa1f403d686dcbc3844d261cc8416de9b0" } ```
-Struct: `usecase.OnboardingRequest` (`internal/interfaces/usecase/loa.go:51`) - -### Response Body (200) -
-Response Body JSON +Response Body ```json { "code": "00", - "refnum": "123456789154", - "id": "", - "desc": "success", + "description": "success", "data": { + "admin_fee_note": { + "description": "1% dari total pinjaman (min. Rp200.000)", + "title": "Ketentuan Biaya Admin" + }, "eligible": true, "eligible_note": "Pinjaman baru belum bisa diajukan karena masih ada proses yang berlangsung.", - "admin_fee_note": { - "title": "Ketentuan Biaya Admin", - "description": "1% dari total pinjaman (min. Rp200.000)" - }, + "list": [ + { + "account_image_name": "britama_bisnis", + "account_image_path": "http://asset-host/account/britama_bisnis.png", + "account_name": "BRItama Bisnis", + "account_no": "029012345678901", + "account_string": "0290 **** **** 112", + "admin_fee_string": "Rp200.000", + "amount_string": "Rp3.200.000", + "card_image_name": "bri_easy_card", + "card_image_path": "http://asset-host/card/bri_easy_card.png", + "card_name": "Agung Harsono", + "card_no": "2003 **** **** 4302", + "card_product_type": "Easy Card", + "interest_rate_string": "0% per bulan", + "monthly_payment_string": "Rp1.015.000", + "progress": [ + { + "current": true, + "order": 1, + "status_time": "09 Feb 2026, 09:41 WIB", + "status_title": "Pengajuan dalam Analisis" + }, + { + "current": false, + "order": 2, + "status_time": "", + "status_title": "Pengajuan Disetujui" + } + ], + "request_time": "09 February 2026, 09:41:02 WIB", + "seq_no": 1, + "sla_note": "Proses pengajuan 1x24 jam kerja. Kamu akan diberi notifikasi saat pencairan disetujui.", + "status": "PROCESSING", + "term_string": "3x Cicilan", + "ticket_no": "TKT001" + } + ], "scheme": [ { - "tenor": "3x Cicilan", - "bunga": "0%" + "description": "0%", + "title": "3x Cicilan" }, { - "tenor": "6 Bulan", - "bunga": "0,5%" + "description": "0,5%", + "title": "6 Bulan" }, - { - "tenor": "12 Bulan", - "bunga": "0,5%" - }, - { - "tenor": "15 Bulan", - "bunga": "0,99%" - }, - { - "tenor": "18 Bulan", - "bunga": "0,99%" - }, - { - "tenor": "24 Bulan", - "bunga": "0,99%" - }, - { - "tenor": "36 Bulan", - "bunga": "0,99%" - } ], "slider_texts": [ { - "title": "Pinjaman Fleksibel", "description": "Pinjaman dana minimal 1 juta hingga 75 juta atau 50% dari sisa limit kartu kredit.", - "image_path": "http://asset-host/example.png" + "image_path": "http://asset-host/example.png", + "title": "Pinjaman Fleksibel" }, { - "title": "Pinjaman Fleksibel", "description": "Pinjaman dana minimal 1 juta hingga 75 juta atau 50% dari sisa limit kartu kredit.", - "image_path": "http://asset-host/example.png" + "image_path": "http://asset-host/example.png", + "title": "Pinjaman Fleksibel" }, { - "title": "Pinjaman Fleksibel", "description": "Pinjaman dana minimal 1 juta hingga 75 juta atau 50% dari sisa limit kartu kredit.", - "image_path": "http://asset-host/example.png" - } - ], - "list": [ - { - "ticket_no": "TKT001", - "card_no": "2003 **** **** 4302", - "card_name": "Agung Harsono", - "card_product_type": "Easy Card", - "card_image_name": "bri_easy_card", - "card_image_path": "http://asset-host/card/bri_easy_card.png", - "account_no": "029012345678901", - "account_string": "0290 **** **** 112", - "account_name": "BRItama Bisnis", - "account_image_name": "britama_bisnis", - "account_image_path": "http://asset-host/account/britama_bisnis.png", - "status": "PROCESSING", - "status_string": "Pengajuan Diproses", - "request_time": "09 February 2026, 09:41:02 WIB", - "amount": "Rp3.200.000", - "monthly_payment": "Rp1.015.000", - "admin_fee": "Rp200.000", - "interest_rate": "0%", - "term": "3x Cicilan", - "seq_no": 1, - "progress": [ - { - "status_title": "Pengajuan dalam Analisis", - "status_time": "09 Feb 2026, 09:41 WIB", - "order": 1, - "current": true - }, - { - "status_title": "Pengajuan Disetujui", - "status_time": "", - "order": 2, - "current": false - } - ] - }, - { - "ticket_no": "TKT002", - "card_no": "2003 **** **** 4302", - "card_name": "Agung Harsono", - "card_product_type": "Easy Card", - "card_image_name": "bri_easy_card", - "card_image_path": "http://asset-host/card/bri_easy_card.png", - "account_no": "029012345678901", - "account_string": "0290 **** **** 112", - "account_name": "BRItama Bisnis", - "account_image_name": "britama_bisnis", - "account_image_path": "http://asset-host/account/britama_bisnis.png", - "status": "REJECTED", - "status_string": "Pengajuan Ditolak", - "request_time": "09 February 2026, 09:41:02 WIB", - "amount": "Rp3.200.000", - "monthly_payment": "Rp1.015.000", - "admin_fee": "Rp200.000", - "interest_rate": "0%", - "term": "3x Cicilan", - "seq_no": 2, - "progress": [ - { - "status_title": "Pengajuan dalam Analisis", - "status_time": "09 Feb 2026, 09:41 WIB", - "order": 1, - "current": false - }, - { - "status_title": "Pengajuan Ditolak", - "status_time": "10 Feb 2026, 06:30 WIB", - "order": 2, - "current": true - } - ] - }, - { - "ticket_no": "TKT003", - "card_no": "2003 **** **** 4302", - "card_name": "Agung Harsono", - "card_product_type": "Easy Card", - "card_image_name": "bri_easy_card", - "card_image_path": "http://asset-host/card/bri_easy_card.png", - "account_no": "029012345678901", - "account_string": "0290 **** **** 112", - "account_name": "BRItama Bisnis", - "account_image_name": "britama_bisnis", - "account_image_path": "http://asset-host/account/britama_bisnis.png", - "status": "APPROVED", - "status_string": "Pengajuan Disetujui", - "request_time": "09 February 2026, 09:41:02 WIB", - "amount": "Rp3.200.000", - "monthly_payment": "Rp1.015.000", - "admin_fee": "Rp200.000", - "interest_rate": "0%", - "term": "3x Cicilan", - "seq_no": 3, - "progress": [ - { - "status_title": "Pengajuan dalam Analisis", - "status_time": "09 Feb 2026, 09:41 WIB", - "order": 1, - "current": false - }, - { - "status_title": "Pengajuan Disetujui", - "status_time": "10 Feb 2026, 06:30 WIB", - "order": 2, - "current": true - } - ] + "image_path": "http://asset-host/example.png", + "title": "Pinjaman Fleksibel" } ] } @@ -298,66 +100,6 @@ Struct: `usecase.OnboardingRequest` (`internal/interfaces/usecase/loa.go:51`)
-`data` is `usecase.OnboardingResponse`. - -`admin_fee_note` carries the admin fee info banner shown in the scheme bottom sheet (title: "Ketentuan Biaya Admin", content: "1% dari total pinjaman (min. Rp200.000)"). - -Each `scheme` item now only contains `tenor` and `bunga` (removed `nominal_transaksi` and `subtitle_transaksi`). - -Each item in `list` is `usecase.Application`: - -
-Application item schema - -```json -{ - "ticket_no": "...", - "card_no": "2003 **** **** 4302", - "card_name": "Agung Harsono", - "card_product_type": "Easy Card", - "card_image_name": "bri_easy_card", - "card_image_path": "http://asset-host/card/bri_easy_card.png", - "account_no": "029012345678901", - "account_string": "0290 **** **** 112", - "account_name": "BRItama Bisnis", - "account_image_name": "britama_bisnis", - "account_image_path": "http://asset-host/account/britama_bisnis.png", - "status": "PROCESSING|APPROVED|REJECTED", - "status_string": "Pengajuan Diproses|Pengajuan Disetujui|Pengajuan Ditolak", - "request_time": "09 February 2026, 09:41:02 WIB", - "amount": "Rp3.200.000", - "monthly_payment": "Rp1.015.000", - "admin_fee": "Rp200.000", - "interest_rate": "0%", - "term": "3x Cicilan", - "seq_no": 1, - "progress": [ - { - "status_title": "Pengajuan dalam Analisis", - "status_time": "09 Feb 2026, 09:41 WIB", - "order": 1, - "current": true - }, - { - "status_title": "Pengajuan Disetujui|Pengajuan Ditolak", - "status_time": "", - "order": 2, - "current": false - } - ] -} -``` - -
- -**`status` / `status_string` mapping:** - -| `status` | `status_string` | Description | -|---|---|---| -| `PROCESSING` | `Pengajuan Diproses` | Loan submitted, under review | -| `APPROVED` | `Pengajuan Disetujui` | Loan approved | -| `REJECTED` | `Pengajuan Ditolak` | Loan rejected | - ### Error Codes | Code | When | @@ -372,26 +114,10 @@ Each item in `list` is `usecase.Application`: ## 2. `POST /api/v1/cc-loan-on-app/form` -**Erangel route:** `POST /v3-cc-loan-on-app-form` → `erangel/application/controllers/api/v3/loan_on_app/Form.php` -**Doom library call:** `Doom::loanform($refnum, $username, $device, $card_token)` → `Doom.php:81` -**Handler:** `loaHandler.Form` → `loauc.Form` (`internal/usecase/loa/form.go`) - -Loads the loan form: source accounts, T&C text, product type, min/max loan range. - -### Request Body - -| Field | Type | Source | Required | Notes | -|---|---|---|---|---| -| `card_token` | string | [FE] | yes | len=64, validated by doom | -| `client` | string | [EG] | yes | `BRIMON` | -| `request_refnum` | string | [EG] | yes | len=12 | -| `username` | string | [EG] | yes | len=10, from session | -| `timestamp` | string | [EG] | yes | len=13, server-generated | -| `channel_id` | string | [EG] | yes | `NBMB` | -| `request_id` | string | [DM] | no | overwritten by doom with process ID | +**Erangel route:** `POST /v3-cc-loan-on-app-form`
-Frontend → Erangel +Request Body ```json { @@ -402,82 +128,58 @@ Loads the loan form: source accounts, T&C text, product type, min/max loan range
-Erangel → Doom (constructed in Doom.php:81) - -```json -{ - "client": "BRIMON", - "request_refnum": "123456789154", - "username": "acctest009", - "timestamp": "1652034352767", - "channel_id": "NBMB", - "card_token": "f104d035a92652987b8c82dd91cb13aa1f403d686dcbc3844d261cc8416de9b0", - "request_id": "" -} -``` - -
- -Struct: `usecase.LoaFormRequest` (`internal/interfaces/usecase/loa.go:20`) - -### Response Body (200) - -
-Response Body JSON +Response Body ```json { "code": "00", - "refnum": "123456789154", - "id": "", - "desc": "success", + "description": "success", "data": { "account_list": [ { "account": "123451234512345", "account_string": "1234 5123 4512 345", - "name": "supedi", - "currency": "IDR", "alias": "Alias-supedi", - "product_type": "karti", + "currency": "IDR", + "default": 1, "image_name": "karti.png", "image_path": "http://.../karti.png", - "default": 1 + "name": "supedi", + "product_type": "karti" } ], "balance_string": "Rp20.000.000,00", - "loan_note":"Pinjaman mulai Rp1 juta s.d Rp75 juta, maksimal 50% dari sisa limit.", - "tnc_text": "tnc-id", - "product_type": "Jcb Platinum", "cc_image_slim": "http://.../card/jcb_platinum_slim.png", - "minimum_loan": 1000000, - "maximum_loan": 20000000, "loan_multiplier": 100000, + "loan_multiplier_string": "Rp100.000", + "maximum_loan": 20000000, + "maximum_loan_string": "Rp20.000.000", + "minimum_loan": 1000000, + "minimum_loan_string": "Rp1.000.000", + "product_type": "Jcb Platinum", "simulation": [ { - "simulation_code": "...", - "term_code": "123", - "monthly_payment": 1700000, - "monthly_payment_string": "Rp1.700.000", "admin_fee": 200000, "admin_fee_string": "Rp200.000", + "interest_rate": 0.5, + "interest_rate_amount": 50000, + "interest_rate_amount_string": "Rp50.000", + "interest_rate_string": "0.5%", + "monthly_payment": 1700000, + "monthly_payment_string": "Rp1.700.000", + "simulation_code": "...", "term": 3, - "term_string": "3x Cicilan", - "interest_rate": 0.5 + "term_code": "123", + "term_string": "3x Cicilan" } - ] + ], + "tnc_text": "text" } } ```
-`data` is `usecase.LoaFormResponse` (`internal/interfaces/usecase/loa.go:30`). - -> **Frontend validation:** the user-entered `amount` must be a multiple of `loan_multiplier` (e.g. if `100000`, only values like `100000`, `200000`, `1500000`, … are accepted). - -> **`simulation` field:** the `/form` response now also includes a pre-fetched list of simulations (same shape as `usecase.LoaTermResponse.simulation`). The simulations are calculated using the card's `maximum_loan` so the frontend can render a sample of available tenors before the user finishes entering an amount. The full per-amount simulation is still returned by `/term`. - ### Error Codes | Code | When | @@ -494,27 +196,10 @@ Struct: `usecase.LoaFormRequest` (`internal/interfaces/usecase/loa.go:20`) ## 3. `POST /api/v1/cc-loan-on-app/term` -**Erangel route:** `POST /v3-cc-loan-on-app-term` → `erangel/application/controllers/api/v3/loan_on_app/Term.php` -**Doom library call:** `Doom::loanterm($refnum, $username, $device, $card_token, $amount)` → `Doom.php:95` -**Handler:** `loaHandler.Term` → `loauc.Term` (`internal/usecase/loa/term.go`) - -Returns a list of loan simulations (tenor / monthly payment / admin fee) for a given amount. - -### Request Body - -| Field | Type | Source | Required | Notes | -|---|---|---|---|---| -| `card_token` | string | [FE] | yes | | -| `amount` | int | [FE] | yes | doom validates `gt=0` | -| `client` | string | [EG] | yes | `BRIMON` | -| `request_refnum` | string | [EG] | yes | len=12 | -| `username` | string | [EG] | yes | len=10, from session | -| `timestamp` | string | [EG] | yes | len=13, server-generated | -| `channel_id` | string | [EG] | yes | `NBMB` | -| `request_id` | string | [DM] | no | overwritten by doom with process ID | +**Erangel route:** `POST /v3-cc-loan-on-app-term`
-Frontend → Erangel +Response Body ```json { @@ -526,48 +211,27 @@ Returns a list of loan simulations (tenor / monthly payment / admin fee) for a g
-Erangel → Doom (constructed in Doom.php:95) - -```json -{ - "client": "BRIMON", - "request_refnum": "123456789154", - "username": "acctest009", - "timestamp": "1652034352767", - "channel_id": "NBMB", - "card_token": "f104d035a92652987b8c82dd91cb13aa1f403d686dcbc3844d261cc8416de9b0", - "amount": 5000, - "request_id": "" -} -``` - -
- -Struct: `usecase.LoaTermRequest` (`internal/interfaces/usecase/loa.go:104`) - -### Response Body (200) - -
-Response Body JSON +Response Body ```json { "code": "00", - "refnum": "123456789154", - "id": "", - "desc": "success", + "description": "success", "data": { "simulation": [ { + "admin_fee": 200000, + "admin_fee_string": "Rp200.000", + "interest_rate": 0.5, + "interest_rate_amount": 50000, + "interest_rate_amount_string": "Rp50.000", + "interest_rate_string": "0.5%", + "monthly_payment": 1700000, + "monthly_payment_string": "Rp1.700.000", "simulation_code": "...", - "term_code": "123", - "monthly_payment": 1015000, - "monthly_payment_string": "Rp1.015.000", - "admin_fee": 5000, - "admin_fee_string": "Rp5.000", "term": 3, - "term_string": "3x Cicilan", - "interest_rate": 0.5 + "term_code": "123", + "term_string": "3x Cicilan" } ] } @@ -576,8 +240,6 @@ Struct: `usecase.LoaTermRequest` (`internal/interfaces/usecase/loa.go:104`)
-`data` is `usecase.LoaTermResponse` (`internal/interfaces/usecase/loa.go:115`). - ### Error Codes | Code | When | @@ -589,36 +251,10 @@ Struct: `usecase.LoaTermRequest` (`internal/interfaces/usecase/loa.go:104`) ## 4. `POST /api/v1/cc-loan-on-app/confirm` -**Erangel route:** `POST /v3-cc-loan-on-app-confirm` → `erangel/application/controllers/api/v3/loan_on_app/Confirm.php` -**Doom library call:** `Doom::loanconfirm($refnum, $username, $device, $card_token, $amount, $simulation_code, $account_no, $account_name, $term, $interest_rate, $monthly_payment, $admin_fee)` → `Doom.php:TBD` -**Handler:** `loaHandler.Confirm` → `loauc.Confirm` (`internal/usecase/loa/confirm.go`) - -Confirmation step in the loan flow. Receives the loan details the user just entered on `/form`, formats them for the confirmation screen, persists the loan context to Redis (key = `reference_number`, value = full loan payload), and returns the formatted display strings plus the `reference_number` the frontend must use for the subsequent `/send-otp` → `/submit-otp` → `/submit` calls. - -The flow is: `/form` → `/confirm` → `/send-otp` (call again on the same endpoint to resend) → `/submit-otp` → `/submit`. Splitting the original single `/submit` call into `/confirm` + `/submit` lets the OTP/PIN verification steps be decoupled from the loan-data submission. - -### Request Body - -| Field | Type | Source | Required | Notes | -|---|---|---|---|---| -| `card_token` | string | [FE] | yes | | -| `amount` | number | [FE] | yes | doom: `gt=0`; erangel casts to int before forwarding | -| `simulation_code` | string | [FE] | yes | from `/form` simulation list | -| `account_no` | string | [FE] | yes | | -| `account_name` | string | [FE] | yes | | -| `term` | int | [FE] | yes | erangel casts to int before forwarding | -| `interest_rate` | number | [FE] | yes | erangel casts to float | -| `monthly_payment` | number | [FE] | yes | doom: `gt=0`; erangel casts to float | -| `admin_fee` | number | [FE] | yes | doom: `gte=0`; erangel casts to float | -| `client` | string | [EG] | yes | `BRIMON` | -| `request_refnum` | string | [EG] | yes | len=12 — also used as the `reference_number` returned to the frontend | -| `username` | string | [EG] | yes | len=10, from session | -| `timestamp` | string | [EG] | yes | len=13, server-generated | -| `channel_id` | string | [EG] | yes | `NBMB` | -| `request_id` | string | [DM] | no | overwritten by doom with process ID | +**Erangel route:** `POST /v3-cc-loan-on-app-confirm`
-Frontend → Erangel +Request Body ```json { @@ -637,64 +273,33 @@ The flow is: `/form` → `/confirm` → `/send-otp` (call again on the same endp
-Erangel → Doom - -```json -{ - "client": "BRIMON", - "request_refnum": "123456789154", - "username": "acctest009", - "timestamp": "1652034352767", - "channel_id": "NBMB", - "card_token": "f104d035a92652987b8c82dd91cb13aa1f403d686dcbc3844d261cc8416de9b0", - "amount": 5000000, - "simulation_code": "...", - "account_no": "123451234512345", - "account_name": "BRItama Bisnis", - "term": 3, - "interest_rate": 0.5, - "monthly_payment": 1700000.0, - "admin_fee": 50000.0, - "request_id": "" -} -``` - -
- -Struct: `usecase.LoaConfirmRequest` (`internal/interfaces/usecase/loa.go:TBD`) - -### Response Body (200) - -
-Response Body JSON +Response Body ```json { "code": "00", - "refnum": "123456789154", - "id": "", - "desc": "success", + "description": "success", "data": { - "amount": "Rp5.000.000", - "account_no": "123451234512345", "account_name": "BRItama Bisnis", - "term_string": "3x Cicilan", - "interest_rate": "0,5%", + "account_no": "123451234512345", + "admin_fee": 50000, "admin_fee_string": "Rp50.000", + "amount_string": "Rp5.000.000", + "interest_rate": 0.5, + "interest_rate_amount": 50000, + "interest_rate_amount_string": "Rp50.000", + "interest_rate_string": "0,5%", + "monthly_payment": 1700000, "monthly_payment_string": "Rp1.700.000", - "reference_number": "123456789154" + "reference_number": "123456789154", + "term": 3, + "term_string": "3x Cicilan" } } ```
-`data` is `usecase.LoaConfirmResponse` (`internal/interfaces/usecase/loa.go:TBD`). - -> **`reference_number`:** the same erangel-generated `request_refnum` echoed back from the response `refnum`. Doom stores the full loan payload in Redis under this key. The frontend must send it back to `/send-otp` (and to `/submit` via the new `reference_number` returned by `/submit-otp`). -> -> **Redis payload** (doom-internal, not exposed to FE): `{ card_token, amount, simulation_code, account_no, account_name, term, interest_rate, monthly_payment, admin_fee, otp_type, request_refnum, username, timestamp, channel_id, request_id }`. TTL is set so an abandoned session expires. - ### Error Codes | Code | When | @@ -708,27 +313,10 @@ Struct: `usecase.LoaConfirmRequest` (`internal/interfaces/usecase/loa.go:TBD`) ## 5. `POST /api/v1/cc-loan-on-app/send-otp` -**Erangel route:** `POST /v3-cc-loan-on-app-send-otp` → `erangel/application/controllers/api/v3/loan_on_app/Send_otp.php` -**Doom library call:** `Doom::loansendotp($refnum, $username, $device, $otp_type, $reference_number)` → `Doom.php:134` -**Handler:** `loaHandler.SendOtp` → `loauc.SendOtp` (`internal/usecase/loa/send_otp.go`) - -Sends a one-time password to the user's registered phone number for the loan-on-app flow. The `reference_number` links the OTP to a loan context previously stored by `/confirm`. - -### Request Body - -| Field | Type | Source | Required | Notes | -|---|---|---|---|---| -| `otp_type` | string | [FE] | yes | `WA` or `SMS` — OTP delivery channel | -| `reference_number` | string | [FE] | yes | len=12, `reference_number` returned by `/confirm`; doom uses it to load the loan context from Redis | -| `client` | string | [EG] | yes | `BRIMON` | -| `request_refnum` | string | [EG] | yes | len=12 | -| `username` | string | [EG] | yes | len=10, from session | -| `timestamp` | string | [EG] | yes | len=13, server-generated | -| `channel_id` | string | [EG] | yes | `NBMB` | -| `request_id` | string | [DM] | no | overwritten by doom with process ID | +**Erangel route:** `POST /v3-cc-loan-on-app-send-otp`
-Frontend → Erangel +Request Body ```json { @@ -740,48 +328,22 @@ Sends a one-time password to the user's registered phone number for the loan-on-
-Erangel → Doom - -```json -{ - "client": "BRIMON", - "request_refnum": "123456789154", - "username": "acctest009", - "timestamp": "1652034352767", - "channel_id": "NBMB", - "otp_type": "WA", - "reference_number": "123456789154", - "request_id": "" -} -``` - -
- -Struct: `usecase.LoaSendOtpRequest` (`internal/interfaces/usecase/loa.go:177`) - -### Response Body (200) - -
-Response Body JSON +Response Body ```json { "code": "00", - "refnum": "123456789154", - "id": "", - "desc": "success", + "description": "success", "data": { - "server_id": "abc123", "cellphone_number": "0812****1234", - "duration_sec": 300 + "duration_sec": 300, + "server_id": "abc123" } } ```
-If `otp_type=loanInApp` is disabled in cipher config, doom returns `code: "00"` with `data: { "skip": true }` — frontend skips OTP entry and proceeds to `/submit-otp` without an OTP code. - ### Error Codes | Code | When | @@ -793,56 +355,14 @@ If `otp_type=loanInApp` is disabled in cipher config, doom returns `code: "00"` | `TK` | Kafka timeout — OTP SMS/WA delivery failed | | `EX` | `reference_number` not found or expired in Redis (no loan context from `/confirm`) | -### Downstream: Cipher-OTP `POST /api/v1/otp/send` - -Doom fetches the user's phone number from **Vikendi** (via `username`), then proxies the send request to **cipher-otp`. - -**Request forwarded to cipher-otp:** - -| Field | Type | Source | Notes | -|---|---|---|---| -| `client` | string | [EG→DM] | `BRIMON` | -| `request_refnum` | string | [EG→DM] | len=12 | -| `username` | string | [EG→DM] | len=10 | -| `timestamp` | string | [EG→DM] | len=13 | -| `channel_id` | string | [EG→DM] | `NBMB` | -| `otp_type` | string | [DM] | hardcoded `loanInApp` (`constant.OtpTypeLoanInApp`) | -| `phone_no` | string | [DM] | from user profile returned by Vikendi | -| `method` | string | [FE→EG→DM] | from `otp_type` field (`WA` or `SMS`) | - -`request_id` is **not** forwarded — cipher-otp generates its own. - -- If an OTP is already active in Redis, cipher returns `desc: "Already Exist"` with the same `server_id`/`duration_sec` (`send.go:84-95`). - -> **Resend behaviour:** the frontend uses the **same** `/send-otp` endpoint to resend an OTP. Calling it again with the same `reference_number` invalidates the previous `server_id` and returns a fresh one (cipher returns `desc: "Already Exist"` if a previous OTP is still active, and doom forwards that as-is). There is no separate `/resend-otp` endpoint. - --- ## 6. `POST /api/v1/cc-loan-on-app/submit-otp` -**Erangel route:** `POST /v3-cc-loan-on-app-submit-otp` → `erangel/application/controllers/api/v3/loan_on_app/Submit_otp.php` -**Doom library call:** `Doom::loansubmitotp($refnum, $username, $device, $server_id, $otp, $otp_type, $reference_number)` → `Doom.php:TBD` -**Handler:** `loaHandler.SubmitOtp` → `loauc.SubmitOtp` (`internal/usecase/loa/submit_otp.go`) - -Validates the OTP the user received via `/send-otp`. On success the frontend proceeds to `/submit` and must pass the **new** `reference_number` returned here (not the one from `/confirm`/`/send-otp`). Modelled after `haven-credit-card /api/v1/apply-cc/submit-otp` (see `erangel/application/libraries/microservices/Haven.php:submit_otp` and `erangel/application/controllers/api/v3/credit_card_application/Submit_otp.php`). - -### Request Body - -| Field | Type | Source | Required | Notes | -|---|---|---|---|---| -| `server_id` | string | [FE] | yes | from the latest `/send-otp` response (whether first call or resend) | -| `otp` | string | [FE] | yes | code entered by user | -| `otp_type` | string | [FE] | yes | `WA` or `SMS` — must match channel used in `/send-otp` | -| `reference_number` | string | [FE] | yes | len=12, `reference_number` returned by `/confirm`; doom uses it to load the loan context from Redis | -| `client` | string | [EG] | yes | `BRIMON` | -| `request_refnum` | string | [EG] | yes | len=12 | -| `username` | string | [EG] | yes | len=10, from session | -| `timestamp` | string | [EG] | yes | len=13, server-generated | -| `channel_id` | string | [EG] | yes | `NBMB` | -| `request_id` | string | [DM] | no | overwritten by doom with process ID | +**Erangel route:** `POST /v3-cc-loan-on-app-submit-otp`
-Frontend → Erangel +Request Body ```json { @@ -856,38 +376,12 @@ Validates the OTP the user received via `/send-otp`. On success the frontend pro
-Erangel → Doom - -```json -{ - "client": "BRIMON", - "request_refnum": "123456789154", - "username": "acctest009", - "timestamp": "1652034352767", - "channel_id": "NBMB", - "server_id": "abc123", - "otp": "123456", - "otp_type": "WA", - "reference_number": "123456789154", - "request_id": "" -} -``` - -
- -Struct: `usecase.LoaSubmitOtpRequest` (`internal/interfaces/usecase/loa.go:TBD`) - -### Response Body (200) - -
-Response Body JSON +Response Body ```json { "code": "00", - "refnum": "123456789154", - "id": "", - "desc": "success", + "description": "success", "data": { "reference_number": "987654321098" } @@ -896,8 +390,6 @@ Struct: `usecase.LoaSubmitOtpRequest` (`internal/interfaces/usecase/loa.go:TBD`)
-`data.reference_number` is a **new** reference number generated by doom/erangel on successful OTP validation. The frontend must pass it to `/submit` (do **not** reuse the `reference_number` from `/confirm` or `/send-otp`). - ### Error Codes | Code | When | @@ -909,57 +401,14 @@ Struct: `usecase.LoaSubmitOtpRequest` (`internal/interfaces/usecase/loa.go:TBD`) | `TC` | Cipher/OTP validation timeout | | `TR` | Redis timeout (cipher) | -### Downstream: Cipher-OTP `POST /api/v1/otp/validate` - -Doom forwards the validate call to cipher-otp: - -| Field | Type | Source | Notes | -|---|---|---|---| -| `client` | string | [EG→DM] | `BRIMON` | -| `request_refnum` | string | [EG→DM] | len=12 | -| `username` | string | [EG→DM] | len=10 | -| `timestamp` | string | [EG→DM] | len=13 | -| `channel_id` | string | [EG→DM] | `NBMB` | -| `otp_type` | string | [DM] | hardcoded `loanInApp` (`constant.OtpTypeLoanInApp`) — doom maps FE's `WA`/`SMS` to this internally | -| `server_id` | string | [FE→EG→DM] | from request | -| `otp` | string | [FE→EG→DM] | from request | - -Cipher validate logic: -1. Rate-limit check in Redis → `TMT` if exceeded. -2. If `otp_type=loanInApp` disabled in cipher config → returns `00` (skip). -3. Reads OTP from Redis by `(otp_type, username)` → `EX` if missing. -4. Compares `server_id` + `otp` → `NV` on mismatch (increments rate-limit counter). -5. On match: deletes OTP from Redis, writes `otp_history`, returns `00`. - -A failed validate returns immediately — cipher's `code`/`desc` forwarded 1:1 to the frontend. - --- ## 7. `POST /api/v1/cc-loan-on-app/submit` -**Erangel route:** `POST /v3-cc-loan-on-app-submit` → `erangel/application/controllers/api/v3/loan_on_app/Submit.php` -**Doom library call:** `Doom::loansubmit($refnum, $username, $device, $reference_number)` → `Doom.php:110` -**Handler:** `loaHandler.Submit` → `loauc.Submit` (`internal/usecase/loa/submit.go`) - -Final step: loads the loan context from Redis using `reference_number` (saved by `/confirm`, rotated by `/submit-otp`), submits the loan to Brigate, persists to DB, and returns the new application's progress. The frontend only sends `pin` + `reference_number` — all loan data fields have already been captured by `/confirm` and are not re-validated here. - -> **Erangel PIN validation (`Submit.php`):** the frontend must send `pin`. Erangel validates it via `check_pin()` **before** calling doom. The `pin` value is **not** forwarded to doom. - -### Request Body - -| Field | Type | Source | Required | Notes | -|---|---|---|---|---| -| `pin` | string | [FE] | yes | consumed by erangel only, not sent to doom | -| `reference_number` | string | [FE] | yes | len=12, `reference_number` returned by `/submit-otp` (NOT the one from `/confirm`); doom uses it to load the loan context from Redis | -| `client` | string | [EG] | yes | `BRIMON` | -| `request_refnum` | string | [EG] | yes | len=12 | -| `username` | string | [EG] | yes | len=10, from session | -| `timestamp` | string | [EG] | yes | len=13, server-generated | -| `channel_id` | string | [EG] | yes | `NBMB` | -| `request_id` | string | [DM] | no | overwritten by doom with process ID | +**Erangel route:** `POST /v3-cc-loan-on-app-submit`
-Frontend → Erangel +Request Body ```json { @@ -971,78 +420,53 @@ Final step: loads the loan context from Redis using `reference_number` (saved by
-Erangel → Doom (pin consumed by erangel, not forwarded) - -```json -{ - "client": "BRIMON", - "request_refnum": "123456789154", - "username": "acctest009", - "timestamp": "1652034352767", - "channel_id": "NBMB", - "reference_number": "987654321098", - "request_id": "" -} -``` - -
- -Struct: `usecase.LoaSubmitRequest` (`internal/interfaces/usecase/loa.go:131`) - -### Response Body (200) - -
-Response Body JSON +Response Body ```json { "code": "00", - "refnum": "123456789154", - "id": "", - "desc": "success", + "description": "success", "data": { - "ticket_no": "TKT001", - "card_no": "2003 **** **** 4302", - "card_name": "Agung Harsono", - "card_product_type": "Easy Card", - "card_image_name": "bri_easy_card", - "card_image_path": "http://asset-host/card/bri_easy_card.png", - "account_no": "029012345678901", - "account_string": "0290 **** **** 112", - "account_name": "BRItama Bisnis", "account_image_name": "britama_bisnis", "account_image_path": "http://asset-host/account/britama_bisnis.png", - "request_time": "09 February 2026, 09:41:02 WIB", - "amount": "Rp3.200.000", - "monthly_payment": "Rp1.015.000", - "admin_fee": "Rp200.000", - "interest_rate": "0%", - "term": "3x Cicilan", - "sla_note": "Proses pengajuan 1x24 jam kerja. Kamu akan diberi notifikasi saat pencairan disetujui.", - "status": "PROCESSING|APPROVED|REJECTED", - "status_string": "Pengajuan Diproses|Pengajuan Disetujui|Pengajuan Ditolak", + "account_name": "BRItama Bisnis", + "account_no": "029012345678901", + "account_string": "0290 **** **** 112", + "admin_fee_string": "Rp200.000", + "amount_string": "Rp3.200.000", + "card_image_name": "bri_easy_card", + "card_image_path": "http://asset-host/card/bri_easy_card.png", + "card_name": "Agung Harsono", + "card_no": "2003 **** **** 4302", + "card_product_type": "Easy Card", + "interest_rate_string": "0% per bulan", + "monthly_payment_string": "Rp1.015.000", "progress": [ { - "status_title": "Pengajuan dalam Analisis", - "status_time": "09 Feb 2026, 09:41 WIB", + "current": true, "order": 1, - "current": true + "status_time": "09 Feb 2026, 09:41 WIB", + "status_title": "Pengajuan dalam Analisis" }, { - "status_title": "Pengajuan Disetujui|Pengajuan Ditolak", - "status_time": "", + "current": false, "order": 2, - "current": false + "status_time": "", + "status_title": "Pengajuan Disetujui" } - ] + ], + "request_time": "09 February 2026, 09:41:02 WIB", + "seq_no": 1, + "sla_note": "Proses pengajuan 1x24 jam kerja. Kamu akan diberi notifikasi saat pencairan disetujui.", + "status": "PROCESSING", + "term_string": "3x Cicilan", + "ticket_no": "TKT001" } } ```
-`data` is `usecase.LoaSubmitResponse` (`internal/interfaces/usecase/loa.go:151`). - ### Error Codes | Code | When | @@ -1052,67 +476,4 @@ Struct: `usecase.LoaSubmitRequest` (`internal/interfaces/usecase/loa.go:131`) | `TBR` | Brigate `LoaSubmitToBrigate` failed | | `TB` | DB insert failed (`InsertLoanOnAppToDB`) or `GetLoaStatusFromBrigate` DB error | ---- - -## 8. `GET /healthz` - -Health check. Defined in `internal/delivery/http/http.go:47`. Returns literal `"ok"`. - -
-Response body - -```json -"ok" -``` - -
- ---- - -## Source Code References - -### Doom (this microservice) - -- Routes: `internal/delivery/http/http.go:39-50` -- Handlers: `internal/delivery/http/loa.go` -- Request/Response structs: `internal/interfaces/usecase/loa.go` -- Usecase logic: - - `internal/usecase/loa/onboarding.go` - - `internal/usecase/loa/list.go` - - `internal/usecase/loa/form.go` - - `internal/usecase/loa/term.go` - - `internal/usecase/loa/confirm.go` - - `internal/usecase/loa/send_otp.go` - - `internal/usecase/loa/submit_otp.go` - - `internal/usecase/loa/submit.go` -- Response codes & messages: `constant/response_code.go` -- Constants & route prefixes: `constant/app.go` - -### Erangel (API Gateway, sibling repo) - -- Routes mapping (frontend-facing URLs): `erangel/application/config/routes.php` -- Doom client library: `erangel/application/libraries/microservices/Doom.php` -- Frontend-facing controllers (each validates FE body, then calls `Doom::*`): - - `erangel/application/controllers/api/v3/loan_on_app/Onboarding.php` - - `erangel/application/controllers/api/v3/loan_on_app/List_loa.php` - - `erangel/application/controllers/api/v3/loan_on_app/Form.php` - - `erangel/application/controllers/api/v3/loan_on_app/Term.php` - - `erangel/application/controllers/api/v3/loan_on_app/Confirm.php` - - `erangel/application/controllers/api/v3/loan_on_app/Send_otp.php` - - `erangel/application/controllers/api/v3/loan_on_app/Submit_otp.php` - - `erangel/application/controllers/api/v3/loan_on_app/Submit.php` -- Base controller (auth/session, PIN checks, device, refnum generation): `erangel/application/libraries/api/Transaction_Controller.php` - -### Cipher-OTP (downstream microservice called by `send-otp` and `submit-otp`) - -Base path: `/api/v1/otp/` (v1) and `/api/v5/otp/` (v5). Doom uses the v1 endpoints. - -- Routes: `cipher-otp/internal/delivery/http/http.go:40-44` -- Handlers: `cipher-otp/internal/delivery/http/otp.go` -- Request/Response structs: `cipher-otp/internal/interfaces/usecase/otp.go` -- Usecase logic: - - `cipher-otp/internal/usecase/otp/send.go` (handles skip-OTP, already-active-OTP, Kafka notification) - - `cipher-otp/internal/usecase/otp/validate.go` (handles rate limit, server_id+otp match, history insert) -- Response wrapper: `cipher-otp/internal/pkg/dto/basic.go` (uses `response_code`/`response_refnum`/`response_id`/`response_desc`/`response_data`) -- Response codes & messages: `cipher-otp/constant/response_code.go` (notable additions: `SK`, `NV`, `TMT`, `EX`, `TR`, `TK`, `THV`) -- Doom's cipher client: `doom-cc-loan-on-app/internal/library/cipher/cipher.go` +--- \ No newline at end of file