Skip to content

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.json từ 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ứcPOST
URLhttps://bn.cakedigibank.tech/api/playground/concat-strings
Mô tảNối hai chuỗi và trả về kết quả.
Requeststring1 (string) — chuỗi thứ nhất; string2 (string) — chuỗi thứ hai
Responseresult (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:

json
{
  "string1": "Hello",
  "string2": "World"
}

JWS payload ​

Mã hóa base64url request body gốc để tạo JWS payload:

eyJzdHJpbmcxIjoiSGVsbG8iLCJzdHJpbmcyIjoiV29ybGQifQ

JWS header ​

TrườngGiá trịMục đích
algES256Thuật toán ký.
typjose+jsonLoại token.
kidID signing key của bạnCho Bahn biết public key đã đăng ký nào để xác minh.
urlđường dẫn requestRàng buộc token với endpoint này — ngăn chặn replay sang một API khác.
iatUnix 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:

json
{
  "alg": "ES256",
  "iat": 1773720077,
  "kid": "00b06c00-3278-4f09-bf9b-6825cf889131",
  "typ": "jose+json",
  "url": "/api/playground/concat-strings"
}

Sau đó mã hóa base64url:

eyJhbGciOiJFUzI1NiIsImlhdCI6MTc3MzcyMDA3Nywia2lkIjoiMDBiMDZjMDAtMzI3OC00ZjA5LWJmOWItNjgyNWNmODg5MTMxIiwidHlwIjoiam9zZStqc29uIiwidXJsIjoiL2FwaS9wbGF5Z3JvdW5kL2NvbmNhdC1zdHJpbmdzIn0

JWS 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-SU0tUUajia4axvQ

Tạ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 .:

text
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):

json
{
  "payload": "eyJzdHJpbmcxIjoiSGVsbG8iLCJzdHJpbmcyIjoiV29ybGQifQ",
  "signatures": [
    {
      "protected": "eyJhbGciOiJFUzI1NiIsImlhdCI6MTc3MzcyMDA3Nywia2lkIjoiMDBiMDZjMDAtMzI3OC00ZjA5LWJmOWItNjgyNWNmODg5MTMxIiwidHlwIjoiam9zZStqc29uIiwidXJsIjoiL2FwaS9wbGF5Z3JvdW5kL2NvbmNhdC1zdHJpbmdzIn0",
      "signature": "bH6Q--yv7BQUZsK1gP-n1kBqMWe2ZlNfbzyRjl2-Qh3VGuAEwSuc_Zvp6kc3LIANSkp5jr-SU0tUUajia4axvQ"
    }
  ]
}

Khi sử dụng application/jose+json với nhiều chữ ký, Bahn sử dụng mục đầu tiên để xác minh.


Chuẩn bị header ​

HTTP header ​

HeaderGiá trịGhi chú
Content-Typeapplication/jose hoặc application/jose+jsonĐịnh dạng của signed body.
Bn-Api-KeyAPI key của bạnNhận diện tài khoản merchant của bạn.
Bn-Jose-MethodjwsCho Bahn biết body đã được ký bằng JWS.
Bn-Request-IdUUID 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-Keykey ổn định cho mỗi thao tácPhải giữ nguyên qua các lần retry của cùng một thao tác.

Gửi request ​

Compact serialization:

bash
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:

bash
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:

json
{
  "result": "HelloWorld"
}

Ví dụ code ​

1. Thiết lập ​

bash
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/uuid

2. Viết code ​

Tạo file main.go:

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 đó:

bash
go run main.go

Kết quả mong đợi:

jws: eyJhbGciOiJFUzI1NiIsImlhdCI6...

status  : 200
response: {"result":"HelloWorld"}

Bước tiếp theo ​

Developers