Skip to main content
This documentation covers integrating WalletConnect Pay through ReownWalletKit. This approach provides a unified API where Pay is automatically initialized alongside WalletKit, simplifying the integration for wallet developers.

Requirements

  • Flutter 3.0+
  • iOS 13.0+
  • Android API 23+
  • ReownWalletKit

Installation

Add reown_walletkit to your pubspec.yaml:
dependencies:
  reown_walletkit: ^1.4.0
Then run:
flutter pub get
WalletConnectPay is automatically included as a dependency of ReownWalletKit.
Check the pub.dev page for the latest version.

Initialization

The WalletConnectPay client is automatically initialized during ReownWalletKit.init(). No additional setup is required.
import 'package:reown_walletkit/reown_walletkit.dart';

final walletKit = await ReownWalletKit.createInstance(
  projectId: 'YOUR_PROJECT_ID',
  metadata: PairingMetadata(
    name: 'My Wallet',
    description: 'My Wallet App',
    url: 'https://mywallet.com',
    icons: ['https://mywallet.com/icon.png'],
  ),
);

Accessing the Pay Client

You can access the WalletConnectPay instance directly:
final payClient = walletKit.pay;
Detect if a URI is a payment link before processing:
if (walletKit.isPaymentLink(uri)) {
  // Handle as payment. See [Get Payment Options] section
} else {
  // Handle as regular WalletConnect pairing
  await walletKit.pair(uri: Uri.parse(uri));
}

Payment Flow

The payment flow consists of five main steps: Detect Payment Link -> Get Options -> Get Actions -> Sign Actions -> Confirm Payment
1

Get Payment Options

Retrieve available payment options for a payment link:
final response = await walletKit.getPaymentOptions(
  request: GetPaymentOptionsRequest(
    paymentLink: 'https://pay.walletconnect.com/pay_123',
    accounts: ['eip155:1:0x...', 'eip155:137:0x...'], // Wallet's CAIP-10 accounts
    includePaymentInfo: true,
  ),
);

print('Payment ID: ${response.paymentId}');
print('Options available: ${response.options.length}');

if (response.info != null) {
  print('Amount: ${response.info!.amount.formatAmount()}');
  print('Merchant: ${response.info!.merchant.name}');
}

// Check if data collection is required
if (response.collectData != null) {
  print('Data collection required: ${response.collectData!.fields.length} fields');
}
2

Get Required Payment Actions

Get the required wallet actions for a selected payment option:
final actions = await walletKit.getRequiredPaymentActions(
  request: GetRequiredPaymentActionsRequest(
    optionId: 'option-id',
    paymentId: 'payment-id',
  ),
);

// Process each action (e.g., sign transactions)
for (final action in actions) {
  final walletRpc = action.walletRpc;
  print('Chain ID: ${walletRpc.chainId}');
  print('Method: ${walletRpc.method}');
  // Sign the transaction using your wallet SDK
}
3

Collect User Data (If Required)

Some payments may require additional user data:
List<CollectDataFieldResult>? collectedData;

if (response.collectData != null) {
  collectedData = <CollectDataFieldResult>[];
  for (final field in response.collectData!.fields) {
    // Collect data from user (e.g., full name, date of birth)
    collectedData.add(CollectDataFieldResult(
      id: field.id,
      value: userInput,
    ));
  }
}
4

Confirm Payment

Confirm the payment with signatures and optional collected data:
final confirmResponse = await walletKit.confirmPayment(
  request: ConfirmPaymentRequest(
    paymentId: 'payment-id',
    optionId: 'option-id',
    signatures: ['0x...', '0x...'], // Signatures from wallet actions
    collectedData: [
      CollectDataFieldResult(id: 'fullName', value: 'John Doe'),
      CollectDataFieldResult(id: 'dob', value: '1990-01-01'),
    ], // Optional: if data collection was required
    maxPollMs: 60000, // Maximum polling time in milliseconds
  ),
);

print('Payment Status: ${confirmResponse.status}');
print('Is Final: ${confirmResponse.isFinal}');

Complete Example

Here’s a complete example of processing a payment:
import 'package:reown_walletkit/reown_walletkit.dart';

class PaymentService {
  final ReownWalletKit walletKit;

  PaymentService(this.walletKit);

  /// Process a payment from a payment link (e.g., after scanning QR code)
  Future<void> processPayment(String paymentLink) async {
    try {
      // Step 1: Get payment options
      final accounts = await getWalletAccounts(); // Your wallet accounts
      final optionsResponse = await walletKit.getPaymentOptions(
        request: GetPaymentOptionsRequest(
          paymentLink: paymentLink,
          accounts: accounts,
          includePaymentInfo: true,
        ),
      );

      if (optionsResponse.options.isEmpty) {
        throw Exception('No payment options available');
      }

      // Step 2: Collect additional data if required (KYB/KYC)
      final collectedData = <CollectDataFieldResult>[];
      if (optionsResponse.collectData != null) {
        for (final field in optionsResponse.collectData!.fields) {
          // Collect data from user (e.g., full name, date of birth)
          // Example: collectedData.add(CollectDataFieldResult(id: field.id, value: userInput));
        }
      }

      // Step 3: Select payment option (or let user choose)
      PaymentOption selectedOption = optionsResponse.options.first;
      final paymentId = optionsResponse.paymentId;
      final optionId = selectedOption.id;

      // Step 4: Get required payment actions (if not already in the option)
      List<Action> actions = selectedOption.actions;
      if (actions.isEmpty) {
        actions = await walletKit.getRequiredPaymentActions(
          request: GetRequiredPaymentActionsRequest(
            optionId: optionId,
            paymentId: paymentId,
          ),
        );
      }

      // Step 5: Execute wallet actions and collect signatures
      final signatures = <String>[];
      for (final action in actions) {
        // Sign the transaction using your wallet SDK
        // Example: signatures.add(await signTransaction(action.walletRpc));
      }

      // Step 6: Confirm payment with polling
      ConfirmPaymentResponse confirmResponse = await walletKit.confirmPayment(
        request: ConfirmPaymentRequest(
          paymentId: paymentId,
          optionId: optionId,
          signatures: signatures,
          collectedData: collectedData.isNotEmpty ? collectedData : null,
          maxPollMs: 60000, // Maximum polling time in milliseconds
        ),
      );

      // Step 7: Poll until final status (if needed)
      while (!confirmResponse.isFinal && confirmResponse.pollInMs != null) {
        await Future.delayed(Duration(milliseconds: confirmResponse.pollInMs!));
        confirmResponse = await walletKit.confirmPayment(
          request: ConfirmPaymentRequest(
            paymentId: paymentId,
            optionId: optionId,
            signatures: signatures,
            collectedData: collectedData.isNotEmpty ? collectedData : null,
            maxPollMs: 60000,
          ),
        );
      }

      // Handle final payment status
      switch (confirmResponse.status) {
        case PaymentStatus.succeeded:
          print('Payment succeeded!');
          break;
        case PaymentStatus.failed:
          throw Exception('Payment failed');
        case PaymentStatus.expired:
          throw Exception('Payment expired');
        case PaymentStatus.requires_action:
          throw Exception('Payment requires additional action');
        case PaymentStatus.processing:
          // Should not happen if isFinal is true
          break;
      }
    } catch (e) {
      print('Payment error: $e');
      rethrow;
    }
  }

  Future<List<String>> getWalletAccounts() async {
    // Return your wallet's CAIP-10 formatted accounts
    // Example: ['eip155:1:0x1234...', 'eip155:137:0x5678...']
    return [];
  }
}

Direct Access

You can also access the underlying WalletConnectPay instance directly if needed:
final payClient = walletKit.pay;
// Use payClient methods directly
final response = await payClient.getPaymentOptions(request: request);

API Reference

ReownWalletKit Pay Methods

MethodDescription
isPaymentLink(String uri)Check if URI is a payment link
getPaymentOptions({required GetPaymentOptionsRequest request})Get available payment options
getRequiredPaymentActions({required GetRequiredPaymentActionsRequest request})Get actions requiring signatures
confirmPayment({required ConfirmPaymentRequest request})Confirm and finalize payment
payAccess the underlying WalletConnectPay instance

Models

GetPaymentOptionsRequest

GetPaymentOptionsRequest({
  required String paymentLink,
  required List<String> accounts,
  @Default(false) bool includePaymentInfo,
})

PaymentOptionsResponse

PaymentOptionsResponse({
  required String paymentId,
  PaymentInfo? info,
  required List<PaymentOption> options,
  CollectDataAction? collectData,
})

PaymentInfo

PaymentInfo({
  required PaymentStatus status,
  required PayAmount amount,
  required int expiresAt,
  required MerchantInfo merchant,
  BuyerInfo? buyer,
})

PaymentOption

PaymentOption({
  required String id,
  required String account,
  required PayAmount amount,
  @JsonKey(name: 'etaS') required int etaSeconds,
  required List<Action> actions,
})

ConfirmPaymentRequest

ConfirmPaymentRequest({
  required String paymentId,
  required String optionId,
  required List<String> signatures,
  List<CollectDataFieldResult>? collectedData,
  int? maxPollMs,
})

ConfirmPaymentResponse

ConfirmPaymentResponse({
  required PaymentStatus status,
  required bool isFinal,
  int? pollInMs,
})

PaymentStatus

enum PaymentStatus {
  requires_action,
  processing,
  succeeded,
  failed,
  expired,
}

Error Handling

The SDK throws specific exception types for different error scenarios. All errors extend the abstract PayError class, which itself extends PlatformException:
abstract class PayError extends PlatformException {
  PayError({
    required super.code,
    required super.message,
    required super.details,
    required super.stacktrace,
  });
}
ExceptionDescription
PayInitializeErrorInitialization failures
GetPaymentOptionsErrorErrors when fetching payment options
GetRequiredActionsErrorErrors when getting required actions
ConfirmPaymentErrorErrors when confirming payment
All errors include:
  • code: Error code
  • message: Error message
  • details: Additional error details
  • stacktrace: Stack trace

Example Error Handling

try {
  final response = await walletKit.getPaymentOptions(request: request);
} on GetPaymentOptionsError catch (e) {
  print('Error code: ${e.code}');
  print('Error message: ${e.message}');
} on PayError catch (e) {
  // Catch any Pay-related error
  print('Pay error: ${e.message}');
} catch (e) {
  print('Unexpected error: $e');
}

Best Practices

  1. Use WalletKit Integration: If your wallet already uses WalletKit, prefer this approach for automatic configuration
  2. Use isPaymentLink() for Detection: Use the utility method instead of manual URL parsing for reliable payment link detection
  3. Account Format: Always use CAIP-10 format for accounts: eip155:{chainId}:{address}
  4. Multiple Chains: Provide accounts for all supported chains to maximize payment options
  5. Signature Order: Maintain the same order of signatures as the actions array
  6. Error Handling: Always handle errors gracefully and show appropriate user feedback
  7. Loading States: Show loading indicators during API calls and signing operations
  8. Expiration: Check paymentInfo.expiresAt and warn users if time is running low
  9. User Data: Only collect data when collectData is present in the response

Examples

For a complete example implementation with UI components showing the full payment flow, see the reown_walletkit example. The example demonstrates:
  • Payment link detection and processing
  • Payment options retrieval with UI
  • Data collection for compliance (KYB/KYC)
  • Payment details display
  • Transaction signing and confirmation
  • Payment status polling and result display