Swish - Python client
Client library to integrate with the swish commerce and payout api.
Supported APIs
- https://developer.swish.nu/documentation/getting-started/swish-commerce-api
- https://developer.swish.nu/documentation/getting-started/swish-payout-api
- https://developer.swish.nu/documentation/getting-started/swish-recurring-payments-api (experimental)
- https://developer.swish.nu/documentation/getting-started/swish-qr-code (experimental)
Recurring and QR support is experimental in 0.4.0. Contract validation and stabilization are deferred to 0.5.0; these interfaces may change. Commerce and Payout are not experimental.
Installation
pip install getswish
Prerequisites
Swish api integration require communication certificates and keys to integrate with the commerce api and additional signing certificates and keys for signing payout in the payout api. The client setup require that all the certificates are available through file paths which are given on client setup. The communication certificates are create through your bank portal after the appropriate agreements are signed and signing certificates are set up after additional payout agreements are signed, trough https://portal.swish.nu/.
The signing certificate, private key and serial is only required when using the payout API.
Example - Test Client
The example uses the public MSS 3.0 test credentials bundled with the installed library, valid until September 11, 2027. Do not use these credentials in production. Source: the MSS 3.0 bundle from Swish test environments.
import getswish
swish_client = getswish.SwishClient()
callback_url = "https://example.com/callback/"
All requests default to a 10-second connect timeout and a 30-second read timeout.
Configure these with getswish.SwishClient(timeout=(5.0, 60.0)), use a single number for both,
or set timeout=None to disable timeouts. The read timeout limits waiting between received data,
not the total request duration. Timeouts raise Requests exceptions, not SwishError.
After a payment or payout timeout, check its status before retrying; the server may have accepted it.
Example - Commerce API
# Perform a payment request - M-commerce.
payment_m = swish_client.create_payment(
100.00, callback_url, message="Product name."
)
# Perform a payment request - E-commerce.
payment_e = swish_client.create_payment(
100.00, callback_url, "46701234567", message="Product name."
)
# Retrieve info about the payment
payment_retrieved = swish_client.retrieve_payment(payment_e.id)
# Cancel the payment
payment_cancelled = swish_client.cancel_payment(payment_e.id)
# After the callback confirms payment_m is PAID, retrieve its payment reference and payer.
paid_payment = swish_client.retrieve_payment(payment_m.id)
# Refund the whole amount to the previous payer, who is now the refund payee.
payment_refund = swish_client.create_refund(
paid_payment.payment_reference, callback_url, paid_payment.payer_alias, paid_payment.amount
)
# Retrieve info about the refund
payment_refund_retrieved = swish_client.retrieve_refund(payment_refund.id)
Example - Recurring Payments API (experimental)
Recurring payments allow a merchant to charge a customer on a consent basis. The customer signs a consent once (via e-commerce or m-commerce flow), and the merchant can then initiate payments against that consent without further customer interaction.
# Check if the payer's bank supports recurring payments for their alias.
availability = swish_client.check_recurring_alias_availability("46701234567")
if not availability.recurring_is_offered:
raise Exception("Recurring payments not available for this payer.")
# Create a recurring consent - E-commerce flow (payer alias known).
consent = swish_client.create_recurring_ecommerce_consent(
callback_url, "46701234567", payee_payment_reference="subscription-001"
)
# Create a recurring consent - M-commerce flow (payer alias unknown; token shown to user).
consent_m = swish_client.create_recurring_mcommerce_consent(
callback_url, payee_payment_reference="subscription-001"
)
# consent_m.payment_request_token -- show this to the payer in the Swish app.
# Retrieve consent info (e.g. after callback confirms ACTIVE status).
consent_active = swish_client.retrieve_recurring_consent(consent.consent_id)
# Charge the customer against the active consent.
payment = swish_client.create_recurring_payment(
consent.consent_id, 100.00, callback_url, message="Monthly subscription."
)
# Check payment status.
payment_status = swish_client.retrieve_recurring_payment(payment.payment_id)
# Update the callback URL on an existing consent.
swish_client.update_recurring_consent_callback_url(consent.consent_id, "https://example.com/new-callback/")
# Delete (cancel) the consent.
swish_client.delete_recurring_consent(consent.consent_id)
Example - QR Code API (experimental)
# Generate a prefilled QR code (payee + amount pre-set, editable by payer).
qr_png = swish_client.create_prefilled_qr_code(
"png",
payee="1234679304",
amount=100.00,
amount_editable=True,
message="Order #1234",
size=300,
)
# qr_png is bytes -- write to a file or serve as an image response.
# Generate an SVG QR code.
qr_svg = swish_client.create_prefilled_qr_code("svg", payee="1234679304")
# qr_svg is a str containing the SVG markup.
# Create a fresh M-commerce request without a payer alias; use its token while pending.
qr_payment = swish_client.create_payment(100.00, callback_url, message="QR purchase.")
# Generate a QR code from this Commerce payment token, not a recurring consent token.
qr_commerce = swish_client.create_commerce_qr_code(
qr_payment.payment_request_token, "png", size=300
)
Example - Payout API
# Generate a merchant specific reference.
# This reference could be order id or similar.
# Use generate_transaction_id for convenience.
from getswish.utils import generate_transaction_id
reference_id = generate_transaction_id()
# Perform a payment request
payout = swish_client.create_payout(
reference_id, "46701234567", "197001019876", 10.00, callback_url, message="Test payout message."
)
# Retrieve info about the payout
payout_retrieved = swish_client.retrieve_payout(payout.payout_instruction_uuid)
Example - Custom Test Client
This example explicitly uses getswish.TestEnvironment and the bundled test certificates. For production, use
getswish.ProductionEnvironment, your issued communication certificate and key, the appropriate TLS trust certificate,
and your merchant Swish number. Do not use the bundled test credentials in production.
from pathlib import Path
import getswish
cert_base = Path(getswish.__file__).resolve().parent / "certificates" / "mss_test_3.0"
swish_client = getswish.SwishClient(
environment=getswish.TestEnvironment,
certificates=getswish.Certificates(
communication=getswish.Certificate(
public=f"{cert_base}/Swish_Merchant_TestCertificate_1234679304.pem",
private_key=f"{cert_base}/Swish_Merchant_TestCertificate_1234679304.key",
),
verify=getswish.Certificate(public=f"{cert_base}/Swish_TLS_RootCA.pem"),
),
merchant_swish_number="1234679304",
)
Example - Custom Test Client with Payout
This is also a test configuration. Payout additionally requires a signing certificate, private key, and serial number.
For production, use getswish.ProductionEnvironment and your issued communication and signing credentials instead of
these bundled test files.
from pathlib import Path
import getswish
cert_base = Path(getswish.__file__).resolve().parent / "certificates" / "mss_test_3.0"
swish_client = getswish.SwishClient(
environment=getswish.TestEnvironment,
certificates=getswish.Certificates(
communication=getswish.Certificate(
public=f"{cert_base}/Swish_Merchant_TestCertificate_1234679304.pem",
private_key=f"{cert_base}/Swish_Merchant_TestCertificate_1234679304.key",
),
verify=getswish.Certificate(public=f"{cert_base}/Swish_TLS_RootCA.pem"),
signing=getswish.Certificate(
public=f"{cert_base}/Swish_Merchant_TestSigningCertificate_1234679304.pem",
private_key=f"{cert_base}/Swish_Merchant_TestSigningCertificate_1234679304.key",
public_serial="4F24C03A0295A0B53596240EA8C0F430",
),
),
merchant_swish_number="1234679304",
)
callback_url = "https://example.com/callback/"
Generating public_serial for signing certificate
The signing certificate public_serial is extracted from the certificate using this command on linux.
openssl x509 -in Swish_Merchant_TestSigningCertificate_1234679304.pem -serial -noout
Error handling
All HTTP 4xx/5xx responses raise getswish.SwishError, including responses with empty bodies.
It inherits from requests.HTTPError (and requests.RequestException). Inspect errors for normalized error
messages, raw_errors for the unchanged parsed JSON or response text, trace_id when supplied, and
response.status_code to distinguish rejections, authentication failures, rate limits, and server errors.
Unexpected error fields are converted to strings; empty errors use an HTTP status message without changing raw_errors.
Connection failures, timeouts, and TLS errors retain their Requests exception types.
import requests
import getswish
try:
payment = swish_client.create_payment(100.00, callback_url)
except getswish.SwishError as error:
print(error.response.status_code, error.errors, error.trace_id)
except requests.RequestException as error:
print("Request failed:", error)
Development setup
Install uv (CI uses uv 0.13.1), clone the repository, and sync the locked development environment. uv installs the required Python if necessary.
git clone https://github.com/nibon/getswish-python.git && cd getswish-python
uv sync --locked
Testing
Run all tests.
uv run --locked pytest
Run one focused test.
uv run --locked pytest tests/test_client.py::test_requests_create_payment
Run coverage the same way as CI.
uv run --locked pytest --cov=src
Run the source lint check.
uv run --locked ruff check src
Run the source-only type check (Python 3.10 is the minimum supported version).
uv run --locked ty check src
Build and check the package.
uv build
uv run --locked twine check dist/getswish-*.whl dist/getswish-*.tar.gz
To test another interpreter locally, run uv run --locked --python 3.10 pytest (or 3.11-3.15).
CI runs the full six-version matrix; uv does not provide a single-command matrix runner.
The lockfile is for development; library consumers use the dependency ranges in pyproject.toml.
Release workflow
Update the changelog, then bump the version with uv version --bump patch (or minor or major).
uv normally updates pyproject.toml and uv.lock and syncs the environment; it does not commit or tag.
Review the diff and commit the version, lockfile, and changelog with ordinary Git commands. To keep unrelated
staged changes out of the release commit, specify paths with git commit --only -- pyproject.toml uv.lock CHANGELOG.md.
CI runs the full matrix, coverage, lint, types, build, distribution validation, and installed-resource checks.
uv build
uv run --locked twine check dist/getswish-*.whl dist/getswish-*.tar.gz
Review the changes and wait for CI. uv publish uploads existing artifacts; it does not build them.
Select only the artifacts for the current version, not all files under dist/. For example, for version 0.4.0:
uv publish --index testpypi dist/getswish-0.4.0-py3-none-any.whl dist/getswish-0.4.0.tar.gz
uv publish dist/getswish-0.4.0-py3-none-any.whl dist/getswish-0.4.0.tar.gz
Set UV_PUBLISH_TOKEN to the appropriate API token for each destination (TestPyPI and PyPI use different tokens).
The testpypi publish index is configured in pyproject.toml. Never reuse a TestPyPI token on PyPI.
Metadata
Release files for getswish 0.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| getswish-0.4.1.tar.gz | 39.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| getswish-0.4.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 68.6 kB
Release files / getswish-0.4.1.tar.gz
| Download URL | getswish-0.4.1.tar.gz |
|---|---|
| Size | 39.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a664d13ca3ad0634e810882a03653abf64bbaf3a73ec71cf4283a598e9ddbf88
|
|
BLAKE2b-256 checksum How to use checksums |
4594f6a45499283bf2b3135e9788b3fbb114e1d7143fad201b75b8b0252a1074
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"44","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / getswish-0.4.1-py3-none-any.whl
| Download URL | getswish-0.4.1-py3-none-any.whl |
|---|---|
| Size | 29.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9cafb02955f26437f17b26a7c618304a4693b146fd07a0b3ad245c74bc010c72
|
|
BLAKE2b-256 checksum How to use checksums |
0d4cfaf0e1c49d2bec40b9cf7728b66d671e8bc8355431965cca9f2c7b11da88
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"44","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for getswish 0.4.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.