b4c53c9122
- Add /confirm endpoint (section 4) — receives the loan data, formats display strings, persists context to Redis keyed by reference_number - /send-otp now requires reference_number; resend reuses the same endpoint (no separate /resend-otp) - /submit-otp requires reference_number and returns a new one for /submit - /submit now takes only pin + reference_number; loan data is loaded from Redis - Rename term label '3 Bulan' to '3x Cicilan' across all examples
1119 lines
38 KiB
Markdown
1119 lines
38 KiB
Markdown
# 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": "<request_refnum>",
|
||
"id": "<process_id / request_id>",
|
||
"desc": "success",
|
||
"data": { ... endpoint-specific payload ... }
|
||
}
|
||
```
|
||
|
||
When an error occurs, `data` is wrapped as `{"error": "<error message>"}` (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.
|
||
|
||
<details>
|
||
<summary>Request Body JSON</summary>
|
||
|
||
```json
|
||
{
|
||
"client": "BRIMON", // [EG]
|
||
"request_refnum": "123456789154", // [EG]
|
||
"username": "acctest009", // [EG]
|
||
"timestamp": "1652034352767", // [EG]
|
||
"channel_id": "NBMB", // [EG]
|
||
"request_id": "f6eb58d24d6842f59ddb40f40d2a7992" // [DM] (overwritten)
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
Struct: `usecase.OnboardingRequest` (`internal/interfaces/usecase/loa.go:51`)
|
||
|
||
### Response Body (200)
|
||
|
||
<details>
|
||
<summary>Response Body JSON</summary>
|
||
|
||
```json
|
||
{
|
||
"code": "00",
|
||
"refnum": "123456789154",
|
||
"id": "<process_id>",
|
||
"desc": "success",
|
||
"data": {
|
||
"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)"
|
||
},
|
||
"scheme": [
|
||
{
|
||
"tenor": "3x Cicilan",
|
||
"bunga": "0%"
|
||
},
|
||
{
|
||
"tenor": "6 Bulan",
|
||
"bunga": "0,5%"
|
||
},
|
||
{
|
||
"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"
|
||
},
|
||
{
|
||
"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"
|
||
},
|
||
{
|
||
"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
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
`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`:
|
||
|
||
<details>
|
||
<summary>Application item schema</summary>
|
||
|
||
```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
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
**`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 |
|
||
|---|---|
|
||
| `00` | `success` – also returned when user has no CC (CC list empty from Eredar). |
|
||
| `TV` | Vikendi timeout (Eredar call) |
|
||
| `NA` | Not authorized (Eredar call) |
|
||
| `TB` | Static param DB read failure |
|
||
| `TBR` | Brigate timeout on `GetLoaStatusFromBrigate` |
|
||
|
||
---
|
||
|
||
## 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 |
|
||
|
||
<details>
|
||
<summary>Frontend → Erangel</summary>
|
||
|
||
```json
|
||
{
|
||
"card_token": "f104d035a92652987b8c82dd91cb13aa1f403d686dcbc3844d261cc8416de9b0"
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary>Erangel → Doom (constructed in Doom.php:81)</summary>
|
||
|
||
```json
|
||
{
|
||
"client": "BRIMON",
|
||
"request_refnum": "123456789154",
|
||
"username": "acctest009",
|
||
"timestamp": "1652034352767",
|
||
"channel_id": "NBMB",
|
||
"card_token": "f104d035a92652987b8c82dd91cb13aa1f403d686dcbc3844d261cc8416de9b0",
|
||
"request_id": "<overwritten by doom>"
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
Struct: `usecase.LoaFormRequest` (`internal/interfaces/usecase/loa.go:20`)
|
||
|
||
### Response Body (200)
|
||
|
||
<details>
|
||
<summary>Response Body JSON</summary>
|
||
|
||
```json
|
||
{
|
||
"code": "00",
|
||
"refnum": "123456789154",
|
||
"id": "<process_id>",
|
||
"desc": "success",
|
||
"data": {
|
||
"account_list": [
|
||
{
|
||
"account": "123451234512345",
|
||
"account_string": "1234 5123 4512 345",
|
||
"name": "supedi",
|
||
"currency": "IDR",
|
||
"alias": "Alias-supedi",
|
||
"product_type": "karti",
|
||
"image_name": "karti.png",
|
||
"image_path": "http://.../karti.png",
|
||
"default": 1
|
||
}
|
||
],
|
||
"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,
|
||
"simulation": [
|
||
{
|
||
"simulation_code": "...",
|
||
"term_code": "123",
|
||
"monthly_payment": 1700000,
|
||
"monthly_payment_string": "Rp1.700.000",
|
||
"admin_fee": 200000,
|
||
"admin_fee_string": "Rp200.000",
|
||
"term": 3,
|
||
"term_string": "3x Cicilan",
|
||
"interest_rate": 0.5
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
`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 |
|
||
|---|---|
|
||
| `00` | Success |
|
||
| `SM` | User is in safety mode (`form.go:37`) |
|
||
| `CNF` | Credit card not found (`form.go:100`) |
|
||
| `TV` | Vikendi timeout (Eredar, parameter, account list) |
|
||
| `TB` | Database timeout (static param) |
|
||
| `TBR` | Brigate timeout (`GetProgramTermFromBrigate`) |
|
||
| `EH` | Error handling (account alias/validation fan-in errors) |
|
||
|
||
---
|
||
|
||
## 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 |
|
||
|
||
<details>
|
||
<summary>Frontend → Erangel</summary>
|
||
|
||
```json
|
||
{
|
||
"card_token": "f104d035a92652987b8c82dd91cb13aa1f403d686dcbc3844d261cc8416de9b0",
|
||
"amount": 5000
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary>Erangel → Doom (constructed in Doom.php:95)</summary>
|
||
|
||
```json
|
||
{
|
||
"client": "BRIMON",
|
||
"request_refnum": "123456789154",
|
||
"username": "acctest009",
|
||
"timestamp": "1652034352767",
|
||
"channel_id": "NBMB",
|
||
"card_token": "f104d035a92652987b8c82dd91cb13aa1f403d686dcbc3844d261cc8416de9b0",
|
||
"amount": 5000,
|
||
"request_id": "<overwritten by doom>"
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
Struct: `usecase.LoaTermRequest` (`internal/interfaces/usecase/loa.go:104`)
|
||
|
||
### Response Body (200)
|
||
|
||
<details>
|
||
<summary>Response Body JSON</summary>
|
||
|
||
```json
|
||
{
|
||
"code": "00",
|
||
"refnum": "123456789154",
|
||
"id": "<process_id>",
|
||
"desc": "success",
|
||
"data": {
|
||
"simulation": [
|
||
{
|
||
"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
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
`data` is `usecase.LoaTermResponse` (`internal/interfaces/usecase/loa.go:115`).
|
||
|
||
### Error Codes
|
||
|
||
| Code | When |
|
||
|---|---|
|
||
| `00` | Success |
|
||
| `TBR` | Brigate timeout (`GetLoaSimulationtFromBrigate`) |
|
||
|
||
---
|
||
|
||
## 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 |
|
||
|
||
<details>
|
||
<summary>Frontend → Erangel</summary>
|
||
|
||
```json
|
||
{
|
||
"card_token": "f104d035a92652987b8c82dd91cb13aa1f403d686dcbc3844d261cc8416de9b0",
|
||
"amount": 5000000,
|
||
"simulation_code": "...",
|
||
"account_no": "123451234512345",
|
||
"account_name": "BRItama Bisnis",
|
||
"term": 3,
|
||
"interest_rate": 0.5,
|
||
"monthly_payment": 1700000,
|
||
"admin_fee": 50000
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary>Erangel → Doom</summary>
|
||
|
||
```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": "<overwritten by doom>"
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
Struct: `usecase.LoaConfirmRequest` (`internal/interfaces/usecase/loa.go:TBD`)
|
||
|
||
### Response Body (200)
|
||
|
||
<details>
|
||
<summary>Response Body JSON</summary>
|
||
|
||
```json
|
||
{
|
||
"code": "00",
|
||
"refnum": "123456789154",
|
||
"id": "<process_id>",
|
||
"desc": "success",
|
||
"data": {
|
||
"amount": "Rp5.000.000",
|
||
"account_no": "123451234512345",
|
||
"account_name": "BRItama Bisnis",
|
||
"term_string": "3x Cicilan",
|
||
"interest_rate": "0,5%",
|
||
"admin_fee_string": "Rp50.000",
|
||
"monthly_payment_string": "Rp1.700.000",
|
||
"reference_number": "123456789154"
|
||
}
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
`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 |
|
||
|---|---|
|
||
| `00` | Success — loan context saved to Redis |
|
||
| `NA` | `account_no` not in user's financial accounts |
|
||
| `TB` | Database/Redis timeout (failed to save loan context) |
|
||
| `EH` | Error handling (validation fan-in errors, e.g. invalid `simulation_code` for the chosen `card_token`) |
|
||
|
||
---
|
||
|
||
## 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 |
|
||
|
||
<details>
|
||
<summary>Frontend → Erangel</summary>
|
||
|
||
```json
|
||
{
|
||
"otp_type": "WA",
|
||
"reference_number": "123456789154"
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary>Erangel → Doom</summary>
|
||
|
||
```json
|
||
{
|
||
"client": "BRIMON",
|
||
"request_refnum": "123456789154",
|
||
"username": "acctest009",
|
||
"timestamp": "1652034352767",
|
||
"channel_id": "NBMB",
|
||
"otp_type": "WA",
|
||
"reference_number": "123456789154",
|
||
"request_id": "<overwritten by doom>"
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
Struct: `usecase.LoaSendOtpRequest` (`internal/interfaces/usecase/loa.go:177`)
|
||
|
||
### Response Body (200)
|
||
|
||
<details>
|
||
<summary>Response Body JSON</summary>
|
||
|
||
```json
|
||
{
|
||
"code": "00",
|
||
"refnum": "123456789154",
|
||
"id": "<process_id>",
|
||
"desc": "success",
|
||
"data": {
|
||
"server_id": "abc123",
|
||
"cellphone_number": "0812****1234",
|
||
"duration_sec": 300
|
||
}
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
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 |
|
||
|---|---|
|
||
| `00` | Success |
|
||
| `TV` | Vikendi timeout (fetching user phone number) |
|
||
| `TC` | Cipher/OTP timeout |
|
||
| `TR` | Redis timeout (cipher) |
|
||
| `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 |
|
||
|
||
<details>
|
||
<summary>Frontend → Erangel</summary>
|
||
|
||
```json
|
||
{
|
||
"server_id": "abc123",
|
||
"otp": "123456",
|
||
"otp_type": "WA",
|
||
"reference_number": "123456789154"
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary>Erangel → Doom</summary>
|
||
|
||
```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": "<overwritten by doom>"
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
Struct: `usecase.LoaSubmitOtpRequest` (`internal/interfaces/usecase/loa.go:TBD`)
|
||
|
||
### Response Body (200)
|
||
|
||
<details>
|
||
<summary>Response Body JSON</summary>
|
||
|
||
```json
|
||
{
|
||
"code": "00",
|
||
"refnum": "123456789154",
|
||
"id": "<process_id>",
|
||
"desc": "success",
|
||
"data": {
|
||
"reference_number": "987654321098"
|
||
}
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
`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 |
|
||
|---|---|
|
||
| `00` | OTP valid |
|
||
| `NV` | Invalid OTP |
|
||
| `TMT` | Too many wrong-OTP attempts |
|
||
| `EX` | OTP not exist or expired, or `reference_number` not found in Redis |
|
||
| `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 |
|
||
|
||
<details>
|
||
<summary>Frontend → Erangel</summary>
|
||
|
||
```json
|
||
{
|
||
"pin": "123456",
|
||
"reference_number": "987654321098"
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary>Erangel → Doom (pin consumed by erangel, not forwarded)</summary>
|
||
|
||
```json
|
||
{
|
||
"client": "BRIMON",
|
||
"request_refnum": "123456789154",
|
||
"username": "acctest009",
|
||
"timestamp": "1652034352767",
|
||
"channel_id": "NBMB",
|
||
"reference_number": "987654321098",
|
||
"request_id": "<overwritten by doom>"
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
Struct: `usecase.LoaSubmitRequest` (`internal/interfaces/usecase/loa.go:131`)
|
||
|
||
### Response Body (200)
|
||
|
||
<details>
|
||
<summary>Response Body JSON</summary>
|
||
|
||
```json
|
||
{
|
||
"code": "00",
|
||
"refnum": "123456789154",
|
||
"id": "<process_id>",
|
||
"desc": "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",
|
||
"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
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
</details>
|
||
|
||
`data` is `usecase.LoaSubmitResponse` (`internal/interfaces/usecase/loa.go:151`).
|
||
|
||
### Error Codes
|
||
|
||
| Code | When |
|
||
|---|---|
|
||
| `00` | Success |
|
||
| `NA` | `account_no` not in user's financial accounts (`submit.go:44`) |
|
||
| `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"`.
|
||
|
||
<details>
|
||
<summary>Response body</summary>
|
||
|
||
```json
|
||
"ok"
|
||
```
|
||
|
||
</details>
|
||
|
||
---
|
||
|
||
## 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`
|