Providers
The provider layer is the only vendor-aware part of the engine. Everything above it — models, services, API — speaks a normalized, provider-neutral vocabulary. Adding a new payment vendor means writing one provider class.
The PaymentProvider interface
A provider is a subclass of PaymentProvider with a name class attribute and
these methods.
Required
| Method | Signature | Returns |
|---|---|---|
create_checkout | (*, payment, idempotency_key) | CheckoutSession |
verify_and_parse_webhook | (*, payload: bytes, signature: str) | WebhookResult |
refund | (*, payment, amount: int | None = None) | RefundResult |
create_checkout creates the provider-side payment for an existing Payment
row (amount is authoritative, in minor units). verify_and_parse_webhook must
verify the signature against the raw body and raise ProviderWebhookError on
a bad/absent signature. refund does a full refund when amount is None, else
a partial refund in minor units.
Optional
These default to raising ProviderNotSupported, so a provider implements only
what it supports:
retrieve_payment(*, external_id) -> PaymentSnapshot— the provider’s current view of a payment; used by reconciliation.ensure_customer(*, external_ref, email, name="") -> strcreate_subscription(...) -> SubscriptionSessioncancel_subscription(*, stripe_subscription_id, at_period_end=True) -> Noneset_subscription_quantity(*, stripe_subscription_id, quantity) -> Nonecreate_billing_portal_session(*, customer_id, return_url) -> strcapabilities() -> dict[str, bool]— advertises what the provider supports.
Normalized DTOs
Providers return frozen dataclasses so the services never touch a vendor object:
CheckoutSession, WebhookResult, RefundResult, PaymentSnapshot,
SubscriptionSession, SubscriptionEventData. Webhook events are normalized to
constants like payment.succeeded, payment.failed, and refund.
Exceptions
ProviderError— an upstream API call failed.ProviderWebhookError— signature/parse failure.ProviderNotSupported— an optional capability isn’t implemented.
The Stripe provider
StripeProvider (name = "stripe") is the built-in implementation and the only
file that imports the stripe SDK (lazily — a missing SDK raises
ImproperlyConfigured with the pip install django-cfg[payments] hint).
It uses an instance-based stripe.StripeClient(secret) rather than mutating
the global stripe.api_key — safe across concurrent RQ workers.
Key behaviours:
- Checkout uses Stripe PaymentIntents (not Checkout Sessions). It passes
amount,currency, metadata (payment_short_id,reference_kind,reference_id,owner_id),automatic_payment_methods, and theidempotency_key, returning the intent id andclient_secret. - Webhook verification tries each configured secret via
Webhook.construct_event(key rotation), then normalizes via a singleparse_event_dictpath shared by live delivery and replay. - Refunds call
refunds.createagainst the PaymentIntent. - Reconciliation (
retrieve_payment) retrieves the PaymentIntent, sumsamount_refundedacross charges, and maps the Stripe status to a normalized one. - Capabilities:
client_confirm,partial_refund,reconcile, andsubscriptionsareTrue;hosted_redirectisFalse.
The Stripe provider fully implements the subscription methods, but the service layer doesn’t invoke them yet — one-time payments only, for now.
Registering a custom provider
Add a key → dotted-path entry to the provider registry, then reference it by key.
from django_cfg import DjangoConfig, PaymentsConfig
class MyConfig(DjangoConfig):
payments = PaymentsConfig(
provider_registry={
"stripe": "django_cfg.apps.payments.providers.stripe.StripeProvider",
"paypal": "apps.billing.providers.PayPalProvider",
},
default_provider="stripe",
)A checkout can then request "provider": "paypal". The factory get_provider
resolves the dotted path (Django settings PAYMENTS_PROVIDER_REGISTRY /
PAYMENTS_DEFAULT_PROVIDER win over config, which is what test fixtures use), and
raises ValueError for an unknown provider name.
Your class must subclass PaymentProvider, set a unique name, and implement the
three required methods. Implement the optional ones (especially retrieve_payment)
to opt into reconciliation.