Tài liệu / Hướng dẫn tích hợp Webview FE

Hướng dẫn tích hợp Webview FE

Tài liệu FE tích hợp EKYC Middleware

Tài liệu bàn giao cho team Frontend (Webview) — hướng dẫn từng bước call API, kèm curl, params và response đầy đủ.

1. Tổng quan luồng

FE (Webview)                        BE Middleware                        Core EKYC (AccessTrade)
     │                                    │                                    │
     │  1. Test session (auto-auth)       │                                    │
     │───────────────────────────────────>│                                    │
     │      ← session_id + webview_token  │                                    │
     │                                    │                                    │
     │  2. Set webview_token              │                                    │
     │     Authorization: Bearer <token>  │                                    │
     │                                    │                                    │
     │  3. init                           │                                    │
     │───────────────────────────────────>│──── FPT SDK URL (nếu có config) ──>│
     │  4. submit-identity                │                                    │
     │───────────────────────────────────>│                                    │
     │  5. submit-face                    │                                    │
     │───────────────────────────────────>│                                    │
     │  6. submit-ekyc-result             │──── tạo hợp đồng (nếu EKYC OK) ───>│
     │───────────────────────────────────>│                                    │
     │  7. sign-contract                  │                                    │
     │───────────────────────────────────>│                                    │
     │      ← contract_ref + status       │                                    │

2. Chuẩn bị dữ liệu partner

Chạy 1 lần trên server (seed 3 partner: VPBANK, TPBANK, AT):

php artisan partner:seed
Partner AT dùng nội bộ để FE test. Lệnh an toàn khi chạy lại (không ghi đè dữ liệu cũ).

3. Format response chuẩn

Mọi API trả về đúng format:

{
  "status": "success",
  "data": { "...": "nội dung nghiệp vụ" },
  "meta": {
    "request_id": "uuid",
    "timestamp": "2026-08-07T00:00:00+00:00",
    "response_time_ms": 15
  },
  "code": null,
  "message": null
}

4. Bước 1 — Tạo test session (auto xác thực)

API này không cần JWT — 1 lần call là có đủ session_id + webview_token để FE set vào webview và test toàn bộ luồng. Chỉ hoạt động ở môi trường khác production.

Request

POST /api/v1/partner/test-session
ParamTypeBắt buộcMô tả
user_idstring✅Mã user bên partner
partner_codestring❌Mã partner, mặc định AT
partner_tx_idstring❌Mã giao dịch partner, tự sinh TEST_... nếu bỏ trống
account_numberstring❌Số tài khoản
identity_infoobject❌Thông tin sẵn (email, full_name...)
expired_minutesint❌Thời hạn session (1-1440), mặc định 30

Curl

curl -X POST https://at-product-family-econtract-api.dev.oneat.org/api/v1/partner/test-session \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "user_id": "USER_001",
    "partner_code": "AT",
    "identity_info": {
      "email": "user001@test.com",
      "full_name": "Nguyễn Văn A"
    }
  }'

Response 200

{
  "status": "success",
  "data": {
    "session_id": "ekyc_CTBi1kLoVsZCV2DsC0Yz",
    "webview_url": "https://ekyc-middleware.test/webview/ekyc_CTBi1kLoVsZCV2DsC0Yz",
    "webview_token": "buvwE2KukoEEbhYiUYGsCQESf1X2XBqClF9xFXiZPRWlpSPRmQLUr5UbmbwLT4kH",
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJwYXJ0bmVyX2NvZGUiOiJBVCIs...",
    "access_token_expires_at": "2026-08-07T00:15:48+00:00",
    "expires_in": 3600,
    "refresh_token": "PcmxzgZE1njKbMy0tGGt6o1gioyc9HR9R9F1vPiAxKHZuiJaRUvKeyW8DWYwG7RG",
    "refresh_token_expires_at": "2026-08-13T23:15:48+00:00",
    "session_expired_at": "2026-08-06T23:45:48+00:00"
  },
  "meta": {
    "request_id": "25258191-cbe0-4bf5-9457-9f094b754690",
    "timestamp": "2026-08-06T23:15:49+00:00",
    "response_time_ms": 244
  }
}

Trường quan trọng cho FE

TrườngDùng để
webview_tokenSet vào header Authorization: Bearer <webview_token> cho mọi call webview API
session_idTruyền trong body session_id của webview API
webview_urlURL load webview (thường .../webview/{session_id})
access_tokenJWT RS256 (dự phòng cho partner flow)
session_expired_atHạn session — hết hạn thì phải tạo session mới

5. Webview API (dùng webview_token)

Bắt buộc: mọi request webview phải có header Authorization: Bearer <webview_token> (lấy từ bước 1). Thiếu/sai token → 401.

5.1. Init webview

POST /api/v1/webview/init
ParamTypeBắt buộcMô tả
session_idstring❌Không bắt buộc — session tự xác định qua Authorization: Bearer <webview_token>
emailstring❌Email từ bước nhập email trên UI — cần có để Core phát ekyc_sdk_url / ekyc_sdk_token

Curl

curl -X POST https://at-product-family-econtract-api.dev.oneat.org/api/v1/webview/init \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <webview_token>" \
  -d '{"email": "user001@test.com"}'

Response 200

{
  "status": "success",
  "data": {
    "session_id": "ekyc_pFPmNibARmmDopXXd3kI",
    "partner_code": "AT",
    "user_id": "USER_002",
    "status": "processing",
    "identity_info": { "email": "b@test.com", "full_name": "Trần Văn B" },
    "contract_ref": "TEST_WCQHIVOB96WY",
    "expired_at": "2026-08-07T00:22:43+00:00",
    "ekyc_sdk_url": null,
    "ekyc_sdk_token": null,
    "ekyc_sdk_token_name": null,
    "ekyc_sdk_access_token": null,
    "ekyc_sdk_resource_id": "4320855b-d9da-4f6f-9807-6b1744bcea37"
  },
  "meta": { "request_id": "...", "timestamp": "...", "response_time_ms": 15 }
}
ekyc_sdk_url / ekyc_sdk_token / ekyc_sdk_token_name / ekyc_sdk_access_token: URL + token + tên token + access_token FPT SDK từ Core EKYC — chỉ trả về khi có email ở request và partner đã cấu hình core_access_key. Nếu null thì FE dùng SDK/UI tự quản lý (hoặc chưa gửi email). ekyc_sdk_resource_id: UUID resource_id mà BE đã gửi lên Core cho phiên SDK này — dùng để đối chiếu/khớp session FPT nếu cần.

5.2. Nộp giấy tờ

POST /api/v1/webview/submit-identity
ParamTypeBắt buộcMô tả
session_idstring❌Không bắt buộc — xác định qua webview_token
identity_data.front_image_urlstring(url)✅Ảnh mặt trước CMND/CCCD
identity_data.back_image_urlstring(url)✅Ảnh mặt sau CMND/CCCD
identity_data.id_numberstring✅9-12 chữ số
identity_data.full_namestring✅Họ tên
identity_data.date_of_birthstring✅Định dạng Y-m-d

Curl

curl -X POST https://at-product-family-econtract-api.dev.oneat.org/api/v1/webview/submit-identity \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <webview_token>" \
  -d '{
    "session_id": "ekyc_CTBi1kLoVsZCV2DsC0Yz",
    "identity_data": {
      "front_image_url": "https://cdn.test/front.jpg",
      "back_image_url": "https://cdn.test/back.jpg",
      "id_number": "079201012345",
      "full_name": "Nguyễn Văn A",
      "date_of_birth": "1990-01-01"
    }
  }'

Response 200

{
  "status": "success",
  "data": { "status": "processing", "next_step": "face_match" },
  "meta": { "...": "..." }
}

5.3. Nộp khuôn mặt

POST /api/v1/webview/submit-face
ParamTypeBắt buộcMô tả
session_idstring❌Không bắt buộc — xác định qua webview_token
face_data.face_image_urlstring(url)✅Ảnh khuôn mặt
face_data.liveness_video_urlstring(url)❌Video liveness

Curl

curl -X POST https://at-product-family-econtract-api.dev.oneat.org/api/v1/webview/submit-face \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <webview_token>" \
  -d '{
    "session_id": "ekyc_CTBi1kLoVsZCV2DsC0Yz",
    "face_data": { "face_image_url": "https://cdn.test/face.jpg" }
  }'

Response 200

{
  "status": "success",
  "data": {
    "face_matched": true,
    "liveness_passed": true,
    "score": 95.5,
    "next_step": "ekyc_result"
  },
  "meta": { "...": "..." }
}

5.4. Gửi kết quả EKYC

POST /api/v1/webview/submit-ekyc-result
ParamTypeBắt buộcMô tả
session_idstring❌Không bắt buộc — xác định qua webview_token
core_ekyc_result.tx_idstring✅Mã giao dịch Core EKYC
core_ekyc_result.statusstring✅completed hoặc failed
core_ekyc_result.identity_verifiedbool❌
core_ekyc_result.face_matchedbool❌
core_ekyc_result.liveness_passedbool❌
core_ekyc_result.scorenumber❌0-100

Curl

curl -X POST https://at-product-family-econtract-api.dev.oneat.org/api/v1/webview/submit-ekyc-result \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <webview_token>" \
  -d '{
    "session_id": "ekyc_CTBi1kLoVsZCV2DsC0Yz",
    "core_ekyc_result": {
      "tx_id": "CORE_TX_001",
      "status": "completed",
      "identity_verified": true,
      "face_matched": true,
      "liveness_passed": true,
      "score": 95.5
    }
  }'

Response 200 (Thành công)

{
  "status": "success",
  "data": {
    "session_status": "awaiting_sign",
    "contract_available": true,
    "contract_ref": "550e8400-e29b-41d4-a716-446655440000"
  },
  "meta": { "...": "..." }
}
Lưu ý: - contract_available: true khi đã tạo hợp đồng thành công trên Core, false khi partner chưa cấu hình Core hoặc chưa có hợp đồng. - contract_ref: Mã reference_id từ Core EKYC. - Nếu quá trình tạo HĐ trên Core bị lỗi, API sẽ trả về HTTP 400 với mã lỗi CONTRACT_CREATION_FAILED: ``json { "status": "error", "code": "CONTRACT_CREATION_FAILED", "message": "Xác thực EKYC thành công nhưng tạo hợp đồng thất bại: <chi tiết lỗi từ Core>", "data": null } ``

5.5. Ký hợp đồng

POST /api/v1/webview/sign-contract
ParamTypeBắt buộcMô tả
session_idstring❌Không bắt buộc — xác định qua webview_token
sign_methodstring✅otp / usb_token / esign
sign_data.signed_contentstring✅Nội dung đã ký (base64/hash)
sign_data.otp_refstring❌OTP reference nếu ký bằng OTP

Curl

curl -X POST https://at-product-family-econtract-api.dev.oneat.org/api/v1/webview/sign-contract \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <webview_token>" \
  -d '{
    "session_id": "ekyc_CTBi1kLoVsZCV2DsC0Yz",
    "sign_method": "otp",
    "sign_data": { "signed_content": "base64...", "otp_ref": "OTP_001" }
  }'

Response 200

{
  "status": "success",
  "data": {
    "contract_ref": "HD/2026/08/06/2",
    "status": "signed",
    "signed_at": "2026-08-06T23:52:11+00:00"
  },
  "meta": { "...": "..." }
}

5.6. Xem thông tin chi tiết hợp đồng

Dùng để kiểm tra trạng thái hợp đồng và lấy link ký trực tiếp trên cổng FPT eContract.
GET /api/v1/webview/contract/{session_id}

Curl

curl -X GET https://at-product-family-econtract-api.dev.oneat.org/api/v1/webview/contract/ekyc_CTBi1kLoVsZCV2DsC0Yz \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <webview_token>"

Response 200

{
  "status": "success",
  "data": {
    "contract_ref": "EKYCC_139",
    "status": "pending",
    "core_status": "WAITING_TO_SIGN",
    "link": "https://demo.econtract.kyta.fpt.com/signing/signature-process/000010zGi2lPcpexk8RKGFlQ6/p_002_r_001?jwt=eyJhbGci...",
    "signed_at": null
  },
  "meta": { "request_id": "...", "timestamp": "...", "response_time_ms": 25 }
}
Lưu ý: - link: Đường dẫn trực tiếp để mở trang ký hợp đồng FPT trên WebView hoặc iframe. - status: Trạng thái chuẩn hóa (pending, signed, failed, revoked, draft). - core_status: Trạng thái gốc từ Core (WAITING_TO_SIGN, SIGNED, SUBMIT, OVERDUE, VOIDED...).

5.7. Danh sách hợp đồng của user (1 User có nhiều HĐ)

Một user có thể có nhiều hợp đồng trong quá khứ (ví dụ: hợp đồng cũ hết hạn, bị hủy hoặc user tạo phiên mới). API này trả về toàn bộ danh sách hợp đồng của user hiện tại (xác thực qua webview_token), sắp xếp mới nhất lên đầu.
GET /api/v1/webview/contracts

Curl

curl -X GET https://at-product-family-econtract-api.dev.oneat.org/api/v1/webview/contracts \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <webview_token>"

Response 200

{
  "status": "success",
  "data": {
    "user_id": "USER_001",
    "total": 2,
    "contracts": [
      {
        "id": 140,
        "session_id": 140,
        "contract_ref": "EKYCC_140",
        "contract_number": "EKYCC_140",
        "template_code": "HOP_DONG_USER",
        "status": "pending",
        "core_status": "WAITING_TO_SIGN",
        "link": "https://demo.econtract.kyta.fpt.com/signing/signature-process/...",
        "signed_at": null,
        "sign_expire_at": "2026-11-16T15:41:07+07:00",
        "created_at": "2026-08-18T15:45:00+07:00"
      },
      {
        "id": 139,
        "session_id": 139,
        "contract_ref": "EKYCC_139",
        "contract_number": "EKYCC_139",
        "template_code": "HOP_DONG_USER",
        "status": "revoked",
        "core_status": "OVERDUE",
        "link": null,
        "signed_at": null,
        "sign_expire_at": "2026-08-15T10:00:00+07:00",
        "created_at": "2026-08-01T10:00:00+07:00"
      }
    ]
  },
  "meta": { "request_id": "...", "timestamp": "...", "response_time_ms": 12 }
}

5.8. Upload ảnh (trả link)

FE upload ảnh (CCCD/face) trước, lấy url rồi truyền vào identity_data.front_image_url / back_image_url / face_data.face_image_url.

Request: multipart/form-data

POST /api/v1/webview/upload
Authorization: Bearer <webview_token>
FieldTypeBắt buộcMô tả
filefile (ảnh)✅Ảnh cần upload, tối đa 10MB

Curl

curl -X POST https://at-product-family-econtract-api.dev.oneat.org/api/v1/webview/upload \
  -H "Authorization: Bearer <webview_token>" \
  -F "file=@front.jpg"

Response 200

{
  "status": "success",
  "data": {
    "url": "https://at-product-family-econtract-api.dev.oneat.org/storage/uploads/2026/08/xxxx.jpg"
  },
  "meta": { "...": "..." }
}
Format response cố định (data.url) — khi BE nối hệ thống upload khác (CDN/S3), FE không cần đổi gì.

6. Mã lỗi thường gặp

HTTPCodeNguyên nhân / Xử lý
401AUTHENTICATION_REQUIREDThiếu header Authorization
401AUTHENTICATION_INVALIDwebview_token sai/hết hạn → tạo test session mới
410SESSION_EXPIREDSession hết hạn (session_expired_at) → tạo session mới
404SESSION_NOT_FOUNDSai session_id
422VALIDATION_ERRORThiếu/sai param (xem message mô tả chi tiết)
403—Gọi test-session trên môi trường production

7. Luồng production (tham chiếu cho partner)

Khi tích hợp chính thức, Bank gọi POST /api/v1/partner/init-session với JWT RS256 (sign bằng private key do BE cấp):

curl -X POST https://at-product-family-econtract-api.dev.oneat.org/api/v1/partner/init-session \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <JWT_RS256>" \
  -d '{
    "partner_tx_id": "TXN_001",
    "user_id": "USER_001",
    "account_number": "123456",
    "identity_info": { "full_name": "Nguyễn Văn A" }
  }'

Response giống hệt test-session (cùng webview_token, session_id, access_token...) — FE không cần đổi logic, chỉ khác cách lấy token.