BBL Integration » History » Version 1
Ryan Supawarapong, 03/31/2026 10:10 AM
| 1 | 1 | Ryan Supawarapong | # BBL Integration |
|---|---|---|---|
| 2 | |||
| 3 | Detail for API on BBL integration. |
||
| 4 | |||
| 5 | - [API Payment](#API-Payment) |
||
| 6 | - [ThaiQR](#ThaiQR) |
||
| 7 | |||
| 8 | ## API Payment |
||
| 9 | |||
| 10 | ### Module Summary |
||
| 11 | |||
| 12 | | Item | Value | |
||
| 13 | |---|---| |
||
| 14 | | Base path | `/api/v1/bank/apipayment` | |
||
| 15 | | Purpose | Receiver lookup, transfer confirmation, and post-transfer inquiry for BBL account, other-bank account, and PromptPay flows | |
||
| 16 | | Inbound content type | `application/json` | |
||
| 17 | | Inbound auth | None at route layer | |
||
| 18 | | Outbound auth to BBL | Internal only: Basic Auth + RSA signature | |
||
| 19 | | Local transaction type | `API_PAYMENT` | |
||
| 20 | | Supported `paymentType` | `BBL`, `OTH`, `PP` | |
||
| 21 | | Supported `receiverType` | `BANKAC`, `NATID`, `MSISDN` | |
||
| 22 | | Upstream `transType` | `CASHLESS` | |
||
| 23 | |||
| 24 | ### Endpoint Matrix |
||
| 25 | |||
| 26 | | Method | Path | Upstream BBL `transCode` | Purpose | Local side effects | |
||
| 27 | |---|---|---|---|---| |
||
| 28 | | `POST` | `/validation` | `LOOKUP_AC`, `LOOKUP_OTH_AC`, `LOOKUP_PP` | Validate receiver and create a local transaction | Inserts `apipayments` + `transactions`; status becomes `1100` or `2000` | |
||
| 29 | | `POST` | `/confirm` | `TRANSFER_AC`, `TRANSFER_OTH_AC`, `TRANSFER_PP` | Execute transfer using a prior `lookupRef` | Marks local transaction `5000`; updates API Payment transmit time and transaction code | |
||
| 30 | | `GET` | `/inquiry/:transactionID` | `INQUIRY` | Query transfer result using local transaction ID | Updates stored BBL reference number on the API Payment record | |
||
| 31 | |||
| 32 | ### `POST /validation` Request Contract |
||
| 33 | |||
| 34 | | Field | Type | Required | Constraints | Notes | |
||
| 35 | |---|---|---|---|---| |
||
| 36 | | `receiverValue` | `string` | Yes | Non-empty | Receiver account / PromptPay target | |
||
| 37 | | `receiverType` | `string` | Yes | `BANKAC`, `NATID`, `MSISDN` | `PP` flow only allows `NATID` or `MSISDN` | |
||
| 38 | | `receiverBankCode` | `string` | Conditional | Length must be `3` for `paymentType=OTH` | Ignored for `BBL`; internally forced to `002` | |
||
| 39 | | `paymentType` | `string` | Yes | `BBL`, `OTH`, `PP` | Selects lookup / confirm flow | |
||
| 40 | | `amount` | `float64` | Yes | Positive amount expected | Used in downstream lookup | |
||
| 41 | | `additionalRef` | `string` | No | None | Passed through to BBL where applicable | |
||
| 42 | | `customerCode` | `int` | Yes | Must map to existing customer data | Used to compare stored customer name vs BBL receiver name | |
||
| 43 | |||
| 44 | ### `POST /validation` Success Response |
||
| 45 | |||
| 46 | | Field | Type | Description | |
||
| 47 | |---|---|---| |
||
| 48 | | `responseCode` | `string` | BBL/business result code | |
||
| 49 | | `responseMesg` | `string` | Human-readable message | |
||
| 50 | | `lookupRef` | `string` | BBL lookup reference, required for `/confirm` | |
||
| 51 | | `receiverName` | `string` | Receiver name returned by BBL | |
||
| 52 | | `requestRef` | `string` | BBL request reference generated by this service | |
||
| 53 | | `transactionID` | `string` | Local `transactions.id` generated by this service | |
||
| 54 | | `storedFullname` | `string` | Uppercased full name loaded from local customer data | |
||
| 55 | | `customerNameMatch` | `bool` | `true` when `storedFullname == receiverName` | |
||
| 56 | |||
| 57 | ### `POST /confirm` Request Contract |
||
| 58 | |||
| 59 | | Field | Type | Required | Constraints | Notes | |
||
| 60 | |---|---|---|---|---| |
||
| 61 | | `receiverValue` | `string` | Yes | Non-empty | Same logical receiver as in validation | |
||
| 62 | | `receiverType` | `string` | Yes | `BANKAC`, `NATID`, `MSISDN` | `PP` flow only allows `NATID` or `MSISDN` | |
||
| 63 | | `receiverBankCode` | `string` | Conditional | Length must be `3` for `paymentType=OTH` | `BBL` flow internally uses `002` | |
||
| 64 | | `paymentType` | `string` | Yes | `BBL`, `OTH`, `PP` | Must match the intended transfer flow | |
||
| 65 | | `amount` | `float64` | Yes | Positive amount expected | Passed to BBL confirm call | |
||
| 66 | | `additionalRef` | `string` | No | None | Optional pass-through | |
||
| 67 | | `smsMobileNo` | `string` | No | None | Only forwarded in PromptPay confirm flow | |
||
| 68 | | `smsLanguage` | `string` | No | None | Only forwarded in PromptPay confirm flow | |
||
| 69 | | `lookupRef` | `string` | Yes | Non-empty | Must come from `/validation` response | |
||
| 70 | | `origRequestRef` | `string` | Yes | Non-empty | Must be the `requestRef` from `/validation`, not the local `transactionID` | |
||
| 71 | |||
| 72 | ### `POST /confirm` Success Response |
||
| 73 | |||
| 74 | | Field | Type | Description | |
||
| 75 | |---|---|---| |
||
| 76 | | `responseCode` | `string` | BBL/business result code | |
||
| 77 | | `responseMesg` | `string` | Human-readable message | |
||
| 78 | | `transactionID` | `string` | Local `transactions.id` resolved from `origRequestRef` | |
||
| 79 | | `customerID` | `int` | Local customer ID from the transaction | |
||
| 80 | | `bankRefNo` | `string` | BBL bank transfer reference number | |
||
| 81 | |||
| 82 | ### `GET /inquiry/:transactionID` Request Contract |
||
| 83 | |||
| 84 | | Item | Type | Required | Notes | |
||
| 85 | |---|---|---|---| |
||
| 86 | | `transactionID` | `path param` | Yes | Local `transactions.id`, not BBL reference | |
||
| 87 | |||
| 88 | ### `GET /inquiry/:transactionID` Success Response |
||
| 89 | |||
| 90 | | Field | Type | Description | |
||
| 91 | |---|---|---| |
||
| 92 | | `responseCode` | `string` | BBL/business result code | |
||
| 93 | | `responseMesg` | `string` | Human-readable message | |
||
| 94 | | `transactionID` | `string` | Local transaction ID | |
||
| 95 | | `customerID` | `int` | Local customer ID | |
||
| 96 | | `inqRspCode` | `string` | BBL inquiry response code | |
||
| 97 | | `inqRspMesg` | `string` | BBL inquiry response message | |
||
| 98 | | `origRequestRef` | `string` | Original BBL request reference used in confirm | |
||
| 99 | | `origTransDateTime` | `string` | Original transmit time used in confirm | |
||
| 100 | | `bankRefNo` | `string` | BBL bank reference number | |
||
| 101 | |||
| 102 | ### API Payment Status Lifecycle |
||
| 103 | |||
| 104 | | Event | Local status | |
||
| 105 | |---|---| |
||
| 106 | | Validation success and stored customer name matches BBL receiver name | `1100` (`VerifyStatus`) | |
||
| 107 | | Validation success but stored customer name does not match BBL receiver name | `2000` (`WaitingApprovalStatus`) | |
||
| 108 | | Confirm success | `5000` (`CompleteStatus`) | |
||
| 109 | |||
| 110 | ### API Payment Implementation Notes |
||
| 111 | |||
| 112 | | Topic | Detail | |
||
| 113 | |---|---| |
||
| 114 | | Route prefix | The actual route prefix is `/api/v1/bank/apipayment`; it does not include `/bbl` | |
||
| 115 | | Error handling | Handler returns HTTP `400` with `{"error":"..."}` on bind, validation, or usecase failures | |
||
| 116 | | Name matching | Validation compares BBL `receiverName` with locally stored `EN_NAME + EN_SURNAME`; mismatch does not fail validation, but changes local status to waiting approval | |
||
| 117 | | Inquiry input model | Inquiry reconstructs the outbound BBL payload from DB state; the caller only supplies local `transactionID` | |
||
| 118 | |||
| 119 | --- |
||
| 120 | |||
| 121 | ## ThaiQR |
||
| 122 | |||
| 123 | ### Module Summary |
||
| 124 | |||
| 125 | | Item | Value | |
||
| 126 | |---|---| |
||
| 127 | | Base path | `/api/v1/bank/bbl/thaiqr` | |
||
| 128 | | Purpose | Generate ThaiQR payloads, receive BBL callbacks, query payment status, and refund completed ThaiQR transactions | |
||
| 129 | | Inbound content type | `application/json` | |
||
| 130 | | Local transaction type | `ThaiQR` | |
||
| 131 | | OAuth model | Local `GET /access-token` fetches BBL token; caller then passes that token payload back into `POST /inquiry/:transactionID` and `POST /refund/:transactionID` | |
||
| 132 | | Callback auth | JWT signature in `Signature` header | |
||
| 133 | | Outbound auth to BBL | Bearer token + RSA/JWT signature | |
||
| 134 | | Primary local lifecycle | `1000` pending -> `1100` verified -> `5000` complete | |
||
| 135 | |||
| 136 | ### Endpoint Matrix |
||
| 137 | |||
| 138 | | Method | Path | Purpose | Local side effects | |
||
| 139 | |---|---|---|---| |
||
| 140 | | `GET` | `/access-token` | Fetch BBL OAuth access token | None | |
||
| 141 | | `POST` | `/generate-qr` | Create local pending ThaiQR transaction and generate QR string | Inserts `thaiqr` + `transactions`; status becomes `1000` | |
||
| 142 | | `POST` | `/verify` | Receive BBL verification callback | Updates ThaiQR transaction date/time; status becomes `1100` | |
||
| 143 | | `POST` | `/noti` | Receive final BBL payment notification callback | Updates ThaiQR payment metadata; status becomes `5000` | |
||
| 144 | | `POST` | `/inquiry/:transactionID` | Query BBL for ThaiQR payment result using local transaction ID | None locally besides read-side lookup | |
||
| 145 | | `POST` | `/refund/:transactionID` | Refund a completed ThaiQR transaction | Inserts refund transaction and updates refund status fields | |
||
| 146 | |||
| 147 | ### `GET /access-token` Contract |
||
| 148 | |||
| 149 | | Item | Value | |
||
| 150 | |---|---| |
||
| 151 | | Request body | None | |
||
| 152 | | Local method | `GET` | |
||
| 153 | | Upstream method | `POST` with `application/x-www-form-urlencoded` | |
||
| 154 | | Upstream grant type | `client_credentials` | |
||
| 155 | | Upstream extra form field | `expireIn=86399` | |
||
| 156 | |||
| 157 | ### `GET /access-token` Success Response |
||
| 158 | |||
| 159 | | Field | Type | Description | |
||
| 160 | |---|---|---| |
||
| 161 | | `accessToken` | `string` | OAuth bearer token for ThaiQR inquiry/refund | |
||
| 162 | | `expiresIn` | `string` | Token TTL from BBL | |
||
| 163 | | `scope` | `string` | Granted scope | |
||
| 164 | |||
| 165 | ### `POST /generate-qr` Request Contract |
||
| 166 | |||
| 167 | | Field | Type | Required | Constraints | Notes | |
||
| 168 | |---|---|---|---|---| |
||
| 169 | | `transactionAmount` | `float64` | Yes | `<= 9999999999999.99` | Amount encoded into QR and stored in DB | |
||
| 170 | | `customerId` | `string` | Yes | Must parse to integer | Used as local transaction customer ID | |
||
| 171 | |||
| 172 | ### `POST /generate-qr` Success Response |
||
| 173 | |||
| 174 | | Field | Type | Description | |
||
| 175 | |---|---|---| |
||
| 176 | | `status` | `string` | Common response wrapper status, expected `success` | |
||
| 177 | | `data.qr.qrCode` | `string` | Generated EMV ThaiQR payload | |
||
| 178 | | `data.qr.billerId` | `string` | Biller ID + suffix used in the QR | |
||
| 179 | | `data.qr.reference1` | `string` | Generated reference 1 | |
||
| 180 | | `data.qr.reference2` | `string` | Generated reference 2 | |
||
| 181 | |||
| 182 | ### ThaiQR Callback Contract |
||
| 183 | |||
| 184 | | Method | Path | Required headers | Request body | Success response | Local side effects | |
||
| 185 | |---|---|---|---|---|---| |
||
| 186 | | `POST` | `/verify` | `Signature`, `Request-Ref` | `billerId`, `amount`, `transDate`, `transTime`, `reference1`, `reference2`, `reference3` | `responseCode="000"`, `responseMesg="Success"` plus response headers `Request-Ref`, `Signature`, `Transmit-Date-Time` | Verifies JWT signature, updates ThaiQR date/time, sets status `1100` | |
||
| 187 | | `POST` | `/noti` | `Signature`, `Request-Ref` | `type`, and `data.{billerId, amount, transDate, transTime, termType, fromBank, fromName, retryFlag, approvalCode, bankRef, reference1, reference2, reference3}` | `responseCode="000"`, `responseMesg="Success"` plus response headers `Request-Ref`, `Signature`, `Transmit-Date-Time` | Verifies JWT signature, updates bank/payment metadata, sets status `5000` | |
||
| 188 | |||
| 189 | ### `POST /inquiry/:transactionID` Request Contract |
||
| 190 | |||
| 191 | | Item | Type | Required | Notes | |
||
| 192 | |---|---|---|---| |
||
| 193 | | `transactionID` | `path param` | Yes | Local `transactions.id` | |
||
| 194 | | `accessToken` | `string` | Yes | Passed in request body using `AccessTokenResponse` shape | |
||
| 195 | | `expiresIn` | `string` | No | Bound but not required by business logic | |
||
| 196 | | `scope` | `string` | No | Bound but not required by business logic | |
||
| 197 | |||
| 198 | ### `POST /inquiry/:transactionID` Success Response |
||
| 199 | |||
| 200 | | Field | Type | Description | |
||
| 201 | |---|---|---| |
||
| 202 | | `responseCode` | `string` | BBL/business result code | |
||
| 203 | | `responseMesg` | `string` | Human-readable message | |
||
| 204 | | `data.billerId` | `string` | Biller ID | |
||
| 205 | | `data.transDate` | `string` | Payment date | |
||
| 206 | | `data.transTime` | `string` | Payment time | |
||
| 207 | | `data.termType` | `string` | Terminal type | |
||
| 208 | | `data.amount` | `string` | Paid amount | |
||
| 209 | | `data.reference1` | `string` | ThaiQR reference 1 | |
||
| 210 | | `data.reference2` | `string` | ThaiQR reference 2 | |
||
| 211 | | `data.reference3` | `string` | ThaiQR reference 3 | |
||
| 212 | | `data.fromBank` | `string` | Payer bank code | |
||
| 213 | | `data.fromName` | `string` | Payer display name | |
||
| 214 | | `data.approvalCode` | `string` | Approval code | |
||
| 215 | |||
| 216 | ### `POST /refund/:transactionID` Request Contract |
||
| 217 | |||
| 218 | | Item | Type | Required | Notes | |
||
| 219 | |---|---|---|---| |
||
| 220 | | `transactionID` | `path param` | Yes | Local `transactions.id` | |
||
| 221 | | `accessToken` | `string` | Yes | Passed in request body using `AccessTokenResponse` shape | |
||
| 222 | | `expiresIn` | `string` | No | Bound but not required by business logic | |
||
| 223 | | `scope` | `string` | No | Bound but not required by business logic | |
||
| 224 | |||
| 225 | ### `POST /refund/:transactionID` Success Response |
||
| 226 | |||
| 227 | | Field | Type | Description | |
||
| 228 | |---|---|---| |
||
| 229 | | `responseCode` | `string` | Expected `000` on success | |
||
| 230 | | `responseMesg` | `string` | Expected `Success` on success | |
||
| 231 | |||
| 232 | ### ThaiQR Refund Preconditions and Lifecycle |
||
| 233 | |||
| 234 | | Step | Rule / Result | |
||
| 235 | |---|---| |
||
| 236 | | Precondition | Target local transaction must already be `5000` (`CompleteStatus`) | |
||
| 237 | | Precondition | Target local transaction must have `refunded=false` | |
||
| 238 | | Refund verification success | Creates refund transaction with status `3100` (`VerifyRefundstatus`) | |
||
| 239 | | Refund verification failure | Creates refund transaction with status `3150` (`VerifyRefundFailStatus`) | |
||
| 240 | | Refund advice success | Updates refund status to `3200` (`AdviceRefundStatus`) and marks original transaction `refunded=true` | |
||
| 241 | | Refund advice failure | Updates refund status to `3250` (`AdviceRefundFailStatus`) | |
||
| 242 | | Refund reversal success | Updates refund status to `3300` (`ReversalRefundStaus`) | |
||
| 243 | | Refund reversal failure | Updates refund status to `3350` (`ReversalRefundFailStaus`) | |
||
| 244 | |||
| 245 | ### ThaiQR Implementation Notes |
||
| 246 | |||
| 247 | | Topic | Detail | |
||
| 248 | |---|---| |
||
| 249 | | Route semantics | The actual callback endpoints registered in the router are `/verify` and `/noti` | |
||
| 250 | | Error handling: access token | Normal failures return HTTP `400` with `{"message":"..."}`; connection-reset case is mapped to response code `999` | |
||
| 251 | | Error handling: generate QR | Validation failures return common response with failure status; usecase failures return common error response | |
||
| 252 | | Error handling: verify/noti/refund | Business-level failures still return HTTP `200`, with non-`000` `responseCode` in the response body | |
||
| 253 | | Token handoff | `/inquiry/:transactionID` and `/refund/:transactionID` expect the caller to POST the previously obtained token payload in the request body rather than using an inbound `Authorization` header | |
||
| 254 | | TLS behavior | Current ThaiQR outbound HTTP calls use clients with TLS verification disabled (`InsecureSkipVerify=true`) | |
||
| 255 | |||
| 256 | --- |
||
| 257 | |||
| 258 | ## Common Identifier Semantics |
||
| 259 | |||
| 260 | | Field | Meaning | |
||
| 261 | |---|---| |
||
| 262 | | `transactionID` | Local `transactions.id` created by this service | |
||
| 263 | | `requestRef` | BBL request reference generated by this service and sent in `Request-Ref` | |
||
| 264 | | `origRequestRef` | Existing `requestRef` from a prior API Payment validation/confirm step | |
||
| 265 | | `lookupRef` | BBL lookup reference returned by API Payment validation | |
||
| 266 | | `bankRefNo` | BBL transfer reference returned in API Payment flows | |
||
| 267 | | `bankRef` | BBL bank reference used in ThaiQR notification data | |
||
| 268 | | `reference1`, `reference2`, `reference3` | ThaiQR merchant/payment references stored in the `thaiqr` record | |
||
| 269 | |||
| 270 | ## Common BBL Response Codes Seen in These Flows |
||
| 271 | |||
| 272 | | Code | Meaning | |
||
| 273 | |---|---| |
||
| 274 | | `000` | Success | |
||
| 275 | | `052` | Unknown Biller ID | |
||
| 276 | | `054` | System unavailable | |
||
| 277 | | `209` | Transaction not found | |
||
| 278 | | `210` | Time out | |
||
| 279 | | `211` | Invalid data | |
||
| 280 | | `215` | Invalid token | |
||
| 281 | | `341` | Service not ready | |
||
| 282 | | `888` | Other error | |
||
| 283 | | `999` | Connection reset by peer | |