Skip to main content

Official Python SDK for the SurePay Payment Gateway API

Project description

surepay-python-sdk

Official Python client library for the SurePay Merchant API.

CI PyPI

Requirements

  • Python 3.9+
  • Zero non-stdlib dependencies

Install

pip install surepay

Quick start

import os
from surepay import SurePay, SurePayException

client = SurePay.builder(
    os.environ["SUREPAY_API_KEY"],    # tpay_live_... or tpay_test_...
    os.environ["SUREPAY_API_SECRET"], # tpay_sec_... — enables auto HMAC signing
).build()

# Check wallet balance
balance = client.balance.get()
print(f"Available: {balance.available} VND")

# Create a deposit order (thu hộ)
deposit = client.deposits.create(
    amount=100_000,
    request_id="ORD-20260610-001",
)
print("Checkout URL:", deposit.checkout_url)

# Create a payout (chi hộ)
try:
    payout = client.payouts.create(
        amount=500_000,
        bank_code="VCB",
        bank_account="1234567890",
        account_name="NGUYEN VAN A",
        memo="Salary June 2026",
    )
    print("Payout ID:", payout.id)
except SurePayException as e:
    if e.is_insufficient_balance():
        print("Not enough balance — top up first")

Configuration

client = SurePay.builder(api_key, api_secret) \
    .base_url("https://api.surepay.one/merchant/v1") \
    .timeout(15) \
    .max_retries(3) \
    .build()

Or use direct instantiation:

client = SurePay(
    api_key=os.environ["SUREPAY_API_KEY"],
    api_secret=os.environ["SUREPAY_API_SECRET"],
)
Option Default Description
base_url(str) https://api.surepay.one/merchant/v1 Override base URL for local dev or staging
timeout(int) 30 HTTP request timeout in seconds
max_retries(int) 3 Retry attempts on 5xx and network errors

Authentication

Every request requires an API key sent as an X-API-Key header. When api_secret is provided to builder(), every outgoing request is automatically signed with HMAC-SHA256 — the X-Signature and X-Timestamp headers are attached with no extra code needed.

API reference

Balance

client.balance.get()

Get current wallet balance. Requires: balance:read scope.

balance = client.balance.get()
# balance.balance   — total wallet balance in VND
# balance.hold      — reserved for in-flight transactions
# balance.available — balance.balance - balance.hold
# balance.currency  — always "VND"

Deposits

client.deposits.list(params)

Paginated list of deposit (thu hộ) orders. Requires: deposits:read scope.

from surepay.params import DepositsListParams

result = client.deposits.list(
    DepositsListParams(
        page=1,
        page_size=20,
        status="success",        # pending|processing|success|failed|expired|cancelled
        from_date="2026-06-01",  # YYYY-MM-DD
        to_date="2026-06-30",
    )
)
# result.items       — list[Deposit]
# result.total       — total matching records
# result.total_pages — total pages

client.deposits.create(**kwargs)

Create a new deposit order. Returns a checkout_url (redirect) and qr_code (VietQR). Requires: deposits:write scope.

deposit = client.deposits.create(
    amount=100_000,                    # VND, required
    request_id="ORD-20260610-001",     # your order ID — optional, for idempotency
    # Chính chủ verification — all optional:
    sender_bank_id="970436",
    sender_bank_name="Vietcombank",
    sender_account="1234567890",
    sender_name="NGUYEN VAN A",
)
print(deposit.checkout_url)
print(deposit.qr_code)

Response fields:

Field Type Description
id str SurePay transaction UUID
request_id str Your order ID
amount int Amount in VND
status str pending, processing, success, failed, expired, cancelled
checkout_url str Redirect URL for payer
qr_code str VietQR data string
created_at str ISO 8601 timestamp
updated_at str ISO 8601 timestamp

client.deposits.get(id)

Fetch a single deposit order by UUID. Requires: deposits:read scope.

deposit = client.deposits.get("uuid-here")
# deposit.status: 'pending' | 'success' | ...

Payouts

client.payouts.list(params)

Paginated list of payout (chi hộ) orders. Requires: payouts:read scope.

from surepay.params import PayoutsListParams

result = client.payouts.list(
    PayoutsListParams(
        page=1,
        page_size=20,
        status="success",        # pending|processing|success|failed
        from_date="2026-06-01",
        to_date="2026-06-30",
    )
)

client.payouts.create(**kwargs)

Initiate a payout bank transfer. Funds are deducted from your wallet immediately on success. Requires: payouts:write scope.

Payouts are irreversible once status moves past pending. Verify bank details with client.bank_inquiry.verify() first.

payout = client.payouts.create(
    amount=500_000,              # VND, required
    bank_code="VCB",             # required (VCB, MB, TCB, ACB, ...)
    bank_account="1234567890",   # required
    account_name="NGUYEN VAN A", # required — use UPPERCASE
    memo="Salary June",          # required — transfer memo
    bank_name="Vietcombank",     # optional
)

client.payouts.get(id)

Fetch a single payout by UUID. Requires: payouts:read scope.

payout = client.payouts.get("uuid-here")
# payout.status: 'pending' | 'success' | ...

Bank Inquiry

client.bank_inquiry.verify(bank_code, bank_account)

Look up the account holder name for a bank account. Call this before creating a payout to confirm the recipient. Requires: payouts:read scope.

result = client.bank_inquiry.verify(
    bank_code="VCB",
    bank_account="1234567890",
)
print("Account name:", result.account_name)

Idempotency

Pass an idempotency_key to any create() method. The key is forwarded as an Idempotency-Key header — safe to retry on network errors without risk of duplicate transactions.

deposit = client.deposits.create(
    amount=100_000,
    request_id="ORD-001",
    idempotency_key="ORD-001",
)

Webhook verification

Every inbound webhook event from SurePay is HMAC-signed. Pass the raw request body bytes (before any JSON parsing) to client.webhooks.verify():

# FastAPI example
from fastapi import FastAPI, Request, Response
import json

app = FastAPI()

@app.post("/webhook/surepay")
async def handle_webhook(request: Request):
    body = await request.body()

    if not client.webhooks.verify(body):
        return Response(status_code=401)

    event = json.loads(body)

    match event.get("event"):
        case "deposit.success" | "deposit.failed":
            handle_deposit(event)
        case "payout.success" | "payout.failed":
            handle_payout(event)

    return Response(status_code=200)

Or pass the X-Surepay-Signature header value explicitly:

sig   = request.headers.get("X-Surepay-Signature")
valid = client.webhooks.verify(body, sig)

Error handling

from surepay import SurePayException

try:
    payout = client.payouts.create(...)
except SurePayException as e:
    # Convenience helpers
    if e.is_not_found():             # 404 not_found
        pass
    if e.is_rate_limit():            # 429 rate_limit_exceeded
        pass
    if e.is_insufficient_balance():  # 422 insufficient_balance
        pass
    if e.is_duplicate():             # 409 duplicate_request
        pass

    # Full details
    print(f"HTTP {e.http_status}  code={e.code}  {e.message}")

Error codes:

HTTP code Meaning
400 validation_error Invalid request body or parameters
401 unauthorized Missing or invalid API key
401 signature_invalid HMAC signature failed or timestamp > 5 min
403 permission_denied API key lacks required scope
403 ip_not_allowed Request IP not in allowlist
404 not_found Resource not found
409 duplicate_request Idempotency key conflict
422 insufficient_balance Top up wallet first
422 invalid_state_transition Operation not allowed for current status
429 rate_limit_exceeded Slow down — back off and retry
500 internal_error Server error

HMAC signing

When api_secret is set, all requests are signed automatically. The signing algorithm for manual use:

body_hash   = sha256(body_bytes).hexdigest()
signing_str = f"{timestamp}\n{method}\n{path}\n{body_hash}"
signature   = "sha256=" + hmac.new(api_secret.encode(), signing_str.encode(), sha256).hexdigest()

Attach as headers: X-Signature: <signature> and X-Timestamp: <unix_timestamp>.

Signatures expire after 300 seconds — generate per-request, never cache or reuse.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

surepay-0.1.0.tar.gz (12.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

surepay-0.1.0-py3-none-any.whl (16.1 kB view details)

Uploaded Python 3

File details

Details for the file surepay-0.1.0.tar.gz.

File metadata

  • Download URL: surepay-0.1.0.tar.gz
  • Upload date:
  • Size: 12.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for surepay-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2064e1438c2cfbc2c96cc048675e8702241aa4dc4f28d70b21a293368ccc2776
MD5 47cbdaff8672e5e0437bae63d1f2d594
BLAKE2b-256 b40a4827057eb13a2972edd0a25ebc60a726dc3da4b873b0c6b66c459866952e

See more details on using hashes here.

Provenance

The following attestation bundles were made for surepay-0.1.0.tar.gz:

Publisher: release.yml on surepay-one/surepay-python-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file surepay-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: surepay-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 16.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for surepay-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5be6026ecdb5745de43ffb36e365b81462932baade4ca412827900d154448970
MD5 26437814d207eff89f221e138010eb60
BLAKE2b-256 31b0d9cb0bfdbfaec3d7fa49083836ec2597a8d1714ef07703b7055bcc3081c3

See more details on using hashes here.

Provenance

The following attestation bundles were made for surepay-0.1.0-py3-none-any.whl:

Publisher: release.yml on surepay-one/surepay-python-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page