Skip to main content

Zoho Payments Python SDK

Official Python SDK for the Zoho Payments API — supports IN, IN Sandbox, and US editions.

API reference:

License

Requirements

  • Python 3.11+
  • requests 2.32+

Installation

pip install zoho-payments

Quick Start

from zohopayments import ZohoPayments, Edition
from zohopayments.params import PaymentLinkCreateParams

# 1. Build the client
client = (
    ZohoPayments.builder()
    .account_id("23137556")
    .edition(Edition.IN)
    .oauth_token("1000.xxxx.yyyy")
    .build()
)

# 2. Use a service
link = client.payment_links().create(
    PaymentLinkCreateParams(
        amount=500.00,
        currency="INR",
        description="Order #1234",
        email="customer@example.com",
    )
)

print("Created:", link.payment_link_id)

# 3. Close when done (or use as a context manager)
client.close()

Or using the client as a context manager:

with ZohoPayments.builder() \
        .account_id("23137556") \
        .edition(Edition.IN) \
        .oauth_token("1000.xxxx.yyyy") \
        .build() as client:
    link = client.payment_links().get("pl_abc")

Editions

Edition Payments API Base URL OAuth Accounts URL
Edition.IN https://payments.zoho.in/api/v1 https://accounts.zoho.in
Edition.IN_SANDBOX https://paymentssandbox.zoho.in/api/v1 https://accounts.zoho.in
Edition.US https://payments.zoho.com/api/v1 https://accounts.zoho.com

Helper methods: edition.is_in() returns True for both IN and IN_SANDBOX; edition.is_us() returns True for US.

Authentication

Access token only

client = (
    ZohoPayments.builder()
    .account_id("23137556")
    .edition(Edition.IN)
    .oauth_token("1000.access_token_here")
    .build()
)

Token refresh

The SDK does not auto-refresh tokens. Use ZohoPayments.generate_access_token() when the access token expires, then push the new token into the client:

fresh = ZohoPayments.generate_access_token(
    refresh_token="...",
    client_id="...",
    client_secret="...",
    redirect_uri="...",
    edition=Edition.IN,
)

# Persist the new token in your storage layer
my_store.save(fresh.access_token, fresh.expires_in)

# Update the running client (thread-safe, no rebuild needed)
client.update_token(fresh.access_token)

You can also pass an OAuthToken directly to the builder:

token = ZohoPayments.generate_access_token(...)
client = (
    ZohoPayments.builder()
    .account_id("23137556")
    .edition(Edition.IN)
    .oauth_token(token)
    .build()
)

OAuthToken exposes access_token and expires_in (token lifetime in seconds, as returned by IAM).

Client configuration

client = (
    ZohoPayments.builder()
    .account_id("23137556")                 # Required
    .edition(Edition.IN)                    # Required
    .oauth_token("1000.xxxx.yyyy")          # Required
    .connect_timeout(15.0)                  # Default: 30 seconds
    .request_timeout(45.0)                  # Default: 60 seconds
    .add_default_header("X-Custom-Header", "value")
    .build()
)

Reserved headers (Authorization, User-Agent, Accept, Content-Type, Content-Length, Host) are managed by the SDK and cannot be overridden via add_default_header.

Services

Accessor Description Editions
client.payment_links() Payment link CRUD All
client.payment_sessions() Payment sessions All
client.customers() Customers All (list/delete: US only)
client.payments() Payments All (create: US only)
client.refunds() Refunds All
client.payment_methods() Saved payment methods US only
client.payment_method_sessions() Payment-method collection sessions US only
client.mandates() Recurring mandates IN only
client.collect() Virtual accounts (Collect) IN only

Examples

Payment link

from zohopayments.params import PaymentLinkCreateParams, NotifyCustomerParams

link = client.payment_links().create(
    PaymentLinkCreateParams(
        amount=500.00,
        currency="INR",
        description="Order #1234",
        email="customer@example.com",
        notify_customer=NotifyCustomerParams(email=True, sms=False),
    )
)

# Retrieve
fetched = client.payment_links().get(link.payment_link_id)

# Cancel
cancelled = client.payment_links().cancel(link.payment_link_id)

Customer (US)

from zohopayments.params import CustomerCreateParams, CustomerListParams, MetaDataParams

customer = client.customers().create(
    CustomerCreateParams(
        name="Jane Doe",
        email="jane@example.com",
        meta_data=[MetaDataParams("source", "web")],
    )
)

page = client.customers().list(CustomerListParams(per_page=25, page=1))
for c in page.data:
    print(c.customer_id, c.customer_name)
print("total:", page.page_context.total)

Refund

from zohopayments.params import RefundCreateParams

refund = client.refunds().create(
    payment_id="pay_abc",
    params=RefundCreateParams(
        amount=100.00,
        reason="requested_by_customer",
        type="full",
    ),
)

Mandate (IN)

from zohopayments.params import (
    MandateDetailsParams,
    MandateEnrollmentSessionCreateParams,
)

enrollment = client.mandates().create_enrollment_session(
    MandateEnrollmentSessionCreateParams(
        amount=0.00,
        currency="INR",
        customer_id="cust_abc",
        description="SIP enrollment",
        mandate_details=MandateDetailsParams(
            payment_method_type="upi",
            frequency="monthly",
            description="Monthly SIP",
            amount_rule="variable",
            max_amount=5000.00,
        ),
    )
)

Error handling

All API errors raise a subclass of ZohoPaymentsException.

Exception HTTP
AuthenticationException 401
PermissionException 403
ResourceNotFoundException 404
InvalidRequestException 400, 422
RateLimitException 429
ZohoPaymentsAPIException Any other non-2xx
ConnectionException Network / IO failure
from zohopayments import (
    AuthenticationException,
    InvalidRequestException,
    ZohoPaymentsAPIException,
)

try:
    client.payments().get("pay_missing")
except AuthenticationException:
    # refresh the token and retry
    ...
except InvalidRequestException as exc:
    print(exc.code_string, exc.api_error_message)
except ZohoPaymentsAPIException as exc:
    print("other API error:", exc.http_status_code)

Custom HTTP transport

DefaultHttpClient wraps a requests.Session. To plug in your own retries, proxy, instrumentation, etc., implement HttpClientInterface:

from zohopayments.net.http_client_interface import HttpClientInterface
from zohopayments.net.response import ZohoResponse

class MyTransport(HttpClientInterface):
    def execute(self, request):
        # ... send request.method / request.url / request.headers / request.body
        return ZohoResponse(status_code=200, headers={}, body='{...}')

    def close(self):
        pass

client = (
    ZohoPayments.builder()
    .account_id("...")
    .edition(Edition.IN)
    .oauth_token("...")
    .http_client(MyTransport())
    .build()
)

When you inject a custom transport you cannot also set connect_timeout — the transport manages its own connection lifecycle.

Thread safety

  • ZohoPaymentsClient is safe to share across threads.
  • client.update_token() is atomic; in-flight requests on other threads see either the old or the new token.
  • Services are eagerly constructed and stateless beyond the shared HTTP client.

License

Apache 2.0

Metadata

Release files for zoho-payments 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for zoho-payments 1.0.1
File Interpreter ABI Platform
zoho_payments-1.0.1-py3-none-any.whl Python 3 none any Details

Release files / zoho_payments-1.0.1-py3-none-any.whl

Download URL zoho_payments-1.0.1-py3-none-any.whl
Size 49.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
718669a8f0a85e51e8b17765b0567795005cc44d1a8dff046086ceb467e8809d
BLAKE2b-256 checksum
How to use checksums
aedd94dd1dda66979b11162200d27198629a311a7e190203da2592f704d34fb6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.11.2

Security for zoho-payments 1.0.1

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

1.0.1 This release

1 release file No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page