Make Payments

Choose payment options

A seller may offer several ways to pay for the same resource. Your application decides which offers are acceptable: how much it will spend, which currencies and networks it can use, and whether it permits a one-time purchase or a subscription.

Apply those rules before creating a payment, then choose among the remaining options. Start with Pay with MPP or Pay with x402 for the complete request flow. This page adds purchase rules to that flow.

Set your purchase rules

Suppose your application can buy a report for up to 0.05 USDC. It accepts one-time purchases on an approved network. It stops if the seller requires another currency, a higher price, or a subscription.

  • Set a maximum price for this purchase, based on what the user or application has authorized.
  • Choose the permitted currency or asset and network. A familiar currency name alone does not identify an on-chain token.
  • Choose the payment methods or schemes the application may use. Keep this separate from where the buyer’s funds come from.
  • Decide whether the purchase may create a recurring commitment. A one-time price limit does not authorize a subscription.
  • Check the seller and payment recipient against the purchase you intended to make, rather than trusting any URL returned by an agent.

A preference is different from a requirement. “Prefer this network” allows alternatives; “only use this network” excludes them. Apply hard limits first. Preferences only decide between offers that already meet those limits.

Read the seller’s offers

Use an ordinary HTTP request to inspect the seller’s response without paying. A payment-aware request may start a purchase automatically, so configure its purchase rules before sending it.

ProtocolWhere the offers appearWhat to check
MPPPayment challenges in WWW-Authenticate.The method and intent, plus the amount, currency, recipient, and other terms in that method’s request.
x402 version 2The accepts list in the decoded Payment-Required header.scheme, network, asset, amount, payTo, and any scheme-specific terms.

Each offer is a complete set of terms. Select an advertised offer without changing its amount, recipient, network, or challenge data. Keep extension fields with the selected offer; payment validation may depend on them.

A method describes how a payment works, not simply the account that funds it. For example, the MPP inflow method can use an InFlow balance or a linked instrument. Read Prices and offers for how sellers express their terms.

Filter first, then choose

  • Identify options your payment integration supports.
  • Exclude options that violate your purchase rules.
  • Apply preferences among the remaining options.
  • Create the payment using the selected offer.

Support checks and purchase rules can run in either order, but both must succeed before payment starts. If the seller advertises 0.10 USDC first and 0.03 USDC second, a 0.05 USDC limit excludes the first offer. The second remains eligible if its other terms are acceptable.

Default selection is not a cheapest-price search. Go’s automatic MPP client selects the first supported challenge in seller order. The InFlow x402 buyers use scheme preferences and may consult available balances. Neither behavior establishes your application’s maximum price.

Sufficient funds do not authorize a purchase. Likewise, an acceptable offer can still fail because funds are insufficient or approval is denied. Keep selection, funding, and owner approval as separate decisions.

Apply the rules in your SDK

These x402 examples accept only exact payments in test USDC on Base Sepolia, up to 0.05 USDC, to an expected seller address. Use Sandbox accounts with these testnet identifiers. They use the Base Sepolia USDC contract and its six-decimal precision: the limit is 50000 atomic units.

Supply expectedRecipient from your trusted seller configuration, not by copying it from the offer being checked. The examples compare Ethereum addresses without case sensitivity and preserve the original offer. They do not register an external wallet or bypass the SDK’s capability checks.

Use the dependencies from Pay with x402. Its balance-only report endpoint will be rejected by this exact-only rule. Test with a seller offering exact test USDC on Base Sepolia; do not change a balance offer into an exact offer locally.

Call ReportPolicy with the expected recipient. Add the returned policy to buyer.Options.Policies when constructing the client. Do, Sign, and Select apply these policies during selection. Calling Prepare directly requires you to check the offer first.

package payments

import (
    "context"
    "math/big"
    "regexp"
    "strings"

    "github.com/inflowpayai/inflow-go/x402"
    "github.com/inflowpayai/inflow-go/x402/buyer"
)

func ReportPolicy(expectedRecipient string) buyer.Policy {
    digits := regexp.MustCompile(`^[0-9]+$`)
    return func(_ context.Context, offers []x402.PaymentRequirements) ([]x402.PaymentRequirements, error) {
        accepted := make([]x402.PaymentRequirements, 0, len(offers))
        for _, offer := range offers {
            if offer.Scheme != "exact" || offer.Network != "eip155:84532" ||
                !strings.EqualFold(offer.Asset, "0x036cbd53842c5426634e7929541ec2318f3dcf7e") ||
                !strings.EqualFold(offer.PayTo, expectedRecipient) || !digits.MatchString(offer.Amount) {
                continue
            }
            amount, ok := new(big.Int).SetString(offer.Amount, 10)
            if ok && amount.Sign() > 0 && amount.Cmp(big.NewInt(50000)) <= 0 {
                accepted = append(accepted, offer)
            }
        }
        return accepted, nil
    }
}

Call reportPolicy with the expected recipient. Pass the returned policy to core.registerPolicy before requesting a payment payload. This applies to selection through x402HTTPClient. If you call prepareInflowPayment with an explicit offer, check that offer before the call.

import type { PaymentPolicy } from '@x402/core/client';

export function reportPolicy(expectedRecipient: string): PaymentPolicy {
  return (version, offers) => version === 2 ? offers.filter((offer) =>
    offer.scheme === 'exact' &&
    offer.network === 'eip155:84532' &&
    offer.asset.toLowerCase() === '0x036cbd53842c5426634e7929541ec2318f3dcf7e' &&
    offer.payTo.toLowerCase() === expectedRecipient.toLowerCase() &&
    offer.amount.length > 0 && !/[^0-9]/.test(offer.amount) &&
    BigInt(offer.amount) > 0n && BigInt(offer.amount) <= 50_000n
  ) : [];
}

Call report_policy with the expected recipient. Pass the returned policy to buyer.register_policy before sending requests through x402AsyncTransport. The buyer applies registered policies during managed selection. If you call prepare with an explicit offer, check it before that call.

import re

from x402.client_base import PaymentPolicy
from x402.schemas import PaymentRequirements


def report_policy(expected_recipient: str) -> PaymentPolicy:
    def allowed(offer: PaymentRequirements) -> bool:
        return (
            offer.scheme == "exact"
            and offer.network == "eip155:84532"
            and offer.asset.lower() == "0x036cbd53842c5426634e7929541ec2318f3dcf7e"
            and offer.pay_to.lower() == expected_recipient.lower()
            and re.fullmatch(r"[0-9]+", offer.amount) is not None
            and 0 < int(offer.amount) <= 50_000
        )

    return lambda version, offers: [
        offer
        for offer in offers
        if version == 2 and isinstance(offer, PaymentRequirements) and allowed(offer)
    ]

In the explicit Rust flow, call report_offers with the decoded requirements and expected recipient. Propagate any error. Pass the returned document to buyer.select and use it for preparation as well. Stop if selection returns no supported offer.

use inflow_x402_buyer::{OriginalJson, PaymentRequired};
use serde_json::Value;

pub fn report_offers(
    required: &PaymentRequired<OriginalJson>,
    expected_recipient: &str,
) -> Result<PaymentRequired<OriginalJson>, Box<dyn std::error::Error>> {
    let mut filtered = required.clone();
    filtered.accepts.clear();
    for offer in &required.accepts {
        let terms: Value = serde_json::from_str(offer.0.get())?;
        let asset_matches = terms["asset"].as_str().is_some_and(|asset| {
            asset.eq_ignore_ascii_case("0x036cbd53842c5426634e7929541ec2318f3dcf7e")
        });
        let recipient_matches = terms["payTo"]
            .as_str()
            .is_some_and(|recipient| recipient.eq_ignore_ascii_case(expected_recipient));
        let amount_allowed = terms["amount"].as_str().is_some_and(|amount| {
            amount.bytes().all(|digit| digit.is_ascii_digit())
                && amount
                    .parse::<u128>()
                    .is_ok_and(|value| (1..=50_000).contains(&value))
        });
        if terms["scheme"] == "exact"
            && terms["network"] == "eip155:84532"
            && asset_matches
            && recipient_matches
            && amount_allowed
        {
            filtered.accepts.push(offer.clone());
        }
    }
    if filtered.accepts.is_empty() {
        return Err("No acceptable offer; no payment was started".into());
    }
    Ok(filtered)
}

HttpBuyer::with_selector applies to external-wallet candidates, not managed InFlow selection. It is not a substitute for this check in the managed flow.

For MPP, check the decoded challenge before the operation that creates a payment. Registering an InFlow charge method chooses a payment method and intent; it does not impose a price ceiling. Use the explicit flow when an automatic client does not expose the purchase checks you need.

SDKCheck the MPP challenge before
GoClient.Prepare or Client.Fulfil.
Node.jsThe registered method’s createCredential call.
PythonBuyerMethod.create_credential.
RustBuyer::prepare or the CARD-specific prepare_card.

For an MPP inflow one-time purchase, require method inflow and intent charge. Read the request’s currency, decimal amount, and recipient, then compare them with your purchase rules. Pass the complete accepted challenge to the method; let the SDK perform its own validation as well.

Compare amounts correctly

Interpret the amount with its method or scheme, asset, network, and units. The exact USDC example uses six decimal places. InFlow’s x402 balance amounts use 18 decimal places. The same numeric string therefore does not represent the same price across those schemes.

MPP InFlow charge requests use decimal currency amounts instead. Compare a request for 0.03 USDC against a limit of 0.05 USDC with decimal arithmetic. Do not use a floating-point approximation or reuse the x402 atomic-unit limit for that request.

Configure units from the method’s contract and the accepted asset, not a seller-supplied display label. Reject amounts you cannot interpret. A USDC limit does not automatically permit another currency with a similar name or price.

A per-purchase cap also does not enforce a daily budget. Ten purchases below the cap can still exceed that budget. See Policies for account spending rules and Approvals and allowances for owner authorization.

Stop when no acceptable option remains

Report the reason without starting a payment. For example: “The seller offers this report for 0.10 USDC. Your purchase limit is 0.05 USDC.” An unsupported network needs a different explanation from a price that exceeds the limit.

Keep the existing rules unless the user authorizes a change. Do not silently raise the limit, enable another currency, or switch to an external wallet. If you configure a fallback payment path, apply the same purchase rules before that path can pay.

An interrupted payment is different from a rejected offer. After payment creation or submission, check the existing purchase through Status and recovery. Selecting another offer and paying again can create a second charge.

Test your selection rules

  • Accept an otherwise valid offer exactly at the limit; reject one above it.
  • Put an expensive offer first and an acceptable offer second. Confirm that the second remains available for selection.
  • Reject the same amount with another asset, network, or recipient.
  • Reject subscription terms when only a one-time purchase was authorized.
  • Reject malformed or unrepresentable amounts without converting them to a smaller price.
  • When no acceptable offer remains, confirm that payment creation and signing were never called.
  • Confirm that filtering preserves the selected offer’s fields and leaves the original seller response unchanged.

Unit tests establish that your rules select and reject the intended terms. A Sandbox integration test must also establish that those checks run before the actual SDK payment operation. Keep both alongside your project’s tests and coverage requirements.

Continue your integration

Approvals and allowances

Guide the buyer through owner approval and spending permissions.

Networks and currencies

Choose supported combinations for the payments your application needs.

Troubleshooting

Investigate rejected offers and other payment integration failures.