Skip to main content

Typed Python client for the Revolut Merchant API (sync + async).

Project description

revolut-merchant-py

CI PyPI Python License: MIT

A typed Python client for the Revolut Merchant API — online payments (orders, payments, refunds, customers) and webhooks — with both synchronous and asynchronous clients.

Status: early development (v0.1.0). See the ROADMAP.

Features

  • Sync (RevolutMerchantClient) and async (AsyncRevolutMerchantClient) clients
  • Typed request/response models (Pydantic v2)
  • Amounts handled safely in minor units (integers)
  • Sandbox and production environments
  • Webhook signature verification (HMAC-SHA256)
  • mypy --strict clean, fully type-annotated

Installation

pip install revolut-merchant-py

The distribution is published as revolut-merchant-py; the import package is revolut:

import revolut

Latest from source:

pip install git+https://github.com/robertruben98/revolut-merchant-py.git

Quick start

from revolut import RevolutMerchantClient

with RevolutMerchantClient(secret_key="sk_...", environment="sandbox") as client:
    order = client.orders.create(amount=1000, currency="GBP")  # 1000 = £10.00
    # `token` is the public id used by the Web SDK / hosted checkout page.
    print(order.id, order.state, order.public_token)

Async — the same API, awaited:

import asyncio
from revolut import AsyncRevolutMerchantClient

async def main():
    async with AsyncRevolutMerchantClient(secret_key="sk_...") as client:
        order = await client.orders.create(amount=1000, currency="GBP")
        print(order.id)

asyncio.run(main())

Working with money

Amounts are integers in minor units (e.g. 1099 = £10.99). The Money helper converts to/from major units, accounting for zero- and three-decimal currencies:

from revolut import Money

Money.from_major("10.99", "GBP").amount   # 1099
Money.from_major("1000", "JPY").amount    # 1000  (JPY has no minor unit)
Money(1234, "BHD").to_major()             # Decimal("1.234")

Orders

order = client.orders.create(
    amount=5000, currency="EUR",
    capture_mode="manual",                      # authorise now, capture later
    merchant_order_data={"reference": "INV-42"},
    idempotency_key="INV-42",                   # safe to retry
)
client.orders.capture(order.id, amount=5000)    # capture authorised funds
client.orders.cancel(order.id)                  # or cancel before capture
client.orders.list(limit=50, state="completed") # paginated listing

Refunds

# Refunds create a new order of type "refund" linked to the original.
refund = client.refunds.create(order.id, amount=2500, idempotency_key="INV-42-r1")
assert refund.type == "refund"

Pagination

List endpoints accept paging params, or use iter() to stream across all pages transparently (sync yields, async is an async iterator):

for order in client.orders.iter(state="completed", limit=100):
    ...

for customer in client.customers.iter(max_items=500):
    ...

# async
async for order in async_client.orders.iter():
    ...

Saved payment methods

Tokenized methods live under a customer; charge one via payments.pay:

methods = client.payment_methods.list(customer_id, only_merchant=True)
client.payment_methods.retrieve(customer_id, methods[0].id)
client.payment_methods.delete(customer_id, methods[0].id)

# charge a saved method against an existing order
client.payments.pay(order.id, payment_method_id=methods[0].id)

Webhooks

Verify the authenticity of incoming webhook deliveries (HMAC-SHA256, replay protected). Pass the raw request body — do not re-serialize it:

from revolut import verify_signature, SignatureVerificationError

try:
    event = verify_signature(
        raw_body=request.body,                                  # bytes or str
        signature_header=request.headers["Revolut-Signature"],
        timestamp_header=request.headers["Revolut-Request-Timestamp"],
        signing_secret="wsk_...",                               # from webhook creation
    )
    print(event.event, event.order_id)
except SignatureVerificationError:
    ...  # reject the request (401)

Manage webhook endpoints via the API:

wh = client.webhooks.create(url="https://example.com/cb", events=["ORDER_COMPLETED"])
print(wh.signing_secret)  # store this to verify future deliveries

More resources

The client also exposes subscriptions, payouts and locations, each with the same sync/async parity:

client.subscriptions.list()
client.payouts.retrieve("po_...")
client.locations.create(name="Web", type="online")

Error handling

Non-2xx responses raise a typed subclass of RevolutError:

from revolut import NotFoundError, RateLimitError, RevolutError

try:
    client.orders.retrieve("missing")
except NotFoundError as exc:
    print(exc.status_code, exc.code, exc.message)
except RateLimitError as exc:
    print("retry after", exc.retry_after)
except RevolutError:
    ...  # base class for everything this library raises

Transient failures (429, 5xx) are retried automatically with exponential backoff; tune via RetryConfig (including optional jitter). Enable request tracing with the revolut logger:

import logging
logging.getLogger("revolut").setLevel(logging.DEBUG)

from revolut import RetryConfig
client = RevolutMerchantClient(secret_key="sk_...", retry=RetryConfig(max_retries=4, jitter=0.3))

Stability and versioning

This project follows SemVer. From 1.0.0 the public API is the set of names exported from the top-level revolut package (i.e. revolut.__all__) plus the documented resource attributes on the clients.

  • Patch (1.0.x): bug fixes, no API changes.
  • Minor (1.x.0): backwards-compatible additions (new resources, fields, optional arguments).
  • Major (x.0.0): backwards-incompatible changes.

Anything under a leading underscore (e.g. revolut._http) is internal and may change at any time. Deprecations are announced in the CHANGELOG, keep working for at least one minor release, and emit DeprecationWarning before removal.

Development

pip install -e ".[dev]"
ruff check . && ruff format --check .
mypy
pytest                       # hermetic unit tests (mocked HTTP)

Live sandbox integration tests are skipped unless a sandbox key is provided:

REVOLUT_SECRET_KEY=sk_... pytest tests/integration -v

License

MIT

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

revolut_merchant_py-1.0.1.tar.gz (33.3 kB view details)

Uploaded Source

Built Distribution

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

revolut_merchant_py-1.0.1-py3-none-any.whl (32.7 kB view details)

Uploaded Python 3

File details

Details for the file revolut_merchant_py-1.0.1.tar.gz.

File metadata

  • Download URL: revolut_merchant_py-1.0.1.tar.gz
  • Upload date:
  • Size: 33.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for revolut_merchant_py-1.0.1.tar.gz
Algorithm Hash digest
SHA256 0590402c6158733d4726382aafa91babdcf93e9862835ff6a0211e635eb1893e
MD5 a66591bf2b6433a7e3236982c4b568ea
BLAKE2b-256 c92813dd5799daaef3b0095f159099e926933fe70dde37530da4ea1e074e76fe

See more details on using hashes here.

Provenance

The following attestation bundles were made for revolut_merchant_py-1.0.1.tar.gz:

Publisher: publish.yml on robertruben98/revolut-merchant-py

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

File details

Details for the file revolut_merchant_py-1.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for revolut_merchant_py-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8aa9d9c13e4081b33de4ba8ab22a246cd77fbd4a75ff6a279cd6356c02ffadb6
MD5 a2c9cbe5fb4ca547d7c722d5a8d7659c
BLAKE2b-256 1dacd6fc8936019c93eb9ecff6ddaf4dd3448170e9db7aeeeab7dae4911f0d04

See more details on using hashes here.

Provenance

The following attestation bundles were made for revolut_merchant_py-1.0.1-py3-none-any.whl:

Publisher: publish.yml on robertruben98/revolut-merchant-py

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

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