TapPay Python SDK
This is the unofficial Python client library for TapPay's Backend API. To use it you'll need a TapPay account. Sign up at tappaysdk.com.
Installation
Install using pip:
pip install tappay
Usage
Initialization
import tappay
# Initialize the client
client = tappay.Client(
is_sandbox=True,
partner_key="YOUR_PARTNER_KEY",
merchant_id="YOUR_MERCHANT_ID",
)
For production, you can set TAPPAY_PARTNER_KEY and TAPPAY_MERCHANT_ID environment variables and omit them in the constructor:
client = tappay.Client(is_sandbox=False)
Timeouts
Every request carries a timeout by default: (3.05, 27.0) seconds, as a
(connect, read) pair. Override it for all calls on a client:
client = tappay.Client(is_sandbox=False, timeout=10.0)
Or for a single call, using the keyword-only timeout argument available on
every API method:
response = client.refund(rec_trade_id="rec_trade_id", amount=100, timeout=(3.05, 60.0))
Passing timeout=None to the constructor disables the timeout entirely, which
lets a stalled request block the calling thread indefinitely. This is almost
never what you want in a server process.
Logging
The client logs request and response details at DEBUG level. Credentials
(partner_key, x-api-key), card handles (prime, card_key, card_token),
and cardholder PII (name, email, phone number, address, national ID) are
replaced with ***REDACTED*** before anything reaches the logger, so enabling
debug logging will not spill payment data into your log aggregator.
import logging
logging.getLogger("tappay.client").setLevel(logging.DEBUG)
Pay by Prime
# Create cardholder data
card_holder = tappay.Models.CardHolderData(
phone_number="0912345678",
name="Wang Xiao Ming",
email="test@example.com",
)
# Make payment
response = client.pay_by_prime(
prime="prime_token_from_frontend",
amount=100,
details="Order #123",
card_holder_data=card_holder,
)
print(response)
Pay by Token
response = client.pay_by_token(
card_key="card_key",
card_token="card_token",
amount=100,
details="Subscription",
)
Refunds
Omit amount to refund the transaction in full; pass it for a partial refund:
# Full refund
response = client.refund(rec_trade_id="rec_trade_id")
# Partial refund
response = client.refund(
rec_trade_id="rec_trade_id",
amount=100,
)
Currencies
Payments settle in TWD by default. TapPay documents four currencies, each tied to the acquirer configured for your merchant account:
| Currency | Acquirer |
|---|---|
TWD |
Most acquirers |
HKD |
Bank of China |
MYR |
RAZER PAY |
USD |
Global Payments |
Pass currency to override the default, using either a Models.Currencies
member or a plain currency string (strings are accepted so a currency TapPay
adds before this list is updated remains usable):
response = client.pay_by_prime(
prime="prime_token_from_frontend",
amount=100,
details="Order #123",
card_holder_data=card_holder,
currency=tappay.Models.Currencies.USD,
)
Handling failures
TapPay reports business failures such as a declined card with an HTTP 200 and a
non-zero status in the response body, so they are invisible to HTTP-level error
handling. By default the response is returned as-is and it is your job to check:
response = client.pay_by_prime(...)
if response["status"] != 0:
... # declined, invalid argument, insufficient balance, and so on
Pass raise_on_error=True to have the client raise TapPayError instead:
client = tappay.Client(is_sandbox=False, raise_on_error=True)
try:
response = client.pay_by_prime(...)
except tappay.TapPayError as exc:
print(exc.status, exc.msg, exc.response["rec_trade_id"])
get_records is exempt from one case: the Record API answers status: 2 for
"no more records under this filter", which is how pagination terminates. That is
treated as a normal result, not an error, even with raise_on_error=True.
The exception hierarchy is:
| Exception | Raised when |
|---|---|
AuthenticationError |
HTTP 401 |
ClientError |
HTTP 4xx |
ServerError |
HTTP 5xx, or an unexpected status code |
InvalidResponseError |
A 2xx body that is not valid JSON (subclasses ServerError) |
TapPayError |
Non-zero status in a 2xx body (only with raise_on_error=True) |
All of them subclass tappay.Error.
An intermediary such as a proxy or WAF can answer with an HTML error page under a
2xx status. That previously surfaced as a bare json.JSONDecodeError from inside
requests; it now raises InvalidResponseError, reporting the status, host,
content type, and body length. The body itself is deliberately left out of the
message, since exception text tends to end up in logs and error trackers.
Connection reuse and retries
Each client owns a pooled session. Close it when you are done, or use the client as a context manager:
with tappay.Client(is_sandbox=False) as client:
client.pay_by_prime(...)
The read-only query endpoints (get_records, get_trade_history) retry twice on
connection errors and on HTTP 429/500/502/503/504, with exponential backoff.
Payment, refund, capture, bind, and remove endpoints are never retried
automatically: TapPay exposes no idempotency key, so retrying a request that
actually succeeded upstream would charge the cardholder twice. Tune the retry
count with max_retries=, or disable it with max_retries=0.
For more API details, please refer to the TapPay Backend API Documentation.
Development
Setup
- Clone the repository:
git clone https://github.com/shihweilo/tappay-python.git
cd tappay-python
- Create a virtual environment and install dependencies:
python3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -e ".[dev]"
Testing
Run tests using pytest:
pytest
Run tests with coverage:
pytest --cov=tappay --cov-report=term-missing
Type check with mypy:
mypy tappay
Linting and Formatting
Check code with ruff:
ruff check .
Format code with ruff:
ruff format .
Contributing
Please see CONTRIBUTING.md for details.
Metadata
Release files for tappay 0.7.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tappay-0.7.3.tar.gz | 28.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tappay-0.7.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 43.9 kB
Release files / tappay-0.7.3.tar.gz
| Download URL | tappay-0.7.3.tar.gz |
|---|---|
| Size | 28.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9376e4199e03c7ccbc050dcbba1896f64fe4b9ec2edc51a8c159171a13528846
|
|
BLAKE2b-256 checksum How to use checksums |
3a98b10747a9596ca7d8538222d9ece3615652d62431e82b3c7c601b0357abcc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.
Transparency logRelease files / tappay-0.7.3-py3-none-any.whl
| Download URL | tappay-0.7.3-py3-none-any.whl |
|---|---|
| Size | 15.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dd6f6c290ad50dd0e4956de54a71d16490692172665464bb9c2d2b4142708d28
|
|
BLAKE2b-256 checksum How to use checksums |
751b01d2c0679e23d013d6c93e87647eef8aeaa3c6858cd3fcdbcb6730ba600e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.
Transparency log