Cost+Docs

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

Installation

pip install nopayn-sdk

Quick 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 UUID

3. 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 "", 200

API Reference

NoPaynClient(api_key, merchant_id, base_url?)

ParameterTypeRequiredDefault
api_keystrYes
merchant_idstrYes
base_urlstrNohttps://api.nopayn.co.uk

client.create_order(**kwargs) -> Order

Creates an order via POST /v1/orders/.

ParameterTypeRequiredDescription
amountintYesAmount in smallest currency unit (cents)
currencystrYesISO 4217 code (EUR, GBP, USD, NOK, SEK)
merchant_order_idstrNoYour internal order reference
descriptionstrNoOrder description
return_urlstrNoRedirect after successful payment
failure_urlstrNoRedirect on cancel/expiry/error
webhook_urlstrNoAsync status-change notifications
localestrNoHPP language (en-GB, de-DE, nl-NL, etc.)
payment_methodslist[str]NoFilter HPP methods
expiration_periodstrNoISO 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:

ClassFields
Orderid, amount, currency, status, created, modified, transactions, description, merchant_order_id, return_url, failure_url, order_url, completed
Transactionid, amount, currency, status, created, modified, payment_method, payment_url, expiration_period
Refundid, amount, status
PaymentUrlResultorder_id, order_url, payment_url, signature, order
VerifiedWebhookorder_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 error

Order Statuses

StatusFinal?Description
newNoOrder created
processingNoPayment in progress
completedYesPayment successful — deliver the goods
cancelledYesPayment cancelled by customer
expiredYesPayment link timed out
errorYesTechnical failure

Webhook Best Practices

  1. Always verify via the API — the webhook payload only contains the order ID, never the status. The SDK's verify_webhook() does this automatically.
  2. Return HTTP 200 to acknowledge receipt. Any other code triggers up to 10 retries (2 minutes apart).
  3. Implement a backup poller — for orders older than 10 minutes that haven't reached a final status, poll get_order() as a safety net.
  4. Be idempotent — you may receive the same webhook multiple times.

Test Cards

Use these cards in Cost+ test mode (sandbox website):

CardNumberNotes
Visa (success)4111 1111 1111 1111Any CVV
Mastercard (success)5544 3300 0003 7Any CVV
Visa (declined)4111 1111 1111 1105Do Not Honor
Visa (insufficient funds)4111 1111 1111 1151Insufficient 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.

On this page