Skip to main content

Payment Module API Reference

Complete API documentation for the dolphinpay::payment module.

Overview

The Payment module (payment.move) handles the core payment lifecycle:

  • Payment creation with customizable parameters
  • Payment execution with automatic fee calculation
  • Payment cancellation by merchants
  • Status tracking and query operations

Package Location: dolphinpay::payment

Data Structures

Payment

The main payment object representing a payment transaction.

public struct Payment has key, store {
id: UID,
merchant: address,
payer: Option<address>,
amount: u64,
currency: TypeName,
created_at: u64,
completed_at: Option<u64>,
expires_at: u64,
status: u8,
metadata: VecMap<String, String>,
description: String,
refundable: bool,
refund_window: u64,
}

Fields:

  • id - Unique object identifier
  • merchant - Merchant's address to receive payment
  • payer - Address of the payer (set after execution)
  • amount - Payment amount in smallest unit (e.g., MIST for SUI)
  • currency - Type of the currency (e.g., 0x2::sui::SUI)
  • created_at - Timestamp when payment was created (milliseconds)
  • completed_at - Timestamp when payment was completed (if applicable)
  • expires_at - Timestamp when payment expires
  • status - Current payment status (see Status Codes)
  • metadata - Custom key-value pairs for tracking
  • description - Human-readable payment description
  • refundable - Whether the payment can be refunded
  • refund_window - Time window for refunds (seconds)

PaymentConfig

Configuration for payment parameters.

public struct PaymentConfig has store {
default_expiry: u64,
min_amount: u64,
max_amount: u64,
enabled: bool,
}

Constants

Status Codes

const STATUS_PENDING: u8 = 0;     // Payment created, awaiting execution
const STATUS_SUCCESS: u8 = 1; // Payment successfully completed
const STATUS_FAILED: u8 = 2; // Payment execution failed
const STATUS_EXPIRED: u8 = 3; // Payment expired without execution
const STATUS_CANCELLED: u8 = 4; // Payment cancelled by merchant

Fee Configuration

const PLATFORM_FEE_BPS: u64 = 30;  // 0.3% platform fee (30 basis points)

Limits

const DEFAULT_EXPIRY_MS: u64 = 3600000;           // 1 hour (default)
const MAX_EXPIRY_SECONDS: u64 = 31536000; // 1 year maximum
const MAX_PAYMENT_AMOUNT: u64 = 1_000_000_000_000; // 1000 SUI
const MAX_DESCRIPTION_LENGTH: u64 = 500; // Max description chars
const MAX_METADATA_ENTRIES: u64 = 10; // Max metadata pairs

Error Codes

const E_NOT_AUTHORIZED: u64 = 1;
const E_INVALID_AMOUNT: u64 = 100;
const E_PAYMENT_EXPIRED: u64 = 101;
const E_PAYMENT_ALREADY_COMPLETED: u64 = 102;
const E_INVALID_STATUS: u64 = 103;
const E_AMOUNT_MISMATCH: u64 = 104;
const E_CURRENCY_MISMATCH: u64 = 105;
const E_INVALID_MERCHANT: u64 = 106;
const E_INVALID_EXPIRY: u64 = 107;
const E_AMOUNT_TOO_LARGE: u64 = 108;
const E_DESCRIPTION_TOO_LONG: u64 = 109;
const E_METADATA_TOO_LARGE: u64 = 110;
const E_MERCHANT_INACTIVE: u64 = 111;
const E_MERCHANT_MISMATCH: u64 = 112;

Public Functions

create_payment

Creates a new payment order.

public fun create_payment(
merchant: address,
amount: u64,
currency: TypeName,
description: String,
metadata: VecMap<String, String>,
expiry_seconds: u64,
ctx: &mut TxContext
): Payment

Parameters:

  • merchant - Merchant's address to receive payment (must not be 0x0)
  • amount - Payment amount in smallest unit (e.g., MIST for SUI)
    • Must be > 0
    • Must be ≤ MAX_PAYMENT_AMOUNT
  • currency - Type of currency (e.g., 0x2::sui::SUI)
  • description - Human-readable description (max 500 chars)
  • metadata - Custom key-value pairs (max 10 entries)
  • expiry_seconds - Expiry time in seconds (0 = use default 1 hour)
    • Must be ≤ MAX_EXPIRY_SECONDS (1 year)
  • ctx - Transaction context

Returns:

  • Payment - New payment object in PENDING status

Emits:

  • PaymentCreated event

Errors:

  • E_INVALID_MERCHANT - Merchant address is 0x0
  • E_INVALID_AMOUNT - Amount is 0 or exceeds max
  • E_DESCRIPTION_TOO_LONG - Description exceeds 500 chars
  • E_METADATA_TOO_LARGE - More than 10 metadata entries
  • E_INVALID_EXPIRY - Expiry exceeds maximum

Example Usage:

// Using SDK
const txb = client.payment.buildCreatePayment({
merchant: '0xMERCHANT_ADDRESS',
amount: suiToMist(10), // 10 SUI
currency: 'SUI',
description: 'Premium subscription',
metadata: { orderId: 'ORD-123' },
expirySeconds: 3600, // 1 hour
});

create_payment_with_merchant

Creates a payment with merchant validation.

public fun create_payment_with_merchant(
merchant_obj: &Merchant,
amount: u64,
currency: TypeName,
description: String,
metadata: VecMap<String, String>,
expiry_seconds: u64,
ctx: &mut TxContext
): Payment

Parameters:

  • merchant_obj - Reference to merchant object (for validation)
  • Other parameters same as create_payment

Additional Validation:

  • Verifies merchant is active
  • Checks currency is supported by merchant

Returns:

  • Payment - New payment object

Emits:

  • PaymentCreated event

Errors:

  • All errors from create_payment plus:
  • E_MERCHANT_INACTIVE - Merchant is not active
  • E_CURRENCY_MISMATCH - Currency not supported by merchant

execute_payment

Executes a pending payment.

public fun execute_payment<T>(
payment: &mut Payment,
payment_coin: Coin<T>,
ctx: &mut TxContext
)

Type Parameters:

  • T - Type of the coin being paid (e.g., SUI)

Parameters:

  • payment - Mutable reference to payment object
  • payment_coin - Coin to pay with (must have sufficient balance)
  • ctx - Transaction context

Effects:

  • Transfers payment_coin to merchant's address
  • Deducts platform fee and sends to platform
  • Updates payment status to SUCCESS
  • Records payer address and completion timestamp

Emits:

  • PaymentCompleted event

Errors:

  • E_PAYMENT_EXPIRED - Payment has expired
  • E_PAYMENT_ALREADY_COMPLETED - Payment already executed
  • E_AMOUNT_MISMATCH - Coin value doesn't match payment amount
  • E_CURRENCY_MISMATCH - Coin type doesn't match payment currency

Fee Calculation:

Platform Fee = Amount × PLATFORM_FEE_BPS / 10000
= Amount × 30 / 10000
= Amount × 0.003 (0.3%)

Net Amount = Amount - Platform Fee

Example:

// Using SDK
const txb = client.payment.buildExecutePayment(
{
paymentId: '0xPAYMENT_ID',
coinObjectId: '0xCOIN_ID',
},
SUI_TYPES.SUI
);

execute_payment_with_merchant

Executes payment with merchant-specific fee handling.

public fun execute_payment_with_merchant<T>(
payment: &mut Payment,
merchant_obj: &Merchant,
payment_coin: Coin<T>,
ctx: &mut TxContext
)

Parameters:

  • payment - Mutable reference to payment
  • merchant_obj - Reference to merchant object
  • payment_coin - Coin to pay with
  • ctx - Transaction context

Additional Validation:

  • Verifies payment belongs to this merchant
  • Applies merchant-specific fee configuration

Returns:

  • None (modifies payment in place)

Emits:

  • PaymentCompleted event

Errors:

  • All errors from execute_payment plus:
  • E_MERCHANT_MISMATCH - Payment merchant doesn't match provided merchant
  • E_MERCHANT_INACTIVE - Merchant is not active

cancel_payment

Cancels a pending payment (merchant only).

public fun cancel_payment(
payment: &mut Payment,
merchant_cap: &MerchantCap,
ctx: &mut TxContext
)

Parameters:

  • payment - Mutable reference to payment
  • merchant_cap - Merchant capability (proves ownership)
  • ctx - Transaction context

Effects:

  • Changes payment status to CANCELLED
  • Payment can no longer be executed

Emits:

  • PaymentCancelled event

Errors:

  • E_NOT_AUTHORIZED - Caller is not the merchant
  • E_INVALID_STATUS - Payment is not in PENDING status

Example:

// Using SDK
const txb = client.payment.buildCancelPayment({
paymentId: '0xPAYMENT_ID',
merchantCapId: '0xMERCHANT_CAP_ID',
});

Query Functions

get_status

Returns the current payment status.

public fun get_status(payment: &Payment): u8

Returns:

  • u8 - Status code (0-4, see Status Codes)

get_amount

Returns the payment amount.

public fun get_amount(payment: &Payment): u64

Returns:

  • u64 - Payment amount in smallest unit

get_merchant

Returns the merchant address.

public fun get_merchant(payment: &Payment): address

Returns:

  • address - Merchant's address

get_payer

Returns the payer address if payment is completed.

public fun get_payer(payment: &Payment): Option<address>

Returns:

  • Option<address> - Payer's address (Some if completed, None otherwise)

get_currency

Returns the payment currency type.

public fun get_currency(payment: &Payment): TypeName

Returns:

  • TypeName - Currency type name

is_expired

Checks if payment has expired.

public fun is_expired(payment: &Payment, current_time: u64): bool

Parameters:

  • payment - Reference to payment
  • current_time - Current timestamp (milliseconds)

Returns:

  • bool - true if payment has expired

Example:

let current_time = tx_context::epoch_timestamp_ms(ctx);
if (is_expired(payment, current_time)) {
// Handle expired payment
}

get_description

Returns the payment description.

public fun get_description(payment: &Payment): String

Returns:

  • String - Payment description

get_metadata

Returns the payment metadata.

public fun get_metadata(payment: &Payment): &VecMap<String, String>

Returns:

  • &VecMap<String, String> - Reference to metadata map

Helper Functions

calculate_fee_with_bps

Calculates fee using basis points.

public fun calculate_fee_with_bps(amount: u64, fee_bps: u64): u64

Parameters:

  • amount - Base amount
  • fee_bps - Fee in basis points (1 bps = 0.01%)

Returns:

  • u64 - Calculated fee

Formula:

fee = (amount × fee_bps) / 10000

Example:

// Calculate 0.3% fee (30 bps)
let fee = calculate_fee_with_bps(1_000_000_000, 30);
// fee = 3_000_000 (0.003 SUI)

Events

PaymentCreated

Emitted when a payment is created.

public struct PaymentCreated has copy, drop {
payment_id: ID,
merchant: address,
amount: u64,
currency: TypeName,
description: String,
timestamp: u64,
}

PaymentCompleted

Emitted when a payment is successfully executed.

public struct PaymentCompleted has copy, drop {
payment_id: ID,
merchant: address,
payer: address,
amount: u64,
currency: TypeName,
fee_amount: u64,
net_amount: u64,
tx_hash: vector<u8>,
timestamp: u64,
}

PaymentCancelled

Emitted when a payment is cancelled.

public struct PaymentCancelled has copy, drop {
payment_id: ID,
reason: String,
timestamp: u64,
}

PaymentExpired

Emitted when a payment expires.

public struct PaymentExpired has copy, drop {
payment_id: ID,
timestamp: u64,
}

PaymentFailed

Emitted when a payment execution fails.

public struct PaymentFailed has copy, drop {
payment_id: ID,
merchant: address,
payer: address,
reason: String,
timestamp: u64,
}

Usage Examples

Complete Payment Flow

// 1. Create payment
let payment = payment::create_payment(
merchant_address,
1_000_000_000, // 1 SUI
type_name::get<SUI>(),
string::utf8(b"Premium subscription"),
vec_map::empty(),
3600, // 1 hour
ctx
);

// 2. Transfer payment object to shared object or merchant
transfer::public_share_object(payment);

// 3. Later, execute payment
let payment_coin = coin::take(&mut coins, 1_000_000_000, ctx);
payment::execute_payment(&mut payment, payment_coin, ctx);

// 4. Check status
let status = payment::get_status(&payment);
assert!(status == STATUS_SUCCESS, 0);

With Merchant Validation

// Create payment with merchant validation
let payment = payment::create_payment_with_merchant(
&merchant_obj,
5_000_000_000, // 5 SUI
type_name::get<SUI>(),
string::utf8(b"Service payment"),
metadata,
7200, // 2 hours
ctx
);

// Execute with merchant object
payment::execute_payment_with_merchant(
&mut payment,
&merchant_obj,
payment_coin,
ctx
);

Security Considerations

Reentrancy Protection

The module updates payment state before transferring funds:

// Safe pattern used in execute_payment:
payment.status = STATUS_SUCCESS; // Update state first
payment.payer = some(sender);
payment.completed_at = some(timestamp);
transfer::public_transfer(coin, merchant); // Then transfer

Integer Overflow Protection

Fee calculations use u128 for intermediate values:

let amount_u128 = (amount as u128);
let fee_u128 = (amount_u128 * (fee_bps as u128)) / 10000;
let fee = (fee_u128 as u64);

Input Validation

All inputs are validated:

  • ✅ Merchant address not zero
  • ✅ Amount within limits
  • ✅ Description length checked
  • ✅ Metadata size limited
  • ✅ Expiry within bounds

Best Practices

  1. Always check expiry before executing payments
  2. Use metadata for tracking (order IDs, customer IDs)
  3. Set appropriate expiry based on use case
  4. Validate merchant when creating payments
  5. Handle events for off-chain tracking
  6. Test with dry run before actual execution

Next Steps