Make Payments

Status and recovery

When a payment request is interrupted, check the existing purchase before starting another. A timeout can leave the outcome unknown even when payment succeeded.

This guide covers payments created through the buyer’s InFlow account. Start with Pay with MPP or Pay with x402. Payments made independently through an external wallet need that wallet’s or provider’s status checks.

Keep the original purchase references

Save the payment references with the purchase as soon as your integration receives them. They let your application investigate the same purchase after a timeout, lost response, or restart.

  • Application purchase ID identifies your order, report request, or other purchase. Your application creates and stores this reference.
  • InFlow transaction ID identifies the payment to inspect. Use the ID returned when InFlow creates the transaction.
  • Approval ID identifies the owner’s authorization request. It is not the transaction ID used for the status read below.
  • Provider reference or transaction hash identifies an external payment or blockchain transaction. Keep it when returned, but do not substitute it for the InFlow transaction ID.

Also retain the account environment, seller, selected price, and enough request details to identify what was purchased. If you retain payment credentials or signed payloads for recovery, store them securely and keep them out of logs.

Convenience payment helpers do not provide durable purchase storage. Use an explicit preparation flow when you need to capture the transaction ID before waiting for approval. The buyer guides describe the available flows for each language.

If creation times out before an ID reaches your application, the server may still have created the request. Check the buyer’s Sandbox Transactions or production Transactions before creating another purchase. An identical amount alone is not enough to identify a transaction. If you cannot identify the request, keep it unresolved and seek support rather than assuming nothing happened.

Check payment status

Approval, credential readiness, and payment completion answer different questions. Approval permits spending. A ready MPP credential or x402 payload can be submitted to the seller. Neither result establishes that payment completed.

Read GET /v1/transactions/{transactionId} to check the existing transaction. This is separate from the MPP credential and x402 payload endpoints. The status read does not create, confirm, or cancel a payment.

These callable functions read once and return the result. Pass the original transaction ID and the API key for the same Sandbox buyer account. For a live payment, configure production instead. A wrong account or environment can prevent the lookup from finding the transaction.

The examples use the MPP client to read InFlow’s shared transaction endpoint, which also reports x402 transactions. They do not create an MPP payment. If your application already has an x402 buyer client, it also exposes the corresponding payment-status method.

Use github.com/inflowpayai/inflow-go v0.3.2. Pass your application’s context to cancel the read without cancelling the payment.

package payments

import (
    "context"

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

func ReadPaymentStatus(
    ctx context.Context, apiKey, transactionID string,
) (inflow.PaymentStatus, error) {
    client, err := buyer.New(buyer.Options{Options: inflow.Options{
        APIKey: apiKey, Environment: inflow.Sandbox,
    }})
    if err != nil {
        return inflow.PaymentStatus{}, err
    }
    return client.PaymentStatus(ctx, transactionID, inflow.PaymentStatusOptions{})
}

Use @inflowpayai/mpp 0.12.0 as a direct dependency. MppClient is the API client, not the payment-aware Mppx request wrapper.

import { MppClient, type PaymentStatusResponse } from '@inflowpayai/mpp';

export async function readPaymentStatus(
    apiKey: string,
    transactionId: string,
): Promise<PaymentStatusResponse> {
    const client = new MppClient({ apiKey, environment: 'sandbox' });
    return client.getPaymentStatus(transactionId);
}

Use inflowpay[mpp] 0.3.2. The context manager closes the client after the read, including when the request fails.

from inflowpay import ClientOptions
from inflowpay.mpp.buyer import BuyerMethod


async def read_payment_status(
    api_key: str, transaction_id: str,
) -> dict[str, object]:
    async with BuyerMethod(
        ClientOptions(environment='sandbox', api_key=api_key)
    ) as buyer:
        return await buyer.get_payment_status(transaction_id)

Use inflow-mpp-buyer 0.4.1 and serde_json 1. Pass your application’s cancellation token; cancelling this read leaves the payment alone.

use inflow_mpp_buyer::{
    Authentication, Buyer, CancellationToken, ClientOptions,
    Environment, Error, PaymentStatusOptions,
};
use serde_json::Value;

pub async fn read_payment_status(
    api_key: &str,
    transaction_id: &str,
    cancellation: &CancellationToken,
) -> Result<Value, Error> {
    let buyer = Buyer::new(ClientOptions {
        environment: Environment::Sandbox,
        authentication: Authentication::ApiKey(api_key.to_owned()),
        ..Default::default()
    })?;
    buyer
        .get_payment_status(
            transaction_id,
            PaymentStatusOptions::default(),
            cancellation,
        )
        .await
}

Read status and any nextAction from the returned result. These examples do not poll, open links, or retry a purchase. Errors propagate to your application; a failed read leaves the payment outcome unconfirmed.

Set a delay between status checks and an overall waiting limit. Respect retry guidance when supplied. If a status read fails temporarily, retry the read for the same transaction rather than rerunning the payment helper.

Respond to the reported outcome

Treat the returned transaction status separately from the HTTP status of the lookup. For example, a successful lookup can report GENERAL_ERROR because the payment failed. That differs from a network error that prevented the lookup itself.

OutcomeNext action
Payment remains pending, such as INITIATED, PENDING, or PROCESSINGFollow the existing transaction and check whether buyer action is available. Pending does not always mean the owner needs to approve again.
Payment is confirmed settled, such as SETTLEDCheck delivery or retrieve the existing purchase. Do not pay again simply because the seller’s response was lost.
Payment is declined, expired, or failedExplain the reported result and address its cause. A new purchase must be deliberate, not an automatic reaction to the error.
Status cannot be read, including a timeout or HTTP 404Keep the outcome unresolved. Check the account, environment, and transaction ID. A failed lookup does not establish that no payment occurred.
The status is unfamiliar to your applicationPreserve the reported value and investigate it. Do not map every unrecognized status to success or start a replacement payment.

A settled payment and a delivered purchase are separate results. If payment succeeded but a report or order response was lost, recover the existing result with the seller. A receipt alone does not establish that the correct product was delivered. See Deliver paid results for seller-side storage and duplicate handling.

Complete a requested card check

A linked-card payment can need authentication from the cardholder before it finishes. When nextAction.type is authenticate_card, present the returned nextAction.url as a link for the buyer.

  • Tell the buyer which purchase needs attention and show the InFlow dashboard link.
  • Let the buyer log in to InFlow and complete the card verification there. Do not collect the buyer’s password or verification code in your application.
  • After the buyer returns, read the same InFlow transaction again. Continue from that result rather than assuming that opening or closing the verification page completed payment.

The action URL is an authenticated dashboard page, not an API endpoint or a card-provider client secret. Do not append your InFlow API key or forward API authentication headers to it. The SDK does not open the link automatically.

No nextAction means no buyer action was supplied in that response; it does not mean the payment settled. An unknown action type also needs investigation rather than automatic success. Card verification is separate from the payment approval described in Approvals and allowances.

Retry the right operation

A retry can mean several different operations. Identify which operation failed before choosing what to repeat.

  • Read status again. This gets another snapshot of the original transaction without starting a purchase.
  • Continue credential preparation. Use the existing payment’s supported wait or lookup operation. Do not call creation again to replace a pending approval. Some helpers attempt cancellation after a timeout, so do not assume every wait can resume.
  • Resend the paid seller request. This submits a credential to the seller again. The payment method must permit that retry, and the seller must prevent duplicate fulfillment. An unchanged credential alone is not a guarantee that the request is safe to repeat.
  • Create a new payment. This starts another purchase and can charge the buyer again. Make that decision only after resolving the earlier outcome and confirming that another purchase is intended.

When a supported paid-request retry is appropriate, preserve the original destination, HTTP method, body, purchase reference, and payment terms. Keep credentials scoped to the original seller. A different product, recipient, or amount is not recovery of the same purchase.

Prevent two workers or browser actions from recovering the same purchase independently. Keep one application record for that purchase and coordinate its recovery. The SDKs do not provide shared application storage or guarantee that repeated business operations are harmless.

Treat cancellation separately

Stopping local waiting, requesting cancellation, and confirming cancellation are different outcomes. Cancelling a status read only stops that read. Payment helpers may separately attempt to cancel an unfinished approval when waiting fails.

A cancellation request can fail or arrive after payment has progressed. Best-effort cancellation helpers may return without proving that the server cancelled anything. Check the existing transaction before offering another purchase. Cancellation does not refund a completed payment.

See Handle cancellation and interruption for approval and allowance cancellation. Tell the buyer what your application knows, such as “Stopped waiting; payment status is still being checked,” rather than claiming a refund or a confirmed cancellation.

Test interrupted purchases

Test recovery at each point where your application can lose a response. Use controlled Sandbox purchases for supported paths alongside your project’s unit and integration tests.

  • Interrupt creation before your application receives a transaction ID. Confirm that it does not silently repeat creation.
  • Interrupt waiting before approval and after credential preparation. Confirm that saved references identify the original request and that credential readiness is not displayed as payment success.
  • Lose the response after submitting a payment. Confirm that recovery checks the existing transaction instead of making another purchase.
  • Let payment settle but prevent delivery of the seller’s response. Confirm that the application retrieves the existing purchase without duplicate payment or fulfillment.
  • Test a failed status lookup separately from a lookup that reports a failed payment. Keep unknown statuses unresolved.
  • For linked cards, test a required authentication step and a fresh status read afterward. Confirm that a missing next action is not treated as payment success.
  • Test cancellation and simultaneous recovery attempts. Confirm that errors remain visible and that credentials stay out of logs.

Simulated interruptions can exercise your application’s decisions, but do not establish a payment provider’s retry guarantees. Verify the selected payment flow separately before relying on automatic recovery.

Continue your integration

Deliver paid results

Connect payment to the purchased result and prevent duplicate fulfillment.

Test your integration

Verify payment, delivery, and failure recovery together.

Troubleshooting

Investigate configuration and payment errors without guessing at their cause.