Official Python SDK for the TangentoPay API
Project description
tangentopay-python
Official Python SDK for the TangentoPay API — accept payments, issue refunds, manage wallets, and verify webhooks with a clean, type-safe interface.
Table of contents
- Requirements
- Installation
- Quick start
- Authentication
- Token expiry and refresh
- Resources
- Provider status
- Currency and provider guide
- Service wallet operations (B2B2C)
- Payouts
- Merchant wallet top-up
- Payment methods
- Async support
- Error handling
- Webhook verification
- Security
- License
Requirements
- Python 3.9 or later
- A TangentoPay account — sign up
Installation
pip install tangentopay
# with async support (HTTP/2 via httpx)
pip install "tangentopay[async]"
Quick start
import tangentopay
# ── Storefront: create a Stripe checkout session ──────────────────────────────
service = tangentopay.ServiceClient(service_key="pk_live_<your_service_key>")
session = service.checkout.create(
products=[{"name": "Pro Plan", "price": 49.99, "quantity": 1}],
currency_code="USD",
customer_email="buyer@example.com",
return_url="https://myshop.com/thank-you",
)
# redirect your customer to session.redirect_url
# ── Or: hosted checkout — let the customer pick the payment method ────────────
# Returns a checkout.tangentopay.com URL showing the methods you've enabled
# (card, Google/Apple Pay, Alipay, WeChat, MTN MoMo, Orange Money).
hosted = service.checkout.create_hosted(
amount=5000,
currency_code="XAF",
return_url="https://myshop.com/thank-you",
cancel_url="https://myshop.com/cart",
)
# redirect your customer to hosted["checkout_url"]
# ── Backend: manage payments with an API token ────────────────────────────────
merchant = tangentopay.MerchantClient(api_token="<your_bearer_token>")
payments = merchant.payments.list(per_page=20)
balance = merchant.wallets.main_balance()
Authentication
TangentoPay has two credential types:
| Credential | Where it goes | Client to use |
|---|---|---|
Service Key (pk_live_… / pk_test_…) |
X-Service-Key header |
ServiceClient |
| API Token (Bearer) | Authorization: Bearer … |
MerchantClient |
Never expose an API token in browser or mobile code. Use ServiceClient on the frontend and MerchantClient only on your server.
ServiceClient
service = tangentopay.ServiceClient(
service_key="pk_live_<your_service_key>",
# optional:
base_url="https://api.tangentopay.com/api/v1",
timeout=30.0,
max_retries=3,
)
MerchantClient
merchant = tangentopay.MerchantClient(api_token="<your_bearer_token>")
Obtain an API token programmatically:
client = tangentopay.MerchantClient()
client.auth.login(email=email, password=password)
token = client.auth.verify_otp(email=email, otp=otp)
merchant = tangentopay.MerchantClient(api_token=token.access_token)
Token expiry and refresh
Catch AuthenticationError and re-authenticate in place:
from tangentopay import AuthenticationError
try:
payments = merchant.payments.list()
except AuthenticationError:
merchant.auth.login(email=email, password=password)
token = merchant.auth.verify_otp(email=email, otp=otp)
merchant.set_token(token.access_token) # updates all resources in place
payments = merchant.payments.list() # retry
Resources
ServiceClient resources
| Attribute | Description |
|---|---|
service.checkout |
Create Stripe-hosted checkout sessions; poll payment status |
service.topups |
Collect money from a customer's MoMo account into the service wallet |
service.withdrawals |
Send money from the service wallet to a customer's MoMo account |
service.provider_status |
Real-time health for MTN MoMo, Orange Money, and Stripe |
MerchantClient resources
| Attribute | Description |
|---|---|
merchant.auth |
Login, OTP verification, profile, logout |
merchant.payments |
View and search your incoming payment history |
merchant.refunds |
Issue refunds on completed payments |
merchant.topups |
Top up your main wallet via card or MoMo |
merchant.payouts |
Send funds out (bank, MoMo, TP wallet, debit card) |
merchant.wallets |
Main and service wallet balances |
merchant.services |
View services; manage enabled payment methods per service |
merchant.customers |
Create and manage customer records |
merchant.analytics |
Payment summaries, revenue, and volume over time |
merchant.logs |
Per-service API request logs |
merchant.transfers |
Internal wallet transfer history |
merchant.provider_status |
Real-time health for MTN MoMo, Orange Money, and Stripe |
Note on service administration: Creating services, rotating API keys, updating webhooks, and other one-time setup tasks are done from the TangentoPay Dashboard. These operations are intentionally not exposed in the SDK.
Provider status
Check provider health before initiating any collection or disbursement — this lets you show users a clear error message instead of a silent payment failure.
status = merchant.provider_status.get()
# or: service.provider_status.get()
# status is a dict keyed by provider slug:
# {
# "mtn_momo": ProviderStatusEntry(slug="mtn_momo", name="MTN Mobile Money", status="operational", ...),
# "orange_money": ProviderStatusEntry(slug="orange_money", name="Orange Money", status="degraded", ...),
# "stripe": ProviderStatusEntry(slug="stripe", name="Stripe", status="operational", ...),
# }
if status["mtn_momo"].status == "down":
raise PaymentUnavailableError(
"MTN Mobile Money is currently unavailable. Try Orange Money or pay by card."
)
Possible status values:
| Value | Meaning |
|---|---|
"operational" |
Fully functional — proceed normally |
"degraded" |
Partial outage — expect higher failure rates |
"down" |
Provider unreachable — do not attempt payments |
Currency and provider guide
| Provider | Supported currencies | Notes |
|---|---|---|
| MTN Mobile Money | XAF only | Cameroon; USSD push via Fapshi. Min 100 XAF, max 500 000 XAF. |
| Orange Money | XAF only | Cameroon; USSD push via Fapshi. Min 100 XAF, max 500 000 XAF. |
| Stripe | USD, EUR, GBP, and more | Multi-currency card checkout and instant payouts. |
When a customer pays via MoMo the transaction currency is XAF. When they pay via Stripe card the currency is whatever currency_code you pass to checkout.create().
Use merchant.wallets.main_balance() to get per-currency balances — the response includes a balances list showing only currencies with a non-zero funded amount, which you can use to build a currency-selector UI in your withdrawal flow.
Service wallet operations (B2B2C)
The service wallet is funded when customers pay through your service's checkout flow.
# ── Collect from a customer's MoMo ───────────────────────────────────────────
topup = service.topups.create(
amount=5000, # XAF
customer_phone="237XXXXXXXXX",
external_ref="ORDER-001",
notify_url="https://yourapp.com/webhooks/momo",
)
# pending — wallet credited after Fapshi webhook confirms
# ── Disburse to a customer's MoMo ────────────────────────────────────────────
withdrawal = service.withdrawals.create(
amount=4000, # XAF
recipient_phone="237XXXXXXXXX",
external_ref="PAYOUT-001",
)
Payouts
Two-step flow: initiate → confirm.
# Step 1 — initiate
initiation = merchant.payouts.initiate(
amount=50_000,
currency_code="XAF",
recipient_type="tangentopay_wallet",
recipient_details={"wallet_address": "user@example.com"},
note="Freelance payment",
)
# Step 2 — confirm with payout PIN
merchant.payouts.confirm(initiation.payout_ref, pin=os.environ["PAYOUT_PIN"])
Virtual card payout (USD, Instant Payout)
# Option A: use a saved card
merchant.payouts.initiate(
amount=100,
currency_code="USD",
recipient_type="virtual_card",
recipient_details={"payout_method_id": "pm_..."},
)
# Option B: one-time Stripe.js token (card never stored)
merchant.payouts.initiate(
amount=100,
currency_code="USD",
recipient_type="virtual_card",
recipient_details={"stripe_token_id": "tok_..."},
)
Bulk payout
with open("payouts.csv", "rb") as f:
batch = merchant.payouts.bulk.initiate(
csv_file=f,
default_recipient_type="tangentopay_wallet",
)
merchant.payouts.bulk.confirm(batch.batch_ref, pin=os.environ["PAYOUT_PIN"])
Merchant wallet top-up
# Via MoMo
topup = merchant.topups.create(
amount=100_000, # XAF
phone="237XXXXXXXXX",
provider="mtn_momo",
)
# Via card (Stripe-hosted page)
card_topup = merchant.topups.create_card_topup(
amount=200,
currency_code="USD",
return_url="https://dashboard.yourapp.com/wallet",
)
Payment methods
methods = merchant.services.list_payment_methods(service_id)
# [{"slug": "mtn_momo", "name": "MTN Mobile Money", "enabled": True, "locked": False, ...}, ...]
# Disable Orange Money if provider is down
status = merchant.provider_status.get()
if status["orange_money"].status == "down":
merchant.services.set_payment_method(service_id, "orange_money", enabled=False)
# Replace entire set (card must always be included)
merchant.services.set_payment_methods(service_id, ["card", "mtn_momo"])
Async support
Every resource has an async equivalent — use AsyncServiceClient and AsyncMerchantClient:
import asyncio
import tangentopay
async def main():
service = tangentopay.AsyncServiceClient(service_key="pk_live_<your_service_key>")
merchant = tangentopay.AsyncMerchantClient(api_token="<your_bearer_token>")
status = await merchant.provider_status.get()
payments = await merchant.payments.list()
session = await service.checkout.create(
products=[{"name": "Pro Plan", "price": 49.99, "quantity": 1}],
currency_code="USD",
customer_email="buyer@example.com",
return_url="https://myshop.com/thank-you",
)
asyncio.run(main())
Error handling
from tangentopay import (
AuthenticationError, # 401
PermissionError, # 403
NotFoundError, # 404
ValidationError, # 422 — includes .errors dict
RateLimitError, # 429 — includes .retry_after seconds
ServerError, # 5xx
NetworkError, # connection-level failure
TangentoPayError, # base class for all above
)
try:
merchant.payouts.initiate(...)
except ValidationError as e:
print(e.errors) # field-level validation messages
except RateLimitError as e:
print(f"Retry after {e.retry_after}s")
except TangentoPayError as e:
raise
Webhook verification
from tangentopay import Webhook, WebhookSignatureError
@app.route("/webhooks/tangentopay", methods=["POST"])
def handle_webhook():
try:
event = Webhook.construct_event(
payload=request.data,
signature=request.headers["X-TangentoPay-Signature"],
secret=os.environ["TANGENTOPAY_WEBHOOK_SECRET"],
)
except WebhookSignatureError:
return "Bad signature", 400
if event.event == "transaction.payment_completed":
fulfill_order(event.payload)
return {"received": True}
Security
Please report security vulnerabilities to security@tangentopay.com rather than opening a public issue.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tangentopay-0.9.0.tar.gz.
File metadata
- Download URL: tangentopay-0.9.0.tar.gz
- Upload date:
- Size: 45.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
85170df2d1bf357b72a691645c558debf059695b4914381d7a53b18bcbd6c676
|
|
| MD5 |
99d72d6132541a56a9383f6fe5f7f487
|
|
| BLAKE2b-256 |
099465e60f00c6612cbeda0b02c22468624699974b2e9b6c6dcdcc44eebe174a
|
File details
Details for the file tangentopay-0.9.0-py3-none-any.whl.
File metadata
- Download URL: tangentopay-0.9.0-py3-none-any.whl
- Upload date:
- Size: 47.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
32a693df3d29b483b64c3b881da513b7657756b60c40c563c7292718bce216a7
|
|
| MD5 |
63a3576a793d30e54a0cb986e118af32
|
|
| BLAKE2b-256 |
dbb3b8431d78445ee0a8ff290d57775125322ef302c678ae60ee2fcf4fa0e73c
|