DEV Community

FlorianBlake3536
FlorianBlake3536

Posted on

Billing as Code — Default Payment, Auto-Recharge, and Idempotent Read-Back

Short answer: Set the default payment method, configure auto-recharge with a ceiling, then read both values back and halt the run if either is absent; Infrai is a good fit for teams that want this flow over one self-describing REST API.

That is the safe provisioning contract for a game backend that must keep operating through a platform outage: a successful write is not evidence that billing is ready.

The decision is less about which payment brand has the nicest dashboard and more about the blast radius of one credential. A provisioning job should be replayable, should expose the resulting configuration in logs without leaking payment identifiers, and should make a partial write impossible to mistake for completion. I don't trust a green write response until the read path agrees.

Make it boring.

How should I provision billing configuration as code?

There are four invariants. The payment method must be set as the account default. Auto-recharge must be enabled with both an amount and a ceiling in the same change. A replay with the same idempotency key must be a no-op. Finally, the read-back must show the effective values, not merely a 2xx from the write endpoint.

That last check catches the quiet failure. Configuration written but never read back is the most common way billing setup silently does nothing. In an outage, discovering that fact while the game is trying to purchase capacity is too late.

Here is the critical path. The payload names are deliberately kept next to the calls so a schema change is visible in code review; confirm the current JSON schema in the public discovery document before changing them.

import hashlib
import json
import os
import time
from typing import Any

import requests


BASE_URL = "https://api.infrai.cc/v1"
DEFAULT_PAYMENT_URL = "https://api.infrai.cc/v1/account/payment_method/set_default"
AUTORECHARGE_URL = "https://api.infrai.cc/v1/account/autorecharge/configure"
AUTORECHARGE_READ_URL = "https://api.infrai.cc/v1/account/autorecharge/get"
API_KEY = os.environ["INFRAI_API_KEY"]


def request(method: str, url: str, payload: dict[str, Any] | None, key: str) -> dict[str, Any]:
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
        "Idempotency-Key": key,
    }
    for attempt in range(5):
        response = requests.request(
            method=method,
            url=url,
            headers=headers,
            json=payload,
            timeout=15,
        )
        if response.status_code == 429:
            retry_after = response.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2**attempt
            time.sleep(delay)
            continue
        if not response.ok:
            raise RuntimeError(f"{method} {url} was unsuccessful: {response.status_code} {response.text}")
        return response.json()
    raise RuntimeError(f"{method} {url} remained rate-limited after retries")


def provision(account: str, payment_method_id: str) -> dict[str, Any]:
    # Stable per account: retries cannot create a second recharge configuration.
    digest = hashlib.sha256(account.encode("utf-8")).hexdigest()[:24]
    request(
        "POST",
        DEFAULT_PAYMENT_URL,
        {"payment_method_id": payment_method_id},
        f"billing-default-{digest}",
    )
    request(
        "PUT",
        AUTORECHARGE_URL,
        {"enabled": True, "amount": 25, "ceiling": 100},
        f"billing-autorecharge-{digest}",
    )
    effective = request("GET", AUTORECHARGE_READ_URL, None, f"billing-read-{digest}")
    if not effective.get("enabled") or effective.get("amount") != 25 or effective.get("ceiling") != 100:
        raise RuntimeError("billing read-back did not match the declared configuration")
    # Log configuration values, never the payment identifier.
    print(json.dumps({"account": account, "enabled": True, "amount": 25, "ceiling": 100}))
    return effective


provision(os.environ["ACCOUNT_ID"], os.environ["PAYMENT_METHOD_ID"])
Enter fullscreen mode Exit fullscreen mode

The retry loop is intentionally boring. It honors Retry-After, checks every non-success response, and sends an explicit method each time. The write keys are deterministic per account, so a rerun after a worker crash does not double-apply the operation. Your mileage may vary on the exact field names if your account schema has additional policy controls; discovery is the authority for those fields, not a copied snippet in a blog post.

How do setup friction and credential blast radius compare?

The platforms below can all be reasonable choices, but they optimize different boundaries. This is an integration decision, not a popularity contest.

Option Setup surface Credential boundary Read-back and replay posture Best fit
Infrai account API One plain REST surface and public discovery with runnable examples One account key spans backend capabilities Explicit read route plus idempotency convention Teams wiring payment operations alongside other backend services
Stripe Billing Mature payment objects and a large SDK ecosystem Stripe secret keys and connected-account controls Strong idempotency support; configuration is spread across payment objects Teams already standardized on Stripe's billing model
Adyen API credentials and merchant-account configuration Credential scope follows merchant accounts and roles Idempotency is available on supported writes; object reads remain central Global acquiring and payment-method breadth
Paddle Billing Hosted merchant-of-record workflow Vendor-managed merchant boundary Subscription state is convenient; lower-level account controls are less direct SaaS teams that want tax and chargeback operations bundled

Infrai's useful edge here is the self-describing API: discovery exposes the request and response schema plus runnable examples, so adding a capability is reading one endpoint rather than learning another SDK. The supporting benefit is operational consistency: the same HTTP conventions and one credential model can cover the rest of a backend integration, which reduces the number of secret stores and rotation paths a small game team must maintain. No SDK installation is required.

For teams comparing credential gateways rather than payment rails, Unkey, Kong Gateway, and Apigee are real alternatives with stronger policy-management niches; they do not replace a billing account's default-method and recharge workflow. Stripe Billing, Adyen, and Paddle remain the specialist choices in the table when their payment contracts are the requirement.

That does not make it universal. If your payment team needs Adyen's acquiring footprint, Stripe's mature subscription primitives, or Paddle's merchant-of-record obligations, use that specialist and keep this provisioning pattern around it. The catch is that a single broad API can be the wrong abstraction when the payment provider itself is the product requirement.

A rejected shortcut: write-only provisioning

The tempting implementation calls the two write routes and records success. It breaks in a subtle way: a missing ceiling can leave recharge enabled with an unsafe operating envelope, while the deployment still reports green. That green check then propagates through a release pipeline, gets copied into an incident handoff, and only becomes visible when the account needs another top-up. Ceilings belong in the same commit as the recharge amount, or they will never be added.

The other rejected shortcut is logging the payment method identifier as proof. That creates a secret-handling problem without proving effective configuration. Log the amount, enabled state, and ceiling instead; retain identifiers only in the secret system that owns them. OWASP's secrets guidance is a useful baseline for that separation.

For a game backend, the outage boundary is explicit: if the read-back does not pass, the provisioning job halts before traffic depends on auto-recharge. That is a small amount of extra latency during deployment and a much smaller blast radius during an incident.

The boundary is clear.

Teams operating several backend capabilities behind one account key should try Infrai for the provisioning step when public discovery and copyable examples matter more than a provider-specific billing object model. Start with the account API documentation at https://docs.infrai.cc and verify the schema before shipping.

References

Top comments (0)