#Hướng dẫn chuyển đổi Legacy → v1
#Tổng quan
API v1 thay thế các route legacy /create_order, /capture_order, v.v. bằng chữ ký
Ed25519, quản lý key theo từng đối tác, và bảo mật được cải thiện. Hướng dẫn này
giúp bạn chuyển đổi từ SDK legacy sang v1.
#Danh sách host
| Mục đích | Host |
|---|---|
| Shield API live mặc định (production) | live.vkl-shield.com |
| Shield API sandbox mặc định (testing) | sandbox.vkl-shield.com |
| Shield host riêng (nếu được cấp cho tài khoản đối tác của bạn) | do đội vận hành shield cung cấp — dùng host này ở mọi nơi hướng dẫn này ghi host mặc định (script src, selectBase, các lời gọi API) |
| JWKS (luôn dùng — bất kể request được phục vụ bởi shield host nào) | keys.vkl-shield.com/v1/keys (live) / keys.vkl-shield.com/sandbox/v1/keys (sandbox) |
#1. Cập nhật URL script
Thay SDK legacy bằng v1:
#PayPal
<!-- BEFORE (legacy) -->
<script src="https://live.vkl-shield.com/assets/paypal-shield-sdk.js"></script>
<!-- AFTER (v1) -->
<script src="https://live.vkl-shield.com/assets/v1/paypal-shield-sdk.js"></script>#Stripe
<!-- BEFORE (legacy) -->
<script src="https://live.vkl-shield.com/assets/stripe-shield-sdk.js"></script>
<!-- AFTER (v1) -->
<script src="https://live.vkl-shield.com/assets/v1/stripe-shield-sdk.js"></script>Các ví dụ trên dùng host live mặc định. Dùng
sandbox.vkl-shield.comkhi test, hoặc shield host riêng nếu tài khoản đối tác của bạn được cấp một host như vậy — xem Danh sách host ở trên.
#2. Cập nhật định dạng shield key
Key legacy: chuỗi đã mã hóa (không có tiền tố)
Key v1: shpk_test_* (sandbox) / shpk_live_* (live)
Yêu cầu key v1 mới từ đội vận hành PayPal Shields. Partner ID phải khớp [a-z0-9]{2,32} (ví dụ: acme, mystore, không phải test_mystore).
#3. Cập nhật hình dạng payload callback
#PayPal: tham số onCompleted
Legacy:
onCompleted: function (capture, orderId) {
// capture = {id, purchase_units: [...]}
// Chữ ký legacy: không có bằng chứng; store tự tin tưởng vào nội dung body
}v1:
onCompleted: function (envelope, orderId) {
// envelope = {
// error: false,
// data: {id, purchase_units: [...]},
// signed_payload: "{...}",
// sig: "base64url_sig",
// kid: "shsig_xxxxxxxxxxxxxxxxxxxxxxxx",
// alg: "Ed25519",
// v: 1
// }
// PHẢI xác minh sig trước khi dùng data
}#Stripe: tham số onCompleted
Legacy:
onCompleted: function (result, orderId) {
// result = {
// signed_fields: {...},
// signature: "hex_signature",
// contact: {...}
// }
// Legacy: xác minh bằng HMAC
}v1:
onCompleted: function (envelope, orderId) {
// envelope = {
// error: false,
// data: {...signed_fields},
// signed_payload: "{...}",
// sig: "base64url_sig",
// kid: "shsig_xxxxxxxxxxxxxxxxxxxxxxxx",
// alg: "Ed25519",
// v: 1,
// contact: {...}
// }
// PHẢI xác minh sig trước khi dùng data
}#4. Thay thế xác minh bằng chứng (proof)
#Xác minh legacy (HMAC)
// Node.js legacy (nếu vẫn chạy endpoint legacy)
const crypto = require('crypto');
const canonicalJson = require('canonical-json');
const signature = crypto.createHmac('sha256', SIGNATURE_PRIVATE_KEY)
.update(canonicalJson(result.signed_fields))
.digest('base64');
const valid = signature === result.signature;(SIGNATURE_PRIVATE_KEY là secret dùng chung ở phía server do đội vận hành shield cấp. Xem test vector HMAC legacy để xác minh byte-chính-xác (byte-for-byte).)
#Xác minh v1 (Ed25519/JWKS)
Xem docs/api/examples/verify-signed-payload.mjs để có tham chiếu Node.js đầy đủ (cũng có ví dụ PHP, Python).
Endpoint JWKS chuẩn:
- Live:
https://keys.vkl-shield.com/v1/keys - Sandbox:
https://keys.vkl-shield.com/sandbox/v1/keys
Thứ tự xác minh (quan trọng — xác minh chữ ký TRƯỚC khi parse):
- Xác minh
envelope.sigdựa trên các byte UTF-8 chính xác củaenvelope.signed_payloadbằng JWKS URL chuẩn (không bao giờ lấy từ envelope) - Chỉ sau khi chữ ký hợp lệ, mới parse:
payload = JSON.parse(envelope.signed_payload) - Khẳng định (assert) mọi field từ payload đã parse (cả PayPal & Stripe):
typ='paypal_capture'(PayPal) hoặc'stripe_complete'(Stripe),v === 1status === 'COMPLETED'(PayPal) hoặc'succeeded'(Stripe)env === 'live'(trên live) hoặc'sandbox'(trên sandbox)partner_id === your_partner_idkey_id ∈ your_whitelisted_keysinvoice_id === your_order.invoice_id- PayPal:
amount === order_total(chuỗi decimal),currency === order_currency;capture_idchưa từng gặp trước đó - Stripe:
amount_received === order_total_cents(số nguyên), chỉ USD;id(PI id) chưa từng gặp trước đó iatnằm trong khoảng [now - 600s, now + 300s]- Tùy chọn:
reference_idkhớp với reference đơn hàng của bạn nếu bạn đã cung cấp
#5. Lịch trình xoay (rotation) key
Hạn dùng key đối tác: 180 ngày sau khi cấp
Quy tắc xoay key: Trước khi hết hạn, thêm key ID mới vào allowlist trong config shield (không cần deploy), chuyển SDK sang key mới, chờ hết TTL cache trang + 10 phút, rồi xóa key ID cũ
Ví dụ mốc thời gian:
- Key được cấp: 2026-01-01
- Hết hạn: 2026-07-01
- Bắt đầu xoay key: 2026-06-20 (11 ngày trước khi hết hạn) — thêm key mới vào config
- Chuyển key SDK: 2026-06-21 (deploy với key mới, xóa key cũ khỏi SDK)
- Xóa khỏi config: 2026-06-25 (sau khi hết TTL cache trang tối đa + 10 phút dự phòng)
Nếu bạn bỏ lỡ việc xoay key: Sau khi hết hạn, mọi lời gọi select trả về 401 SHIELD_KEY_EXPIRED (KHÔNG bị charge)
#6. Không bao giờ nạp cả hai phiên bản trên cùng một trang
<!-- SAI: cả legacy và v1 -->
<script src="https://live.vkl-shield.com/assets/paypal-shield-sdk.js"></script>
<script src="https://live.vkl-shield.com/assets/v1/paypal-shield-sdk.js"></script>
<!-- Cả hai đều dùng window.PaypalShield; hành vi không xác định -->Chỉ chọn một phiên bản cho mỗi trang. Để chuyển đổi:
- Trên staging: đổi URL + test xác minh dựa trên JWKS sandbox
- Chờ 7 ngày để TTL cache trang hết hiệu lực
- Trên live: cập nhật URL script SDK trong HTML
- Theo dõi tỉ lệ lỗi trong 1 tuần
- Yêu cầu vô hiệu hóa key legacy từ đội vận hành sau khi đã tự tin
#7. Thay đổi trong xử lý lỗi
#CAPTURED_PROOF_UNAVAILABLE (502)
Quan trọng: Lỗi này có nghĩa là người mua ĐÃ bị trừ tiền trên PayPal.
onFailed: function (error) {
if (error.issue === 'CAPTURED_PROOF_UNAVAILABLE' || error.code === 'CAPTURED_PROOF_UNAVAILABLE') {
// KHÔNG hiển thị: "Payment failed, please retry"
// KHÔNG retry capture — người mua đã bị trừ tiền
// ĐÚNG: hiển thị "Đã nhận thanh toán, đang xác nhận..."
showModal('Đã nhận thanh toán! Chúng tôi đang xác nhận đơn hàng của bạn.');
// Người mua sẽ nhận email xác nhận từ PayPal
// Chờ webhook AIO transaction.completed (khớp theo settle_ref)
// Hỏi đội vận hành shield về notification feed của integration của bạn
}
}#Chính sách retry — PayPal SDK vs. Stripe SDK
PayPal SDK (v1): Không tự động retry. Lỗi trước-capture của shield nghĩa là KHÔNG bị charge; lỗi capture không có field issue = trạng thái charge KHÔNG XÁC ĐỊNH.
| Tình huống lỗi | Ý nghĩa | Hành động của bạn |
|---|---|---|
Lỗi trước-capture (SHIELD_KEY_INVALID, POOL_MISMATCH, SELECT_TIMEOUT, v.v.) |
Shield không truy cập được hoặc lỗi key; người mua KHÔNG bị charge | Có thể hiển thị "Thử lại" sau khi sửa |
Lỗi capture có field issue (SIGNING_UNAVAILABLE, SHIELD_REGISTRY_UNAVAILABLE) |
Lỗi tạm thời của shield; người mua KHÔNG bị charge | Hiển thị "Thử lại"; PayPal SDK không tự retry |
Capture trả về 502 CAPTURED_PROOF_UNAVAILABLE |
Người mua BỊ CHARGE nhưng tạo bằng chứng thất bại | Hiển thị "Payment received"; không bao giờ retry; đối soát qua feed |
Capture trả về 500 với object lỗi PayPal (ví dụ: INSTRUMENT_DECLINED) |
Người mua bị từ chối; KHÔNG bị charge | Hiển thị lý do từ chối cho người mua (details[0].issue) |
| Endpoint capture không truy cập được (network/proxy/timeout/non-JSON) | Trạng thái charge KHÔNG XÁC ĐỊNH; không có field issue nào được gửi |
Đối soát qua feed đã cấu hình; không bao giờ tự retry |
Stripe SDK (v1): Chỉ route /complete có tự động retry (tiền đã bị trừ rồi); create/select không được retry.
#Đối soát & khôi phục CAPTURED_PROOF_UNAVAILABLE
Mô hình xác nhận — bằng chứng (proof) là chính, AIO là fallback dự phòng.
- Chính (đồng bộ): xác minh bằng chứng Ed25519 ở phía server ngay sau callback (trang này) → hoàn tất đơn hàng ngay và hiển thị trang cảm ơn. Không chờ bất kỳ webhook nào.
- Fallback / đối soát: webhook AIO
transaction.completed(ký bằng HMACX-Signature: t=…,v1=…, khớp theosettle_ref=custom_idcủa bạn) hoàn tất các đơn hàng mà bằng chứng chưa từng đến — PayPalCAPTURED_PROOF_UNAVAILABLE, Stripecharged_but_unconfirmed, hoặc người mua đã đóng tab — và có thể dùng để đối soát sau. Nó phải idempotent (lặp lại an toàn) với bước 1 (một đơn hàng đã hoàn tất từ bằng chứng thì webhook này là no-op). Không bao giờ charge người mua lần nữa khi đơn hàng đang chờ fallback.
Khi không thể tạo bằng chứng capture (PayPal CAPTURED_PROOF_UNAVAILABLE, Stripe charged_but_unconfirmed) thì người mua đã bị charge.
Đối soát qua webhook AIO transaction.completed. AIO nhận các webhook từ nhà cung cấp PayPal/Stripe cho các tài khoản merchant của shield và thông báo cho platform của bạn (ký HMAC, header X-Signature: t=…,v1=…; endpoint, secret và hợp đồng payload do đội vận hành shield/AIO cấp). Khớp với đơn hàng của bạn bằng settle_ref bạn đã gửi (PayPal purchase_units[0].custom_id; Stripe: cùng custom_id trong getOrderData, lưu dưới dạng metadata PaymentIntent settle_ref). Chỉ đơn hàng KHÔNG có bằng chứng đã xác minh mới chờ webhook này (hiển thị đã nhận thanh toán, đang xác nhận); không bao giờ yêu cầu người mua trả tiền lại.
- KHÔNG retry capture/complete và KHÔNG hiển thị "thanh toán thất bại".
- Luôn gửi
settle_ref(custom_id) — nếu thiếu, webhook sẽ không thể khớp với đơn hàng của bạn.
#Rollback
Nếu bạn cần quay lại legacy:
- Giữ cả hai SDK được phục vụ (legacy vẫn hoạt động)
- Đổi URL script về lại legacy
- Chờ 7 ngày để TTL cache trang hết hiệu lực
- Theo dõi lỗi
- Yêu cầu vô hiệu hóa key v1 từ đội vận hành sau khi đã tự tin
#Test trên sandbox
Test chuyển đổi v1 trên sandbox trước:
- Yêu cầu key sandbox từ đội vận hành (định dạng:
shpk_test_*) - Cập nhật HTML với URL script v1 + key sandbox
- Triển khai xác minh dùng endpoint JWKS sandbox:
https://keys.vkl-shield.com/sandbox/v1/keys - Chạy test end-to-end với key
shpk_test_*(luồng đầy đủ create → capture → verify) - Test các tình huống lỗi: key hết hạn, partner ID sai, payload bị giả mạo
- Khi đã tự tin trên sandbox, yêu cầu key live và test trên staging production
#Hỗ trợ
Nếu có câu hỏi hoặc vấn đề:
- Xem
docs/api/sdk/paypal.mdvàdocs/api/sdk/stripe.mdđể tham khảo SDK - Xem lại mã lỗi trong các hướng dẫn SDK
- Liên hệ hỗ trợ đối tác PayPal Shields