32 KiB
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):
{
"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 inerangel/application/config/routes.php). The frontend sends only the user-action fields (see per-endpoint tables). - Erangel → Doom: the
Doomlibrary (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-otpandsubmit-otpendpoints (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.
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)
}
Struct: usecase.OnboardingRequest (internal/interfaces/usecase/loa.go:51)
Response Body (200)
Response Body 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": "3 Bulan",
"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": "3 Bulan",
"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": "3 Bulan",
"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": "3 Bulan",
"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
}
]
}
]
}
}
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
{
"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": "3 Bulan",
"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 |
|---|---|
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 |
Frontend → Erangel
{
"card_token": "f104d035a92652987b8c82dd91cb13aa1f403d686dcbc3844d261cc8416de9b0"
}
Erangel → Doom (constructed in Doom.php:81)
{
"client": "BRIMON",
"request_refnum": "123456789154",
"username": "acctest009",
"timestamp": "1652034352767",
"channel_id": "NBMB",
"card_token": "f104d035a92652987b8c82dd91cb13aa1f403d686dcbc3844d261cc8416de9b0",
"request_id": "<overwritten by doom>"
}
Struct: usecase.LoaFormRequest (internal/interfaces/usecase/loa.go:20)
Response Body (200)
Response Body 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
}
}
data is usecase.LoaFormResponse (internal/interfaces/usecase/loa.go:30).
Frontend validation: the user-entered
amountmust be a multiple ofloan_multiplier(e.g. if100000, only values like100000,200000,1500000, … are accepted).
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 |
Frontend → Erangel
{
"card_token": "f104d035a92652987b8c82dd91cb13aa1f403d686dcbc3844d261cc8416de9b0",
"amount": 5000
}
Erangel → Doom (constructed in Doom.php:95)
{
"client": "BRIMON",
"request_refnum": "123456789154",
"username": "acctest009",
"timestamp": "1652034352767",
"channel_id": "NBMB",
"card_token": "f104d035a92652987b8c82dd91cb13aa1f403d686dcbc3844d261cc8416de9b0",
"amount": 5000,
"request_id": "<overwritten by doom>"
}
Struct: usecase.LoaTermRequest (internal/interfaces/usecase/loa.go:104)
Response Body (200)
Response Body 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": "3 Bulan",
"interest_rate": 0.5
}
]
}
}
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/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
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.
Request Body
| Field | Type | Source | Required | Notes |
|---|---|---|---|---|
otp_type |
string | [FE] | yes | WA or SMS — OTP delivery channel |
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 |
Frontend → Erangel
{
"otp_type": "WA"
}
Erangel → Doom
{
"client": "BRIMON",
"request_refnum": "123456789154",
"username": "acctest009",
"timestamp": "1652034352767",
"channel_id": "NBMB",
"otp_type": "WA",
"request_id": "<overwritten by doom>"
}
Struct: usecase.LoaSendOtpRequest (internal/interfaces/usecase/loa.go:177)
Response Body (200)
Response Body JSON
{
"code": "00",
"refnum": "123456789154",
"id": "<process_id>",
"desc": "success",
"data": {
"server_id": "abc123",
"cellphone_number": "0812****1234",
"duration_sec": 300
}
}
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 |
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 sameserver_id/duration_sec(send.go:84-95).
5. 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
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).
Request Body
| Field | Type | Source | Required | Notes |
|---|---|---|---|---|
server_id |
string | [FE] | yes | from /send-otp response |
otp |
string | [FE] | yes | code entered by user |
otp_type |
string | [FE] | yes | WA or SMS — must match channel used in /send-otp |
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 |
Frontend → Erangel
{
"server_id": "abc123",
"otp": "123456",
"otp_type": "WA"
}
Erangel → Doom
{
"client": "BRIMON",
"request_refnum": "123456789154",
"username": "acctest009",
"timestamp": "1652034352767",
"channel_id": "NBMB",
"server_id": "abc123",
"otp": "123456",
"otp_type": "WA",
"request_id": "<overwritten by doom>"
}
Struct: usecase.LoaSubmitOtpRequest (internal/interfaces/usecase/loa.go:TBD)
Response Body (200)
Response Body JSON
{
"code": "00",
"refnum": "123456789154",
"id": "<process_id>",
"desc": "success",
"data": null
}
No data payload on success — frontend simply checks code == "00" before proceeding to /submit.
Error Codes
| Code | When |
|---|---|
00 |
OTP valid |
NV |
Invalid OTP |
TMT |
Too many wrong-OTP attempts |
EX |
OTP not exist or expired |
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:
- Rate-limit check in Redis →
TMTif exceeded. - If
otp_type=loanInAppdisabled in cipher config → returns00(skip). - Reads OTP from Redis by
(otp_type, username)→EXif missing. - Compares
server_id+otp→NVon mismatch (increments rate-limit counter). - On match: deletes OTP from Redis, writes
otp_history, returns00.
A failed validate returns immediately — cipher's code/desc forwarded 1:1 to the frontend.
6. 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
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.
Erangel PIN validation (
Submit.php): the frontend must sendpin. Erangel validates it viacheck_pin()before calling doom. Thepinvalue is not forwarded to doom.
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 | |
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 |
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 |
Frontend → Erangel
{
"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"
}
Erangel → Doom (pin consumed by erangel, not forwarded)
{
"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>"
}
Struct: usecase.LoaSubmitRequest (internal/interfaces/usecase/loa.go:131)
Response Body (200)
Response Body 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": "3 Bulan",
"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
}
]
}
}
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 |
7. GET /healthz
Health check. Defined in internal/delivery/http/http.go:47. Returns literal "ok".
Response body
"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.gointernal/usecase/loa/list.gointernal/usecase/loa/form.gointernal/usecase/loa/term.gointernal/usecase/loa/send_otp.gointernal/usecase/loa/submit_otp.gointernal/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.phperangel/application/controllers/api/v3/loan_on_app/List_loa.phperangel/application/controllers/api/v3/loan_on_app/Form.phperangel/application/controllers/api/v3/loan_on_app/Term.phperangel/application/controllers/api/v3/loan_on_app/Send_otp.phperangel/application/controllers/api/v3/loan_on_app/Submit_otp.phperangel/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(usesresponse_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