Tapaya
Chấp nhận thanh toán tại cửa hàngHướng dẫn tích hợpCloud Accept APIBeta

Cloud Accept API

Beta

Bắt đầu thanh toán tại cửa hàng trên một thiết bị Tapaya đã ghép nối từ ECR hoặc backend POS của bạn.

Cloud Accept API cho phép máy tính tiền điện tử (ECR) hoặc backend POS của bạn bắt đầu một khoản thanh toán trên thiết bị Tapaya qua đám mây. Hệ thống của bạn không bao giờ giao tiếp trực tiếp với thiết bị: hệ thống gửi một yêu cầu thanh toán tới Tapaya, Tapaya thông báo cho thiết bị đã ghép nối, và thiết bị hiển thị màn hình thanh toán cho khách hàng. Tiến trình được gửi lại cho bạn dưới dạng webhook, hoặc bạn tự thăm dò (poll).

Chọn cách ghép nối thiết bị

Mỗi thiết bị mà bạn gửi thanh toán tới đều cần một externalDeviceId: tên mà hệ thống của bạn dùng cho thiết bị đó, chẳng hạn till-01. Gán định danh này theo một trong hai cách:

Trước khi bắt đầu

  1. Lấy Server Secret Token của bạn từ trang API Keys. Bạn đổi token này lấy một access token theo phạm vi merchant ở bên dưới (xem xác thực Platform API).
  2. Đăng ký merchant và lưu lại merchantToken của merchant đó. Xem Đăng ký merchant mới.
  3. Chuẩn bị từng thiết bị với ứng dụng Tapaya đã đăng nhập cho merchant đó, để thiết bị có thể được ghép nối và nhận yêu cầu.

Một token, một merchant

Mỗi lệnh gọi Cloud Accept API chỉ áp dụng cho đúng một merchant. Để phục vụ nhiều merchant, hãy đổi lấy một access token riêng cho từng merchant.

Ghép nối thiết bị

Việc ghép nối gán một externalDeviceId cho thiết bị Tapaya, để bạn có thể tìm thấy thiết bị khi liệt kê danh sách thiết bị và chỉ định thiết bị đó khi bắt đầu một khoản thanh toán.

Gán từ ứng dụng của bạn

Ứng dụng của bạn gọi một hàm của Accept SDK trên thiết bị để gán externalDeviceId cho thiết bị. Hãy dùng cùng một định danh trong mọi lệnh gọi Cloud Accept API cho thiết bị đó.

Quét mã QR

Merchant mở quy trình ghép nối trong Tapaya Platform, nơi hiển thị một mã QR cho externalDeviceId cần gán. Merchant quét mã bằng ứng dụng merchant của Tapaya trên thiết bị đích, và ứng dụng sẽ hoàn tất việc ghép nối.

Đổi lấy access token

Đổi Server Secret Token của bạn và merchantToken của merchant lấy một access token bằng POST /ecr/auth. merchantToken chỉ được gửi ở đây; mọi lệnh gọi sau đó chỉ gửi Authorization: Bearer <accessToken>.

curl -X 'POST' 'https://api.tapaya.com/ecr/auth' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer REPLACE_ME' \
-d '{
        "merchantToken": "unique_merchant_id_from_your_db"
    }'
200 OK
{
    "accessToken": "...",
    "tokenType": "Bearer",
    "expiresAt": "2026-01-13T16:30:59Z"
}
MãMô tả
401API token không hợp lệ hoặc đã hết hạn.
403Không được phép truy cập merchant.

Gia hạn trước expiresAt. Gọi POST /ecr/auth/refresh với accessToken hiện tại, vẫn còn hiệu lực, trong header Authorization. Endpoint này trả về cùng nội dung phản hồi với một token mới; hãy lưu token đó và dùng nó từ đó trở đi.

curl -X 'POST' 'https://api.tapaya.com/ecr/auth/refresh' \
-H 'Authorization: Bearer ACCESS_TOKEN'
MãMô tả
401Access token không hợp lệ, đã hết hạn hoặc đã bị thu hồi. Đổi lấy token mới bằng POST /ecr/auth.
403Bạn không còn quyền truy cập merchant. Không có token mới nào được cấp.

Tìm thiết bị sẵn sàng

Liệt kê các thiết bị của merchant bằng GET /ecr/devices. Thêm ?connected=true để loại bỏ các thiết bị đã ngắt kết nối, hoặc lấy một thiết bị bằng GET /ecr/devices/{externalDeviceId}.

curl 'https://api.tapaya.com/ecr/devices/till-01' \
-H 'Authorization: Bearer ACCESS_TOKEN'
200 OK
{
    "externalDeviceId": "till-01",
    "name": "Front counter",
    "connected": true,
    "availability": "ready",
    "lastSeenAt": "2026-01-13T16:30:59Z",
    "updatedAt": "2026-01-13T16:30:59Z"
}

Chỉ gửi thanh toán tới thiết bị có availability là ready. Danh sách chỉ là bản lưu (snapshot) tại một thời điểm: tình trạng sẵn sàng được kiểm tra lại khi bạn gửi yêu cầu, nên một thiết bị ready vẫn có thể từ chối yêu cầu. Xem Tình trạng sẵn sàng của thiết bị.

Gửi yêu cầu thanh toán

Gửi số tiền tới một thiết bị bằng POST /ecr/payment-requests. Tapaya lưu yêu cầu và thông báo cho thiết bị, thiết bị sẽ hiển thị màn hình thanh toán.

curl -X 'POST' 'https://api.tapaya.com/ecr/payment-requests' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H 'Idempotency-Key: order-1042-attempt-1' \
-d '{
        "externalDeviceId": "till-01",
        "amount": 12500,
        "tipAmount": 500,
        "currency": "CZK"
    }'

Tham số:

TrườngKiểuBắt buộcMô tả
externalDeviceIdstring✓Thiết bị đã ghép nối sẽ hiển thị khoản thanh toán.
amountinteger✓Số tiền theo đơn vị nhỏ nhất của loại tiền tệ (12500 là 125,00 Kč).
tipAmountintegerTiền tip theo đơn vị nhỏ nhất của loại tiền tệ.
currencystring✓Mã tiền tệ ISO 4217.
paymentTokenstringMột khoản thanh toán hiện có để gắn yêu cầu vào.
202 Accepted
{
    "requestId": "...",
    "status": "pending",
    "paymentToken": null,
    "expiresAt": "2026-01-13T16:30:59Z"
}

202 không phải là kết quả thanh toán

202 Accepted nghĩa là Tapaya đã lưu yêu cầu. Điều đó không có nghĩa là thiết bị đã nhận được yêu cầu hay khách hàng đã thanh toán. Mọi lỗi xảy ra sau đó sẽ được gửi tới bạn dưới dạng thay đổi trạng thái.

Đảm bảo thử lại an toàn. Header Idempotency-Key là bắt buộc. Dùng lại một key với cùng nội dung yêu cầu sẽ trả về yêu cầu ban đầu thay vì bắt đầu khoản thanh toán thứ hai; dùng lại key đó với nội dung khác sẽ trả về 409 idempotency_conflict. Hãy gắn key với giao dịch bán hàng và lần thử, như trong order-1042-attempt-1.

Theo dõi kết quả

Theo dõi yêu cầu qua các trạng thái của nó bằng webhook, hoặc thăm dò GET /ecr/payment-requests/{requestId}. Khi yêu cầu đạt payment_created, phản hồi sẽ chứa paymentToken, và các sự kiện payment.updated báo tiến trình của chính khoản thanh toán.

Để nhận webhook, hãy đăng ký endpoint HTTPS của bạn bằng POST /ecr/webhook-subscriptions, hoặc đặt URL callback và các loại sự kiện cho merchant trong Tapaya Platform.

curl -X 'POST' 'https://api.tapaya.com/ecr/webhook-subscriptions' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-d '{
        "url": "https://integrator.example.com/webhooks/tapaya",
        "events": ["payment_request.updated", "payment.updated"]
    }'
201 Created
{
    "subscriptionId": "...",
    "url": "https://integrator.example.com/webhooks/tapaya",
    "events": ["payment_request.updated", "payment.updated"],
    "signingSecret": "..."
}

Lưu signing secret

signingSecret chỉ được trả về khi tạo đăng ký (subscription). Hãy lưu trữ nó an toàn; bạn cần nó để xác minh chữ ký của các webhook gửi đến.

Một đăng ký thuộc về bên tích hợp và merchant, không thuộc về access token, nên nó vẫn được giữ nguyên khi gia hạn token. Hãy phản hồi mỗi sự kiện bằng mã 2xx khi bạn đã chấp nhận sự kiện đó.

Xác minh trước khi chốt giao dịch bán hàng

Webhook là thông báo, không phải bằng chứng thanh toán, và một webhook có thể đến muộn hoặc hoàn toàn không đến. Không bao giờ chốt giao dịch bán hàng chỉ dựa vào webhook:

  • Khi có webhook đến, hãy xác nhận kết quả cuối cùng qua API, ví dụ bằng Truy xuất chi tiết thanh toán.
  • Khi không có webhook nào đến trong khoảng thời gian chờ phù hợp với quy trình thanh toán của bạn, và không muộn hơn expiresAt của yêu cầu, hãy gọi GET /ecr/payment-requests/{requestId} để đọc trạng thái hiện tại, sau đó xác nhận khoản thanh toán qua API như trên.

Tham chiếu

Các endpoint

Phương thứcEndpointMô tả
POST/ecr/authĐổi API token của bạn và một merchantToken lấy access token.
POST/ecr/auth/refreshGia hạn một access token vẫn còn hiệu lực.
GET/ecr/devicesLiệt kê các thiết bị của merchant. Bộ lọc connected=true tùy chọn.
GET/ecr/devices/{externalDeviceId}Trả về một thiết bị và tình trạng sẵn sàng hiện tại của thiết bị đó.
POST/ecr/payment-requestsLưu một yêu cầu thanh toán và thông báo cho thiết bị. Bắt buộc có header Idempotency-Key.
GET/ecr/payment-requests/{requestId}Trả về trạng thái yêu cầu, lý do từ chối (nếu có) và paymentToken.
POST/ecr/webhook-subscriptionsĐăng ký một URL HTTPS và các loại sự kiện. Trả về signing secret một lần duy nhất.
GET/ecr/webhook-subscriptionsLiệt kê các đăng ký của bạn cho merchant, không kèm signing secret.
DELETE/ecr/webhook-subscriptions/{subscriptionId}Xóa một đăng ký.

Tình trạng sẵn sàng của thiết bị

Cả hai endpoint thiết bị đều trả về externalDeviceId, name, connected, availability, lastSeenAt và updatedAt.

Giá trịMô tả
readyĐã kết nối, có thể nhận và hiển thị yêu cầu.
busyĐang xử lý một khoản thanh toán hoặc đang giữ một yêu cầu đang hoạt động.
unavailableĐã ngắt kết nối, không có phiên hợp lệ hoặc không thể hiển thị màn hình thanh toán.

Trạng thái yêu cầu

Trạng tháiMô tả
pendingĐã được lưu và đang chờ thiết bị chấp nhận.
acknowledgedThiết bị đã nhận được yêu cầu; khoản thanh toán có thể chưa tồn tại.
payment_createdĐã có khoản thanh toán và paymentToken của nó. Theo dõi tiến trình tiếp theo trên khoản thanh toán.
rejectedThiết bị hoặc Tapaya đã từ chối yêu cầu.

Lỗi

MãLý doMô tả
403 / 404Đối tượng không khả dụngBạn không có quyền, hoặc đối tượng không tồn tại.
409device_busyThiết bị đã có một yêu cầu hoặc khoản thanh toán đang hoạt động.
409device_unavailableThiết bị đã kết nối nhưng chưa sẵn sàng.
409idempotency_conflictKey đã được dùng với một nội dung yêu cầu khác.
503device_offlineThiết bị không có kết nối.

Lỗi kiểm tra tính hợp lệ của số tiền và loại tiền tệ tuân theo cùng quy ước như phần còn lại của Platform API.

Sự kiện webhook

Sự kiệnĐược gửi khiNội dung data
device.connectedMột thiết bị mở kết nối trực tiếp mới.externalDeviceId, connected, availability, lastSeenAt
device.disconnectedKết nối bị đóng hoặc việc kiểm tra hoạt động (liveness check) hết thời gian chờ.externalDeviceId, connected, availability, lastSeenAt
device.availability_changedTình trạng sẵn sàng chuyển giữa ready, busy và unavailable.externalDeviceId, availability, reason tùy chọn
payment_request.updatedYêu cầu được lưu, được xác nhận đã nhận, bị từ chối, hết hạn hoặc có khoản thanh toán.requestId, externalDeviceId, status, paymentToken, reference, reason, expiresAt
payment.updatedTrạng thái của khoản thanh toán thay đổi.paymentToken, requestId, externalDeviceId, status, reference, amount, currency, receiptUrl tùy chọn

Mọi sự kiện đều đến dưới dạng HTTP POST trong cùng một cấu trúc bao (envelope):

{
    "eventId": "...",
    "type": "payment_request.updated",
    "occurredAt": "2026-01-13T16:30:59Z",
    "data": {
        "requestId": "...",
        "externalDeviceId": "till-01",
        "status": "acknowledged",
        "paymentToken": null,
        "reference": "ORDER-1042",
        "reason": null,
        "expiresAt": "2026-01-13T16:30:59Z"
    }
}
Trang này còn thiếu điều gì?Đề xuất tính năng. Chúng tôi sẽ đính kèm trang này để đội ngũ nắm được bối cảnh.