From bdbef8239819378d7519ee91bab2bd7484f07ae7 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 23 Jun 2026 12:44:11 -0300 Subject: [PATCH 1/5] docs(wcp): add custom theming to data collection form + clarity cleanup Add themeVariables/theme query params for the embedded data collection form (generated from the Pay Dashboard), documented via a shared "Form URL parameters" reference table and "Customizing the form appearance" section in the overview snippet. Cleanup done alongside: - Standardize prefill encoding to base64url across all SDKs - Dedupe the web iframe copy into the shared overview snippet; render the previously-imported-but-unused best-practices snippet - Rename buildPrefillUrl -> buildFormUrl (now takes theme params) - Make data collection consistently per-option: add collectData to the PaymentOption type and switch examples/prose to selectedOption.collectData across the 4 pages that used the deprecated top-level field - Add a theming FAQ to the wallet overview - Update the WalletKit ai-prompt files to match Co-Authored-By: Claude Opus 4.8 --- payments/wallets/overview.mdx | 11 ++- payments/wallets/standalone/flutter.mdx | 60 +++++++----- payments/wallets/standalone/kotlin.mdx | 38 +++++--- payments/wallets/standalone/react-native.mdx | 38 ++++++-- payments/wallets/standalone/swift.mdx | 42 +++++++-- payments/wallets/standalone/web.mdx | 76 +++++++-------- .../wallets/walletkit/ai-prompts/flutter.mdx | 32 ++++++- .../wallets/walletkit/ai-prompts/kotlin.mdx | 34 ++++--- .../walletkit/ai-prompts/react-native.mdx | 22 +++++ .../wallets/walletkit/ai-prompts/swift.mdx | 34 ++++++- payments/wallets/walletkit/flutter.mdx | 46 +++++---- payments/wallets/walletkit/kotlin.mdx | 11 ++- payments/wallets/walletkit/react-native.mdx | 55 +++++++---- payments/wallets/walletkit/swift.mdx | 12 ++- payments/wallets/walletkit/web.mdx | 93 ++++++++----------- ...webview-data-collection-best-practices.mdx | 13 +-- snippets/webview-data-collection-overview.mdx | 35 +++++-- 17 files changed, 439 insertions(+), 213 deletions(-) diff --git a/payments/wallets/overview.mdx b/payments/wallets/overview.mdx index ca8fbc1..7cf9f74 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 8600812..ea6dd65 100644 --- a/payments/wallets/standalone/flutter.mdx +++ b/payments/wallets/standalone/flutter.mdx @@ -169,9 +169,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'); + } } ``` @@ -214,18 +216,25 @@ Some payments may require additional user data. Check for `collectData` in the p ```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))); + 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 @@ -277,7 +286,7 @@ if (!confirmResponse.isFinal && confirmResponse.pollInMs != null) { ## WebView 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: @@ -285,6 +294,8 @@ dependencies: url_launcher: ^6.1.0 ``` + + ```dart import 'dart:convert'; import 'package:flutter/material.dart'; @@ -370,14 +381,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))); + } + if (theme != null) params['theme'] = theme; + if (themeVariables != null) params['themeVariables'] = themeVariables; + return uri.replace(queryParameters: params).toString(); } ``` @@ -433,10 +450,10 @@ class PaymentService { signatures.add(signature); } - // Step 5: Collect data via WebView if required - if (optionsResponse.collectData?.url != null) { + // Step 5: Collect data via WebView if required for selected option + if (selectedOption.collectData?.url != null) { // Show WebView and wait for IC_COMPLETE - await showDataCollectionWebView(optionsResponse.collectData!.url); + await showDataCollectionWebView(selectedOption.collectData!.url); } // Step 6: Confirm payment @@ -561,6 +578,7 @@ PaymentOption({ required PayAmount amount, @JsonKey(name: 'etaS') required int etaSeconds, required List actions, + CollectDataAction? collectData, // Per-option data collection (null if not required) }) ``` @@ -689,7 +707,7 @@ 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. +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. ## Examples diff --git a/payments/wallets/standalone/kotlin.mdx b/payments/wallets/standalone/kotlin.mdx index 168b2dd..e27621c 100644 --- a/payments/wallets/standalone/kotlin.mdx +++ b/payments/wallets/standalone/kotlin.mdx @@ -295,12 +295,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 — see WebView Implementation section below @@ -358,7 +363,9 @@ confirmResult.onSuccess { response -> ## WebView Implementation -When a selected option has `collectData.url` present, display the URL in a WebView. The WebView handles form rendering, validation, and T&C acceptance. +When `selectedOption.collectData.url` is present, display the URL in a WebView. The WebView handles form rendering, validation, and T&C acceptance. + + ```kotlin import android.webkit.JavascriptInterface @@ -420,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() } ``` diff --git a/payments/wallets/standalone/react-native.mdx b/payments/wallets/standalone/react-native.mdx index fa155da..063ee83 100644 --- a/payments/wallets/standalone/react-native.mdx +++ b/payments/wallets/standalone/react-native.mdx @@ -266,9 +266,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 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 showDataCollectionWebView(webViewUrl); @@ -306,12 +314,14 @@ if (result.status === "succeeded") { ## WebView 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"; @@ -376,14 +386,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("&")}`; } ``` diff --git a/payments/wallets/standalone/swift.mdx b/payments/wallets/standalone/swift.mdx index e46aad3..003cb4a 100644 --- a/payments/wallets/standalone/swift.mdx +++ b/payments/wallets/standalone/swift.mdx @@ -306,10 +306,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! @@ -357,7 +365,9 @@ case .cancelled: ## WebView 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 @@ -438,15 +448,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 } diff --git a/payments/wallets/standalone/web.mdx b/payments/wallets/standalone/web.mdx index 846e6fa..2cd9231 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. @@ -243,41 +245,7 @@ Signatures must be in the same order as the actions array. Some payments may require additional user data. Check for `collectData` on the selected payment option: -## 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 `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 @@ -288,9 +256,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); @@ -335,7 +311,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 { @@ -376,14 +354,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("&")}`; } ``` diff --git a/payments/wallets/walletkit/ai-prompts/flutter.mdx b/payments/wallets/walletkit/ai-prompts/flutter.mdx index a6300e3..0bf853c 100644 --- a/payments/wallets/walletkit/ai-prompts/flutter.mdx +++ b/payments/wallets/walletkit/ai-prompts/flutter.mdx @@ -223,12 +223,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))); + } + if (theme != null) params['theme'] = theme; + if (themeVariables != null) params['themeVariables'] = themeVariables; + return uri.replace(queryParameters: params).toString(); +} +``` ```dart import 'dart:convert'; @@ -587,11 +611,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 2d53b0b..59327d5 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 11f659c..53e1815 100644 --- a/payments/wallets/walletkit/ai-prompts/react-native.mdx +++ b/payments/wallets/walletkit/ai-prompts/react-native.mdx @@ -582,6 +582,28 @@ const signature = await web3.eth.signTypedData(address, typedData); When `selectedOption.collectData?.url` is present, display the URL in a WebView. The URL comes from the selected option's `collectData.url`, which is already scoped to that option's account. 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. + +```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 f8d4224..6da081e 100644 --- a/payments/wallets/walletkit/ai-prompts/swift.mdx +++ b/payments/wallets/walletkit/ai-prompts/swift.mdx @@ -435,7 +435,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 a88c1a6..bfd2d3b 100644 --- a/payments/wallets/walletkit/flutter.mdx +++ b/payments/wallets/walletkit/flutter.mdx @@ -161,9 +161,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'); + } } ``` @@ -204,18 +206,25 @@ Some payments may require additional user data: ```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))); + 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 @@ -255,7 +264,7 @@ print('Is Final: ${confirmResponse.isFinal}'); ## WebView 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: @@ -263,6 +272,8 @@ dependencies: url_launcher: ^6.1.0 ``` + + ```dart import 'dart:convert'; import 'package:flutter/material.dart'; @@ -367,16 +378,16 @@ class PaymentService { throw Exception('No payment options available'); } - // Step 2: Collect data via WebView if required - if (optionsResponse.collectData?.url != null) { - await showDataCollectionWebView(optionsResponse.collectData!.url); - } - - // Step 3: Select payment option (or let user choose) + // Step 2: Select payment option (or let user choose) PaymentOption selectedOption = optionsResponse.options.first; final paymentId = optionsResponse.paymentId; final optionId = selectedOption.id; + // Step 3: Collect data via WebView if required for selected option + if (selectedOption.collectData?.url != null) { + await showDataCollectionWebView(selectedOption.collectData!.url); + } + // Step 4: Get required payment actions (if not already in the option) List actions = selectedOption.actions; if (actions.isEmpty) { @@ -528,6 +539,7 @@ PaymentOption({ required PayAmount amount, @JsonKey(name: 'etaS') required int etaSeconds, required List actions, + CollectDataAction? collectData, // Per-option data collection (null if not required) }) ``` @@ -639,7 +651,7 @@ 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. +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. ## Examples diff --git a/payments/wallets/walletkit/kotlin.mdx b/payments/wallets/walletkit/kotlin.mdx index b2393b8..de44a25 100644 --- a/payments/wallets/walletkit/kotlin.mdx +++ b/payments/wallets/walletkit/kotlin.mdx @@ -311,12 +311,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 @@ -375,7 +380,9 @@ confirmResult.onSuccess { response -> ## WebView Implementation -When a selected option has `collectData.url` present, display the URL in a WebView. The WebView handles form rendering, validation, and T&C acceptance. +When `selectedOption.collectData.url` is present, display the URL in a WebView. The WebView handles form rendering, validation, and T&C acceptance. + + ```kotlin import android.webkit.JavascriptInterface diff --git a/payments/wallets/walletkit/react-native.mdx b/payments/wallets/walletkit/react-native.mdx index 1a34d53..e895342 100644 --- a/payments/wallets/walletkit/react-native.mdx +++ b/payments/wallets/walletkit/react-native.mdx @@ -167,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`); + } } ``` @@ -239,16 +241,24 @@ Some payments may require additional user data: ```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); @@ -290,12 +300,14 @@ if (result.status === "succeeded") { ## WebView 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"; @@ -346,11 +358,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("&")}`; } ``` @@ -422,9 +442,9 @@ class PaymentManager { actions.map((action) => this.signAction(action, walletAddress)) ); - // Step 5: Collect data via WebView if required - if (options.collectData?.url) { - await this.showDataCollectionWebView(options.collectData.url); + // Step 5: Collect data via WebView if required for selected option + if (selectedOption.collectData?.url) { + await this.showDataCollectionWebView(selectedOption.collectData.url); } // Step 6: Confirm payment @@ -537,6 +557,7 @@ 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) } ``` @@ -660,4 +681,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 7e6ddb0..c97cb29 100644 --- a/payments/wallets/walletkit/swift.mdx +++ b/payments/wallets/walletkit/swift.mdx @@ -285,10 +285,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! @@ -336,7 +344,9 @@ case .cancelled: ## WebView 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 diff --git a/payments/wallets/walletkit/web.mdx b/payments/wallets/walletkit/web.mdx index c0996f7..6f24da4 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. @@ -164,9 +166,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`); + } } ``` @@ -233,53 +237,27 @@ Signatures must be in the same order as the actions array. Some payments may require additional user data: -## 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 `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. - + ```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); @@ -328,7 +306,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) { @@ -369,11 +349,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("&")}`; } ``` @@ -445,9 +433,9 @@ class PaymentManager { actions.map((action) => this.signAction(action, walletAddress)) ); - // Step 5: Collect data via iframe if required - if (options.collectData?.url) { - await this.showDataCollectionView(options.collectData.url); + // Step 5: Collect data via iframe if required for selected option + if (selectedOption.collectData?.url) { + await this.showDataCollectionView(selectedOption.collectData.url); } // Step 6: Confirm payment @@ -560,6 +548,7 @@ 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) } ``` @@ -683,4 +672,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/webview-data-collection-best-practices.mdx b/snippets/webview-data-collection-best-practices.mdx index 6841e5f..9d66183 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..de19dbb 100644 --- a/snippets/webview-data-collection-overview.mdx +++ b/snippets/webview-data-collection-overview.mdx @@ -1,18 +1,20 @@ -## 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. + +### 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`, proceed to `confirmPayment()` **without** passing `collectedData` — the form submits data directly to the backend ### Decision Matrix @@ -22,14 +24,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. From e0fddb8fc52b199c40cb24dc9e04e62e505fdbd4 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:16:54 -0300 Subject: [PATCH 2/5] docs(wcp): unify wallet page structure and declutter navigation Cross-platform consistency pass on the wallet integration docs: - Unify section names and ordering across all 10 SDK pages (Data Collection Implementation, Data Models, Payment Link Detection; consistent API Reference -> Error Handling -> Best Practices tail) - Nav: order React Native above Flutter in the WalletKit group - Sync Flutter Payment Flow steps with the rest (add Sign Actions, rename to Get Required Actions); remove Flutter's duplicate Examples - Merge Swift deep-link + QR handling into Payment Link Detection - Rename WalletKit Swift "Configuration" -> "Initialization" - Fold the WCP ID prerequisite under Requirements (drop Pre-Requisites) - Trim the in-page TOC: demote per-item Data Models / Error Handling / API Reference sub-headings and redundant install/config sub-headings to bold so only meaningful sections remain Co-Authored-By: Claude Opus 4.8 --- docs.json | 4 +- payments/wallets/standalone/flutter.mdx | 72 ++++++--- payments/wallets/standalone/kotlin.mdx | 40 ++--- payments/wallets/standalone/react-native.mdx | 142 ++++++++--------- payments/wallets/standalone/swift.mdx | 149 ++++++++---------- payments/wallets/standalone/web.mdx | 130 +++++++-------- payments/wallets/walletkit/flutter.mdx | 70 +++++--- payments/wallets/walletkit/kotlin.mdx | 28 ++-- payments/wallets/walletkit/react-native.mdx | 32 ++-- payments/wallets/walletkit/swift.mdx | 84 +++++----- payments/wallets/walletkit/web.mdx | 30 ++-- snippets/app-id.mdx | 6 +- ...webview-data-collection-best-practices.mdx | 2 +- snippets/webview-message-types.mdx | 2 +- 14 files changed, 409 insertions(+), 382 deletions(-) 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/standalone/flutter.mdx b/payments/wallets/standalone/flutter.mdx index ea6dd65..ab77dd9 100644 --- a/payments/wallets/standalone/flutter.mdx +++ b/payments/wallets/standalone/flutter.mdx @@ -55,7 +55,7 @@ try { } ``` -### Configuration Parameters +**Configuration Parameters** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| @@ -179,7 +179,7 @@ for (final option in response.options) { - + Get the required wallet actions (e.g., transactions to sign) for a selected payment option: @@ -203,10 +203,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. + + @@ -237,7 +268,7 @@ if (selectedOption.collectData?.url != null) { }, ).toString(); - // Show WebView — see WebView Implementation section below + // Show WebView — see Data Collection Implementation section below showDataCollectionWebView(webViewUrl); } ``` @@ -284,7 +315,7 @@ if (!confirmResponse.isFinal && confirmResponse.pollInMs != null) { -## WebView Implementation +## Data Collection Implementation 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`: @@ -500,11 +531,11 @@ class PaymentService { ## API Reference -### WalletConnectPay +**WalletConnectPay** The main class for interacting with the WalletConnect Pay SDK. -#### Constructor +**Constructor** ```dart WalletConnectPay({ @@ -515,7 +546,7 @@ WalletConnectPay({ }) ``` -#### Methods +**Methods** | Method | Description | |--------|-------------| @@ -524,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({ @@ -536,7 +567,7 @@ GetPaymentOptionsRequest({ }) ``` -### PaymentOptionsResponse +**PaymentOptionsResponse** ```dart PaymentOptionsResponse({ @@ -548,7 +579,7 @@ PaymentOptionsResponse({ }) ``` -### PaymentResultInfo +**PaymentResultInfo** ```dart class PaymentResultInfo { @@ -557,7 +588,7 @@ class PaymentResultInfo { } ``` -### PaymentInfo +**PaymentInfo** ```dart PaymentInfo({ @@ -569,7 +600,7 @@ PaymentInfo({ }) ``` -### PaymentOption +**PaymentOption** ```dart PaymentOption({ @@ -582,7 +613,7 @@ PaymentOption({ }) ``` -### ConfirmPaymentRequest +**ConfirmPaymentRequest** ```dart ConfirmPaymentRequest({ @@ -594,7 +625,7 @@ ConfirmPaymentRequest({ }) ``` -### ConfirmPaymentResponse +**ConfirmPaymentResponse** ```dart ConfirmPaymentResponse({ @@ -604,7 +635,7 @@ ConfirmPaymentResponse({ }) ``` -### PaymentStatus +**PaymentStatus** ```dart enum PaymentStatus { @@ -616,7 +647,7 @@ enum PaymentStatus { } ``` -### Action & WalletRpcAction +**Action & WalletRpcAction** ```dart class Action { @@ -630,7 +661,7 @@ class WalletRpcAction { } ``` -### CollectDataAction +**CollectDataAction** ```dart class CollectDataAction { @@ -667,7 +698,7 @@ All errors include: - `details`: Additional error details - `stacktrace`: Stack trace -### Example Error Handling +**Example Error Handling** ```dart try { @@ -709,6 +740,3 @@ try { 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. -## Examples - -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 e27621c..02d0782 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: @@ -66,7 +66,7 @@ WalletConnectPay.initialize( ) ``` -### Configuration Parameters +**Configuration Parameters** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| @@ -308,7 +308,7 @@ selectedOption.collectData?.let { collectAction -> // .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) } } @@ -361,7 +361,7 @@ 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. @@ -547,17 +547,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 | |--------|-------------| @@ -568,7 +568,7 @@ Main entry point for the Pay SDK (singleton object). ## Data Models -### Pay.PaymentOptionsResponse +**Pay.PaymentOptionsResponse** ```kotlin data class PaymentOptionsResponse( @@ -585,7 +585,7 @@ data class PaymentResultInfo( ) ``` -### Pay.PaymentInfo +**Pay.PaymentInfo** ```kotlin data class PaymentInfo( @@ -601,7 +601,7 @@ data class MerchantInfo( ) ``` -### Pay.PaymentOption +**Pay.PaymentOption** ```kotlin data class PaymentOption( @@ -613,7 +613,7 @@ data class PaymentOption( ) ``` -### Pay.Amount +**Pay.Amount** ```kotlin data class Amount( @@ -632,7 +632,7 @@ data class AmountDisplay( ) ``` -### Pay.WalletRpcAction +**Pay.WalletRpcAction** ```kotlin data class WalletRpcAction( @@ -642,7 +642,7 @@ data class WalletRpcAction( ) ``` -### Pay.RequiredAction +**Pay.RequiredAction** ```kotlin sealed class RequiredAction { @@ -650,7 +650,7 @@ sealed class RequiredAction { } ``` -### Pay.CollectDataAction +**Pay.CollectDataAction** ```kotlin data class CollectDataAction( @@ -659,7 +659,7 @@ data class CollectDataAction( ) ``` -### Pay.ConfirmPaymentResponse +**Pay.ConfirmPaymentResponse** ```kotlin data class ConfirmPaymentResponse( @@ -670,7 +670,7 @@ data class ConfirmPaymentResponse( ) ``` -### Pay.PaymentStatus +**Pay.PaymentStatus** | Status | Description | |--------|-------------| @@ -685,7 +685,7 @@ data class ConfirmPaymentResponse( The SDK provides typed errors for different failure scenarios: -### GetPaymentOptionsError +**GetPaymentOptionsError** | Error | Description | |-------|-------------| @@ -698,7 +698,7 @@ The SDK provides typed errors for different failure scenarios: | `Http` | Network error | | `InternalError` | Server error | -### GetPaymentRequestError +**GetPaymentRequestError** | Error | Description | |-------|-------------| @@ -707,7 +707,7 @@ The SDK provides typed errors for different failure scenarios: | `InvalidAccount` | Invalid account format | | `Http` | Network error | -### ConfirmPaymentError +**ConfirmPaymentError** | Error | Description | |-------|-------------| @@ -718,7 +718,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 063ee83..59ced8b 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: @@ -71,7 +71,7 @@ const client = new WalletConnectPay({ }); ``` -### Configuration Parameters +**Configuration Parameters** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| @@ -278,7 +278,7 @@ if (selectedOption.collectData?.url) { const separator = selectedOption.collectData.url.includes("?") ? "&" : "?"; 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); } ``` @@ -312,7 +312,7 @@ if (result.status === "succeeded") { -## WebView Implementation +## Data Collection Implementation When `selectedOption.collectData.url` is present, display the URL in a WebView using `react-native-webview`. Install the dependency: @@ -526,72 +526,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 | |--------|-------------| @@ -600,9 +547,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 = @@ -613,19 +560,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 { @@ -654,7 +601,7 @@ interface ConfirmPaymentParams { } ``` -#### Response Types +**Response Types** ```typescript interface PaymentOptionsResponse { @@ -687,7 +634,7 @@ interface ConfirmPaymentResponse { } ``` -#### PaymentOption +**PaymentOption** ```typescript interface PaymentOption { @@ -704,7 +651,7 @@ interface PaymentOption { } ``` -#### Action +**Action** ```typescript interface Action { @@ -721,7 +668,7 @@ interface WalletRpcAction { } ``` -#### Amount Types +**Amount Types** ```typescript interface PayAmount { @@ -747,7 +694,7 @@ interface AmountDisplay { } ``` -#### Payment Info Types +**Payment Info Types** ```typescript interface PaymentInfo { @@ -780,7 +727,7 @@ interface BuyerInfo { } ``` -#### Collect Data Types +**Collect Data Types** ```typescript interface CollectDataAction { @@ -791,6 +738,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 003cb4a..eee99e1 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`: @@ -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 four main steps: @@ -321,7 +340,7 @@ if let collectData = selectedOption.collectData, let url = collectData.url { 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) } ``` @@ -363,7 +382,7 @@ 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. @@ -586,73 +605,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. @@ -661,7 +616,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. @@ -671,9 +626,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 { @@ -690,7 +645,7 @@ struct PaymentResultInfo { } ``` -#### PaymentInfo +**PaymentInfo** ```swift struct PaymentInfo { @@ -702,7 +657,7 @@ struct PaymentInfo { } ``` -#### PaymentOption +**PaymentOption** ```swift struct PaymentOption { @@ -714,7 +669,7 @@ struct PaymentOption { } ``` -#### PayAmount +**PayAmount** ```swift struct PayAmount { @@ -732,7 +687,7 @@ struct AmountDisplay { } ``` -#### Action & WalletRpcAction +**Action & WalletRpcAction** ```swift struct Action { @@ -746,7 +701,7 @@ struct WalletRpcAction { } ``` -#### CollectDataAction & CollectDataField +**CollectDataAction & CollectDataField** ```swift struct CollectDataAction { @@ -755,7 +710,7 @@ struct CollectDataAction { } ``` -#### ConfirmPaymentResultResponse +**ConfirmPaymentResultResponse** ```swift struct ConfirmPaymentResultResponse { @@ -775,6 +730,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 2cd9231..b909010 100644 --- a/payments/wallets/standalone/web.mdx +++ b/payments/wallets/standalone/web.mdx @@ -61,7 +61,7 @@ const client = new WalletConnectPay({ }); ``` -### Configuration Parameters +**Configuration Parameters** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| @@ -483,69 +483,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 | |--------|-------------| @@ -554,9 +504,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 = @@ -567,19 +517,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 { @@ -608,7 +558,7 @@ interface ConfirmPaymentParams { } ``` -#### Response Types +**Response Types** ```typescript interface PaymentOptionsResponse { @@ -641,7 +591,7 @@ interface ConfirmPaymentResponse { } ``` -#### PaymentOption +**PaymentOption** ```typescript interface PaymentOption { @@ -658,7 +608,7 @@ interface PaymentOption { } ``` -#### Action +**Action** ```typescript interface Action { @@ -675,7 +625,7 @@ interface WalletRpcAction { } ``` -#### Amount Types +**Amount Types** ```typescript interface PayAmount { @@ -701,7 +651,7 @@ interface AmountDisplay { } ``` -#### Payment Info Types +**Payment Info Types** ```typescript interface PaymentInfo { @@ -734,7 +684,7 @@ interface BuyerInfo { } ``` -#### Collect Data Types +**Collect Data Types** ```typescript interface CollectDataAction { @@ -745,6 +695,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/flutter.mdx b/payments/wallets/walletkit/flutter.mdx index bfd2d3b..6ab14e2 100644 --- a/payments/wallets/walletkit/flutter.mdx +++ b/payments/wallets/walletkit/flutter.mdx @@ -171,7 +171,7 @@ for (final option in response.options) { - + Get the required wallet actions for a selected payment option: @@ -193,10 +193,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. + + @@ -262,7 +293,7 @@ print('Is Final: ${confirmResponse.isFinal}'); -## WebView Implementation +## Data Collection Implementation When `selectedOption.collectData.url` is present, display the URL in a WebView using `webview_flutter` (v4.10.0+). Add dependencies: @@ -475,7 +506,7 @@ final response = await payClient.getPaymentOptions(request: request); ## API Reference -### ReownWalletKit Pay Methods +**ReownWalletKit Pay Methods** | Method | Description | |--------|-------------| @@ -485,9 +516,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({ @@ -497,7 +528,7 @@ GetPaymentOptionsRequest({ }) ``` -#### PaymentOptionsResponse +**PaymentOptionsResponse** ```dart PaymentOptionsResponse({ @@ -509,7 +540,7 @@ PaymentOptionsResponse({ }) ``` -#### PaymentResultInfo +**PaymentResultInfo** ```dart class PaymentResultInfo { @@ -518,7 +549,7 @@ class PaymentResultInfo { } ``` -#### PaymentInfo +**PaymentInfo** ```dart PaymentInfo({ @@ -530,7 +561,7 @@ PaymentInfo({ }) ``` -#### PaymentOption +**PaymentOption** ```dart PaymentOption({ @@ -543,7 +574,7 @@ PaymentOption({ }) ``` -#### ConfirmPaymentRequest +**ConfirmPaymentRequest** ```dart ConfirmPaymentRequest({ @@ -554,7 +585,7 @@ ConfirmPaymentRequest({ }) ``` -#### ConfirmPaymentResponse +**ConfirmPaymentResponse** ```dart ConfirmPaymentResponse({ @@ -565,7 +596,7 @@ ConfirmPaymentResponse({ }) ``` -#### PaymentStatus +**PaymentStatus** ```dart enum PaymentStatus { @@ -578,7 +609,7 @@ enum PaymentStatus { } ``` -#### CollectDataAction +**CollectDataAction** ```dart class CollectDataAction { @@ -615,7 +646,7 @@ All errors include: - `details`: Additional error details - `stacktrace`: Stack trace -### Example Error Handling +**Example Error Handling** ```dart try { @@ -653,14 +684,3 @@ try { 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. -## 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). - -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 de44a25..654e882 100644 --- a/payments/wallets/walletkit/kotlin.mdx +++ b/payments/wallets/walletkit/kotlin.mdx @@ -378,7 +378,7 @@ 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. @@ -550,11 +550,11 @@ class PaymentViewModel : ViewModel() { ## API Reference -### WalletKit.Pay +**WalletKit.Pay** The payment operations object within WalletKit. -#### Methods +**Methods** | Method | Description | |--------|-------------| @@ -563,9 +563,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( @@ -574,7 +574,7 @@ data class RequiredPaymentActions( ) ``` -#### Wallet.Params.ConfirmPayment +**Wallet.Params.ConfirmPayment** ```kotlin data class ConfirmPayment( @@ -586,7 +586,7 @@ data class ConfirmPayment( ## Data Models -### Wallet.Model.PaymentOptionsResponse +**Wallet.Model.PaymentOptionsResponse** ```kotlin data class PaymentOptionsResponse( @@ -603,7 +603,7 @@ data class PaymentResultInfo( ) ``` -### Wallet.Model.PaymentInfo +**Wallet.Model.PaymentInfo** ```kotlin data class PaymentInfo( @@ -619,7 +619,7 @@ data class MerchantInfo( ) ``` -### Wallet.Model.PaymentOption +**Wallet.Model.PaymentOption** ```kotlin data class PaymentOption( @@ -631,7 +631,7 @@ data class PaymentOption( ) ``` -### Wallet.Model.PaymentAmount +**Wallet.Model.PaymentAmount** ```kotlin data class PaymentAmount( @@ -650,7 +650,7 @@ data class PaymentAmountDisplay( ) ``` -### Wallet.Model.WalletRpcAction +**Wallet.Model.WalletRpcAction** ```kotlin data class WalletRpcAction( @@ -660,7 +660,7 @@ data class WalletRpcAction( ) ``` -### Wallet.Model.CollectDataAction +**Wallet.Model.CollectDataAction** ```kotlin data class CollectDataAction( @@ -669,7 +669,7 @@ data class CollectDataAction( ) ``` -### Wallet.Model.ConfirmPaymentResponse +**Wallet.Model.ConfirmPaymentResponse** ```kotlin data class ConfirmPaymentResponse( @@ -680,7 +680,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 e895342..e7a6cc6 100644 --- a/payments/wallets/walletkit/react-native.mdx +++ b/payments/wallets/walletkit/react-native.mdx @@ -298,7 +298,7 @@ if (result.status === "succeeded") { -## WebView Implementation +## Data Collection Implementation When `selectedOption.collectData.url` is present, display the URL in a WebView using `react-native-webview`. Install the dependency: @@ -481,17 +481,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 | |--------|-------------| @@ -499,9 +499,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 { @@ -511,7 +511,7 @@ interface GetPaymentOptionsParams { } ``` -#### GetRequiredPaymentActionsParams +**GetRequiredPaymentActionsParams** ```typescript interface GetRequiredPaymentActionsParams { @@ -520,7 +520,7 @@ interface GetRequiredPaymentActionsParams { } ``` -#### ConfirmPaymentParams +**ConfirmPaymentParams** ```typescript interface ConfirmPaymentParams { @@ -530,9 +530,9 @@ interface ConfirmPaymentParams { } ``` -### Response Types +**Response Types** -#### PaymentOptionsResponse +**PaymentOptionsResponse** ```typescript interface PaymentOptionsResponse { @@ -549,7 +549,7 @@ interface PaymentResultInfo { } ``` -#### PaymentOption +**PaymentOption** ```typescript interface PaymentOption { @@ -561,7 +561,7 @@ interface PaymentOption { } ``` -#### Action +**Action** ```typescript interface Action { @@ -575,7 +575,7 @@ interface WalletRpcAction { } ``` -#### ConfirmPaymentResponse +**ConfirmPaymentResponse** ```typescript interface ConfirmPaymentResponse { @@ -594,7 +594,7 @@ type PaymentStatus = | "cancelled"; ``` -#### PaymentInfo +**PaymentInfo** ```typescript interface PaymentInfo { @@ -611,7 +611,7 @@ interface MerchantInfo { } ``` -#### PayAmount +**PayAmount** ```typescript interface PayAmount { @@ -629,7 +629,7 @@ interface AmountDisplay { } ``` -#### CollectDataAction +**CollectDataAction** ```typescript interface CollectDataAction { diff --git a/payments/wallets/walletkit/swift.mdx b/payments/wallets/walletkit/swift.mdx index c97cb29..524e105 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 five main steps: @@ -342,7 +361,7 @@ 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. @@ -536,48 +555,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 | |--------|-------------| @@ -586,9 +576,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 { @@ -605,7 +595,7 @@ struct PaymentResultInfo { } ``` -#### PaymentInfo +**PaymentInfo** ```swift struct PaymentInfo { @@ -617,7 +607,7 @@ struct PaymentInfo { } ``` -#### PaymentOption +**PaymentOption** ```swift struct PaymentOption { @@ -629,7 +619,7 @@ struct PaymentOption { } ``` -#### PayAmount +**PayAmount** ```swift struct PayAmount { @@ -647,7 +637,7 @@ struct AmountDisplay { } ``` -#### Action & WalletRpcAction +**Action & WalletRpcAction** ```swift struct Action { @@ -661,7 +651,7 @@ struct WalletRpcAction { } ``` -#### CollectDataAction +**CollectDataAction** ```swift struct CollectDataAction { @@ -670,7 +660,7 @@ struct CollectDataAction { } ``` -#### ConfirmPaymentResultResponse +**ConfirmPaymentResultResponse** ```swift struct ConfirmPaymentResultResponse { @@ -681,7 +671,7 @@ struct ConfirmPaymentResultResponse { } ``` -#### PaymentStatus +**PaymentStatus** ```swift enum PaymentStatus { @@ -698,7 +688,7 @@ enum PaymentStatus { The SDK throws specific error types for different failure scenarios: -### GetPaymentOptionsError +**GetPaymentOptionsError** | Error | Description | |-------|-------------| @@ -710,7 +700,7 @@ The SDK throws specific error types for different failure scenarios: | `.http` | Network error | | `.internalError` | Server error | -### GetPaymentRequestError +**GetPaymentRequestError** | Error | Description | |-------|-------------| @@ -719,7 +709,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 6f24da4..7e84998 100644 --- a/payments/wallets/walletkit/web.mdx +++ b/payments/wallets/walletkit/web.mdx @@ -472,17 +472,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 | |--------|-------------| @@ -490,9 +490,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 { @@ -502,7 +502,7 @@ interface GetPaymentOptionsParams { } ``` -#### GetRequiredPaymentActionsParams +**GetRequiredPaymentActionsParams** ```typescript interface GetRequiredPaymentActionsParams { @@ -511,7 +511,7 @@ interface GetRequiredPaymentActionsParams { } ``` -#### ConfirmPaymentParams +**ConfirmPaymentParams** ```typescript interface ConfirmPaymentParams { @@ -521,9 +521,9 @@ interface ConfirmPaymentParams { } ``` -### Response Types +**Response Types** -#### PaymentOptionsResponse +**PaymentOptionsResponse** ```typescript interface PaymentOptionsResponse { @@ -540,7 +540,7 @@ interface PaymentResultInfo { } ``` -#### PaymentOption +**PaymentOption** ```typescript interface PaymentOption { @@ -552,7 +552,7 @@ interface PaymentOption { } ``` -#### Action +**Action** ```typescript interface Action { @@ -566,7 +566,7 @@ interface WalletRpcAction { } ``` -#### ConfirmPaymentResponse +**ConfirmPaymentResponse** ```typescript interface ConfirmPaymentResponse { @@ -585,7 +585,7 @@ type PaymentStatus = | "cancelled"; ``` -#### PaymentInfo +**PaymentInfo** ```typescript interface PaymentInfo { @@ -602,7 +602,7 @@ interface MerchantInfo { } ``` -#### PayAmount +**PayAmount** ```typescript interface PayAmount { @@ -620,7 +620,7 @@ interface AmountDisplay { } ``` -#### CollectDataAction +**CollectDataAction** ```typescript interface CollectDataAction { 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 9d66183..2d8652d 100644 --- a/snippets/webview-data-collection-best-practices.mdx +++ b/snippets/webview-data-collection-best-practices.mdx @@ -1,4 +1,4 @@ -### Data Collection Best Practices +**Data Collection Best Practices** - **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 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 | |----------|------------|---------| From be3b963485cdcebf37d85854050beda12670fd68 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:46:16 -0300 Subject: [PATCH 3/5] docs(wcp): tidy WalletKit Flutter sections Fold "Accessing the Pay Client" into Initialization and remove the redundant "Direct Access" section (both just showed `walletKit.pay`). Co-Authored-By: Claude Opus 4.8 --- payments/wallets/walletkit/flutter.mdx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/payments/wallets/walletkit/flutter.mdx b/payments/wallets/walletkit/flutter.mdx index dd61a5a..84f0e98 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; @@ -500,16 +498,6 @@ 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** From 1808f60b861037378143ebc681f6488bb981247d Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:49:38 -0300 Subject: [PATCH 4/5] docs(wcp): rename standalone "Configuration" to "Initialization" Unify the SDK-setup section heading across standalone and WalletKit pages (all now "Initialization"). Co-Authored-By: Claude Opus 4.8 --- payments/wallets/standalone/flutter.mdx | 2 +- payments/wallets/standalone/kotlin.mdx | 2 +- payments/wallets/standalone/react-native.mdx | 2 +- payments/wallets/standalone/swift.mdx | 2 +- payments/wallets/standalone/web.mdx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/payments/wallets/standalone/flutter.mdx b/payments/wallets/standalone/flutter.mdx index 1959e55..062053c 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: diff --git a/payments/wallets/standalone/kotlin.mdx b/payments/wallets/standalone/kotlin.mdx index b270e1d..2bac1d5 100644 --- a/payments/wallets/standalone/kotlin.mdx +++ b/payments/wallets/standalone/kotlin.mdx @@ -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: diff --git a/payments/wallets/standalone/react-native.mdx b/payments/wallets/standalone/react-native.mdx index 910d7d5..06f1eaf 100644 --- a/payments/wallets/standalone/react-native.mdx +++ b/payments/wallets/standalone/react-native.mdx @@ -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: diff --git a/payments/wallets/standalone/swift.mdx b/payments/wallets/standalone/swift.mdx index 2c5c67f..2d1f275 100644 --- a/payments/wallets/standalone/swift.mdx +++ b/payments/wallets/standalone/swift.mdx @@ -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`: diff --git a/payments/wallets/standalone/web.mdx b/payments/wallets/standalone/web.mdx index fb68f6b..75e94fc 100644 --- a/payments/wallets/standalone/web.mdx +++ b/payments/wallets/standalone/web.mdx @@ -47,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: From 266d439704c300e4970624f189be5426d800b688 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 23 Jun 2026 15:03:17 -0300 Subject: [PATCH 5/5] docs(wcp): strip padding from Dart base64url prefill encoding Dart's base64Url.encode includes `=` padding; strip it so the Flutter prefill examples match the unpadded base64url used by the other SDKs. Addresses Copilot review feedback on PR #74. Co-Authored-By: Claude Opus 4.8 --- payments/wallets/standalone/flutter.mdx | 4 ++-- payments/wallets/walletkit/ai-prompts/flutter.mdx | 2 +- payments/wallets/walletkit/flutter.mdx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/payments/wallets/standalone/flutter.mdx b/payments/wallets/standalone/flutter.mdx index 062053c..eea4e47 100644 --- a/payments/wallets/standalone/flutter.mdx +++ b/payments/wallets/standalone/flutter.mdx @@ -195,7 +195,7 @@ if (selectedOption.collectData?.url != null) { 'pobAddress': '123 Main St, New York, NY 10001', }; // Encode prefill as base64url - final prefillBase64 = base64Url.encode(utf8.encode(jsonEncode(prefillData))); + final prefillBase64 = base64Url.encode(utf8.encode(jsonEncode(prefillData))).replaceAll('=', ''); final uri = Uri.parse(selectedOption.collectData!.url); final webViewUrl = uri.replace( queryParameters: { @@ -416,7 +416,7 @@ String buildFormUrl( final uri = Uri.parse(baseUrl); final params = {...uri.queryParameters}; if (prefillData.isNotEmpty) { - params['prefill'] = base64Url.encode(utf8.encode(jsonEncode(prefillData))); + params['prefill'] = base64Url.encode(utf8.encode(jsonEncode(prefillData))).replaceAll('=', ''); } if (theme != null) params['theme'] = theme; if (themeVariables != null) params['themeVariables'] = themeVariables; diff --git a/payments/wallets/walletkit/ai-prompts/flutter.mdx b/payments/wallets/walletkit/ai-prompts/flutter.mdx index 7c4ae7c..3b614d0 100644 --- a/payments/wallets/walletkit/ai-prompts/flutter.mdx +++ b/payments/wallets/walletkit/ai-prompts/flutter.mdx @@ -247,7 +247,7 @@ String buildFormUrl( final uri = Uri.parse(baseUrl); final params = {...uri.queryParameters}; if (prefillData.isNotEmpty) { - params['prefill'] = base64Url.encode(utf8.encode(jsonEncode(prefillData))); + params['prefill'] = base64Url.encode(utf8.encode(jsonEncode(prefillData))).replaceAll('=', ''); } if (theme != null) params['theme'] = theme; if (themeVariables != null) params['themeVariables'] = themeVariables; diff --git a/payments/wallets/walletkit/flutter.mdx b/payments/wallets/walletkit/flutter.mdx index 84f0e98..ea60d0b 100644 --- a/payments/wallets/walletkit/flutter.mdx +++ b/payments/wallets/walletkit/flutter.mdx @@ -184,8 +184,8 @@ if (selectedOption.collectData?.url != null) { 'dob': '1990-01-15', 'pobAddress': '123 Main St, New York, NY 10001', }; - // Encode prefill as base64url - final prefillBase64 = base64Url.encode(utf8.encode(jsonEncode(prefillData))); + // 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: {