Thực hiện request đầu tiên
Hướng dẫn này đi qua từng bước xây dựng và gửi một request đã xác thực đến Bahn API. Chúng ta sử dụng POST /api/playground/concat-strings làm ví dụ minh họa.
Điều kiện tiên quyết
private_key.jsontừ Tạo cặp khóa.- Một
Bn-Api-Keyđược Bahn cấp trong quá trình onboarding.
Chi tiết API
| Phương thức | POST |
| URL | https://bn.cakedigibank.tech/api/playground/concat-strings |
| Mô tả | Nối hai chuỗi và trả về kết quả. |
| Request | string1 (string) — chuỗi thứ nhất; string2 (string) — chuỗi thứ hai |
| Response | result (string) — kết quả nối hai chuỗi |
Chuẩn bị nội dung request
Nội dung request gốc
Request body JSON thô cho endpoint này:
{
"string1": "Hello",
"string2": "World"
}JWS payload
Mã hóa base64url request body gốc để tạo JWS payload:
eyJzdHJpbmcxIjoiSGVsbG8iLCJzdHJpbmcyIjoiV29ybGQifQJWS header
| Trường | Giá trị | Mục đích |
|---|---|---|
alg | ES256 | Thuật toán ký. |
typ | jose+json | Loại token. |
kid | ID signing key của bạn | Cho Bahn biết public key đã đăng ký nào để xác minh. |
url | đường dẫn request | Ràng buộc token với endpoint này — ngăn chặn replay sang một API khác. |
iat | Unix timestamp (giây) | Cho phép Bahn từ chối các token đã cũ. |
Xây dựng protected header JSON với các trường bắt buộc:
{
"alg": "ES256",
"iat": 1773720077,
"kid": "00b06c00-3278-4f09-bf9b-6825cf889131",
"typ": "jose+json",
"url": "/api/playground/concat-strings"
}Sau đó mã hóa base64url:
eyJhbGciOiJFUzI1NiIsImlhdCI6MTc3MzcyMDA3Nywia2lkIjoiMDBiMDZjMDAtMzI3OC00ZjA5LWJmOWItNjgyNWNmODg5MTMxIiwidHlwIjoiam9zZStqc29uIiwidXJsIjoiL2FwaS9wbGF5Z3JvdW5kL2NvbmNhdC1zdHJpbmdzIn0JWS signature
Ký chuỗi base64url(header) + "." + base64url(payload) bằng private signing key ES256 của bạn, sau đó mã hóa base64url kết quả:
bH6Q--yv7BQUZsK1gP-n1kBqMWe2ZlNfbzyRjl2-Qh3VGuAEwSuc_Zvp6kc3LIANSkp5jr-SU0tUUajia4axvQTạo signed body
Kết hợp ba phần để tạo thành signed body cuối cùng. Chọn định dạng phù hợp với hệ thống tích hợp của bạn.
Phương án A — Compact serialization (application/jose)
Nối header, payload, và signature với dấu phân cách .:
eyJhbGciOiJFUzI1NiIsImlhdCI6MTc3MzcyMDA3Nywia2lkIjoiMDBiMDZjMDAtMzI3OC00ZjA5LWJmOWItNjgyNWNmODg5MTMxIiwidHlwIjoiam9zZStqc29uIiwidXJsIjoiL2FwaS9wbGF5Z3JvdW5kL2NvbmNhdC1zdHJpbmdzIn0
.
eyJzdHJpbmcxIjoiSGVsbG8iLCJzdHJpbmcyIjoiV29ybGQifQ
.
bH6Q--yv7BQUZsK1gP-n1kBqMWe2ZlNfbzyRjl2-Qh3VGuAEwSuc_Zvp6kc3LIANSkp5jr-SU0tUUajia4axvQ<base64url(header)>.<base64url(payload)>.<base64url(signature)>
Phương án B — JSON serialization (application/jose+json)
Bọc các phần trong một đối tượng JSON (RFC 7515 §7.2):
{
"payload": "eyJzdHJpbmcxIjoiSGVsbG8iLCJzdHJpbmcyIjoiV29ybGQifQ",
"signatures": [
{
"protected": "eyJhbGciOiJFUzI1NiIsImlhdCI6MTc3MzcyMDA3Nywia2lkIjoiMDBiMDZjMDAtMzI3OC00ZjA5LWJmOWItNjgyNWNmODg5MTMxIiwidHlwIjoiam9zZStqc29uIiwidXJsIjoiL2FwaS9wbGF5Z3JvdW5kL2NvbmNhdC1zdHJpbmdzIn0",
"signature": "bH6Q--yv7BQUZsK1gP-n1kBqMWe2ZlNfbzyRjl2-Qh3VGuAEwSuc_Zvp6kc3LIANSkp5jr-SU0tUUajia4axvQ"
}
]
}Khi sử dụng
application/jose+jsonvới nhiều chữ ký, Bahn sử dụng mục đầu tiên để xác minh.
Chuẩn bị header
HTTP header
| Header | Giá trị | Ghi chú |
|---|---|---|
Content-Type | application/jose hoặc application/jose+json | Định dạng của signed body. |
Bn-Api-Key | API key của bạn | Nhận diện tài khoản merchant của bạn. |
Bn-Jose-Method | jws | Cho Bahn biết body đã được ký bằng JWS. |
Bn-Request-Id | UUID mới cho mỗi lần thử | Phải là một giá trị mới ở mỗi lần thử, kể cả khi retry. |
Bn-Idempotency-Key | key ổn định cho mỗi thao tác | Phải giữ nguyên qua các lần retry của cùng một thao tác. |
Gửi request
Compact serialization:
curl -X POST 'https://bn.cakedigibank.tech/api/playground/concat-strings' \
-H 'Content-Type: application/jose' \
-H 'Bn-Api-Key: api_key-ab318df034' \
-H 'Bn-Jose-Method: jws' \
-H 'Bn-Request-Id: 7f3e9c1a-4b2d-4e8f-a1c0-123456789abc' \
-H 'Bn-Idempotency-Key: demo-concat-001' \
-d 'eyJhbGciOiJFUzI1NiIsImlhdCI6MTc3MzcyMDA3Nywia2lkIjoiMDBiMDZjMDAtMzI3OC00ZjA5LWJmOWItNjgyNWNmODg5MTMxIiwidHlwIjoiam9zZStqc29uIiwidXJsIjoiL2FwaS9wbGF5Z3JvdW5kL2NvbmNhdC1zdHJpbmdzIn0.eyJzdHJpbmcxIjoiSGVsbG8iLCJzdHJpbmcyIjoiV29ybGQifQ.bH6Q--yv7BQUZsK1gP-n1kBqMWe2ZlNfbzyRjl2-Qh3VGuAEwSuc_Zvp6kc3LIANSkp5jr-SU0tUUajia4axvQ'JSON serialization:
curl -X POST 'https://bn.cakedigibank.tech/api/playground/concat-strings' \
-H 'Content-Type: application/jose+json' \
-H 'Bn-Api-Key: api_key-ab318df034' \
-H 'Bn-Jose-Method: jws' \
-H 'Bn-Request-Id: 7f3e9c1a-4b2d-4e8f-a1c0-123456789abc' \
-H 'Bn-Idempotency-Key: demo-concat-001' \
-d '{
"payload": "eyJzdHJpbmcxIjoiSGVsbG8iLCJzdHJpbmcyIjoiV29ybGQifQ",
"signatures": [
{
"protected": "eyJhbGciOiJFUzI1NiIsImlhdCI6MTc3MzcyMDA3Nywia2lkIjoiMDBiMDZjMDAtMzI3OC00ZjA5LWJmOWItNjgyNWNmODg5MTMxIiwidHlwIjoiam9zZStqc29uIiwidXJsIjoiL2FwaS9wbGF5Z3JvdW5kL2NvbmNhdC1zdHJpbmdzIn0",
"signature": "bH6Q--yv7BQUZsK1gP-n1kBqMWe2ZlNfbzyRjl2-Qh3VGuAEwSuc_Zvp6kc3LIANSkp5jr-SU0tUUajia4axvQ"
}
]
}'Response mong đợi:
{
"result": "HelloWorld"
}Ví dụ code
1. Thiết lập
mkdir bahn-first-request && cd bahn-first-request
go mod init bahn-first-request
go get github.com/go-jose/go-jose/v4
go get github.com/google/uuid2. Viết code
Tạo file main.go:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"time"
"github.com/go-jose/go-jose/v4"
"github.com/google/uuid"
)
const (
baseURL = "https://bn.cakedigibank.tech"
endpoint = "/api/playground/concat-strings"
signKid = "00b06c00-3278-4f09-bf9b-6825cf889131"
apiKey = "api_key-ab318df034"
)
// loadSignKey đọc private_key.json và trả về key khớp với signKid.
func loadSignKey(path string) (jose.JSONWebKey, error) {
data, err := os.ReadFile(path)
if err != nil {
return jose.JSONWebKey{}, fmt.Errorf("read %s: %w", path, err)
}
var keySet jose.JSONWebKeySet
if err := json.Unmarshal(data, &keySet); err != nil {
return jose.JSONWebKey{}, fmt.Errorf("unmarshal jwks: %w", err)
}
keys := keySet.Key(signKid)
if len(keys) == 0 {
return jose.JSONWebKey{}, fmt.Errorf("key %s not found in %s", signKid, path)
}
return keys[0], nil
}
func main() {
// 1. Nạp private signing key.
signKey, err := loadSignKey("private_key.json")
if err != nil {
log.Fatalf("load sign key: %v", err)
}
// 2. Xây dựng request payload.
payload, err := json.Marshal(map[string]string{
"string1": "Hello",
"string2": "World",
})
if err != nil {
log.Fatalf("marshal payload: %v", err)
}
// 3. Ký payload dưới dạng JWS compact.
signer, err := jose.NewSigner(
jose.SigningKey{Algorithm: jose.ES256, Key: signKey},
(&jose.SignerOptions{}).
WithType("jose+json").
WithHeader("kid", signKid).
WithHeader("url", endpoint).
WithHeader("iat", time.Now().Unix()),
)
if err != nil {
log.Fatalf("new signer: %v", err)
}
jws, err := signer.Sign(payload)
if err != nil {
log.Fatalf("sign payload: %v", err)
}
compact, err := jws.CompactSerialize()
if err != nil {
log.Fatalf("serialize jws: %v", err)
}
fmt.Printf("jws: %s\n\n", compact)
// 4. Gửi HTTP request.
req, err := http.NewRequest(http.MethodPost, baseURL+endpoint, bytes.NewBufferString(compact))
if err != nil {
log.Fatalf("build request: %v", err)
}
req.Header.Set("Content-Type", "application/jose")
req.Header.Set("Bn-Api-Key", apiKey)
req.Header.Set("Bn-Jose-Method", "jws")
req.Header.Set("Bn-Request-Id", uuid.NewString())
req.Header.Set("Bn-Idempotency-Key", "demo-concat-001")
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatalf("do request: %v", err)
}
defer resp.Body.Close()
// 5. In ra response.
body, _ := io.ReadAll(resp.Body)
fmt.Printf("status : %d\nresponse: %s\n", resp.StatusCode, body)
}3. Chạy
Sao chép private_key.json vào thư mục dự án, sau đó:
go run main.goKết quả mong đợi:
jws: eyJhbGciOiJFUzI1NiIsImlhdCI6...
status : 200
response: {"result":"HelloWorld"}Bước tiếp theo
- Khám phá Tài liệu tham chiếu API để bắt đầu xây dựng các luồng thanh toán thực tế.