Achmad Setyabudi Susilo b4c53c9122 docs(loa): split /submit into /confirm + /submit and add reference_number flow
- 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
2026-06-29 15:28:32 +07:00

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 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-onboardingerangel/application/controllers/api/v3/loan_on_app/Onboarding.php Doom library call: Doom::onboarding($refnum, $username, $device)Doom.php:68 Handler: loaHandler.Onboardingloauc.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": "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
          }
        ]
      }
    ]
  }
}

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": "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
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-formerangel/application/controllers/api/v3/loan_on_app/Form.php Doom library call: Doom::loanform($refnum, $username, $device, $card_token)Doom.php:81 Handler: loaHandler.Formloauc.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,
    "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
      }
    ]
  }
}

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-termerangel/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.Termloauc.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": "3x Cicilan",
        "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/confirm

Erangel route: POST /v3-cc-loan-on-app-confirmerangel/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.Confirmloauc.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
{
  "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
{
  "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.LoaConfirmRequest (internal/interfaces/usecase/loa.go:TBD)

Response Body (200)

Response Body 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"
  }
}

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-otperangel/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.SendOtploauc.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
Frontend → Erangel
{
  "otp_type": "WA",
  "reference_number": "123456789154"
}
Erangel → Doom
{
  "client": "BRIMON",
  "request_refnum": "123456789154",
  "username": "acctest009",
  "timestamp": "1652034352767",
  "channel_id": "NBMB",
  "otp_type": "WA",
  "reference_number": "123456789154",
  "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
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-otperangel/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.SubmitOtploauc.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
Frontend → Erangel
{
  "server_id": "abc123",
  "otp": "123456",
  "otp_type": "WA",
  "reference_number": "123456789154"
}
Erangel → Doom
{
  "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>"
}

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": {
    "reference_number": "987654321098"
  }
}

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 + otpNV 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-submiterangel/application/controllers/api/v3/loan_on_app/Submit.php Doom library call: Doom::loansubmit($refnum, $username, $device, $reference_number)Doom.php:110 Handler: loaHandler.Submitloauc.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
Frontend → Erangel
{
  "pin": "123456",
  "reference_number": "987654321098"
}
Erangel → Doom (pin consumed by erangel, not forwarded)
{
  "client": "BRIMON",
  "request_refnum": "123456789154",
  "username": "acctest009",
  "timestamp": "1652034352767",
  "channel_id": "NBMB",
  "reference_number": "987654321098",
  "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": "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
      }
    ]
  }
}

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".

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.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
S
Description
No description provided
Readme 107 KiB