Skip to main content

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.

PyPI version CI Python 3.9+ License: MIT


Table of contents


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

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

tangentopay-0.9.0.tar.gz (45.7 kB view details)

Uploaded Source

Built Distribution

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

tangentopay-0.9.0-py3-none-any.whl (47.6 kB view details)

Uploaded Python 3

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

Hashes for tangentopay-0.9.0.tar.gz
Algorithm Hash digest
SHA256 85170df2d1bf357b72a691645c558debf059695b4914381d7a53b18bcbd6c676
MD5 99d72d6132541a56a9383f6fe5f7f487
BLAKE2b-256 099465e60f00c6612cbeda0b02c22468624699974b2e9b6c6dcdcc44eebe174a

See more details on using hashes here.

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

Hashes for tangentopay-0.9.0-py3-none-any.whl
Algorithm Hash digest
SHA256 32a693df3d29b483b64c3b881da513b7657756b60c40c563c7292718bce216a7
MD5 63a3576a793d30e54a0cb986e118af32
BLAKE2b-256 dbb3b8431d78445ee0a8ff290d57775125322ef302c678ae60ee2fcf4fa0e73c

See more details on using hashes here.

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