StrimzStrimz
Guides

Process a refund

Full or partial, with on-chain audit trail.

A refund is a two-step flow. First you create the refund on the server. Strimz validates the amount against the original transaction and returns wallet-signing instructions. Then you sign the on-chain transfer from your own wallet to the customer's. The indexer watches Arc and marks the refund completed once the transfer confirms.

Why Strimz doesn't refund for you

The obvious alternative would be to hold your USDC and refund out of it. That would put Strimz in the money-transmission business, which is a regulated activity in most jurisdictions, and would put your money on the Strimz balance sheet instead of yours. Every refund signed from your own wallet leaves a clean on-chain trail between you and the customer, with no intermediary in between.

Issue a refund

Create the refund with POST /v1/refunds. amount is in the token's smallest unit (6 decimals for USDC), and partial refunds are fine. reason is one of customer_request, product_issue, duplicate_charge, fraudulent or other.

curl https://api.strimz.finance/v1/refunds \
  -H "Authorization: Bearer sk_test_xxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionId": "cmq4wf2b80004je6l7xq1c9ad",
    "amount": "50000000",
    "reason": "customer_request",
    "note": "Customer asked nicely"
  }'

The API returns the refund and the signing instructions together:

{
  "refund": {
    "id": "cmq4wh9de0006je6l2n5v0kqt",
    "transactionId": "cmq4wf2b80004je6l7xq1c9ad",
    "amount": "50000000",
    "status": "awaiting_signature"
  },
  "signingInstructions": {
    "token": "0x...USDC",
    "to": "0x...payerAddress",
    "amount": "50000000",
    "note": "Customer asked nicely"
  }
}

With the SDK:

const { refund, signingInstructions } = await strimz.refunds.create({
  transactionId: 'cmq4wf2b80004je6l7xq1c9ad',
  amount: '50000000',
  reason: 'customer_request',
  note: 'Customer asked nicely',
})

The signingInstructions object tells the merchant's wallet what to sign:

import { createWalletClient, http, erc20Abi } from 'viem'
 
const wallet = createWalletClient({
  account: merchantAddress,
  chain: arcTestnet,
  transport: http(),
})
 
const txHash = await wallet.writeContract({
  address: signingInstructions.token,
  abi: erc20Abi,
  functionName: 'transfer',
  args: [signingInstructions.to, BigInt(signingInstructions.amount)],
})

Submit the tx hash

Once the merchant has the tx hash, post it back to Strimz:

await strimz.refunds.submitSignature({ id: refund.id, refundTxHash: txHash })

This flips the refund from awaiting_signature to submitted. The indexer watches for the transfer with this tx hash; when it lands, it flips the refund to completed and fires refund.completed.

You can submit only once, while the refund is awaiting_signature. A second call returns 403 session_invalid_state.

Cumulative caps

Strimz won't let you refund more than the original transaction. On a 100 USDC transaction, a 60 USDC refund succeeds, and a second refund of 50 USDC returns 400 invalid_request because 60 + 50 > 100.

The cap counts every refund in pending, awaiting_signature, submitted or completed. An unsigned awaiting_signature refund still holds its share, and there is no way to void or delete it today.

What if the indexer never sees the on-chain transfer?

This happens if the merchant submitted the wrong tx hash. The refund stays in submitted, and the API won't accept a second hash. Contact support@strimz.finance with the refund id.

What if the customer's address is invalid?

You can't get this far. The refund goes to the payer address recorded on the original transaction, not to anything you pass in. The only way to could be wrong is if the customer's wallet itself is wrong, which Strimz can't prevent.

On this page