diff --git a/docs.json b/docs.json index 6a1880f..0fc39c4 100644 --- a/docs.json +++ b/docs.json @@ -79,8 +79,8 @@ "pages": [ "payments/wallets/walletkit/kotlin", "payments/wallets/walletkit/swift", - "payments/wallets/walletkit/flutter", "payments/wallets/walletkit/react-native", + "payments/wallets/walletkit/flutter", "payments/wallets/walletkit/web" ] }, @@ -221,8 +221,8 @@ "pages": [ "payments/wallets/walletkit/kotlin", "payments/wallets/walletkit/swift", - "payments/wallets/walletkit/flutter", "payments/wallets/walletkit/react-native", + "payments/wallets/walletkit/flutter", "payments/wallets/walletkit/web" ] }, diff --git a/payments/wallets/overview.mdx b/payments/wallets/overview.mdx index d6995b7..b11ff7e 100644 --- a/payments/wallets/overview.mdx +++ b/payments/wallets/overview.mdx @@ -138,7 +138,7 @@ Wallets can earn interchange-like revenue on eligible WalletConnect Pay payments - Wallets that already have verified user PII (e.g. a neobank, a card issuing wallet) can prefill the WebView form by appending a `?prefill=` query parameter to the WebView URL. The `required` list from the `collectDataAction.schema` tells you which fields the form expects (e.g., `fullName`, `dob`, `pobAddress`). The user will still see the form but with pre-populated fields, reducing friction. + Wallets that already have verified user PII (e.g. a neobank, a card issuing wallet) can prefill the WebView form by appending a `?prefill=` query parameter to the WebView URL. The `required` list from the `collectDataAction.schema` tells you which fields the form expects (e.g., `fullName`, `dob`, `pobAddress`). The user will still see the form but with pre-populated fields, reducing friction. @@ -198,4 +198,13 @@ Wallets can earn interchange-like revenue on eligible WalletConnect Pay payments - [WalletConnect Terms and Conditions](https://walletconnect.com/terms) - [WalletConnect Privacy Policy](https://walletconnect.com/privacy) + + + Yes. The hosted form accepts two optional appearance parameters on its URL: + + - `theme=light` or `theme=dark` — sets the form's base color mode. Match it to your wallet's active mode. + - `themeVariables=` — overrides design tokens (font, font size, some colors, button border radius, and input border radius). Generate and export this value from the [WalletConnect Pay Dashboard](https://dashboard.walletconnect.com) and append it to the form URL verbatim. + + Both are optional and independent — when omitted, the form uses its default styling. See your platform's integration guide for the URL-building code. + diff --git a/payments/wallets/standalone/flutter.mdx b/payments/wallets/standalone/flutter.mdx index 651ec31..eea4e47 100644 --- a/payments/wallets/standalone/flutter.mdx +++ b/payments/wallets/standalone/flutter.mdx @@ -32,7 +32,7 @@ Add `walletconnect_pay` package to your `pubspec.yaml` or simply run: flutter pub add walletconnect_pay ``` -## Configuration +## Initialization Initialize the `WalletConnectPay` client with your WCP ID and client ID or API key: @@ -55,7 +55,7 @@ try { } ``` -### Configuration Parameters +**Configuration Parameters** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| @@ -170,9 +170,11 @@ if (response.info != null) { 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'); +// Check which options require data collection (per-option) +for (final option in response.options) { + if (option.collectData != null) { + print('Option ${option.id} requires info capture'); + } } ``` @@ -180,37 +182,42 @@ if (response.collectData != null) { -Some payments may require additional user data. After the user selects an option, check for `collectData` in the payment options response and run this step **before** fetching the required payment actions — the backend rejects the actions request with `400 IC data required` for options needing Information Capture if data has not yet been collected: +After the user selects an option, check for `collectData` on it. If present, collect the data **before** fetching the required actions. ```dart -if (response.collectData?.url != null) { - // Use the "required" list from response.collectData.schema to determine which fields to prefill +if (selectedOption.collectData?.url != null) { + // Use the "required" list from selectedOption.collectData.schema to determine which fields to prefill final prefillData = { 'fullName': 'John Doe', 'dob': '1990-01-15', 'pobAddress': '123 Main St, New York, NY 10001', }; - final prefillJson = jsonEncode(prefillData); - final prefillBase64 = base64Url.encode(utf8.encode(prefillJson)); - final uri = Uri.parse(response.collectData!.url); + // Encode prefill as base64url + final prefillBase64 = base64Url.encode(utf8.encode(jsonEncode(prefillData))).replaceAll('=', ''); + final uri = Uri.parse(selectedOption.collectData!.url); final webViewUrl = uri.replace( - queryParameters: {...uri.queryParameters, 'prefill': prefillBase64}, + queryParameters: { + ...uri.queryParameters, + 'prefill': prefillBase64, + // Optional appearance params (see "Form URL parameters"): + 'theme': 'dark', // "light" | "dark" + // themeVariables is a base64url string exported from the Pay Dashboard: + // 'themeVariables': themeVariables, + }, ).toString(); - // Show WebView — see WebView Implementation section below + // Show WebView — see Data Collection Implementation section below showDataCollectionWebView(webViewUrl); } ``` -The WebView submits the collected data directly to the backend, so you do **not** pass `collectedData` to `confirmPayment` later. - - + Get the required wallet actions (e.g., transactions to sign) for a selected payment option: @@ -234,10 +241,41 @@ for (final action in actions) { } ``` + + + + +Sign each action using your wallet's signing implementation, dispatching on the RPC method: + +```dart +// Sign each action based on its RPC method +final signatures = []; +for (final action in actions) { + final rpc = action.walletRpc; + switch (rpc.method) { + case 'eth_signTypedData_v4': + signatures.add(await signTypedData(rpc.chainId, rpc.params)); + break; + case 'eth_sendTransaction': + signatures.add(await sendTransaction(rpc.chainId, rpc.params)); + break; + case 'personal_sign': + signatures.add(await personalSign(rpc.chainId, rpc.params)); + break; + default: + throw UnimplementedError('Unsupported RPC method: ${rpc.method}'); + } +} +``` + Payment options may include multiple actions with different RPC methods. For example, a Permit2 payment where the user lacks sufficient allowance returns two actions: an `eth_sendTransaction` to approve the token allowance, followed by an `eth_signTypedData_v4` to sign the Permit2 transfer. Your wallet must check `action.walletRpc.method` and dispatch to the appropriate handler. For full implementation guidance, see [USDT support](/payments/wallets/token-chain-support/usdt-support). + +Signatures must be in the same order as the actions array. + + @@ -272,9 +310,9 @@ When using the WebView data-collection approach, you do **not** pass `collectedD -## WebView Implementation +## Data Collection Implementation -When `collectData.url` is present, display the URL in a WebView using the `webview_flutter` package (v4.10.0+). Add it to your `pubspec.yaml`: +When `selectedOption.collectData.url` is present, display the URL in a WebView using the `webview_flutter` package (v4.10.0+). Add it to your `pubspec.yaml`: ```yaml dependencies: @@ -282,6 +320,8 @@ dependencies: url_launcher: ^6.1.0 ``` + + ```dart import 'dart:convert'; import 'package:flutter/material.dart'; @@ -367,14 +407,20 @@ class _PayDataCollectionWebViewState extends State { } } -String buildPrefillUrl(String baseUrl, Map prefillData) { - if (prefillData.isEmpty) return baseUrl; - final json = jsonEncode(prefillData); - final base64 = base64Url.encode(utf8.encode(json)); +String buildFormUrl( + String baseUrl, { + Map prefillData = const {}, + String? theme, // "light" or "dark" + String? themeVariables, // base64url string exported from the Pay Dashboard +}) { final uri = Uri.parse(baseUrl); - return uri.replace( - queryParameters: {...uri.queryParameters, 'prefill': base64}, - ).toString(); + final params = {...uri.queryParameters}; + if (prefillData.isNotEmpty) { + params['prefill'] = base64Url.encode(utf8.encode(jsonEncode(prefillData))).replaceAll('=', ''); + } + if (theme != null) params['theme'] = theme; + if (themeVariables != null) params['themeVariables'] = themeVariables; + return uri.replace(queryParameters: params).toString(); } ``` @@ -421,9 +467,9 @@ class PaymentService { // for options needing Information Capture if data wasn't collected first. // The WebView submits the data directly to the backend, so it is NOT // passed to confirmPayment later. - if (optionsResponse.collectData?.url != null) { + if (selectedOption.collectData?.url != null) { // Show WebView and wait for IC_COMPLETE - await showDataCollectionWebView(optionsResponse.collectData!.url); + await showDataCollectionWebView(selectedOption.collectData!.url); } // Step 4: Get required payment actions @@ -485,11 +531,11 @@ class PaymentService { ## API Reference -### WalletConnectPay +**WalletConnectPay** The main class for interacting with the WalletConnect Pay SDK. -#### Constructor +**Constructor** ```dart WalletConnectPay({ @@ -500,7 +546,7 @@ WalletConnectPay({ }) ``` -#### Methods +**Methods** | Method | Description | |--------|-------------| @@ -509,9 +555,9 @@ WalletConnectPay({ | `Future> getRequiredPaymentActions({required GetRequiredPaymentActionsRequest request})` | Gets the required wallet actions for a selected option (to be called if the selected option does not have actions included) | | `Future confirmPayment({required ConfirmPaymentRequest request})` | Confirms a payment | -## Models +## Data Models -### GetPaymentOptionsRequest +**GetPaymentOptionsRequest** ```dart GetPaymentOptionsRequest({ @@ -521,7 +567,7 @@ GetPaymentOptionsRequest({ }) ``` -### PaymentOptionsResponse +**PaymentOptionsResponse** ```dart PaymentOptionsResponse({ @@ -533,7 +579,7 @@ PaymentOptionsResponse({ }) ``` -### PaymentResultInfo +**PaymentResultInfo** ```dart class PaymentResultInfo { @@ -542,7 +588,7 @@ class PaymentResultInfo { } ``` -### PaymentInfo +**PaymentInfo** ```dart PaymentInfo({ @@ -554,7 +600,7 @@ PaymentInfo({ }) ``` -### PaymentOption +**PaymentOption** ```dart PaymentOption({ @@ -563,10 +609,11 @@ PaymentOption({ required PayAmount amount, @JsonKey(name: 'etaS') required int etaSeconds, required List actions, + CollectDataAction? collectData, // Per-option data collection (null if not required) }) ``` -### ConfirmPaymentRequest +**ConfirmPaymentRequest** ```dart ConfirmPaymentRequest({ @@ -578,7 +625,7 @@ ConfirmPaymentRequest({ }) ``` -### ConfirmPaymentResponse +**ConfirmPaymentResponse** ```dart ConfirmPaymentResponse({ @@ -588,7 +635,7 @@ ConfirmPaymentResponse({ }) ``` -### PaymentStatus +**PaymentStatus** ```dart enum PaymentStatus { @@ -600,7 +647,7 @@ enum PaymentStatus { } ``` -### Action & WalletRpcAction +**Action & WalletRpcAction** ```dart class Action { @@ -614,7 +661,7 @@ class WalletRpcAction { } ``` -### CollectDataAction +**CollectDataAction** ```dart class CollectDataAction { @@ -651,7 +698,7 @@ All errors include: - `details`: Additional error details - `stacktrace`: Stack trace -### Example Error Handling +**Example Error Handling** ```dart try { @@ -691,8 +738,5 @@ try { 8. **User Data**: Only collect data when `collectData` is present in the response and you don't already have the required user data. If you already have the required data, you can submit this without collecting from the user. You must make sure the user accepts WalletConnect Terms and Conditions and Privacy Policy before submitting user information to WalletConnect. -9. **WebView Data Collection**: When `collectData.url` is present, display the URL in a WebView using `webview_flutter` rather than building native forms. The WebView handles form rendering, validation, and T&C acceptance. - -## Examples +9. **WebView Data Collection**: When `selectedOption.collectData.url` is present, display the URL in a WebView using `webview_flutter` rather than building native forms. The WebView handles form rendering, validation, and T&C acceptance. -For a complete example implementation, see the [reown_walletkit example](https://github.com/reown-com/reown_flutter/tree/master/packages/reown_walletkit/example/lib/walletconnect_pay). diff --git a/payments/wallets/standalone/kotlin.mdx b/payments/wallets/standalone/kotlin.mdx index 013e75c..2bac1d5 100644 --- a/payments/wallets/standalone/kotlin.mdx +++ b/payments/wallets/standalone/kotlin.mdx @@ -38,7 +38,7 @@ dependencies { The version shown above may not be the latest. Check the [GitHub releases](https://github.com/reown-com/reown-kotlin/releases) for the most recent version. -### JNA Dependency Configuration +**JNA Dependency Configuration** If you encounter JNA-related errors (e.g., `UnsatisfiedLinkError` or class loading issues), explicitly configure the JNA dependency: @@ -49,7 +49,7 @@ implementation("com.walletconnect:pay:1.0.0") { implementation("net.java.dev.jna:jna:5.17.0@aar") ``` -## Configuration +## Initialization Initialize the SDK in your `Application` class or before any payment operations: @@ -66,7 +66,7 @@ WalletConnectPay.initialize( ) ``` -### Configuration Parameters +**Configuration Parameters** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| @@ -222,7 +222,7 @@ result.onSuccess { response -> -Some payments require collecting additional user information. After the user selects an option, check for `collectData` on that option and complete data collection **before** fetching the required actions — the backend rejects the actions request if data collection is required but not yet captured: +After the user selects an option, check for `collectData` on it. If present, collect the data **before** fetching the required actions. @@ -238,15 +238,20 @@ selectedOption.collectData?.let { collectAction -> put("dob", "1990-01-15") put("pobAddress", "123 Main St, New York, NY 10001") }.toString() + // Encode prefill as base64url (URL-safe, no padding) val prefillBase64 = Base64.encodeToString( prefillJson.toByteArray(), - Base64.NO_WRAP or Base64.URL_SAFE + Base64.NO_WRAP or Base64.URL_SAFE or Base64.NO_PADDING ) val webViewUrl = Uri.parse(url).buildUpon() .appendQueryParameter("prefill", prefillBase64) + // Optional appearance params (see "Form URL parameters"): + .appendQueryParameter("theme", "dark") // "light" | "dark" + // themeVariables is a base64url string exported from the Pay Dashboard: + // .appendQueryParameter("themeVariables", themeVariables) .build().toString() - // Show WebView for this specific option — see WebView Implementation section below + // Show WebView for this specific option — see Data Collection Implementation section below showWebView(webViewUrl) } } @@ -254,10 +259,6 @@ selectedOption.collectData?.let { collectAction -> - -The WebView submits the collected data directly to the backend. You only receive an `IC_COMPLETE`/`IC_ERROR` signal — you do **not** pass `collectedData` to `confirmPayment`. - - @@ -360,9 +361,11 @@ confirmResult.onSuccess { response -> -## WebView Implementation +## Data Collection Implementation + +When `selectedOption.collectData.url` is present, display the URL in a WebView. The WebView handles form rendering, validation, and T&C acceptance. -When a selected option has `collectData.url` present, display the URL in a WebView. The WebView handles form rendering, validation, and T&C acceptance. + ```kotlin import android.webkit.JavascriptInterface @@ -424,16 +427,23 @@ fun PayDataCollectionWebView( }) } -fun buildPrefillUrl(baseUrl: String, prefillData: Map): String { - if (prefillData.isEmpty()) return baseUrl - val json = JSONObject(prefillData).toString() - val base64 = Base64.encodeToString( - json.toByteArray(), - Base64.NO_WRAP or Base64.URL_SAFE - ) - return Uri.parse(baseUrl).buildUpon() - .appendQueryParameter("prefill", base64) - .build().toString() +fun buildFormUrl( + baseUrl: String, + prefillData: Map = emptyMap(), + theme: String? = null, // "light" or "dark" + themeVariables: String? = null // base64url string exported from the Pay Dashboard +): String { + val builder = Uri.parse(baseUrl).buildUpon() + if (prefillData.isNotEmpty()) { + val prefill = Base64.encodeToString( + JSONObject(prefillData).toString().toByteArray(), + Base64.NO_WRAP or Base64.URL_SAFE or Base64.NO_PADDING + ) + builder.appendQueryParameter("prefill", prefill) + } + theme?.let { builder.appendQueryParameter("theme", it) } + themeVariables?.let { builder.appendQueryParameter("themeVariables", it) } + return builder.build().toString() } ``` @@ -539,17 +549,17 @@ class PaymentViewModel : ViewModel() { ## API Reference -### WalletConnectPay +**WalletConnectPay** Main entry point for the Pay SDK (singleton object). -#### Properties +**Properties** | Property | Type | Description | |----------|------|-------------| | `isInitialized` | `Boolean` | Whether the SDK has been initialized | -#### Methods +**Methods** | Method | Description | |--------|-------------| @@ -560,7 +570,7 @@ Main entry point for the Pay SDK (singleton object). ## Data Models -### Pay.PaymentOptionsResponse +**Pay.PaymentOptionsResponse** ```kotlin data class PaymentOptionsResponse( @@ -577,7 +587,7 @@ data class PaymentResultInfo( ) ``` -### Pay.PaymentInfo +**Pay.PaymentInfo** ```kotlin data class PaymentInfo( @@ -593,7 +603,7 @@ data class MerchantInfo( ) ``` -### Pay.PaymentOption +**Pay.PaymentOption** ```kotlin data class PaymentOption( @@ -605,7 +615,7 @@ data class PaymentOption( ) ``` -### Pay.Amount +**Pay.Amount** ```kotlin data class Amount( @@ -624,7 +634,7 @@ data class AmountDisplay( ) ``` -### Pay.WalletRpcAction +**Pay.WalletRpcAction** ```kotlin data class WalletRpcAction( @@ -634,7 +644,7 @@ data class WalletRpcAction( ) ``` -### Pay.RequiredAction +**Pay.RequiredAction** ```kotlin sealed class RequiredAction { @@ -642,7 +652,7 @@ sealed class RequiredAction { } ``` -### Pay.CollectDataAction +**Pay.CollectDataAction** ```kotlin data class CollectDataAction( @@ -651,7 +661,7 @@ data class CollectDataAction( ) ``` -### Pay.ConfirmPaymentResponse +**Pay.ConfirmPaymentResponse** ```kotlin data class ConfirmPaymentResponse( @@ -662,7 +672,7 @@ data class ConfirmPaymentResponse( ) ``` -### Pay.PaymentStatus +**Pay.PaymentStatus** | Status | Description | |--------|-------------| @@ -677,7 +687,7 @@ data class ConfirmPaymentResponse( The SDK provides typed errors for different failure scenarios: -### GetPaymentOptionsError +**GetPaymentOptionsError** | Error | Description | |-------|-------------| @@ -690,7 +700,7 @@ The SDK provides typed errors for different failure scenarios: | `Http` | Network error | | `InternalError` | Server error | -### GetPaymentRequestError +**GetPaymentRequestError** | Error | Description | |-------|-------------| @@ -699,7 +709,7 @@ The SDK provides typed errors for different failure scenarios: | `InvalidAccount` | Invalid account format | | `Http` | Network error | -### ConfirmPaymentError +**ConfirmPaymentError** | Error | Description | |-------|-------------| @@ -710,7 +720,7 @@ The SDK provides typed errors for different failure scenarios: | `RouteExpired` | Payment route expired | | `Http` | Network error | -### Example Error Handling +**Example Error Handling** ```kotlin val result = WalletConnectPay.getPaymentOptions(paymentLink, accounts) diff --git a/payments/wallets/standalone/react-native.mdx b/payments/wallets/standalone/react-native.mdx index c17b290..06f1eaf 100644 --- a/payments/wallets/standalone/react-native.mdx +++ b/payments/wallets/standalone/react-native.mdx @@ -40,7 +40,7 @@ yarn add @walletconnect/pay -### React Native Setup +**React Native Setup** This SDK requires the WalletConnect React Native native module. Make sure you have `@walletconnect/react-native-compat` installed and linked in your React Native project: @@ -57,7 +57,7 @@ The SDK uses a provider abstraction that allows different implementations: The SDK auto-detects the best available provider for your environment. -## Configuration +## Initialization Initialize the WalletConnect Pay client with your credentials: @@ -71,7 +71,7 @@ const client = new WalletConnectPay({ }); ``` -### Configuration Parameters +**Configuration Parameters** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| @@ -199,7 +199,7 @@ for (const option of options.options) { -Some payments may require additional user data. Check for `collectData` on the selected payment option. If present, complete data collection **before** fetching the required actions — the backend rejects the actions request unless the information has been captured first: +After the user selects an option, check for `collectData` on it. If present, collect the data **before** fetching the required actions. @@ -212,11 +212,19 @@ if (selectedOption.collectData?.url) { dob: "1990-01-15", pobAddress: "123 Main St, New York, NY 10001", }; - const prefillBase64 = btoa(JSON.stringify(prefillData)); + // Encode prefill as base64url (URL-safe, no padding) + const prefill = btoa(JSON.stringify(prefillData)) + .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); + + // Optional appearance params (see "Form URL parameters"): + // theme=light|dark — base color mode + // themeVariables= — exported from the WalletConnect Pay Dashboard + const themeVariables = ""; + const query = `prefill=${prefill}&theme=dark&themeVariables=${themeVariables}`; const separator = selectedOption.collectData.url.includes("?") ? "&" : "?"; - const webViewUrl = `${selectedOption.collectData.url}${separator}prefill=${prefillBase64}`; + const webViewUrl = `${selectedOption.collectData.url}${separator}${query}`; - // Show WebView for this specific option — see WebView Implementation section below + // Show WebView for this specific option — see Data Collection Implementation section below showDataCollectionWebView(webViewUrl); } ``` @@ -305,14 +313,16 @@ if (result.status === "succeeded") { -## WebView Implementation +## Data Collection Implementation -When a selected option has `collectData.url` present, display the URL in a WebView using `react-native-webview`. Install the dependency: +When `selectedOption.collectData.url` is present, display the URL in a WebView using `react-native-webview`. Install the dependency: ```bash npm install react-native-webview@13.16.0 ``` + + ```tsx import React, { useCallback } from "react"; import { WebView, WebViewMessageEvent } from "react-native-webview"; @@ -377,14 +387,26 @@ function PayDataCollectionWebView({ ); } -function buildPrefillUrl( +function buildFormUrl( baseUrl: string, - prefillData: Record + options: { + prefill?: Record; + theme?: "light" | "dark"; + themeVariables?: string; // base64url string exported from the Pay Dashboard + } = {} ): string { - if (Object.keys(prefillData).length === 0) return baseUrl; - const base64 = btoa(JSON.stringify(prefillData)); + const params: string[] = []; + if (options.prefill && Object.keys(options.prefill).length > 0) { + // base64url: URL-safe, no padding + const prefill = btoa(JSON.stringify(options.prefill)) + .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); + params.push(`prefill=${prefill}`); + } + if (options.theme) params.push(`theme=${options.theme}`); + if (options.themeVariables) params.push(`themeVariables=${options.themeVariables}`); + if (params.length === 0) return baseUrl; const separator = baseUrl.includes("?") ? "&" : "?"; - return `${baseUrl}${separator}prefill=${base64}`; + return `${baseUrl}${separator}${params.join("&")}`; } ``` @@ -507,72 +529,19 @@ import { NativeModules } from "react-native"; setNativeModule(NativeModules.RNWalletConnectPay); ``` -## Error Handling - -The SDK throws typed errors for different failure scenarios: - -```typescript -import { - PayError, - PaymentOptionsError, - PaymentActionsError, - ConfirmPaymentError, - NativeModuleNotFoundError -} from "@walletconnect/pay"; - -try { - const options = await client.getPaymentOptions({ - paymentLink: link, - accounts, - }); -} catch (error) { - if (error instanceof PaymentOptionsError) { - console.error("Failed to get options:", error.originalMessage); - } else if (error instanceof PayError) { - console.error("Pay error:", error.code, error.message); - } -} -``` - -### Error Types - -| Error Class | Description | -|-------------|-------------| -| `PayError` | Base error class for all Pay SDK errors | -| `PaymentOptionsError` | Error when fetching payment options | -| `PaymentActionsError` | Error when fetching required payment actions | -| `ConfirmPaymentError` | Error when confirming payment | -| `NativeModuleNotFoundError` | Error when native module is not available | - -### Error Codes - -The `PayError` class includes a `code` property with one of the following values: - -```typescript -type PayErrorCode = - | "JSON_PARSE" - | "JSON_SERIALIZE" - | "PAYMENT_OPTIONS" - | "PAYMENT_REQUEST" - | "CONFIRM_PAYMENT" - | "NATIVE_MODULE_NOT_FOUND" - | "INITIALIZATION_ERROR" - | "UNKNOWN"; -``` - ## API Reference -### WalletConnectPay +**WalletConnectPay** Main client for payment operations. -#### Constructor +**Constructor** ```typescript new WalletConnectPay(options: WalletConnectPayOptions) ``` -#### Methods +**Methods** | Method | Description | |--------|-------------| @@ -581,9 +550,9 @@ new WalletConnectPay(options: WalletConnectPayOptions) | `confirmPayment(params)` | Confirm and execute the payment | | `static isAvailable()` | Check if a provider is available | -### Data Types +**Data Types** -#### PaymentStatus +**PaymentStatus** ```typescript type PaymentStatus = @@ -594,19 +563,19 @@ type PaymentStatus = | "expired"; ``` -#### PayProviderType +**PayProviderType** ```typescript type PayProviderType = "native" | "wasm"; ``` -#### CollectDataFieldType +**CollectDataFieldType** ```typescript type CollectDataFieldType = "text" | "date"; ``` -#### Method Parameters +**Method Parameters** ```typescript interface GetPaymentOptionsParams { @@ -635,7 +604,7 @@ interface ConfirmPaymentParams { } ``` -#### Response Types +**Response Types** ```typescript interface PaymentOptionsResponse { @@ -668,7 +637,7 @@ interface ConfirmPaymentResponse { } ``` -#### PaymentOption +**PaymentOption** ```typescript interface PaymentOption { @@ -685,7 +654,7 @@ interface PaymentOption { } ``` -#### Action +**Action** ```typescript interface Action { @@ -702,7 +671,7 @@ interface WalletRpcAction { } ``` -#### Amount Types +**Amount Types** ```typescript interface PayAmount { @@ -728,7 +697,7 @@ interface AmountDisplay { } ``` -#### Payment Info Types +**Payment Info Types** ```typescript interface PaymentInfo { @@ -761,7 +730,7 @@ interface BuyerInfo { } ``` -#### Collect Data Types +**Collect Data Types** ```typescript interface CollectDataAction { @@ -772,6 +741,59 @@ interface CollectDataAction { } ``` +## Error Handling + +The SDK throws typed errors for different failure scenarios: + +```typescript +import { + PayError, + PaymentOptionsError, + PaymentActionsError, + ConfirmPaymentError, + NativeModuleNotFoundError +} from "@walletconnect/pay"; + +try { + const options = await client.getPaymentOptions({ + paymentLink: link, + accounts, + }); +} catch (error) { + if (error instanceof PaymentOptionsError) { + console.error("Failed to get options:", error.originalMessage); + } else if (error instanceof PayError) { + console.error("Pay error:", error.code, error.message); + } +} +``` + +**Error Types** + +| Error Class | Description | +|-------------|-------------| +| `PayError` | Base error class for all Pay SDK errors | +| `PaymentOptionsError` | Error when fetching payment options | +| `PaymentActionsError` | Error when fetching required payment actions | +| `ConfirmPaymentError` | Error when confirming payment | +| `NativeModuleNotFoundError` | Error when native module is not available | + +**Error Codes** + +The `PayError` class includes a `code` property with one of the following values: + +```typescript +type PayErrorCode = + | "JSON_PARSE" + | "JSON_SERIALIZE" + | "PAYMENT_OPTIONS" + | "PAYMENT_REQUEST" + | "CONFIRM_PAYMENT" + | "NATIVE_MODULE_NOT_FOUND" + | "INITIALIZATION_ERROR" + | "UNKNOWN"; +``` + ## Best Practices 1. **Check Provider Availability**: Always check if a provider is available before using the SDK diff --git a/payments/wallets/standalone/swift.mdx b/payments/wallets/standalone/swift.mdx index ee44ef6..2d1f275 100644 --- a/payments/wallets/standalone/swift.mdx +++ b/payments/wallets/standalone/swift.mdx @@ -26,7 +26,7 @@ For a complete working example, check out our sample wallet implementation: ## Installation -### Swift Package Manager +**Swift Package Manager** Add WalletConnectPay to your `Package.swift`: @@ -49,7 +49,7 @@ Then add `WalletConnectPay` to your target dependencies: The version shown above may not be the latest. Check the [GitHub releases](https://github.com/reown-com/reown-swift/releases) for the most recent version. -## Configuration +## Initialization Configure the Pay client during app initialization, typically in your `AppDelegate` or `SceneDelegate`: @@ -70,7 +70,7 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions...) } ``` -### Configuration Parameters +**Configuration Parameters** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| @@ -115,7 +115,7 @@ WalletConnect Pay currently supports the following tokens and networks: Include accounts for all supported networks to maximize payment options for your users. -## Detecting Payment Links +## Payment Link Detection The `isPaymentLink` utility method detects WalletConnect Pay links by checking for: - `pay.` hosts (e.g., pay.walletconnect.com) @@ -131,6 +131,25 @@ func isPaymentLink(_ string: String) -> Bool { } ``` +Call it wherever your wallet receives a link — from a deep link or a scanned QR code: + +```swift +// Deep link opened from outside your app (SceneDelegate or AppDelegate) +func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + guard let url = URLContexts.first?.url else { return } + if isPaymentLink(url.absoluteString) { + startPaymentFlow(paymentLink: url.absoluteString) + } +} + +// QR code payload +func handleScannedQR(_ content: String) { + if isPaymentLink(content) { + startPaymentFlow(paymentLink: content) + } +} +``` + ## Payment Flow The payment flow consists of five main steps: @@ -226,7 +245,7 @@ do { -If the selected option has `collectData` set, you must collect user information **before** fetching the required actions. The backend requires Information Capture to be completed first and will reject `getRequiredPaymentActions` otherwise: +After the user selects an option, check for `collectData` on it. If present, collect the data **before** fetching the required actions. @@ -241,24 +260,28 @@ if let collectData = selectedOption.collectData, let url = collectData.url { "pobAddress": "123 Main St, New York, NY 10001" ] let jsonData = try JSONSerialization.data(withJSONObject: prefillData) + // Encode prefill as base64url (URL-safe, no padding) let prefillBase64 = jsonData.base64EncodedString() + .replacingOccurrences(of: "+", with: "-") + .replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: "=", with: "") var components = URLComponents(string: url)! var queryItems = components.queryItems ?? [] queryItems.append(URLQueryItem(name: "prefill", value: prefillBase64)) + // Optional appearance params (see "Form URL parameters"): + queryItems.append(URLQueryItem(name: "theme", value: "dark")) // "light" | "dark" + // themeVariables is a base64url string exported from the WalletConnect Pay Dashboard: + // queryItems.append(URLQueryItem(name: "themeVariables", value: themeVariables)) components.queryItems = queryItems let webViewUrl = components.string! - // Show WebView for this specific option — see WebView Implementation section below + // Show WebView for this specific option — see Data Collection Implementation section below showWebView(url: webViewUrl) } ``` - -The WebView submits the captured data directly to the backend. You do **not** pass `collectedData` to `confirmPayment` when using the WebView approach. - - @@ -363,9 +386,11 @@ case .cancelled: -## WebView Implementation +## Data Collection Implementation + +When `selectedOption.collectData.url` is present, display the URL in a `WKWebView`. The WebView handles form rendering, validation, and T&C acceptance. -When a selected option has `collectData.url` present, display the URL in a `WKWebView`. The WebView handles form rendering, validation, and T&C acceptance. + ```swift import WebKit @@ -446,15 +471,29 @@ struct PayDataCollectionWebView: UIViewRepresentable { } } -func buildPrefillUrl(baseUrl: String, prefillData: [String: String]) -> String { - guard !prefillData.isEmpty, - let jsonData = try? JSONSerialization.data(withJSONObject: prefillData) else { - return baseUrl - } - let base64 = jsonData.base64EncodedString() - var components = URLComponents(string: baseUrl)! +func buildFormUrl( + baseUrl: String, + prefillData: [String: String] = [:], + theme: String? = nil, // "light" or "dark" + themeVariables: String? = nil // base64url string exported from the Pay Dashboard +) -> String { + guard var components = URLComponents(string: baseUrl) else { return baseUrl } var queryItems = components.queryItems ?? [] - queryItems.append(URLQueryItem(name: "prefill", value: base64)) + if !prefillData.isEmpty, + let jsonData = try? JSONSerialization.data(withJSONObject: prefillData) { + // base64url: URL-safe, no padding + let prefill = jsonData.base64EncodedString() + .replacingOccurrences(of: "+", with: "-") + .replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: "=", with: "") + queryItems.append(URLQueryItem(name: "prefill", value: prefill)) + } + if let theme = theme { + queryItems.append(URLQueryItem(name: "theme", value: theme)) + } + if let themeVariables = themeVariables { + queryItems.append(URLQueryItem(name: "themeVariables", value: themeVariables)) + } components.queryItems = queryItems return components.string ?? baseUrl } @@ -570,73 +609,9 @@ class PaymentManager { } ``` -## Deep Link Handling - -To handle payment links opened from outside your app: - -```swift -// In SceneDelegate or AppDelegate -func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { - guard let url = URLContexts.first?.url else { return } - - if isPaymentLink(url.absoluteString) { - startPaymentFlow(paymentLink: url.absoluteString) - } -} -``` - -## QR Code Scanning - -Payment links can be encoded as QR codes. Use the `isPaymentLink` utility for detection: - -```swift -func handleScannedQR(_ content: String) { - if isPaymentLink(content) { - // WalletConnect Pay QR code - startPaymentFlow(paymentLink: content) - } -} -``` - -## Error Handling - -The SDK throws specific error types for different failure scenarios: - -### GetPaymentOptionsError - -| Error | Description | -|-------|-------------| -| `.paymentNotFound` | Payment ID doesn't exist | -| `.paymentExpired` | Payment has expired | -| `.invalidRequest` | Invalid request parameters | -| `.invalidAccount` | Invalid account format | -| `.complianceFailed` | Compliance check failed | -| `.http` | Network error | -| `.internalError` | Server error | - -### GetPaymentRequestError - -| Error | Description | -|-------|-------------| -| `.optionNotFound` | Selected option doesn't exist | -| `.paymentNotFound` | Payment ID doesn't exist | -| `.invalidAccount` | Invalid account format | -| `.http` | Network error | - -### ConfirmPaymentError - -| Error | Description | -|-------|-------------| -| `.paymentNotFound` | Payment ID doesn't exist | -| `.paymentExpired` | Payment has expired | -| `.invalidOption` | Invalid option ID | -| `.invalidSignature` | Signature verification failed | -| `.routeExpired` | Payment route expired | -| `.http` | Network error | - ## API Reference -### WalletConnectPay +**WalletConnectPay** Static configuration class for the Pay SDK. @@ -645,7 +620,7 @@ Static configuration class for the Pay SDK. | `configure(apiKey:appId:baseUrl:logging:)` | Initialize the SDK with your credentials | | `instance` | Access the shared `PayClient` instance | -### PayClient +**PayClient** Main client for payment operations. @@ -655,9 +630,9 @@ Main client for payment operations. | `getRequiredPaymentActions(paymentId:optionId:)` | Get signing actions for a payment option | | `confirmPayment(paymentId:optionId:signatures:maxPollMs:)` | Confirm and execute the payment | -### Data Types +**Data Types** -#### PaymentOptionsResponse +**PaymentOptionsResponse** ```swift struct PaymentOptionsResponse { @@ -674,7 +649,7 @@ struct PaymentResultInfo { } ``` -#### PaymentInfo +**PaymentInfo** ```swift struct PaymentInfo { @@ -686,7 +661,7 @@ struct PaymentInfo { } ``` -#### PaymentOption +**PaymentOption** ```swift struct PaymentOption { @@ -698,7 +673,7 @@ struct PaymentOption { } ``` -#### PayAmount +**PayAmount** ```swift struct PayAmount { @@ -716,7 +691,7 @@ struct AmountDisplay { } ``` -#### Action & WalletRpcAction +**Action & WalletRpcAction** ```swift struct Action { @@ -730,7 +705,7 @@ struct WalletRpcAction { } ``` -#### CollectDataAction & CollectDataField +**CollectDataAction & CollectDataField** ```swift struct CollectDataAction { @@ -739,7 +714,7 @@ struct CollectDataAction { } ``` -#### ConfirmPaymentResultResponse +**ConfirmPaymentResultResponse** ```swift struct ConfirmPaymentResultResponse { @@ -759,6 +734,42 @@ enum PaymentStatus { } ``` +## Error Handling + +The SDK throws specific error types for different failure scenarios: + +**GetPaymentOptionsError** + +| Error | Description | +|-------|-------------| +| `.paymentNotFound` | Payment ID doesn't exist | +| `.paymentExpired` | Payment has expired | +| `.invalidRequest` | Invalid request parameters | +| `.invalidAccount` | Invalid account format | +| `.complianceFailed` | Compliance check failed | +| `.http` | Network error | +| `.internalError` | Server error | + +**GetPaymentRequestError** + +| Error | Description | +|-------|-------------| +| `.optionNotFound` | Selected option doesn't exist | +| `.paymentNotFound` | Payment ID doesn't exist | +| `.invalidAccount` | Invalid account format | +| `.http` | Network error | + +**ConfirmPaymentError** + +| Error | Description | +|-------|-------------| +| `.paymentNotFound` | Payment ID doesn't exist | +| `.paymentExpired` | Payment has expired | +| `.invalidOption` | Invalid option ID | +| `.invalidSignature` | Signature verification failed | +| `.routeExpired` | Payment route expired | +| `.http` | Network error | + ## Best Practices 1. **Account Format**: Always use CAIP-10 format for accounts: `eip155:{chainId}:{address}` diff --git a/payments/wallets/standalone/web.mdx b/payments/wallets/standalone/web.mdx index 82a7499..75e94fc 100644 --- a/payments/wallets/standalone/web.mdx +++ b/payments/wallets/standalone/web.mdx @@ -4,6 +4,8 @@ description: "Integrate WalletConnect Pay into your Web/Node.js wallet to enable sidebarTitle: "Web/Node.js" --- +import WebViewOverview from "/snippets/webview-data-collection-overview.mdx"; +import WebViewBestPractices from "/snippets/webview-data-collection-best-practices.mdx"; The WalletConnect Pay SDK allows wallet users to pay merchants using their crypto assets. The SDK handles payment option discovery, permit signing coordination, and payment confirmation while leveraging your wallet's existing signing infrastructure. @@ -45,7 +47,7 @@ The SDK uses a provider abstraction that allows different implementations: The SDK auto-detects the best available provider for your environment. -## Configuration +## Initialization Initialize the WalletConnect Pay client with your credentials: @@ -59,7 +61,7 @@ const client = new WalletConnectPay({ }); ``` -### Configuration Parameters +**Configuration Parameters** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| @@ -187,43 +189,9 @@ for (const option of options.options) { -After the user selects an option — and **before** fetching required actions — check for `collectData` on the selected payment option. If it requires data collection, it must be captured first, otherwise fetching the required actions will fail. +After the user selects an option, check for `collectData` on it. If present, collect the data **before** fetching the required actions. -## Iframe-Based Data Collection - -When a payment requires user information (e.g., for Travel Rule compliance), the SDK returns a `collectData` field on individual payment options. Each option may independently require data collection — some options may require it while others don't. - -### Recommended Flow (Per-Option) - -The recommended approach is to display all payment options upfront, then handle data collection only when the user selects an option that requires it: - -1. Call `getPaymentOptions` and display all available options to the user -2. Show a visual indicator (e.g., "Info required" badge) on options where `option.collectData` is present -3. When the user selects an option, check `selectedOption.collectData` -4. If present, open `selectedOption.collectData.url` in an iframe within your wallet -5. Optionally append a `prefill=` query parameter with known user data (e.g., name, date of birth, address). Use proper URL building to handle existing query parameters. -6. Listen for `postMessage` events: `IC_COMPLETE` (success) or `IC_ERROR` (failure) -7. On `IC_COMPLETE`, proceed to `getRequiredPaymentActions()` and then `confirmPayment()` **without** passing `collectedData` — the iframe submits data directly to the backend - -### Decision Matrix - -| Response `collectData` | `option.collectData` | Behavior | -|---|---|---| -| present | present | Option requires IC — use `option.collectData.url` | -| present | `null` | Option does NOT require IC (others might) — skip IC for this option | -| `null` | `null` | No IC needed for any option | - - -The `collectData` also includes a `schema` field — a JSON schema string describing the required fields. The `required` list in this schema tells you which fields the form expects. Wallets can use these field names as keys when building the prefill JSON object. For example, if the schema's `required` array contains `["fullName", "dob", "pobAddress"]`, you can prefill with `{"fullName": "...", "dob": "...", "pobAddress": "..."}`. - - - -The top-level `collectData` on the payment options response is still available for backward compatibility. However, the per-option `collectData` is the recommended approach as it provides more granular control over the flow. - - - -When using the iframe approach, do **not** pass `collectedData` to `confirmPayment()`. The iframe handles data submission directly. - + ```typescript // Check per-option data collection requirement after user selects an option @@ -234,9 +202,17 @@ if (selectedOption.collectData?.url) { dob: "1990-01-15", pobAddress: "123 Main St, New York, NY 10001", }; - const prefillBase64 = btoa(JSON.stringify(prefillData)); + // Encode prefill as base64url (URL-safe, no padding) + const prefill = btoa(JSON.stringify(prefillData)) + .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); + + // Optional appearance params (see "Form URL parameters"): + // theme=light|dark — base color mode + // themeVariables= — exported from the WalletConnect Pay Dashboard + const themeVariables = ""; + const query = `prefill=${prefill}&theme=dark&themeVariables=${themeVariables}`; const separator = selectedOption.collectData.url.includes("?") ? "&" : "?"; - const iframeUrl = `${selectedOption.collectData.url}${separator}prefill=${prefillBase64}`; + const iframeUrl = `${selectedOption.collectData.url}${separator}${query}`; // Show data collection in an iframe or popup — see Data Collection Implementation section below showDataCollectionView(iframeUrl); @@ -336,7 +312,9 @@ if (result.status === "succeeded") { ## Data Collection Implementation -When a selected option has `collectData.url` present, display the URL in an iframe or modal. Listen for `postMessage` events to handle completion: +When `selectedOption.collectData.url` is present, display the URL in an iframe or modal. Listen for `postMessage` events to handle completion: + + ```typescript function showDataCollectionView(url: string): Promise { @@ -377,14 +355,26 @@ function showDataCollectionView(url: string): Promise { }); } -function buildPrefillUrl( +function buildFormUrl( baseUrl: string, - prefillData: Record + options: { + prefill?: Record; + theme?: "light" | "dark"; + themeVariables?: string; // base64url string exported from the Pay Dashboard + } = {} ): string { - if (Object.keys(prefillData).length === 0) return baseUrl; - const base64 = btoa(JSON.stringify(prefillData)); + const params: string[] = []; + if (options.prefill && Object.keys(options.prefill).length > 0) { + // base64url: URL-safe, no padding + const prefill = btoa(JSON.stringify(options.prefill)) + .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); + params.push(`prefill=${prefill}`); + } + if (options.theme) params.push(`theme=${options.theme}`); + if (options.themeVariables) params.push(`themeVariables=${options.themeVariables}`); + if (params.length === 0) return baseUrl; const separator = baseUrl.includes("?") ? "&" : "?"; - return `${baseUrl}${separator}prefill=${base64}`; + return `${baseUrl}${separator}${params.join("&")}`; } ``` @@ -496,69 +486,19 @@ if (isProviderAvailable()) { const providerType = detectProviderType(); // 'native' | 'wasm' | null ``` -## Error Handling - -The SDK throws typed errors for different failure scenarios: - -```typescript -import { - PayError, - PaymentOptionsError, - PaymentActionsError, - ConfirmPaymentError, -} from "@walletconnect/pay"; - -try { - const options = await client.getPaymentOptions({ - paymentLink: link, - accounts, - }); -} catch (error) { - if (error instanceof PaymentOptionsError) { - console.error("Failed to get options:", error.originalMessage); - } else if (error instanceof PayError) { - console.error("Pay error:", error.code, error.message); - } -} -``` - -### Error Types - -| Error Class | Description | -|-------------|-------------| -| `PayError` | Base error class for all Pay SDK errors | -| `PaymentOptionsError` | Error when fetching payment options | -| `PaymentActionsError` | Error when fetching required payment actions | -| `ConfirmPaymentError` | Error when confirming payment | - -### Error Codes - -The `PayError` class includes a `code` property with one of the following values: - -```typescript -type PayErrorCode = - | "JSON_PARSE" - | "JSON_SERIALIZE" - | "PAYMENT_OPTIONS" - | "PAYMENT_REQUEST" - | "CONFIRM_PAYMENT" - | "INITIALIZATION_ERROR" - | "UNKNOWN"; -``` - ## API Reference -### WalletConnectPay +**WalletConnectPay** Main client for payment operations. -#### Constructor +**Constructor** ```typescript new WalletConnectPay(options: WalletConnectPayOptions) ``` -#### Methods +**Methods** | Method | Description | |--------|-------------| @@ -567,9 +507,9 @@ new WalletConnectPay(options: WalletConnectPayOptions) | `confirmPayment(params)` | Confirm and execute the payment | | `static isAvailable()` | Check if a provider is available | -### Data Types +**Data Types** -#### PaymentStatus +**PaymentStatus** ```typescript type PaymentStatus = @@ -580,19 +520,19 @@ type PaymentStatus = | "expired"; ``` -#### PayProviderType +**PayProviderType** ```typescript type PayProviderType = "native" | "wasm"; ``` -#### CollectDataFieldType +**CollectDataFieldType** ```typescript type CollectDataFieldType = "text" | "date"; ``` -#### Method Parameters +**Method Parameters** ```typescript interface GetPaymentOptionsParams { @@ -621,7 +561,7 @@ interface ConfirmPaymentParams { } ``` -#### Response Types +**Response Types** ```typescript interface PaymentOptionsResponse { @@ -654,7 +594,7 @@ interface ConfirmPaymentResponse { } ``` -#### PaymentOption +**PaymentOption** ```typescript interface PaymentOption { @@ -671,7 +611,7 @@ interface PaymentOption { } ``` -#### Action +**Action** ```typescript interface Action { @@ -688,7 +628,7 @@ interface WalletRpcAction { } ``` -#### Amount Types +**Amount Types** ```typescript interface PayAmount { @@ -714,7 +654,7 @@ interface AmountDisplay { } ``` -#### Payment Info Types +**Payment Info Types** ```typescript interface PaymentInfo { @@ -747,7 +687,7 @@ interface BuyerInfo { } ``` -#### Collect Data Types +**Collect Data Types** ```typescript interface CollectDataAction { @@ -758,6 +698,56 @@ interface CollectDataAction { } ``` +## Error Handling + +The SDK throws typed errors for different failure scenarios: + +```typescript +import { + PayError, + PaymentOptionsError, + PaymentActionsError, + ConfirmPaymentError, +} from "@walletconnect/pay"; + +try { + const options = await client.getPaymentOptions({ + paymentLink: link, + accounts, + }); +} catch (error) { + if (error instanceof PaymentOptionsError) { + console.error("Failed to get options:", error.originalMessage); + } else if (error instanceof PayError) { + console.error("Pay error:", error.code, error.message); + } +} +``` + +**Error Types** + +| Error Class | Description | +|-------------|-------------| +| `PayError` | Base error class for all Pay SDK errors | +| `PaymentOptionsError` | Error when fetching payment options | +| `PaymentActionsError` | Error when fetching required payment actions | +| `ConfirmPaymentError` | Error when confirming payment | + +**Error Codes** + +The `PayError` class includes a `code` property with one of the following values: + +```typescript +type PayErrorCode = + | "JSON_PARSE" + | "JSON_SERIALIZE" + | "PAYMENT_OPTIONS" + | "PAYMENT_REQUEST" + | "CONFIRM_PAYMENT" + | "INITIALIZATION_ERROR" + | "UNKNOWN"; +``` + ## Best Practices 1. **Check Provider Availability**: Always check if a provider is available before using the SDK diff --git a/payments/wallets/walletkit/ai-prompts/flutter.mdx b/payments/wallets/walletkit/ai-prompts/flutter.mdx index cc80720..3b614d0 100644 --- a/payments/wallets/walletkit/ai-prompts/flutter.mdx +++ b/payments/wallets/walletkit/ai-prompts/flutter.mdx @@ -224,12 +224,36 @@ class PaymentOption { final PayAmount amount; // Amount in this option's token final int etaSeconds; // Estimated completion time final List actions; // Signing actions (may be empty initially) + final CollectDataAction? collectData; // Per-option data collection (null if not required) } ``` ### Step 5: Handle Data Collection via WebView -If `response.collectData` is not null and has a `url`, display the URL in a WebView for data collection. The hosted form handles rendering, validation, and T&C acceptance. +If the selected option's `collectData` is not null and has a `url`, display the URL in a WebView for data collection. The hosted form handles rendering, validation, and T&C acceptance. + +The form URL accepts optional query parameters — append them to `selectedOption.collectData.url` before loading, preserving existing query parameters: +- `prefill` — base64url-encoded JSON of known user fields. Keys must match the `required` fields from `collectData.schema` (e.g. `fullName`, `dob`, `pobAddress`). +- `theme` — `light` or `dark`, to match your wallet's color mode. +- `themeVariables` — a base64url string exported from the [WalletConnect Pay Dashboard](https://dashboard.walletconnect.com) that overrides design tokens (font, font size, some colors, button/input border radius). Append it verbatim. + +```dart +String buildFormUrl( + String baseUrl, { + Map prefillData = const {}, + String? theme, // "light" or "dark" + String? themeVariables, // base64url string exported from the Pay Dashboard +}) { + final uri = Uri.parse(baseUrl); + final params = {...uri.queryParameters}; + if (prefillData.isNotEmpty) { + params['prefill'] = base64Url.encode(utf8.encode(jsonEncode(prefillData))).replaceAll('=', ''); + } + if (theme != null) params['theme'] = theme; + if (themeVariables != null) params['themeVariables'] = themeVariables; + return uri.replace(queryParameters: params).toString(); +} +``` ```dart import 'dart:convert'; @@ -588,11 +612,11 @@ Future processPayment(String paymentLink) async { final selectedOption = await showPaymentOptionsModal(options); if (selectedOption == null) return; // User cancelled - // Step 3: Collect data via WebView if required - if (options.collectData?.url != null) { + // Step 3: Collect data via WebView if required for selected option + if (selectedOption.collectData?.url != null) { final success = await showDataCollectionWebView( context, - options.collectData!.url, + selectedOption.collectData!.url, ); if (!success) return; // User cancelled or error } diff --git a/payments/wallets/walletkit/ai-prompts/kotlin.mdx b/payments/wallets/walletkit/ai-prompts/kotlin.mdx index 5e0dd53..c4aa44b 100644 --- a/payments/wallets/walletkit/ai-prompts/kotlin.mdx +++ b/payments/wallets/walletkit/ai-prompts/kotlin.mdx @@ -226,7 +226,12 @@ fun processPaymentOptionsResponse(response: Wallet.Model.PaymentOptionsResponse) } ``` -When a selected option has `collectData?.url`, display the URL in a WebView before proceeding with signing. The hosted form handles rendering, validation, and Terms & Conditions acceptance. The WebView communicates completion via JavaScript bridge messages (`IC_COMPLETE` / `IC_ERROR`). +When `selectedOption.collectData?.url` is present, display the URL in a WebView before proceeding with signing. The hosted form handles rendering, validation, and Terms & Conditions acceptance. The WebView communicates completion via JavaScript bridge messages (`IC_COMPLETE` / `IC_ERROR`). + +The form URL accepts optional query parameters (append them with `buildFormUrl` before loading): +- `prefill` — base64url-encoded JSON of known user fields. Keys must match the `required` fields from `collectData.schema` (e.g. `fullName`, `dob`, `pobAddress`). +- `theme` — `light` or `dark`, to match your wallet's color mode. +- `themeVariables` — a base64url string exported from the [WalletConnect Pay Dashboard](https://dashboard.walletconnect.com) that overrides design tokens (font, font size, some colors, button/input border radius). Append it verbatim. > **Important:** When using the WebView approach, do **not** pass `collectedData` to `confirmPayment()`. The WebView submits data directly to the backend. @@ -291,16 +296,23 @@ fun PayDataCollectionWebView( }) } -fun buildPrefillUrl(baseUrl: String, prefillData: Map): String { - if (prefillData.isEmpty()) return baseUrl - val json = JSONObject(prefillData).toString() - val base64 = Base64.encodeToString( - json.toByteArray(), - Base64.NO_WRAP or Base64.URL_SAFE - ) - return Uri.parse(baseUrl).buildUpon() - .appendQueryParameter("prefill", base64) - .build().toString() +fun buildFormUrl( + baseUrl: String, + prefillData: Map = emptyMap(), + theme: String? = null, // "light" or "dark" + themeVariables: String? = null // base64url string exported from the Pay Dashboard +): String { + val builder = Uri.parse(baseUrl).buildUpon() + if (prefillData.isNotEmpty()) { + val prefill = Base64.encodeToString( + JSONObject(prefillData).toString().toByteArray(), + Base64.NO_WRAP or Base64.URL_SAFE or Base64.NO_PADDING + ) + builder.appendQueryParameter("prefill", prefill) + } + theme?.let { builder.appendQueryParameter("theme", it) } + themeVariables?.let { builder.appendQueryParameter("themeVariables", it) } + return builder.build().toString() } ``` diff --git a/payments/wallets/walletkit/ai-prompts/react-native.mdx b/payments/wallets/walletkit/ai-prompts/react-native.mdx index 8693e3b..f9c3602 100644 --- a/payments/wallets/walletkit/ai-prompts/react-native.mdx +++ b/payments/wallets/walletkit/ai-prompts/react-native.mdx @@ -625,6 +625,28 @@ When `selectedOption.collectData?.url` is present, display the URL in a WebView. Wire `onComplete` to your `onDataCollectionComplete` handler (which then calls `fetchPaymentActions`), so action fetching only happens once the WebView has submitted the Information Capture data to the backend. +The form URL accepts optional query parameters — append them to `selectedOption.collectData.url` before loading, preserving existing query parameters: +- `prefill` — base64url-encoded JSON of known user fields. Keys must match the `required` fields from `collectData.schema` (e.g. `fullName`, `dob`, `pobAddress`). +- `theme` — `light` or `dark`, to match your wallet's color mode. +- `themeVariables` — a base64url string exported from the [WalletConnect Pay Dashboard](https://dashboard.walletconnect.com) that overrides design tokens (font, font size, some colors, button/input border radius). Append it verbatim. + +```typescript +function buildFormUrl(baseUrl, options = {}) { + const params = []; + if (options.prefill && Object.keys(options.prefill).length > 0) { + // base64url: URL-safe, no padding + const prefill = btoa(JSON.stringify(options.prefill)) + .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); + params.push(`prefill=${prefill}`); + } + if (options.theme) params.push(`theme=${options.theme}`); + if (options.themeVariables) params.push(`themeVariables=${options.themeVariables}`); + if (params.length === 0) return baseUrl; + const separator = baseUrl.includes("?") ? "&" : "?"; + return `${baseUrl}${separator}${params.join("&")}`; +} +``` + Install `react-native-webview`: ```bash diff --git a/payments/wallets/walletkit/ai-prompts/swift.mdx b/payments/wallets/walletkit/ai-prompts/swift.mdx index 7786333..955e65d 100644 --- a/payments/wallets/walletkit/ai-prompts/swift.mdx +++ b/payments/wallets/walletkit/ai-prompts/swift.mdx @@ -436,7 +436,39 @@ func confirmPayment( ## WebView Data Collection -When `collectData.url` is present, display the URL in a `WKWebView`. The hosted form handles rendering, validation, and T&C acceptance. +When `selectedOption.collectData.url` is present, display the URL in a `WKWebView`. The hosted form handles rendering, validation, and T&C acceptance. + +The form URL accepts optional query parameters — append them to `selectedOption.collectData.url` before loading, preserving existing query parameters: +- `prefill` — base64url-encoded JSON of known user fields. Keys must match the `required` fields from `collectData.schema` (e.g. `fullName`, `dob`, `pobAddress`). +- `theme` — `light` or `dark`, to match your wallet's color mode. +- `themeVariables` — a base64url string exported from the [WalletConnect Pay Dashboard](https://dashboard.walletconnect.com) that overrides design tokens (font, font size, some colors, button/input border radius). Append it verbatim. + +```swift +func buildFormUrl( + baseUrl: String, + prefillData: [String: String] = [:], + theme: String? = nil, // "light" or "dark" + themeVariables: String? = nil // base64url string exported from the Pay Dashboard +) -> String { + guard var components = URLComponents(string: baseUrl) else { return baseUrl } + var queryItems = components.queryItems ?? [] + if !prefillData.isEmpty, + let jsonData = try? JSONSerialization.data(withJSONObject: prefillData) { + // base64url: URL-safe, no padding + let prefill = jsonData.base64EncodedString() + .replacingOccurrences(of: "+", with: "-") + .replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: "=", with: "") + queryItems.append(URLQueryItem(name: "prefill", value: prefill)) + } + if let theme = theme { queryItems.append(URLQueryItem(name: "theme", value: theme)) } + if let themeVariables = themeVariables { + queryItems.append(URLQueryItem(name: "themeVariables", value: themeVariables)) + } + components.queryItems = queryItems + return components.string ?? baseUrl +} +``` ```swift import WebKit diff --git a/payments/wallets/walletkit/flutter.mdx b/payments/wallets/walletkit/flutter.mdx index 1e9cc94..ea60d0b 100644 --- a/payments/wallets/walletkit/flutter.mdx +++ b/payments/wallets/walletkit/flutter.mdx @@ -71,9 +71,7 @@ final walletKit = await ReownWalletKit.createInstance( ); ``` -## Accessing the Pay Client - -You can access the `WalletConnectPay` instance directly: +Access the `WalletConnectPay` client directly via `walletKit.pay`: ```dart final payClient = walletKit.pay; @@ -162,9 +160,11 @@ if (response.info != null) { 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'); +// Check which options require data collection (per-option) +for (final option in response.options) { + if (option.collectData != null) { + print('Option ${option.id} requires info capture'); + } } ``` @@ -172,23 +172,30 @@ if (response.collectData != null) { -Some payments may require additional user data. After the user selects an option, check for `collectData` on the response and run this step **before** fetching the required payment actions — the backend rejects the actions request with `400 IC data required` for options needing Information Capture if data has not yet been collected: +After the user selects an option, check for `collectData` on it. If present, collect the data **before** fetching the required actions. ```dart -if (response.collectData?.url != null) { - // Use the "required" list from response.collectData.schema to determine which fields to prefill +if (selectedOption.collectData?.url != null) { + // Use the "required" list from selectedOption.collectData.schema to determine which fields to prefill final prefillData = { 'fullName': 'John Doe', 'dob': '1990-01-15', 'pobAddress': '123 Main St, New York, NY 10001', }; - final prefillJson = jsonEncode(prefillData); - final prefillBase64 = base64Url.encode(utf8.encode(prefillJson)); - final uri = Uri.parse(response.collectData!.url); + // Encode prefill as base64url (no padding, consistent with the other SDKs) + final prefillBase64 = base64Url.encode(utf8.encode(jsonEncode(prefillData))).replaceAll('=', ''); + final uri = Uri.parse(selectedOption.collectData!.url); final webViewUrl = uri.replace( - queryParameters: {...uri.queryParameters, 'prefill': prefillBase64}, + queryParameters: { + ...uri.queryParameters, + 'prefill': prefillBase64, + // Optional appearance params (see "Form URL parameters"): + 'theme': 'dark', // "light" | "dark" + // themeVariables is a base64url string exported from the Pay Dashboard: + // 'themeVariables': themeVariables, + }, ).toString(); // Show WebView and wait for IC_COMPLETE message @@ -198,11 +205,9 @@ if (response.collectData?.url != null) { -The WebView submits the collected data directly to the backend, so you do **not** pass `collectedData` to `confirmPayment` later. - - + Get the required wallet actions for a selected payment option: @@ -224,10 +229,41 @@ for (final action in actions) { } ``` + + + + +Sign each action using your wallet's signing implementation, dispatching on the RPC method: + +```dart +// Sign each action based on its RPC method +final signatures = []; +for (final action in actions) { + final rpc = action.walletRpc; + switch (rpc.method) { + case 'eth_signTypedData_v4': + signatures.add(await signTypedData(rpc.chainId, rpc.params)); + break; + case 'eth_sendTransaction': + signatures.add(await sendTransaction(rpc.chainId, rpc.params)); + break; + case 'personal_sign': + signatures.add(await personalSign(rpc.chainId, rpc.params)); + break; + default: + throw UnimplementedError('Unsupported RPC method: ${rpc.method}'); + } +} +``` + Payment options may include multiple actions with different RPC methods. For example, a Permit2 payment where the user lacks sufficient allowance returns two actions: an `eth_sendTransaction` to approve the token allowance, followed by an `eth_signTypedData_v4` to sign the Permit2 transfer. Your wallet must check `action.walletRpc.method` and dispatch to the appropriate handler. For full implementation guidance, see [USDT support](/payments/wallets/token-chain-support/usdt-support). + +Signatures must be in the same order as the actions array. + + @@ -256,9 +292,9 @@ When using the WebView data-collection approach, you do **not** pass `collectedD -## WebView Implementation +## Data Collection Implementation -When `collectData.url` is present, display the URL in a WebView using `webview_flutter` (v4.10.0+). Add dependencies: +When `selectedOption.collectData.url` is present, display the URL in a WebView using `webview_flutter` (v4.10.0+). Add dependencies: ```yaml dependencies: @@ -266,6 +302,8 @@ dependencies: url_launcher: ^6.1.0 ``` + + ```dart import 'dart:convert'; import 'package:flutter/material.dart'; @@ -375,14 +413,14 @@ class PaymentService { final paymentId = optionsResponse.paymentId; final optionId = selectedOption.id; - // Step 3: Collect data via WebView if required. + // Step 3: Collect data via WebView if required for selected option. // This must happen BEFORE fetching the required payment actions — // the backend rejects the actions request with 400 "IC data required" // for options needing Information Capture if data wasn't collected first. // The WebView submits the data directly to the backend, so it is NOT // passed to confirmPayment later. - if (optionsResponse.collectData?.url != null) { - await showDataCollectionWebView(optionsResponse.collectData!.url); + if (selectedOption.collectData?.url != null) { + await showDataCollectionWebView(selectedOption.collectData!.url); } // Step 4: Get required payment actions (if not already in the option) @@ -460,19 +498,9 @@ class PaymentService { } ``` -## Direct Access - -You can also access the underlying `WalletConnectPay` instance directly if needed: - -```dart -final payClient = walletKit.pay; -// Use payClient methods directly -final response = await payClient.getPaymentOptions(request: request); -``` - ## API Reference -### ReownWalletKit Pay Methods +**ReownWalletKit Pay Methods** | Method | Description | |--------|-------------| @@ -482,9 +510,9 @@ final response = await payClient.getPaymentOptions(request: request); | `confirmPayment({required ConfirmPaymentRequest request})` | Confirm and finalize payment | | `pay` | Access the underlying WalletConnectPay instance | -### Models +**Models** -#### GetPaymentOptionsRequest +**GetPaymentOptionsRequest** ```dart GetPaymentOptionsRequest({ @@ -494,7 +522,7 @@ GetPaymentOptionsRequest({ }) ``` -#### PaymentOptionsResponse +**PaymentOptionsResponse** ```dart PaymentOptionsResponse({ @@ -506,7 +534,7 @@ PaymentOptionsResponse({ }) ``` -#### PaymentResultInfo +**PaymentResultInfo** ```dart class PaymentResultInfo { @@ -515,7 +543,7 @@ class PaymentResultInfo { } ``` -#### PaymentInfo +**PaymentInfo** ```dart PaymentInfo({ @@ -527,7 +555,7 @@ PaymentInfo({ }) ``` -#### PaymentOption +**PaymentOption** ```dart PaymentOption({ @@ -536,10 +564,11 @@ PaymentOption({ required PayAmount amount, @JsonKey(name: 'etaS') required int etaSeconds, required List actions, + CollectDataAction? collectData, // Per-option data collection (null if not required) }) ``` -#### ConfirmPaymentRequest +**ConfirmPaymentRequest** ```dart ConfirmPaymentRequest({ @@ -550,7 +579,7 @@ ConfirmPaymentRequest({ }) ``` -#### ConfirmPaymentResponse +**ConfirmPaymentResponse** ```dart ConfirmPaymentResponse({ @@ -561,7 +590,7 @@ ConfirmPaymentResponse({ }) ``` -#### PaymentStatus +**PaymentStatus** ```dart enum PaymentStatus { @@ -574,7 +603,7 @@ enum PaymentStatus { } ``` -#### CollectDataAction +**CollectDataAction** ```dart class CollectDataAction { @@ -611,7 +640,7 @@ All errors include: - `details`: Additional error details - `stacktrace`: Stack trace -### Example Error Handling +**Example Error Handling** ```dart try { @@ -647,16 +676,5 @@ try { 9. **User Data**: Only collect data when `collectData` is present in the response and you don't already have the required user data. If you already have the required data, you can submit this without collecting from the user. You must make sure the user accepts WalletConnect Terms and Conditions and Privacy Policy before submitting user information to WalletConnect. -10. **WebView Data Collection**: When `collectData.url` is present, display the URL in a WebView using `webview_flutter` rather than building native forms. The WebView handles form rendering, validation, and T&C acceptance. - -## Examples - -For a complete example implementation with UI components showing the full payment flow, see the [reown_walletkit example](https://github.com/reown-com/reown_flutter/tree/master/packages/reown_walletkit/example/lib/walletconnect_pay). +10. **WebView Data Collection**: When `selectedOption.collectData.url` is present, display the URL in a WebView using `webview_flutter` rather than building native forms. The WebView handles form rendering, validation, and T&C acceptance. -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 diff --git a/payments/wallets/walletkit/kotlin.mdx b/payments/wallets/walletkit/kotlin.mdx index 5a1b76e..55627e0 100644 --- a/payments/wallets/walletkit/kotlin.mdx +++ b/payments/wallets/walletkit/kotlin.mdx @@ -206,7 +206,7 @@ viewModelScope.launch { -Some payments require additional user information. After the user selects an option, check for `collectData` on that option and complete data collection **before** fetching the required actions — the backend rejects the actions request if data collection is required but not yet captured: +After the user selects an option, check for `collectData` on it. If present, collect the data **before** fetching the required actions. @@ -222,12 +222,17 @@ selectedOption.collectData?.let { collectAction -> put("dob", "1990-01-15") put("pobAddress", "123 Main St, New York, NY 10001") }.toString() + // Encode prefill as base64url (URL-safe, no padding) val prefillBase64 = Base64.encodeToString( prefillJson.toByteArray(), - Base64.NO_WRAP or Base64.URL_SAFE + Base64.NO_WRAP or Base64.URL_SAFE or Base64.NO_PADDING ) val webViewUrl = Uri.parse(url).buildUpon() .appendQueryParameter("prefill", prefillBase64) + // Optional appearance params (see "Form URL parameters"): + .appendQueryParameter("theme", "dark") // "light" | "dark" + // themeVariables is a base64url string exported from the Pay Dashboard: + // .appendQueryParameter("themeVariables", themeVariables) .build().toString() // Show WebView for this specific option and wait for IC_COMPLETE message @@ -238,10 +243,6 @@ selectedOption.collectData?.let { collectAction -> - -The WebView submits the collected data directly to the backend. You only receive an `IC_COMPLETE`/`IC_ERROR` signal — you do **not** pass `collectedData` to `confirmPayment`. - - @@ -377,9 +378,11 @@ confirmResult.onSuccess { response -> -## WebView Implementation +## Data Collection Implementation + +When `selectedOption.collectData.url` is present, display the URL in a WebView. The WebView handles form rendering, validation, and T&C acceptance. -When a selected option has `collectData.url` present, display the URL in a WebView. The WebView handles form rendering, validation, and T&C acceptance. + ```kotlin import android.webkit.JavascriptInterface @@ -549,11 +552,11 @@ class PaymentViewModel : ViewModel() { ## API Reference -### WalletKit.Pay +**WalletKit.Pay** The payment operations object within WalletKit. -#### Methods +**Methods** | Method | Description | |--------|-------------| @@ -562,9 +565,9 @@ The payment operations object within WalletKit. | `getRequiredPaymentActions(params)` | Get actions requiring signatures | | `confirmPayment(params)` | Confirm and finalize payment | -### Parameters +**Parameters** -#### Wallet.Params.RequiredPaymentActions +**Wallet.Params.RequiredPaymentActions** ```kotlin data class RequiredPaymentActions( @@ -573,7 +576,7 @@ data class RequiredPaymentActions( ) ``` -#### Wallet.Params.ConfirmPayment +**Wallet.Params.ConfirmPayment** ```kotlin data class ConfirmPayment( @@ -585,7 +588,7 @@ data class ConfirmPayment( ## Data Models -### Wallet.Model.PaymentOptionsResponse +**Wallet.Model.PaymentOptionsResponse** ```kotlin data class PaymentOptionsResponse( @@ -602,7 +605,7 @@ data class PaymentResultInfo( ) ``` -### Wallet.Model.PaymentInfo +**Wallet.Model.PaymentInfo** ```kotlin data class PaymentInfo( @@ -618,7 +621,7 @@ data class MerchantInfo( ) ``` -### Wallet.Model.PaymentOption +**Wallet.Model.PaymentOption** ```kotlin data class PaymentOption( @@ -630,7 +633,7 @@ data class PaymentOption( ) ``` -### Wallet.Model.PaymentAmount +**Wallet.Model.PaymentAmount** ```kotlin data class PaymentAmount( @@ -649,7 +652,7 @@ data class PaymentAmountDisplay( ) ``` -### Wallet.Model.WalletRpcAction +**Wallet.Model.WalletRpcAction** ```kotlin data class WalletRpcAction( @@ -659,7 +662,7 @@ data class WalletRpcAction( ) ``` -### Wallet.Model.CollectDataAction +**Wallet.Model.CollectDataAction** ```kotlin data class CollectDataAction( @@ -668,7 +671,7 @@ data class CollectDataAction( ) ``` -### Wallet.Model.ConfirmPaymentResponse +**Wallet.Model.ConfirmPaymentResponse** ```kotlin data class ConfirmPaymentResponse( @@ -679,7 +682,7 @@ data class ConfirmPaymentResponse( ) ``` -### Wallet.Model.PaymentStatus +**Wallet.Model.PaymentStatus** | Status | Description | |--------|-------------| diff --git a/payments/wallets/walletkit/react-native.mdx b/payments/wallets/walletkit/react-native.mdx index 235ca73..c37356b 100644 --- a/payments/wallets/walletkit/react-native.mdx +++ b/payments/wallets/walletkit/react-native.mdx @@ -168,9 +168,11 @@ if (options.info) { console.log("Amount:", options.info.amount.display.assetSymbol, options.info.amount.value); } -// Check if data collection is required -if (options.collectData) { - console.log("Required fields:", options.collectData.fields); +// Check which options require data collection (per-option) +for (const option of options.options) { + if (option.collectData) { + console.log(`Option ${option.id} requires info capture`); + } } ``` @@ -178,21 +180,29 @@ if (options.collectData) { -Some payments may require additional user data. If the selected option requires data collection, complete it **before** fetching the required actions — the backend rejects the actions request unless the information has been captured first: +After the user selects an option, check for `collectData` on it. If present, collect the data **before** fetching the required actions. ```javascript -if (options.collectData?.url) { - // Use the "required" list from options.collectData.schema to determine which fields to prefill +if (selectedOption.collectData?.url) { + // Use the "required" list from selectedOption.collectData.schema to determine which fields to prefill const prefillData = { fullName: "John Doe", dob: "1990-01-15", pobAddress: "123 Main St, New York, NY 10001", }; - const prefillBase64 = btoa(JSON.stringify(prefillData)); - const separator = options.collectData.url.includes("?") ? "&" : "?"; - const webViewUrl = `${options.collectData.url}${separator}prefill=${prefillBase64}`; + // Encode prefill as base64url (URL-safe, no padding) + const prefill = btoa(JSON.stringify(prefillData)) + .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); + + // Optional appearance params (see "Form URL parameters"): + // theme=light|dark — base color mode + // themeVariables= — exported from the WalletConnect Pay Dashboard + const themeVariables = ""; + const query = `prefill=${prefill}&theme=dark&themeVariables=${themeVariables}`; + const separator = selectedOption.collectData.url.includes("?") ? "&" : "?"; + const webViewUrl = `${selectedOption.collectData.url}${separator}${query}`; // Show WebView and wait for IC_COMPLETE message showDataCollectionWebView(webViewUrl); @@ -289,14 +299,16 @@ if (result.status === "succeeded") { -## WebView Implementation +## Data Collection Implementation -When `collectData.url` is present, display the URL in a WebView using `react-native-webview`. Install the dependency: +When `selectedOption.collectData.url` is present, display the URL in a WebView using `react-native-webview`. Install the dependency: ```bash npm install react-native-webview@13.16.0 ``` + + ```jsx import React, { useCallback } from "react"; import { WebView } from "react-native-webview"; @@ -347,11 +359,19 @@ function PayDataCollectionWebView({ url, onComplete, onError }) { ); } -function buildPrefillUrl(baseUrl, prefillData) { - if (Object.keys(prefillData).length === 0) return baseUrl; - const base64 = btoa(JSON.stringify(prefillData)); +function buildFormUrl(baseUrl, options = {}) { + const params = []; + if (options.prefill && Object.keys(options.prefill).length > 0) { + // base64url: URL-safe, no padding + const prefill = btoa(JSON.stringify(options.prefill)) + .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); + params.push(`prefill=${prefill}`); + } + if (options.theme) params.push(`theme=${options.theme}`); + if (options.themeVariables) params.push(`themeVariables=${options.themeVariables}`); + if (params.length === 0) return baseUrl; const separator = baseUrl.includes("?") ? "&" : "?"; - return `${baseUrl}${separator}prefill=${base64}`; + return `${baseUrl}${separator}${params.join("&")}`; } ``` @@ -414,8 +434,8 @@ class PaymentManager { // Step 3: Collect data via WebView if required — must happen BEFORE // fetching the required actions, otherwise the backend rejects the request - if (options.collectData?.url) { - await this.showDataCollectionWebView(options.collectData.url); + if (selectedOption.collectData?.url) { + await this.showDataCollectionWebView(selectedOption.collectData.url); } // Step 4: Get required actions @@ -463,17 +483,17 @@ class PaymentManager { ## API Reference -### WalletKit Pay Methods +**WalletKit Pay Methods** Pay methods are accessed via `walletkit.pay.*`. -#### Utility Functions +**Utility Functions** | Function | Description | |----------|-------------| | `isPaymentLink(uri: string): boolean` | Check if URI is a payment link (imported from `@reown/walletkit`) | -#### Instance Methods (walletkit.pay) +**Instance Methods (walletkit.pay)** | Method | Description | |--------|-------------| @@ -481,9 +501,9 @@ Pay methods are accessed via `walletkit.pay.*`. | `getRequiredPaymentActions(params)` | Get signing actions for a payment option | | `confirmPayment(params)` | Confirm and execute the payment | -### Parameters +**Parameters** -#### GetPaymentOptionsParams +**GetPaymentOptionsParams** ```typescript interface GetPaymentOptionsParams { @@ -493,7 +513,7 @@ interface GetPaymentOptionsParams { } ``` -#### GetRequiredPaymentActionsParams +**GetRequiredPaymentActionsParams** ```typescript interface GetRequiredPaymentActionsParams { @@ -502,7 +522,7 @@ interface GetRequiredPaymentActionsParams { } ``` -#### ConfirmPaymentParams +**ConfirmPaymentParams** ```typescript interface ConfirmPaymentParams { @@ -512,9 +532,9 @@ interface ConfirmPaymentParams { } ``` -### Response Types +**Response Types** -#### PaymentOptionsResponse +**PaymentOptionsResponse** ```typescript interface PaymentOptionsResponse { @@ -531,7 +551,7 @@ interface PaymentResultInfo { } ``` -#### PaymentOption +**PaymentOption** ```typescript interface PaymentOption { @@ -539,10 +559,11 @@ interface PaymentOption { amount: PayAmount; // Amount in this asset etaS: number; // Estimated time to complete (seconds) actions: Action[]; // Required signing actions + collectData?: CollectDataAction; // Per-option data collection (undefined if not required) } ``` -#### Action +**Action** ```typescript interface Action { @@ -556,7 +577,7 @@ interface WalletRpcAction { } ``` -#### ConfirmPaymentResponse +**ConfirmPaymentResponse** ```typescript interface ConfirmPaymentResponse { @@ -575,7 +596,7 @@ type PaymentStatus = | "cancelled"; ``` -#### PaymentInfo +**PaymentInfo** ```typescript interface PaymentInfo { @@ -592,7 +613,7 @@ interface MerchantInfo { } ``` -#### PayAmount +**PayAmount** ```typescript interface PayAmount { @@ -610,7 +631,7 @@ interface AmountDisplay { } ``` -#### CollectDataAction +**CollectDataAction** ```typescript interface CollectDataAction { @@ -662,4 +683,4 @@ try { 9. **User Data**: Only collect data when `collectData` is present in the response and you don't already have the required user data. If you already have the required data, you can submit this without collecting from the user. You must make sure the user accepts WalletConnect Terms and Conditions and Privacy Policy before submitting user information to WalletConnect. -10. **WebView Data Collection**: When `collectData.url` is present, display the URL in a WebView using `react-native-webview` rather than building native forms. The WebView handles form rendering, validation, and T&C acceptance. +10. **WebView Data Collection**: When `selectedOption.collectData.url` is present, display the URL in a WebView using `react-native-webview` rather than building native forms. The WebView handles form rendering, validation, and T&C acceptance. diff --git a/payments/wallets/walletkit/swift.mdx b/payments/wallets/walletkit/swift.mdx index 95899a3..d892ebb 100644 --- a/payments/wallets/walletkit/swift.mdx +++ b/payments/wallets/walletkit/swift.mdx @@ -34,7 +34,7 @@ For a complete working example, check out our sample wallet implementation: ## Installation -### Swift Package Manager +**Swift Package Manager** Add ReownWalletKit to your `Package.swift`: @@ -59,7 +59,7 @@ WalletConnectPay is automatically included as a dependency of WalletKit. Check the [GitHub releases](https://github.com/reown-com/reown-swift/releases) for the latest version. -## Configuration +## Initialization When using WalletKit, Pay is automatically configured using your project's `Networking.projectId`. No separate configuration is needed. @@ -102,6 +102,25 @@ The `isPaymentLink` utility method detects WalletConnect Pay links by checking f - `pay=` parameter in WalletConnect URIs - `pay_` prefix in bare payment IDs +Call it wherever your wallet receives a link — from a deep link or a scanned QR code: + +```swift +// Deep link opened from outside your app (SceneDelegate or AppDelegate) +func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + guard let url = URLContexts.first?.url else { return } + if WalletKit.isPaymentLink(url.absoluteString) { + startPaymentFlow(paymentLink: url.absoluteString) + } +} + +// QR code payload +func handleScannedQR(_ content: String) { + if WalletKit.isPaymentLink(content) { + startPaymentFlow(paymentLink: content) + } +} +``` + ## Payment Flow The payment flow consists of six main steps: @@ -185,7 +204,7 @@ for option in options.options { -If the selected option has `collectData` set, you must collect user information **before** fetching the required actions. The backend requires Information Capture to be completed first and will reject `getRequiredPaymentActions` otherwise: +After the user selects an option, check for `collectData` on it. If present, collect the data **before** fetching the required actions. @@ -199,10 +218,18 @@ if let collectData = selectedOption.collectData, let url = collectData.url { "pobAddress": "123 Main St, New York, NY 10001" ] let jsonData = try JSONSerialization.data(withJSONObject: prefillData) + // Encode prefill as base64url (URL-safe, no padding) let prefillBase64 = jsonData.base64EncodedString() + .replacingOccurrences(of: "+", with: "-") + .replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: "=", with: "") var components = URLComponents(string: url)! var queryItems = components.queryItems ?? [] queryItems.append(URLQueryItem(name: "prefill", value: prefillBase64)) + // Optional appearance params (see "Form URL parameters"): + queryItems.append(URLQueryItem(name: "theme", value: "dark")) // "light" | "dark" + // themeVariables is a base64url string exported from the WalletConnect Pay Dashboard: + // queryItems.append(URLQueryItem(name: "themeVariables", value: themeVariables)) components.queryItems = queryItems let webViewUrl = components.string! @@ -213,10 +240,6 @@ if let collectData = selectedOption.collectData, let url = collectData.url { - -The WebView submits the captured data directly to the backend. You do **not** pass `collectedData` to `confirmPayment` when using the WebView approach. - - @@ -342,9 +365,11 @@ case .cancelled: -## WebView Implementation +## Data Collection Implementation -When a selected option has `collectData.url` present, display the URL in a `WKWebView`. The WebView handles form rendering, validation, and T&C acceptance. +When `selectedOption.collectData.url` is present, display the URL in a `WKWebView`. The WebView handles form rendering, validation, and T&C acceptance. + + ```swift import WebKit @@ -534,48 +559,19 @@ class PaymentManager { } ``` -## Deep Link Handling - -To handle payment links opened from outside your app: - -```swift -// In SceneDelegate or AppDelegate -func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { - guard let url = URLContexts.first?.url else { return } - - // Use isPaymentLink for reliable detection - if WalletKit.isPaymentLink(url.absoluteString) { - startPaymentFlow(paymentLink: url.absoluteString) - } -} -``` - -## QR Code Scanning - -Payment links can be encoded as QR codes. Use the `isPaymentLink` utility for detection: - -```swift -func handleScannedQR(_ content: String) { - if WalletKit.isPaymentLink(content) { - // WalletConnect Pay QR code - startPaymentFlow(paymentLink: content) - } -} -``` - ## API Reference -### WalletKit Integration +**WalletKit Integration** When using WalletKit, Pay methods are accessed via `WalletKit.instance.Pay.*`. -#### Static Methods +**Static Methods** | Method | Description | |--------|-------------| | `WalletKit.isPaymentLink(_:)` | Check if a string is a payment link (can call before `configure()`) | -#### Instance Methods (WalletKit.instance.Pay) +**Instance Methods (WalletKit.instance.Pay)** | Method | Description | |--------|-------------| @@ -584,9 +580,9 @@ When using WalletKit, Pay methods are accessed via `WalletKit.instance.Pay.*`. | `getRequiredPaymentActions(paymentId:optionId:)` | Get signing actions for a payment option | | `confirmPayment(paymentId:optionId:signatures:maxPollMs:)` | Confirm and execute the payment | -### Data Types +**Data Types** -#### PaymentOptionsResponse +**PaymentOptionsResponse** ```swift struct PaymentOptionsResponse { @@ -603,7 +599,7 @@ struct PaymentResultInfo { } ``` -#### PaymentInfo +**PaymentInfo** ```swift struct PaymentInfo { @@ -615,7 +611,7 @@ struct PaymentInfo { } ``` -#### PaymentOption +**PaymentOption** ```swift struct PaymentOption { @@ -627,7 +623,7 @@ struct PaymentOption { } ``` -#### PayAmount +**PayAmount** ```swift struct PayAmount { @@ -645,7 +641,7 @@ struct AmountDisplay { } ``` -#### Action & WalletRpcAction +**Action & WalletRpcAction** ```swift struct Action { @@ -659,7 +655,7 @@ struct WalletRpcAction { } ``` -#### CollectDataAction +**CollectDataAction** ```swift struct CollectDataAction { @@ -668,7 +664,7 @@ struct CollectDataAction { } ``` -#### ConfirmPaymentResultResponse +**ConfirmPaymentResultResponse** ```swift struct ConfirmPaymentResultResponse { @@ -679,7 +675,7 @@ struct ConfirmPaymentResultResponse { } ``` -#### PaymentStatus +**PaymentStatus** ```swift enum PaymentStatus { @@ -696,7 +692,7 @@ enum PaymentStatus { The SDK throws specific error types for different failure scenarios: -### GetPaymentOptionsError +**GetPaymentOptionsError** | Error | Description | |-------|-------------| @@ -708,7 +704,7 @@ The SDK throws specific error types for different failure scenarios: | `.http` | Network error | | `.internalError` | Server error | -### GetPaymentRequestError +**GetPaymentRequestError** | Error | Description | |-------|-------------| @@ -717,7 +713,7 @@ The SDK throws specific error types for different failure scenarios: | `.invalidAccount` | Invalid account format | | `.http` | Network error | -### ConfirmPaymentError +**ConfirmPaymentError** | Error | Description | |-------|-------------| diff --git a/payments/wallets/walletkit/web.mdx b/payments/wallets/walletkit/web.mdx index 32f494f..608151a 100644 --- a/payments/wallets/walletkit/web.mdx +++ b/payments/wallets/walletkit/web.mdx @@ -5,6 +5,8 @@ sidebarTitle: "Web/Node.js" --- import AppIdSnippet from "/snippets/app-id.mdx"; +import WebViewOverview from "/snippets/webview-data-collection-overview.mdx"; +import WebViewBestPractices from "/snippets/webview-data-collection-best-practices.mdx"; This documentation covers integrating WalletConnect Pay through WalletKit for Web/Node.js wallets. This approach provides a unified API where Pay is built into WalletKit, simplifying the integration for wallet developers. @@ -165,9 +167,11 @@ if (options.info) { console.log("Amount:", options.info.amount.display.assetSymbol, options.info.amount.value); } -// Check if data collection is required -if (options.collectData) { - console.log("Required fields:", options.collectData.fields); +// Check which options require data collection (per-option) +for (const option of options.options) { + if (option.collectData) { + console.log(`Option ${option.id} requires info capture`); + } } ``` @@ -175,55 +179,29 @@ if (options.collectData) { -After the user selects an option — and **before** fetching required actions — check whether the selected option requires data collection. If it does, the data must be captured first, otherwise fetching the required actions will fail. - -## Iframe-Based Data Collection - -When a payment requires user information (e.g., for Travel Rule compliance), the SDK returns a `collectData` field on individual payment options. Each option may independently require data collection — some options may require it while others don't. - -### Recommended Flow (Per-Option) - -The recommended approach is to display all payment options upfront, then handle data collection only when the user selects an option that requires it: - -1. Call `getPaymentOptions` and display all available options to the user -2. Show a visual indicator (e.g., "Info required" badge) on options where `option.collectData` is present -3. When the user selects an option, check `selectedOption.collectData` -4. If present, open `selectedOption.collectData.url` in an iframe within your wallet -5. Optionally append a `prefill=` query parameter with known user data (e.g., name, date of birth, address). Use proper URL building to handle existing query parameters. -6. Listen for `postMessage` events: `IC_COMPLETE` (success) or `IC_ERROR` (failure) -7. On `IC_COMPLETE`, proceed to `getRequiredPaymentActions()` and then `confirmPayment()` **without** passing `collectedData` — the iframe submits data directly to the backend - -### Decision Matrix - -| Response `collectData` | `option.collectData` | Behavior | -|---|---|---| -| present | present | Option requires IC — use `option.collectData.url` | -| present | `null` | Option does NOT require IC (others might) — skip IC for this option | -| `null` | `null` | No IC needed for any option | - - -The `collectData` also includes a `schema` field — a JSON schema string describing the required fields. The `required` list in this schema tells you which fields the form expects. Wallets can use these field names as keys when building the prefill JSON object. For example, if the schema's `required` array contains `["fullName", "dob", "pobAddress"]`, you can prefill with `{"fullName": "...", "dob": "...", "pobAddress": "..."}`. - +After the user selects an option, check for `collectData` on it. If present, collect the data **before** fetching the required actions. - -The top-level `collectData` on the payment options response is still available for backward compatibility. However, the per-option `collectData` is the recommended approach as it provides more granular control over the flow. - - - -When using the iframe approach, do **not** pass `collectedData` to `confirmPayment()`. The iframe handles data submission directly. - + ```javascript -if (options.collectData?.url) { - // Use the "required" list from options.collectData.schema to determine which fields to prefill +if (selectedOption.collectData?.url) { + // Use the "required" list from selectedOption.collectData.schema to determine which fields to prefill const prefillData = { fullName: "John Doe", dob: "1990-01-15", pobAddress: "123 Main St, New York, NY 10001", }; - const prefillBase64 = btoa(JSON.stringify(prefillData)); - const separator = options.collectData.url.includes("?") ? "&" : "?"; - const iframeUrl = `${options.collectData.url}${separator}prefill=${prefillBase64}`; + // Encode prefill as base64url (URL-safe, no padding) + const prefill = btoa(JSON.stringify(prefillData)) + .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); + + // Optional appearance params (see "Form URL parameters"): + // theme=light|dark — base color mode + // themeVariables= — exported from the WalletConnect Pay Dashboard + const themeVariables = ""; + const query = `prefill=${prefill}&theme=dark&themeVariables=${themeVariables}`; + const separator = selectedOption.collectData.url.includes("?") ? "&" : "?"; + const iframeUrl = `${selectedOption.collectData.url}${separator}${query}`; // Show data collection in an iframe or popup and wait for IC_COMPLETE message showDataCollectionView(iframeUrl); @@ -329,7 +307,9 @@ if (result.status === "succeeded") { ## Data Collection Implementation -When `collectData.url` is present, display the URL in an iframe or modal. Listen for `postMessage` events to handle completion: +When `selectedOption.collectData.url` is present, display the URL in an iframe or modal. Listen for `postMessage` events to handle completion: + + ```javascript function showDataCollectionView(url) { @@ -370,11 +350,19 @@ function showDataCollectionView(url) { }); } -function buildPrefillUrl(baseUrl, prefillData) { - if (Object.keys(prefillData).length === 0) return baseUrl; - const base64 = btoa(JSON.stringify(prefillData)); +function buildFormUrl(baseUrl, options = {}) { + const params = []; + if (options.prefill && Object.keys(options.prefill).length > 0) { + // base64url: URL-safe, no padding + const prefill = btoa(JSON.stringify(options.prefill)) + .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); + params.push(`prefill=${prefill}`); + } + if (options.theme) params.push(`theme=${options.theme}`); + if (options.themeVariables) params.push(`themeVariables=${options.themeVariables}`); + if (params.length === 0) return baseUrl; const separator = baseUrl.includes("?") ? "&" : "?"; - return `${baseUrl}${separator}prefill=${base64}`; + return `${baseUrl}${separator}${params.join("&")}`; } ``` @@ -435,11 +423,11 @@ class PaymentManager { // Step 2: Let user select an option (simplified - use first option) const selectedOption = options.options[0]; - // Step 3: Collect data via iframe if required. + // Step 3: Collect data via iframe if required for selected option. // This must happen BEFORE fetching required actions, otherwise the // backend rejects the fetch for options that require info capture. - if (options.collectData?.url) { - await this.showDataCollectionView(options.collectData.url); + if (selectedOption.collectData?.url) { + await this.showDataCollectionView(selectedOption.collectData.url); } // Step 4: Get required actions @@ -487,17 +475,17 @@ class PaymentManager { ## API Reference -### WalletKit Pay Methods +**WalletKit Pay Methods** Pay methods are accessed via `walletkit.pay.*`. -#### Utility Functions +**Utility Functions** | Function | Description | |----------|-------------| | `isPaymentLink(uri: string): boolean` | Check if URI is a payment link (imported from `@reown/walletkit`) | -#### Instance Methods (walletkit.pay) +**Instance Methods (walletkit.pay)** | Method | Description | |--------|-------------| @@ -505,9 +493,9 @@ Pay methods are accessed via `walletkit.pay.*`. | `getRequiredPaymentActions(params)` | Get signing actions for a payment option | | `confirmPayment(params)` | Confirm and execute the payment | -### Parameters +**Parameters** -#### GetPaymentOptionsParams +**GetPaymentOptionsParams** ```typescript interface GetPaymentOptionsParams { @@ -517,7 +505,7 @@ interface GetPaymentOptionsParams { } ``` -#### GetRequiredPaymentActionsParams +**GetRequiredPaymentActionsParams** ```typescript interface GetRequiredPaymentActionsParams { @@ -526,7 +514,7 @@ interface GetRequiredPaymentActionsParams { } ``` -#### ConfirmPaymentParams +**ConfirmPaymentParams** ```typescript interface ConfirmPaymentParams { @@ -536,9 +524,9 @@ interface ConfirmPaymentParams { } ``` -### Response Types +**Response Types** -#### PaymentOptionsResponse +**PaymentOptionsResponse** ```typescript interface PaymentOptionsResponse { @@ -555,7 +543,7 @@ interface PaymentResultInfo { } ``` -#### PaymentOption +**PaymentOption** ```typescript interface PaymentOption { @@ -563,10 +551,11 @@ interface PaymentOption { amount: PayAmount; // Amount in this asset etaS: number; // Estimated time to complete (seconds) actions: Action[]; // Required signing actions + collectData?: CollectDataAction; // Per-option data collection (undefined if not required) } ``` -#### Action +**Action** ```typescript interface Action { @@ -580,7 +569,7 @@ interface WalletRpcAction { } ``` -#### ConfirmPaymentResponse +**ConfirmPaymentResponse** ```typescript interface ConfirmPaymentResponse { @@ -599,7 +588,7 @@ type PaymentStatus = | "cancelled"; ``` -#### PaymentInfo +**PaymentInfo** ```typescript interface PaymentInfo { @@ -616,7 +605,7 @@ interface MerchantInfo { } ``` -#### PayAmount +**PayAmount** ```typescript interface PayAmount { @@ -634,7 +623,7 @@ interface AmountDisplay { } ``` -#### CollectDataAction +**CollectDataAction** ```typescript interface CollectDataAction { @@ -686,4 +675,4 @@ try { 9. **User Data**: Only collect data when `collectData` is present in the response and you don't already have the required user data. If you already have the required data, you can submit this without collecting from the user. You must make sure the user accepts WalletConnect Terms and Conditions and Privacy Policy before submitting user information to WalletConnect. -10. **Data Collection**: When `collectData.url` is present, display the URL in an iframe or modal rather than building custom forms. The hosted form handles rendering, validation, and T&C acceptance. +10. **Data Collection**: When `selectedOption.collectData.url` is present, display the URL in an iframe or modal rather than building custom forms. The hosted form handles rendering, validation, and T&C acceptance. diff --git a/snippets/app-id.mdx b/snippets/app-id.mdx index a42241b..e9217d9 100644 --- a/snippets/app-id.mdx +++ b/snippets/app-id.mdx @@ -1,8 +1,6 @@ -## Pre-Requisites +You also need a WCP ID for your project, obtained from the [WalletConnect Dashboard](https://dashboard.walletconnect.com). -In order to use your WalletConnect Pay, you need to obtain a WCP ID for your project from the [WalletConnect Dashboard](https://dashboard.walletconnect.com). - -### How to obtain a WCP ID +**How to obtain a WCP ID** 1. Navigate to the [WalletConnect Dashboard](https://dashboard.walletconnect.com). 2. Select the project that is associated with your wallet (as in, the projectId that is being used for your wallet's WalletConnect integration). diff --git a/snippets/webview-data-collection-best-practices.mdx b/snippets/webview-data-collection-best-practices.mdx index 6841e5f..2d8652d 100644 --- a/snippets/webview-data-collection-best-practices.mdx +++ b/snippets/webview-data-collection-best-practices.mdx @@ -1,9 +1,10 @@ -### WebView Data Collection Best Practices +**Data Collection Best Practices** -- **Display prominently**: Show the WebView full-screen or as a prominent modal so users can interact with the form easily -- **Loading indicator**: Show a loading indicator while the WebView page loads +- **Display prominently**: Show the form full-screen or as a prominent modal so users can interact with it easily +- **Loading indicator**: Show a loading indicator while the form loads - **Handle errors**: Listen for `IC_ERROR` messages and display a user-facing error message with an option to retry -- **External links**: Open Terms & Conditions and Privacy Policy links in the system browser rather than navigating within the WebView +- **External links**: Open Terms & Conditions and Privacy Policy links in the system browser rather than navigating within the form - **Domain restriction**: Only allow navigation to WalletConnect pay domains and HTTPS URLs -- **Back navigation**: Handle back/dismiss gracefully — confirm cancellation with the user before closing the WebView mid-flow -- **Keyboard behavior**: Test that the soft keyboard appears and behaves correctly when users tap on form inputs within the WebView +- **Back navigation**: Handle back/dismiss gracefully — confirm cancellation with the user before closing the form mid-flow +- **Keyboard behavior**: Test that the soft keyboard appears and behaves correctly when users tap on form inputs +- **Theme to match your brand**: Pass `theme=light` or `theme=dark` to match your wallet's active color mode, and apply brand tokens with `themeVariables` exported from the [WalletConnect Pay Dashboard](https://dashboard.walletconnect.com). See [Form URL parameters](#form-url-parameters). diff --git a/snippets/webview-data-collection-overview.mdx b/snippets/webview-data-collection-overview.mdx index cc903b4..c4bdc45 100644 --- a/snippets/webview-data-collection-overview.mdx +++ b/snippets/webview-data-collection-overview.mdx @@ -1,18 +1,22 @@ -## WebView-Based Data Collection +## Embedded Data Collection Form When a payment requires user information (e.g., for Travel Rule compliance), the SDK returns a `collectData` field on individual payment options. Each option may independently require data collection — some options may require it while others don't. -### Recommended Flow (Per-Option) +The form is loaded from `selectedOption.collectData.url` and embedded in your wallet (a WebView on mobile, an iframe on web). It handles field rendering, validation, Terms & Conditions and Privacy Policy acceptance, and submits data directly to the backend. + +Collect this data **before** fetching the required actions. For an option that requires Information Capture, `getRequiredPaymentActions` fails with `400 IC data required` until the data has been submitted. + +### Recommended Flow The recommended approach is to display all payment options upfront, then handle data collection only when the user selects an option that requires it: 1. Call `getPaymentOptions` and display all available options to the user 2. Show a visual indicator (e.g., "Info required" badge) on options where `option.collectData` is present 3. When the user selects an option, check `selectedOption.collectData` -4. If present, open `selectedOption.collectData.url` in a WebView within your wallet -5. Optionally append a `prefill=` query parameter with known user data (e.g., name, date of birth, address). Use proper URL building to handle existing query parameters. -6. Listen for JS bridge messages: `IC_COMPLETE` (success) or `IC_ERROR` (failure) -7. On `IC_COMPLETE`, proceed to `confirmPayment()` **without** passing `collectedData` — the WebView submits data directly to the backend +4. If present, load `selectedOption.collectData.url` in the embedded form +5. Optionally append query parameters to the form URL — `prefill` (known user data), `theme`, and `themeVariables` (appearance). See [Form URL parameters](#form-url-parameters) below. Use proper URL building so existing query parameters are preserved. +6. Listen for completion messages: `IC_COMPLETE` (success) or `IC_ERROR` (failure) +7. On `IC_COMPLETE`, continue the flow — fetch the required actions, sign, and confirm the payment. Don't pass `collectedData` to `confirmPayment()`; the form submits data directly to the backend ### Decision Matrix @@ -22,14 +26,31 @@ The recommended approach is to display all payment options upfront, then handle | present | `null` | Option does NOT require IC (others might) — skip IC for this option | | `null` | `null` | No IC needed for any option | +### Form URL parameters + +The form URL accepts the following optional query parameters. Append them to `selectedOption.collectData.url` before loading it, preserving any existing query parameters. + +| Parameter | Format | Description | +|---|---|---| +| `prefill` | base64url-encoded JSON | Pre-populates known user fields so the user doesn't re-enter them. Keys must match the `required` fields from `collectData.schema` (e.g. `fullName`, `dob`, `pobAddress`). | +| `theme` | `light` or `dark` | Sets the form's base color mode. | +| `themeVariables` | base64url-encoded JSON | Overrides design tokens to match your brand — font, font size, select colors, button border radius, and input border radius. Generate and export this value from the [WalletConnect Pay Dashboard](https://dashboard.walletconnect.com). | + -The `collectData` also includes a `schema` field — a JSON schema string describing the required fields. The `required` list in this schema tells you which fields the form expects. Wallets can use these field names as keys when building the prefill JSON object. For example, if the schema's `required` array contains `["fullName", "dob", "pobAddress"]`, you can prefill with `{"fullName": "...", "dob": "...", "pobAddress": "..."}`. +`collectData.schema` is a JSON schema **string** — parse it and read its `required` array to discover the field keys for `prefill`. For example, a `required` array of `["fullName", "dob", "pobAddress"]` maps to a prefill object of `{"fullName": "...", "dob": "...", "pobAddress": "..."}`. +#### Customizing the form appearance + +`theme` and `themeVariables` are optional and independent — pass either, both, or neither: + +- **`theme`** switches the form between `light` and `dark` base color modes. Match it to your wallet's active mode for a seamless transition. +- **`themeVariables`** applies brand-level overrides (font, font size, select colors, button border radius, and input border radius). Generate the theme in the [WalletConnect Pay Dashboard](https://dashboard.walletconnect.com), export it as a base64url string, and append it to the form URL verbatim — you don't need to encode it at runtime. + The top-level `collectData` on the payment options response is still available for backward compatibility. However, the per-option `collectData` is the recommended approach as it provides more granular control over the flow. -When using the WebView approach, do **not** pass `collectedData` to `confirmPayment()`. The WebView handles data submission directly. +Do **not** pass `collectedData` to `confirmPayment()` when using the embedded form. The form handles data submission directly. diff --git a/snippets/webview-message-types.mdx b/snippets/webview-message-types.mdx index d143d55..4f4cfe0 100644 --- a/snippets/webview-message-types.mdx +++ b/snippets/webview-message-types.mdx @@ -7,7 +7,7 @@ The WebView communicates with your wallet through JavaScript bridge messages. Th | `IC_COMPLETE` | `{ "type": "IC_COMPLETE", "success": true }` | User completed the form successfully. Proceed to payment confirmation. | | `IC_ERROR` | `{ "type": "IC_ERROR", "error": "..." }` | An error occurred. Display the error message and allow the user to retry. | -#### Platform-Specific Bridge Names +**Platform-Specific Bridge Names** | Platform | Bridge Name | Handler | |----------|------------|---------|