DEV Community

Cover image for Building a Production-Ready Stellar Payment App Beyond sendPayment()
Kate Steele
Kate Steele

Posted on

Building a Production-Ready Stellar Payment App Beyond sendPayment()

A basic Stellar payment demo can be built quickly. Load an account, create a transaction, add a payment operation, sign it, submit it, and display the hash.

A production application must also prevent duplicate transfers, coordinate sequence numbers, support multiple assets, connect to traditional rails, explain pending states, and reconcile every financial event.

The network transaction is one step in a larger workflow. This article outlines the architecture around it.

Define the account and custody model first

A separate Stellar account for each user provides a distinct on-chain identity and observable balances. It also requires account creation, reserve management, trustlines for issued assets, and a custody model.

A pooled account represents customers in an internal ledger and can differentiate them through mixed accounts or memos. It reduces network account overhead but increases reliance on internal accounting and controls.

A non-custodial wallet lets the user sign. A custodial product signs on the customer’s behalf and therefore needs hardened key management, withdrawal controls, approval policies, and monitoring.

Before implementation, answer:

  • Who can authorize a transfer?
  • Where do signing keys live?
  • Who pays network fees and reserves?
  • How is an on-chain account linked to a customer?
  • What happens when credentials are lost?

These decisions cannot be hidden behind a wallet library.

Keep an internal payment record

Never use a transaction hash as the only application record.
Create a payment entity before submitting anything:

payment_id
idempotency_key
customer_id
source_account
destination_account
send_asset
send_amount
receive_asset
minimum_receive_amount
quote_id
stellar_transaction_hash
status
failure_code

The application owns the request and business state. Stellar provides the authoritative network result.

A useful state machine may include:

CREATED
AWAITING_SIGNATURE
READY_TO_SUBMIT
SUBMITTED
CONFIRMED
PAYOUT_PENDING
COMPLETED
FAILED
REFUND_REQUIRED
REFUNDED

Transitions should be explicit. The same idempotency key must return the existing payment. After an uncertain HTTP response, query stored state and the network before building another transaction.

Manage sequence numbers under concurrency

Every Stellar transaction uses a source account sequence number. Two workers that load the same account simultaneously can prepare transactions with the same next sequence. One succeeds and the other fails.

Serialize submissions per source account with a queue or lock. At higher rates, channel accounts can distribute transaction sources where the signing model permits.

Do not trust an in-memory counter. Restarts, timeouts, or external submissions can invalidate it. Reconcile sequence state whenever the result is uncertain.

Add time bounds so a signed transaction cannot remain valid indefinitely. Stellar sequence numbers are consumed by transactions and help enforce ordered, one-time transaction execution.

Build transactions from validated intent

The backend should accept a business command such as “send 50 units of asset A to recipient B,” not an arbitrary serialized transaction from an untrusted client.

Validate:

  • source authorization;
  • destination format;
  • asset code and issuer;
  • required trustline;
  • available balance;
  • transaction limits;
  • quote expiration;
  • compliance status;
  • fee and memo requirements.

Build the transaction deterministically and show the destination, asset, amount, fees, and conversion limits before signing.

When a client constructs the transaction, the signer should still decode and verify every operation instead of approving opaque XDR.

Handle issued assets deliberately

XLM does not require a trustline, while classic issued assets generally do. A recipient without the appropriate trustline cannot receive the asset through a normal payment.

Check this before submission. A wallet can guide trustline creation, while a custodial service may create or sponsor required entries.

Asset identity is the combination of code and issuer, not the code alone. Store and display both. Maintain an allowlist when the product supports a controlled asset catalog.

Authorization-required, revocable, and clawback-enabled assets have different risk and support implications. Make relevant restrictions visible. Stellar assets can also interact with Soroban contracts through the built-in Stellar Asset Contract.

Match path payments to the quote

Path payments allow the source and destination assets to differ.

Choose the operation according to the customer promise:

  • _PathPaymentStrictSend _fixes the source amount and sets a minimum destination amount.
  • PathPaymentStrictReceive fixes the destination amount and sets a maximum source amount.

Query paths before building the transaction, then apply limits that match the quoted rate and expiration. Never silently widen the tolerance after approval.

A quote record should preserve the source amount, destination amount, fees, expiry, and route assumptions. When it expires, return to pricing rather than submitting under new economics.

Integrate anchors as durable workflows

Anchors connect Stellar assets with off-chain payment rails. Common Stellar Ecosystem Proposals cover service discovery, authentication, customer information, deposits, withdrawals, cross-border payments, and quotes.

A typical integration may use SEP-1, SEP-10, SEP-12, SEP-6 or SEP-24, SEP-31, and SEP-38.

Use an anchor adapter and persist the provider transaction ID, customer reference, quote, rail, required actions, and status history.

SEP-24 commonly gives the anchor control of an interactive deposit or withdrawal flow. SEP-6 supports a more programmatic experience. The choice affects UX and compliance responsibilities.

Callbacks and polling results may arrive more than once or out of order. Updates must be idempotent, and provider-specific evidence should be retained for support and reconciliation.

Ingest network results independently

The transaction submitter should not be the only component that knows whether a transfer succeeded.

Run an ingestion process that follows relevant accounts, operations, or contract events from a durable cursor. Convert network records into internal events such as:
TransactionConfirmed
IncomingPaymentObserved
OutgoingPaymentObserved
TrustlineChanged

Store the hash, ledger, operation index, asset, amount, source, destination, memo, and cursor. Deduplicate by stable network identifiers.

Independent ingestion catches external submissions and cases in which Stellar accepts a transaction but the client times out.

Advance the cursor only after the related database changes are committed.

Separate confirmation from business completion

A confirmed Stellar transaction may not mean the customer journey is finished.

For a wallet transfer, confirmation may be final. For a withdrawal, an anchor may still need to send funds through a bank or mobile-money rail. For a merchant payment, another service may need to release the order.

Model these stages separately. A payment can be confirmed on Stellar while its payout remains pending. This prevents premature notifications and makes support investigations clearer.

Sponsor fees with policy controls

Fee-bump transactions allow one account to pay the fee for another account’s transaction without changing the inner transaction’s authorization. Sponsored reserves can help cover selected ledger-entry requirements.

Before the sponsor signs, validate the inner transaction, customer, asset, amount, and allowed operations. Add rate limits and abuse monitoring. Sponsorship must not become an arbitrary transaction relay.

Reconcile every view of value

A custodial or hybrid product usually has three views:

  1. the application’s customer ledger;
  2. balances and transactions on Stellar;
  3. balances and statuses held by anchor, banking, or payment partners.

Compare them continuously and classify differences as expected delay, missing callback, duplicate event, failed payout, unmatched deposit, or manual adjustment.

Operator tools should search by internal payment ID, network hash, account, memo, mixed ID, anchor transaction, and bank reference. Corrections need approvals and audit history.

Test uncertainty, not only valid transfers

Cover duplicate API requests, concurrent submissions, expired time bounds, missing trustlines, stale paths, rejected signatures, submission timeouts, duplicate anchor updates, cursor restarts, and payout failure after on-chain settlement.

Use Testnet for integration behavior, but keep deterministic unit tests for construction and state transitions. External service changes should not make the entire suite unreliable.

Expert Stellar blockchain development services should therefore cover more than SDK integration. The critical work lies in account architecture, custody, orchestration, anchor connectivity, reconciliation, observability, and safe operations.

A payment app becomes production-ready when every transfer has one understandable history. Customers see clear status, support can explain events, finance can reconcile value, and engineers can retry safe steps without duplication.

sendPayment() is the visible action. The system around it is the product.

Top comments (0)