diff --git a/README.md b/README.md index 58bb029..5b70cfe 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ Struct: `usecase.OnboardingRequest` (`internal/interfaces/usecase/loa.go:51`) }, "scheme": [ { - "tenor": "3 Bulan", + "tenor": "3x Cicilan", "bunga": "0%" }, { @@ -202,7 +202,7 @@ Struct: `usecase.OnboardingRequest` (`internal/interfaces/usecase/loa.go:51`) "monthly_payment": "Rp1.015.000", "admin_fee": "Rp200.000", "interest_rate": "0%", - "term": "3 Bulan", + "term": "3x Cicilan", "seq_no": 1, "progress": [ { @@ -238,7 +238,7 @@ Struct: `usecase.OnboardingRequest` (`internal/interfaces/usecase/loa.go:51`) "monthly_payment": "Rp1.015.000", "admin_fee": "Rp200.000", "interest_rate": "0%", - "term": "3 Bulan", + "term": "3x Cicilan", "seq_no": 2, "progress": [ { @@ -274,7 +274,7 @@ Struct: `usecase.OnboardingRequest` (`internal/interfaces/usecase/loa.go:51`) "monthly_payment": "Rp1.015.000", "admin_fee": "Rp200.000", "interest_rate": "0%", - "term": "3 Bulan", + "term": "3x Cicilan", "seq_no": 3, "progress": [ { @@ -329,7 +329,7 @@ Each item in `list` is `usecase.Application`: "monthly_payment": "Rp1.015.000", "admin_fee": "Rp200.000", "interest_rate": "0%", - "term": "3 Bulan", + "term": "3x Cicilan", "seq_no": 1, "progress": [ { @@ -462,7 +462,7 @@ Struct: `usecase.LoaFormRequest` (`internal/interfaces/usecase/loa.go:20`) "admin_fee": 200000, "admin_fee_string": "Rp200.000", "term": 3, - "term_string": "3 Bulan", + "term_string": "3x Cicilan", "interest_rate": 0.5 } ] @@ -566,7 +566,7 @@ Struct: `usecase.LoaTermRequest` (`internal/interfaces/usecase/loa.go:104`) "admin_fee": 5000, "admin_fee_string": "Rp5.000", "term": 3, - "term_string": "3 Bulan", + "term_string": "3x Cicilan", "interest_rate": 0.5 } ] @@ -587,19 +587,139 @@ Struct: `usecase.LoaTermRequest` (`internal/interfaces/usecase/loa.go:104`) --- -## 4. `POST /api/v1/cc-loan-on-app/send-otp` +## 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 | + +
+Frontend → Erangel + +```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 +} +``` + +
+ +
+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 + +```json +{ + "code": "00", + "refnum": "123456789154", + "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" + } +} +``` + +
+ +`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)` → `Doom.php:134` +**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. +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 | @@ -612,7 +732,8 @@ Sends a one-time password to the user's registered phone number for the loan-on- ```json { - "otp_type": "WA" + "otp_type": "WA", + "reference_number": "123456789154" } ``` @@ -629,6 +750,7 @@ Sends a one-time password to the user's registered phone number for the loan-on- "timestamp": "1652034352767", "channel_id": "NBMB", "otp_type": "WA", + "reference_number": "123456789154", "request_id": "" } ``` @@ -669,10 +791,11 @@ If `otp_type=loanInApp` is disabled in cipher config, doom returns `code: "00"` | `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**. +Doom fetches the user's phone number from **Vikendi** (via `username`), then proxies the send request to **cipher-otp`. **Request forwarded to cipher-otp:** @@ -691,23 +814,26 @@ Doom fetches the user's phone number from **Vikendi** (via `username`), then pro - 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. + --- -## 5. `POST /api/v1/cc-loan-on-app/submit-otp` +## 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)` → `Doom.php:TBD` +**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`. 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`). +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 `/send-otp` response | +| `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 | @@ -722,7 +848,8 @@ Validates the OTP the user received via `/send-otp`. On success the frontend pro { "server_id": "abc123", "otp": "123456", - "otp_type": "WA" + "otp_type": "WA", + "reference_number": "123456789154" } ``` @@ -741,6 +868,7 @@ Validates the OTP the user received via `/send-otp`. On success the frontend pro "server_id": "abc123", "otp": "123456", "otp_type": "WA", + "reference_number": "123456789154", "request_id": "" } ``` @@ -760,13 +888,15 @@ Struct: `usecase.LoaSubmitOtpRequest` (`internal/interfaces/usecase/loa.go:TBD`) "refnum": "123456789154", "id": "", "desc": "success", - "data": null + "data": { + "reference_number": "987654321098" + } } ``` -No data payload on success — frontend simply checks `code == "00"` before proceeding to `/submit`. +`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 @@ -775,7 +905,7 @@ No data payload on success — frontend simply checks `code == "00"` before proc | `00` | OTP valid | | `NV` | Invalid OTP | | `TMT` | Too many wrong-OTP attempts | -| `EX` | OTP not exist or expired | +| `EX` | OTP not exist or expired, or `reference_number` not found in Redis | | `TC` | Cipher/OTP validation timeout | | `TR` | Redis timeout (cipher) | @@ -805,13 +935,13 @@ A failed validate returns immediately — cipher's `code`/`desc` forwarded 1:1 t --- -## 6. `POST /api/v1/cc-loan-on-app/submit` +## 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, $card_token, $amount, $simulation_code, $account_no, $account_name, $term, $interest_rate, $monthly_payment, $admin_fee, $pin)` → `Doom.php:110` +**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: submits the loan to Brigate, persists to DB, and returns the new application's progress. OTP validation has moved to `/submit-otp`; this endpoint now requires PIN instead. +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. @@ -819,16 +949,8 @@ Final step: submits the loan to Brigate, persists to DB, and returns the new app | 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 | | -| `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 | | `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 | @@ -841,16 +963,8 @@ Final step: submits the loan to Brigate, persists to DB, and returns the new app ```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, - "pin": "123456" + "pin": "123456", + "reference_number": "987654321098" } ``` @@ -866,15 +980,7 @@ Final step: submits the loan to Brigate, persists to DB, and returns the new app "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, + "reference_number": "987654321098", "request_id": "" } ``` @@ -911,7 +1017,7 @@ Struct: `usecase.LoaSubmitRequest` (`internal/interfaces/usecase/loa.go:131`) "monthly_payment": "Rp1.015.000", "admin_fee": "Rp200.000", "interest_rate": "0%", - "term": "3 Bulan", + "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", @@ -948,7 +1054,7 @@ Struct: `usecase.LoaSubmitRequest` (`internal/interfaces/usecase/loa.go:131`) --- -## 7. `GET /healthz` +## 8. `GET /healthz` Health check. Defined in `internal/delivery/http/http.go:47`. Returns literal `"ok"`. @@ -975,6 +1081,7 @@ Health check. Defined in `internal/delivery/http/http.go:47`. Returns literal `" - `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` @@ -990,6 +1097,7 @@ Health check. Defined in `internal/delivery/http/http.go:47`. Returns literal `" - `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`