Python
Official Python SDK for the Cost+ payment gateway
Official Python SDK for the Cost+ payment gateway. Simplifies the HPP (Hosted Payment Page) redirect flow, HMAC payload signing, and webhook verification.
Features
- Minimal dependencies — only
requests - Full type hints and frozen dataclass return types
- HMAC-SHA256 signature generation and constant-time verification
- Webhook parsing + API-based order verification
- Tested across Python 3.10, 3.11, and 3.12
Requirements
- Python 3.10 or later
- A Cost+ merchant account — dashboard.costplus.io
Installation
pip install nopayn-sdkQuick Start
1. Initialise the Client
from nopayn import NoPaynClient
client = NoPaynClient(
api_key="your-api-key",
merchant_id="your-project",
)2. Create a Payment and Redirect to the HPP
result = client.generate_payment_url(
amount=1295, # €12.95 in cents
currency="EUR",
merchant_order_id="ORDER-001",
description="Premium Widget",
return_url="https://shop.example.com/success",
failure_url="https://shop.example.com/failure",
webhook_url="https://shop.example.com/webhook",
locale="en-GB",
expiration_period="PT30M",
)
# Redirect the customer
# result.order_url → HPP (customer picks payment method)
# result.payment_url → direct link to the first transaction's payment method
# result.signature → HMAC-SHA256 for verification
# result.order_id → NoPayn order UUID3. Handle the Webhook (Flask Example)
@app.route("/webhook", methods=["POST"])
def webhook():
verified = client.verify_webhook(request.get_data(as_text=True))
print(verified.order.status) # 'completed', 'cancelled', etc.
print(verified.is_final) # True when the order won't change
if verified.order.status == "completed":
# Fulfil the order
pass
return "", 200API Reference
NoPaynClient(api_key, merchant_id, base_url?)
| Parameter | Type | Required | Default |
|---|---|---|---|
api_key | str | Yes | — |
merchant_id | str | Yes | — |
base_url | str | No | https://api.nopayn.co.uk |
client.create_order(**kwargs) -> Order
Creates an order via POST /v1/orders/.
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | int | Yes | Amount in smallest currency unit (cents) |
currency | str | Yes | ISO 4217 code (EUR, GBP, USD, NOK, SEK) |
merchant_order_id | str | No | Your internal order reference |
description | str | No | Order description |
return_url | str | No | Redirect after successful payment |
failure_url | str | No | Redirect on cancel/expiry/error |
webhook_url | str | No | Async status-change notifications |
locale | str | No | HPP language (en-GB, de-DE, nl-NL, etc.) |
payment_methods | list[str] | No | Filter HPP methods |
expiration_period | str | No | ISO 8601 duration (PT30M) |
Available payment methods: credit-card, apple-pay, google-pay, vipps-mobilepay
client.get_order(order_id) -> Order
Fetches order details via GET /v1/orders/{id}/.
client.create_refund(order_id, amount, description?) -> Refund
Issues a full or partial refund via POST /v1/orders/{id}/refunds/.
client.generate_payment_url(**kwargs) -> PaymentUrlResult
Convenience method that creates an order and returns:
PaymentUrlResult(
order_id="...", # NoPayn order UUID
order_url="...", # HPP URL
payment_url="...", # Direct payment URL (first transaction)
signature="...", # HMAC-SHA256 of amount:currency:order_id
order=Order(...), # Full order object
)client.generate_signature(amount, currency, order_id) -> str
Generates an HMAC-SHA256 hex signature.
client.verify_signature(amount, currency, order_id, signature) -> bool
Constant-time verification of an HMAC-SHA256 signature.
client.verify_webhook(raw_body) -> VerifiedWebhook
Parses the webhook body, then calls GET /v1/orders/{id}/ to verify the actual status.
Standalone HMAC Utilities
from nopayn import generate_signature, verify_signature
sig = generate_signature("your-api-key", 1295, "EUR", "order-uuid")
ok = verify_signature("your-api-key", 1295, "EUR", "order-uuid", sig)Data Types
All API responses are returned as frozen dataclasses:
| Class | Fields |
|---|---|
Order | id, amount, currency, status, created, modified, transactions, description, merchant_order_id, return_url, failure_url, order_url, completed |
Transaction | id, amount, currency, status, created, modified, payment_method, payment_url, expiration_period |
Refund | id, amount, status |
PaymentUrlResult | order_id, order_url, payment_url, signature, order |
VerifiedWebhook | order_id, order, is_final |
Error Handling
from nopayn import ApiError, NoPaynError, WebhookError
try:
client.create_order(amount=100, currency="EUR")
except ApiError as exc:
print(exc.status_code) # 401, 400, etc.
print(exc.error_body) # Raw API error response
except NoPaynError as exc:
print(exc) # Network or parsing errorOrder Statuses
| Status | Final? | Description |
|---|---|---|
new | No | Order created |
processing | No | Payment in progress |
completed | Yes | Payment successful — deliver the goods |
cancelled | Yes | Payment cancelled by customer |
expired | Yes | Payment link timed out |
error | Yes | Technical failure |
Webhook Best Practices
- Always verify via the API — the webhook payload only contains the order ID, never the status. The SDK's
verify_webhook()does this automatically. - Return HTTP 200 to acknowledge receipt. Any other code triggers up to 10 retries (2 minutes apart).
- Implement a backup poller — for orders older than 10 minutes that haven't reached a final status, poll
get_order()as a safety net. - Be idempotent — you may receive the same webhook multiple times.
Test Cards
Use these cards in Cost+ test mode (sandbox website):
| Card | Number | Notes |
|---|---|---|
| Visa (success) | 4111 1111 1111 1111 | Any CVV |
| Mastercard (success) | 5544 3300 0003 7 | Any CVV |
| Visa (declined) | 4111 1111 1111 1105 | Do Not Honor |
| Visa (insufficient funds) | 4111 1111 1111 1151 | Insufficient Funds |
Use any future expiry date and any 3-digit CVC.
Demo App
A Flask-based demo app is included in the GitHub repository for testing the full payment flow.
Support
Need help? Reach out to our support team at support@costplus.io.