Payment Operations Guide
Create, execute, cancel, and query payments with the DolphinPay TypeScript SDK.
Payments are USDC only (native Circle USDC, 6 decimals) — the contract validates the coin type against the shared AdminConfig and aborts on anything else. The platform fee is 0 bps by policy, there is no merchant fee, and there is no refund flow.
All build* methods return a Transaction that you sign and execute with your own wallet (e.g. useSignAndExecuteTransaction from @mysten/dapp-kit). Query methods read chain state over gRPC.
Creating Payments
Basic Payment Creation
import { createClient, usdcToUnits, USDC_TYPES } from '@dolphinpay/sdk';
const client = createClient({
network: 'testnet',
packageId: '<PACKAGE_ID>',
adminConfigId: '<ADMIN_CONFIG_ID>',
});
const txb = client.payment.buildCreatePayment({
merchant: '0xMERCHANT_ADDRESS',
amount: usdcToUnits('10'), // "10000000" — USDC base units (6 decimals)
currencyType: USDC_TYPES.testnet, // native Circle USDC coin type
description: 'Premium subscription',
expirySeconds: 3600, // default 3600 (1 hour), max 1 year
});
// Sign and execute with your wallet
const result = await signAndExecute({ transaction: txb });
console.log('Payment created:', result.digest);
CreatePaymentParams:
| Field | Type | Notes |
|---|---|---|
merchant | string | Merchant's receiving address |
amount | string | number | USDC base units; max 1000000000000 (1,000,000 USDC) |
currencyType | string | Fully qualified coin type — must be the network's native Circle USDC |
description | string | Max 500 chars |
metadata? | Record<string, string> | Up to 10 entries, inserted on-chain in record order |
expirySeconds? | number | Default 3600 |
The created Payment object is shared on-chain so that any payer can execute it.
metadata is validated client-side (max 10 entries) and its entries are inserted into the on-chain VecMap in the order they appear in the record; omitted or empty metadata produces an empty map. Metadata is parsed back into payment.metadata when you query the payment.
const txb = client.payment.buildCreatePayment({
merchant: '0xMERCHANT_ADDRESS',
amount: usdcToUnits('10'),
currencyType: USDC_TYPES.testnet,
description: 'Premium subscription',
metadata: { orderId: 'ORDER-123', customerRef: 'CUST-42' },
expirySeconds: 3600,
});
Payment with Merchant Validation
Validates on-chain that the Merchant object exists and is active, and routes to its receiving address:
const txb = client.payment.buildCreatePaymentWithMerchant({
merchantObjectId: '0xMERCHANT_OBJECT_ID', // Merchant object, not an address
amount: usdcToUnits('50'),
currencyType: USDC_TYPES.testnet,
description: 'Service payment',
expirySeconds: 1800,
});
Executing Payments
Payment execution takes the parameters and the currency type as a separate second argument:
import { USDC_TYPES } from '@dolphinpay/sdk';
const txb = client.payment.buildExecutePayment(
{
paymentId: '0xPAYMENT_OBJECT_ID',
coinObjectId: '0xUSDC_COIN_OBJECT_ID', // a Coin<USDC> you own
amount: '10000000', // optional but recommended — see below
},
USDC_TYPES.testnet // currency type argument
);
const result = await signAndExecute({ transaction: txb });
The contract requires the coin value to exactly equal the payment amount. When you pass amount, the SDK splits exactly that value from coinObjectId inside the transaction and the remainder stays with you. If you omit amount, the whole coin is consumed and must already match exactly.
Execution with Merchant Routing
const txb = client.payment.buildExecutePaymentWithMerchant(
{
paymentId: '0xPAYMENT_OBJECT_ID',
merchantObjectId: '0xMERCHANT_OBJECT_ID',
coinObjectId: '0xUSDC_COIN_OBJECT_ID',
amount: '10000000',
},
USDC_TYPES.testnet
);
Finding a USDC Coin to Pay With
import { USDC_TYPES } from '@dolphinpay/sdk';
const { objects } = await client.suiClient.listOwnedObjects({
owner: payerAddress,
type: `0x2::coin::Coin<${USDC_TYPES.testnet}>`,
limit: 50,
include: { json: true },
});
const coin = objects.find(
(obj) => BigInt((obj.json as any).balance) >= BigInt(paymentAmount)
);
if (!coin) throw new Error('No USDC coin with sufficient balance');
Cancelling Payments
Only pending payments can be cancelled (by the merchant that created them):
const txb = client.payment.buildCancelPayment({
paymentId: '0xPAYMENT_OBJECT_ID',
reason: 'Order cancelled by customer',
});
const result = await signAndExecute({ transaction: txb });
There are no refunds: once a payment is completed, funds have been transferred and cannot be reversed on-chain.
Querying Payment Data
Get Payment Details
getPayment returns a PaymentQueryResult — the parsed Payment plus object metadata:
const result = await client.payment.getPayment('0xPAYMENT_OBJECT_ID');
const { payment } = result;
console.log('Amount (base units):', payment.amount);
console.log('Currency:', payment.currency);
console.log('Merchant:', payment.merchant);
console.log('Payer:', payment.payer); // null until executed
console.log('Status:', payment.status); // PaymentStatus enum value
console.log('Created:', new Date(Number(payment.createdAt)));
console.log('Expires:', new Date(Number(payment.expiresAt)));
console.log('Object version:', result.version, 'digest:', result.digest);
console.log('Metadata:', payment.metadata); // e.g. { orderId: 'ORDER-123' }
payment.metadata is strictly parsed from the on-chain VecMap back into a Record<string, string>; malformed on-chain data throws a diagnostic error naming the field path (e.g. Payment: invalid value at "metadata.contents[1]": …).
Check Payment Status
import { PaymentStatus } from '@dolphinpay/sdk';
const status = await client.payment.getPaymentStatus('0xPAYMENT_OBJECT_ID');
// PaymentStatus: PENDING = 0, SUCCESS = 1, FAILED = 2, EXPIRED = 3, CANCELLED = 4
if (status === PaymentStatus.PENDING) {
console.log('Payment is pending');
} else if (status === PaymentStatus.SUCCESS) {
console.log('Payment completed');
}
Check Expiry
const isExpired = await client.payment.isPaymentExpired('0xPAYMENT_OBJECT_ID');
isPaymentExpired compares expiresAt with local time. Use it instead of
waiting for PaymentStatus.EXPIRED: the current contract's expired execution
path aborts, so its attempted status update is rolled back and the stored
payment remains PENDING.
Dry Run Testing
Each builder has a dryRun* counterpart that simulates via gRPC:
const result = await client.payment.dryRunCreatePayment(
{
merchant: '0xMERCHANT_ADDRESS',
amount: usdcToUnits('10'),
currencyType: USDC_TYPES.testnet,
description: 'Test payment',
expirySeconds: 3600,
},
'0xSENDER_ADDRESS'
);
if (result.success) {
// result.effects, result.events, result.balanceChanges
const txb = client.payment.buildCreatePayment({ /* same params */ });
await signAndExecute({ transaction: txb });
} else {
console.error('Would fail:', result.error ?? result.status);
}
Also available: dryRunCreatePaymentWithMerchant(params, sender), dryRunExecutePayment(params, currencyType, sender), dryRunExecutePaymentWithMerchant(params, currencyType, sender), dryRunCancelPayment(params, sender).
Fees
The platform fee is 0 bps by policy, read from AdminConfig at execution. There is no merchant fee. To display a breakdown using the live rate:
import { calculateFeeBreakdown, usdcToUnits } from '@dolphinpay/sdk';
const { defaultPlatformFeeBps } = await client.admin.getAdminConfig();
const fees = calculateFeeBreakdown(usdcToUnits('100'), defaultPlatformFeeBps);
// With 0 bps: { amount: "100000000", platformFee: "0", totalFee: "0", netAmount: "100000000" }
Error Handling
Builders validate locally and throw before signing:
import { validatePaymentAmount, validateExpiry, usdcToUnits } from '@dolphinpay/sdk';
try {
validatePaymentAmount(usdcToUnits('10')); // throws if ≤ 0 or > 1,000,000 USDC
validateExpiry(3600); // throws if ≤ 0 or > 1 year
const txb = client.payment.buildCreatePayment({ /* ... */ });
const result = await signAndExecute({ transaction: txb });
} catch (error) {
console.error(error.message);
}
On-chain aborts map to PaymentErrorCode, e.g. E_INVALID_AMOUNT (100), E_PAYMENT_EXPIRED (101), E_AMOUNT_MISMATCH (104), E_CURRENCY_MISMATCH (105), E_MERCHANT_INACTIVE (111).
Next Steps
- Merchant Operations — merchant management
- Event Querying — payment history
- Basic Payment Example — complete integration
- TypeScript SDK overview