Skip to main content

PayPlus Python SDK (Unofficial)

Note: This is an unofficial SDK and is not affiliated with or endorsed by PayPlus.

A Python SDK for PayPlus payment gateway with built-in subscription management for SaaS applications.

PyPI version Python 3.9+ License: MIT

Features

  • Full PayPlus API coverage — payment pages, transactions, recurring payments, customers
  • Subscription management — payment-link-based recurring billing for SaaS apps
  • Database integration — MongoDB and in-memory storage backends
  • Webhook handling — IPN/webhook integration with HMAC signature verification
  • Async support — full async/await for modern Python apps
  • Type safe — Pydantic models with full type hints

Installation

pip install payplus-python

With optional dependencies:

pip install payplus-python[fastapi]    # FastAPI webhook integration
pip install payplus-python[mongodb]    # MongoDB storage

Implementation Steps

A step-by-step guide covering the full subscription lifecycle in your app.

Step 1: Initialize the SDK

from decimal import Decimal
from payplus import PayPlus, SubscriptionManager
from payplus.models.subscription import BillingCycle
from payplus.subscriptions.storage import MongoDBStorage
from payplus.webhooks import WebhookHandler
from motor.motor_asyncio import AsyncIOMotorClient

client = PayPlus(
    api_key="your_api_key",
    secret_key="your_secret_key",
    sandbox=True,
)
mongo = AsyncIOMotorClient("mongodb://localhost:27017")
storage = MongoDBStorage(mongo.your_database)
manager = SubscriptionManager(client, storage)
webhook_handler = WebhookHandler(client)

Step 2: Define your plans (run once on app setup)

await manager.create_tier(
    tier_id="basic",
    name="Basic",
    price=Decimal("29"),
    billing_cycle=BillingCycle.MONTHLY,
    trial_days=7,
)

await manager.create_tier(
    tier_id="pro",
    name="Pro",
    price=Decimal("79"),
    billing_cycle=BillingCycle.MONTHLY,
    trial_days=14,
)

Step 3: User signs up

customer = await manager.create_customer(
    email="user@example.com",
    name="John Doe",
    phone="050-1234567",
)
# Save customer.id in your user record

Step 4: User subscribes to a plan

subscription = await manager.create_subscription(
    customer_id=customer.id,
    tier_id="pro",
    callback_url="https://yourapp.com/webhooks/payplus",
    payment_page_uid="your-payplus-payment-page-uid",  # required by PayPlus
    success_url="https://yourapp.com/subscription/success",
    failure_url="https://yourapp.com/subscription/failure",
)

# Redirect user to complete payment
redirect(subscription.payment_page_link)

# Save subscription.id in your user record

Behind the scenes this:

  1. Creates the customer on PayPlus (POST /Customers/Add) if not already created
  2. Generates a payment link with charge_method=3 and recurring_settings derived from the tier
  3. Saves the subscription locally with status=INCOMPLETE

The user fills in their card details on the PayPlus hosted page. You never touch card data.

Step 5: Set up the webhook endpoint

from fastapi import FastAPI, Request, HTTPException
from payplus.exceptions import WebhookSignatureError

app = FastAPI()

@app.post("/webhooks/payplus")
async def payplus_webhook(request: Request):
    payload = await request.body()
    signature = request.headers.get("hash")  # PayPlus signs the callback in the `hash` header
    try:
        event = await webhook_handler.handle_async(payload, signature)
        await manager.handle_webhook_event(event)
        return {"received": True}
    except WebhookSignatureError:
        raise HTTPException(status_code=400, detail="Invalid signature")

This single endpoint handles every subscription event automatically:

Webhook event What happens
First payment succeeds INCOMPLETE -> ACTIVE, recurring_uid stored
Recurring charge succeeds Billing period advanced, status stays ACTIVE
Recurring charge fails Status -> PAST_DUE (-> UNPAID after 4 failures)
Recurring canceled Status -> CANCELED
Cancel at period end flagged After last charge, cancels on PayPlus and sets CANCELED

Step 6: Check access in your app

sub = await manager.get_subscription(subscription_id)
if sub and sub.is_active:
    # User has access
    ...

Step 7: User upgrades plan

await manager.change_tier(subscription.id, new_tier_id="enterprise")

For an active subscription this creates a new PayPlus recurring order for the new tier (starting at the current period end, with no immediate charge) and cancels the old one. The card token is saved automatically from the first payment webhook.

Step 8: User pauses subscription

await manager.pause_subscription(subscription.id)

# Later, resume it
await manager.resume_subscription(subscription.id)

Step 9: User cancels subscription

# Cancel at end of billing period (user keeps access until then)
await manager.cancel_subscription(
    subscription.id,
    at_period_end=True,
    reason="Customer requested",
)

# Or cancel immediately
await manager.cancel_subscription(subscription.id, at_period_end=False)

Step 10: React to lifecycle events (optional)

Register hooks to trigger your own business logic:

manager.on("subscription.activated", lambda sub: send_welcome_email(sub))
manager.on("subscription.renewed", lambda sub: log_renewal(sub))
manager.on("subscription.payment_failed", lambda sub: send_dunning_email(sub))
manager.on("subscription.canceled", lambda sub: handle_offboarding(sub))

Trials

If a tier has trial_days set, the subscription flow changes:

  • create_subscription() sets jump_payments in recurring_settings, telling PayPlus to wait N days before the first charge
  • The subscription starts as INCOMPLETE (waiting for the user to enter card details on the payment page)
  • When the user completes the payment page, PayPlus validates the card but doesn't charge yet
  • The webhook activates the subscription as TRIALING (since trial_end is in the future)
  • After the trial period, PayPlus charges automatically and sends a recurring.charged webhook
  • is_active returns True for both ACTIVE and TRIALING statuses
# Tier with a 14-day trial
await manager.create_tier(
    tier_id="pro",
    name="Pro",
    price=Decimal("79"),
    trial_days=14,  # 14 free days before first charge
)

# After subscription is created and user completes payment page:
# sub.status == "trialing"
# sub.is_active == True
# sub.trial_end == ~14 days from now

How it all fits together

User clicks "Subscribe to Pro"
        |
        v
create_subscription()
  - Creates customer on PayPlus
  - Generates payment link with recurring settings
  - Subscription status: INCOMPLETE
        |
        v
User redirected to PayPlus payment page
User enters card details and pays
        |
        v
PayPlus sends webhook to callback_url
        |
        v
handle_webhook_event()
  - Matches webhook to subscription via page_request_uid
  - Saves card token and recurring_uid
  - Sets status: ACTIVE (or TRIALING if trial_days > 0)
        |
        v
Every billing cycle, PayPlus charges automatically
  - recurring.charged  -> period advanced, still ACTIVE
  - recurring.failed   -> PAST_DUE (-> UNPAID after 4 failures)

Lifecycle actions (from your app):
  - change_tier()      -> new recurring order on PayPlus + cancels the old one
  - pause/resume       -> updates local status
  - cancel(at_period_end=True)  -> flags locally, cancels on PayPlus after last charge
  - cancel(at_period_end=False) -> cancels on PayPlus immediately, status: CANCELED

Direct API Usage

You can also use the PayPlus API directly without the subscription manager:

Payment Link

result = client.payment_pages.generate_link(
    amount=100.00,
    currency="ILS",
    payment_page_uid="your-payplus-payment-page-uid",  # required
    description="One-time payment",
    customer_email="customer@example.com",
    success_url="https://yourapp.com/success",
    callback_url="https://yourapp.com/webhooks/payplus",
)
print(result["data"]["payment_page_link"])

Payment Link with Recurring

from payplus.api.payment_pages import build_recurring_settings

result = client.payment_pages.generate_link(
    amount=79.00,
    currency="ILS",
    payment_page_uid="your-payplus-payment-page-uid",  # required
    charge_method=3,  # Recurring
    customer_uid="payplus-customer-uid",
    callback_url="https://yourapp.com/webhooks/payplus",
    recurring_settings=build_recurring_settings(
        billing_cycle="monthly",
        trial_days=14,
        number_of_charges=0,  # Unlimited
    ),
)

Create Customer

result = client.customers.add(
    customer_name="John Doe",
    email="john@example.com",
    phone="050-1234567",
)
customer_uid = result["data"]["customer_uid"]

Transactions

# Charge a saved card token (needs terminal_uid + cashier_uid on the client)
result = client.transactions.charge(
    99.00,
    token="card_token",
    customer_uid="payplus-customer-uid",
    currency="ILS",
)

# Refund a transaction by its UID
client.transactions.refund(
    transaction_uid=result["data"]["transaction_uid"],
    amount=99.00,
)

Recurring Payments

# Create a recurring order (monthly) from a saved token
result = client.recurring.add(
    customer_uid="payplus-customer-uid",
    card_token="card_token",
    start_date="2026-02-01",
    items=[{"name": "Pro plan", "price": 49.00, "quantity": 1}],
    recurring_type=2,   # 0=Daily, 1=Weekly, 2=Monthly
    recurring_range=1,
)

# Cancel (deactivate) a recurring order
client.recurring.cancel(result["data"]["recurring_uid"])

Storage Backends

MongoDB

from motor.motor_asyncio import AsyncIOMotorClient
from payplus.subscriptions.storage import MongoDBStorage

mongo = AsyncIOMotorClient("mongodb://localhost:27017")
storage = MongoDBStorage(mongo.your_database)
await storage.create_indexes()  # Run once

In-Memory (development/testing)

# Used automatically when no storage is provided
manager = SubscriptionManager(client)

API Reference

PayPlus Client

Module Methods
client.customers add()
client.payment_pages generate_link(), get_status()
client.transactions charge(), get(), refund(), list()
client.recurring add(), update(), charge(), cancel(), get(), list()
client.payments check_card(), tokenize(), get_token()

Subscription Manager

Method Description
create_customer() Create a new customer
get_customer() Get a customer by ID
create_tier() Create a pricing tier
get_tier() Get a tier by ID
list_tiers() List all tiers
create_subscription() Create subscription and generate payment link
get_subscription() Get a subscription by ID
change_tier() Upgrade/downgrade (new PayPlus recurring order + cancels old)
pause_subscription() Pause a subscription
resume_subscription() Resume a paused subscription
cancel_subscription() Cancel immediately or at period end
handle_webhook_event() Process webhook and update subscription state

Configuration

PAYPLUS_API_KEY=your_api_key
PAYPLUS_SECRET_KEY=your_secret_key
PAYPLUS_TERMINAL_UID=your_terminal_uid  # Optional
PAYPLUS_SANDBOX=true  # For testing
# Sandbox (restapidev.payplus.co.il)
client = PayPlus(api_key="...", secret_key="...", sandbox=True)

# Production (restapi.payplus.co.il)
client = PayPlus(api_key="...", secret_key="...", sandbox=False)

Error Handling

from payplus.exceptions import (
    PayPlusError,
    PayPlusAPIError,
    PayPlusAuthError,
    SubscriptionError,
    WebhookSignatureError,
)

try:
    result = client.transactions.charge(100, token="...", customer_uid="...")
except PayPlusAuthError:
    print("Invalid API credentials")
except PayPlusAPIError as e:
    print(f"API error [{e.status_code}]: {e.message}")
except PayPlusError as e:
    print(f"General error: {e}")

License

MIT License - see LICENSE for details.

Links

Download files

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

Source Distribution

payplus_python-0.3.0.tar.gz (38.5 kB view details)

Uploaded Source

Built Distribution

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

payplus_python-0.3.0-py3-none-any.whl (44.2 kB view details)

Uploaded Python 3

File details

Details for the file payplus_python-0.3.0.tar.gz.

File metadata

  • Download URL: payplus_python-0.3.0.tar.gz
  • Upload date:
  • Size: 38.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for payplus_python-0.3.0.tar.gz
Algorithm Hash digest
SHA256 a72b858d5b26fe4d01b8209e2fc74e378c16a97f7193dd0c5fa7fe2a4c14555b
MD5 f7722d251c1c8d8b706c0198f3c16aa5
BLAKE2b-256 b71720037654334f477b44061269f027c5b401892912a7d9e99ee8988508e468

See more details on using hashes here.

Provenance

The following attestation bundles were made for payplus_python-0.3.0.tar.gz:

Publisher: publish.yml on Two-Solutions/payplus-python

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

File details

Details for the file payplus_python-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: payplus_python-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 44.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for payplus_python-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 171eec28af5e3153ef71bfd43db464701c3aef1a30a01bae681fd4417e4e11b2
MD5 afff6074c439cf07b5dd59c10b1eef92
BLAKE2b-256 6cdb4ce35058d11da6b0c65f23448223f2005e104ac32444dff5235efd3be0ac

See more details on using hashes here.

Provenance

The following attestation bundles were made for payplus_python-0.3.0-py3-none-any.whl:

Publisher: publish.yml on Two-Solutions/payplus-python

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

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

0.1.2

2 files

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