From f05c90917158a09fd74228349e54310bdfdf7de0 Mon Sep 17 00:00:00 2001 From: Jakub Kasprzyk Date: Mon, 19 Oct 2026 21:12:32 +0200 Subject: [PATCH 1/2] feat: let the app settle Apple Pay authorization with onAuthorize - Add `StartPaymentOptions.onAuthorize` (and the same option on `usePaymentCheckout`). It receives the token after the user authorizes and resolves with `{ status: 'success' | 'failure', error? }`. - iOS: `didAuthorizePayment` waits for the callback and passes its outcome to PassKit, so the sheet shows the real result. A failure, a rejection or a 25 s timeout completes the sheet with `.failure` and resolves `startPayment` with `success: false`. Each retry from the sheet settles its own completion once, and a dismissal mid-callback waits for the outcome. - Without `onAuthorize`, the sheet completes immediately; the fixed one-second delay is gone. - Android: Google Pay has no merchant step in the basic flow, so the callback runs after the sheet closes, with the same timeout, and decides the `PaymentResult`. The same callback works on both platforms. - Docs: README, API pages and the server-processing guide. The example app gets a simulated server toggle (off / approve / decline). Closes #14 --- README.md | 45 ++++- docs/docs/api/hybrid-payment-handler.md | 17 +- docs/docs/api/types.md | 19 ++ docs/docs/api/use-payment-checkout.md | 5 + docs/docs/guides/manual-flow.md | 2 + docs/docs/guides/server-processing.md | 36 ++++ docs/docs/payment-flow.md | 2 + example/app/(tabs)/index.tsx | 34 ++++ .../java/com/margelo/nitro/pay/Constants.kt | 5 + .../margelo/nitro/pay/HybridPaymentHandler.kt | 78 ++++++++- .../com/margelo/nitro/pay/PaymentMapper.kt | 15 ++ package/ios/HybridPaymentHandler.swift | 162 +++++++++++++----- .../generated/android/NitroPayOnLoad.cpp | 2 + ...ntAuthorizationResult_____PaymentToken.hpp | 156 +++++++++++++++++ .../android/c++/JHybridPaymentHandlerSpec.cpp | 21 ++- .../android/c++/JHybridPaymentHandlerSpec.hpp | 2 +- .../c++/JPaymentAuthorizationResult.hpp | 64 +++++++ .../c++/JPaymentAuthorizationStatus.hpp | 58 +++++++ .../android/c++/JStartPaymentOptions.hpp | 102 +++++++++++ ...entAuthorizationResult_____PaymentToken.kt | 80 +++++++++ .../nitro/pay/HybridPaymentHandlerSpec.kt | 2 +- .../nitro/pay/PaymentAuthorizationResult.kt | 56 ++++++ .../nitro/pay/PaymentAuthorizationStatus.kt | 23 +++ .../margelo/nitro/pay/StartPaymentOptions.kt | 55 ++++++ .../ios/NitroPay-Swift-Cxx-Bridge.cpp | 25 +++ .../ios/NitroPay-Swift-Cxx-Bridge.hpp | 130 ++++++++++++++ .../ios/NitroPay-Swift-Cxx-Umbrella.hpp | 9 + .../ios/c++/HybridPaymentHandlerSpecSwift.hpp | 14 +- ...AuthorizationResult_____PaymentToken.swift | 61 +++++++ ...Func_void_PaymentAuthorizationResult.swift | 46 +++++ ...Promise_PaymentAuthorizationResult__.swift | 66 +++++++ .../ios/swift/HybridPaymentHandlerSpec.swift | 2 +- .../swift/HybridPaymentHandlerSpec_cxx.swift | 4 +- .../swift/PaymentAuthorizationResult.swift | 47 +++++ .../swift/PaymentAuthorizationStatus.swift | 40 +++++ .../ios/swift/StartPaymentOptions.swift | 71 ++++++++ .../shared/c++/HybridPaymentHandlerSpec.hpp | 6 +- .../shared/c++/PaymentAuthorizationResult.hpp | 90 ++++++++++ .../shared/c++/PaymentAuthorizationStatus.hpp | 76 ++++++++ .../shared/c++/StartPaymentOptions.hpp | 90 ++++++++++ .../usePaymentCheckout.integration.test.ts | 94 ++++++++++ package/src/hooks/usePaymentCheckout.ts | 24 ++- package/src/specs/PaymentHandler.nitro.ts | 6 +- package/src/types/Payment.ts | 24 +++ 44 files changed, 1902 insertions(+), 64 deletions(-) create mode 100644 package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.hpp create mode 100644 package/nitrogen/generated/android/c++/JPaymentAuthorizationResult.hpp create mode 100644 package/nitrogen/generated/android/c++/JPaymentAuthorizationStatus.hpp create mode 100644 package/nitrogen/generated/android/c++/JStartPaymentOptions.hpp create mode 100644 package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.kt create mode 100644 package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/PaymentAuthorizationResult.kt create mode 100644 package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/PaymentAuthorizationStatus.kt create mode 100644 package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/StartPaymentOptions.kt create mode 100644 package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.swift create mode 100644 package/nitrogen/generated/ios/swift/Func_void_PaymentAuthorizationResult.swift create mode 100644 package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__.swift create mode 100644 package/nitrogen/generated/ios/swift/PaymentAuthorizationResult.swift create mode 100644 package/nitrogen/generated/ios/swift/PaymentAuthorizationStatus.swift create mode 100644 package/nitrogen/generated/ios/swift/StartPaymentOptions.swift create mode 100644 package/nitrogen/generated/shared/c++/PaymentAuthorizationResult.hpp create mode 100644 package/nitrogen/generated/shared/c++/PaymentAuthorizationStatus.hpp create mode 100644 package/nitrogen/generated/shared/c++/StartPaymentOptions.hpp diff --git a/README.md b/README.md index e05df7c..43a4dca 100644 --- a/README.md +++ b/README.md @@ -370,6 +370,8 @@ interface UsePaymentCheckoutConfig { googlePayEnvironment?: "TEST" | "PRODUCTION"; googlePayGateway?: string; googlePayGatewayMerchantId?: string; + // Charge the token while the sheet waits (see "Charging before the sheet closes") + onAuthorize?: (token: PaymentToken) => Promise; } ``` @@ -456,7 +458,7 @@ interface PayServiceStatus { } ``` -##### `startPayment(request: PaymentRequest): Promise` +##### `startPayment(request: PaymentRequest, options?: StartPaymentOptions): Promise` Start a payment request. @@ -471,6 +473,20 @@ const result = await HybridPaymentHandler.startPayment({ }); ``` +**Options:** + +```typescript +interface StartPaymentOptions { + // Charge the token while the sheet waits (see "Charging before the sheet closes") + onAuthorize?: (token: PaymentToken) => Promise; +} + +interface PaymentAuthorizationResult { + status: "success" | "failure"; + error?: string; // Becomes PaymentResult.error on failure +} +``` + **Returns:** ```typescript @@ -883,6 +899,33 @@ function CustomPayButton({ onPress }) { ``` +### Charging Before the Sheet Closes + +Pass `onAuthorize` to charge the token while the Apple Pay sheet is still up. The sheet then shows your real outcome instead of an unconditional success: + +```typescript +const checkout = usePaymentCheckout({ + currencyCode: "USD", + onAuthorize: async (token) => { + const response = await fetch("https://api.yourserver.com/process-payment", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ token, orderId }), + }); + return response.ok + ? { status: "success" } + : { status: "failure", error: "Payment failed" }; + }, +}); + +const result = await checkout.startPayment(); +// result.success reflects your server's answer +``` + +- `onAuthorize` must settle within 25 seconds. Otherwise the payment fails with "Payment authorization timed out", and its later result is ignored, so make your server charge idempotent. +- A rejected promise counts as a failure. +- **Android:** Google Pay closes its sheet before returning the token, so `onAuthorize` runs afterwards and only decides the `PaymentResult`. + ### Server-Side Processing After receiving the payment token, process it on your server: diff --git a/docs/docs/api/hybrid-payment-handler.md b/docs/docs/api/hybrid-payment-handler.md index 8cd583d..c83de3e 100644 --- a/docs/docs/api/hybrid-payment-handler.md +++ b/docs/docs/api/hybrid-payment-handler.md @@ -24,7 +24,7 @@ const status = HybridPaymentHandler.payServiceStatus() --- -### startPayment(request) +### startPayment(request, options?) Starts the native payment flow with the given request. Returns a promise that resolves with the payment result. @@ -51,6 +51,21 @@ interface PaymentResult { } ``` +**options.onAuthorize** charges the token before the Apple Pay sheet closes. The result follows your callback: + +```ts +const result = await HybridPaymentHandler.startPayment(request, { + onAuthorize: async (token) => { + const charged = await chargeOnServer(token) + return charged + ? { status: 'success' } + : { status: 'failure', error: 'Payment failed' } + }, +}) +``` + +See [Charge before the sheet closes](/docs/guides/server-processing#charge-before-the-sheet-closes) for the timeout and Android behavior. + On iOS, the merchant ID is resolved from the app entitlements by default. Use `applePayMerchantIdentifier` only when you need to override the configured value at runtime. Build the request yourself or use [createPaymentRequest](/docs/api/utils#createpaymentrequestoptions) for a single line item with defaults. diff --git a/docs/docs/api/types.md b/docs/docs/api/types.md index 63c1c9e..804adf1 100644 --- a/docs/docs/api/types.md +++ b/docs/docs/api/types.md @@ -71,6 +71,25 @@ Send `token` (and optionally `transactionId`) to your backend to complete the ch --- +## StartPaymentOptions + +Optional second argument of `HybridPaymentHandler.startPayment()`. The hook takes `onAuthorize` in its config instead. + +```ts +interface StartPaymentOptions { + onAuthorize?: (token: PaymentToken) => Promise +} + +interface PaymentAuthorizationResult { + status: 'success' | 'failure' + error?: string // becomes PaymentResult.error on failure +} +``` + +See [Charge before the sheet closes](/docs/guides/server-processing#charge-before-the-sheet-closes). + +--- + ## PaymentToken The token you send to your server. **Do not log or expose; treat as sensitive.** diff --git a/docs/docs/api/use-payment-checkout.md b/docs/docs/api/use-payment-checkout.md index 647377c..43dd51f 100644 --- a/docs/docs/api/use-payment-checkout.md +++ b/docs/docs/api/use-payment-checkout.md @@ -27,9 +27,13 @@ interface UsePaymentCheckoutConfig { googlePayEnvironment?: 'TEST' | 'PRODUCTION' googlePayGateway?: string googlePayGatewayMerchantId?: string + // Charge the token before the sheet closes + onAuthorize?: (token: PaymentToken) => Promise } ``` +- **onAuthorize** — Called with the token after the user authorizes the payment. Charge it on your server and resolve with `{ status: 'success' }` or `{ status: 'failure', error }`. See [Charge before the sheet closes](/docs/guides/server-processing#charge-before-the-sheet-closes). + ## Return value ```ts @@ -68,6 +72,7 @@ interface UsePaymentCheckoutReturn { - Returns `null` and sets `error` when the cart is empty. - Returns `PaymentResult` (including `{ success: false, error }`) when the native flow completes with a failure result. - Returns `null` if an exception is thrown while starting/processing the payment. + - With `onAuthorize`, resolves after your callback settles. A `'failure'` result becomes `{ success: false, error }` and sets `error`. - **reset()** — Clears `isProcessing`, `result`, and `error` (does not clear cart). ## Example diff --git a/docs/docs/guides/manual-flow.md b/docs/docs/guides/manual-flow.md index 590efc5..3c81186 100644 --- a/docs/docs/guides/manual-flow.md +++ b/docs/docs/guides/manual-flow.md @@ -52,6 +52,8 @@ const handlePayment = async () => { } ``` +To charge the token before the Apple Pay sheet closes, pass `startPayment(paymentRequest, { onAuthorize })`. See [Charge before the sheet closes](/docs/guides/server-processing#charge-before-the-sheet-closes). + ## 4. Render the button Use the platform-specific button and, if needed, `callback()` for Nitro: diff --git a/docs/docs/guides/server-processing.md b/docs/docs/guides/server-processing.md index e8ae138..1242e3b 100644 --- a/docs/docs/guides/server-processing.md +++ b/docs/docs/guides/server-processing.md @@ -30,6 +30,42 @@ if (result?.success && result.token) { } ``` +## Charge before the sheet closes + +Without `onAuthorize`, Apple Pay shows its success checkmark as soon as the user authorizes, before your server has charged anything. If the charge then fails, the user has already seen "Done". + +Pass `onAuthorize` to charge the token while the sheet waits. Resolve with the outcome, and the sheet shows success or failure: + +```ts +const { startPayment } = usePaymentCheckout({ + currencyCode: 'USD', + onAuthorize: async (token) => { + const response = await fetch('https://api.yourserver.com/process-payment', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token, amount: total, currency: 'USD', orderId }), + }) + if (!response.ok) { + const { error } = await response.json() + return { status: 'failure', error } + } + return { status: 'success' } + }, +}) + +const result = await startPayment() +// result.success is true only when onAuthorize returned 'success' +``` + +With `HybridPaymentHandler`, pass the same callback as `startPayment(request, { onAuthorize })`. + +How it behaves: + +- **Failure:** `{ status: 'failure', error }` makes the sheet show the failure state. `startPayment` resolves with `success: false` and your `error`. A rejected promise is treated the same way. +- **Retries on iOS:** if the user retries from the sheet after a failure, `onAuthorize` runs again with the new token. The result reflects the last attempt. +- **Timeout:** `onAuthorize` must settle within 25 seconds (Apple Pay itself gives up after about 30). Otherwise the payment fails with "Payment authorization timed out" and the late result is ignored. Your server may still have charged the card, so make the charge idempotent and reconcile by order ID. +- **Android:** Google Pay closes its sheet before it returns the token, and its basic flow has no step that waits for the merchant. `onAuthorize` still runs, with the same timeout, after the sheet closes, and its outcome decides the `PaymentResult`. The same callback works on both platforms. + ## Example: Node.js (conceptual, gateway-specific) Your backend receives the token and calls your gateway SDK/API. The exact request body and field mapping depend on your gateway + tokenization mode. Treat this example as shape guidance, not drop-in code. diff --git a/docs/docs/payment-flow.md b/docs/docs/payment-flow.md index 7a58d91..351e179 100644 --- a/docs/docs/payment-flow.md +++ b/docs/docs/payment-flow.md @@ -41,6 +41,8 @@ sequenceDiagram ## PaymentResult outcomes +With [`onAuthorize`](/docs/guides/server-processing#charge-before-the-sheet-closes), your backend charges the token before the wallet sheet closes, and the result below already reflects the charge. + ### 1) Success - `result.success === true` diff --git a/example/app/(tabs)/index.tsx b/example/app/(tabs)/index.tsx index 66296c7..3c1aaab 100644 --- a/example/app/(tabs)/index.tsx +++ b/example/app/(tabs)/index.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import { StyleSheet, Alert, @@ -15,8 +16,13 @@ import { } from "@gmisoftware/react-native-pay"; import { callback } from "react-native-nitro-modules"; +const SIMULATED_SERVER_MODES = ["off", "approve", "decline"] as const; +type SimulatedServerMode = (typeof SIMULATED_SERVER_MODES)[number]; + export default function TabOneScreen() { const paymentServiceName = Platform.OS === "ios" ? "Apple Pay" : "Google Pay"; + const [simulatedServer, setSimulatedServer] = + useState("off"); // Initialize checkout hook const { @@ -36,8 +42,25 @@ export default function TabOneScreen() { } = usePaymentCheckout({ countryCode: "US", currencyCode: "USD", + // Stands in for your backend charging the token while the sheet waits. + onAuthorize: + simulatedServer === "off" + ? undefined + : async () => { + await new Promise((resolve) => setTimeout(resolve, 1500)); + return simulatedServer === "approve" + ? { status: "success" } + : { status: "failure", error: "Declined by the test server" }; + }, }); + const handleCycleSimulatedServer = () => { + const next = + (SIMULATED_SERVER_MODES.indexOf(simulatedServer) + 1) % + SIMULATED_SERVER_MODES.length; + setSimulatedServer(SIMULATED_SERVER_MODES[next]); + }; + // Show status info const handleCheckStatus = () => { if (canMakePayments) { @@ -153,6 +176,17 @@ export default function TabOneScreen() { + {/* Simulated Server Section */} + + Simulated Server (onAuthorize) + + Mode: {simulatedServer} + + + {/* Quick Add Section */} Quick Add diff --git a/package/android/src/main/java/com/margelo/nitro/pay/Constants.kt b/package/android/src/main/java/com/margelo/nitro/pay/Constants.kt index c81db8c..ea1c5f9 100644 --- a/package/android/src/main/java/com/margelo/nitro/pay/Constants.kt +++ b/package/android/src/main/java/com/margelo/nitro/pay/Constants.kt @@ -37,6 +37,11 @@ object PaymentConstants { const val DISPLAY_ITEM_STATUS_FINAL = "FINAL" const val DISPLAY_ITEM_STATUS_PENDING = "PENDING" + // Merchant authorization (`StartPaymentOptions.onAuthorize`), same limit as iOS + const val AUTHORIZATION_TIMEOUT_MS = 25_000L + const val ERROR_AUTHORIZATION_FAILED = "Payment authorization failed" + const val ERROR_AUTHORIZATION_TIMED_OUT = "Payment authorization timed out" + // Supported networks (lowercase for comparison) val SUPPORTED_NETWORKS = listOf("visa", "mastercard", "amex", "discover") diff --git a/package/android/src/main/java/com/margelo/nitro/pay/HybridPaymentHandler.kt b/package/android/src/main/java/com/margelo/nitro/pay/HybridPaymentHandler.kt index 6f5c475..7553926 100644 --- a/package/android/src/main/java/com/margelo/nitro/pay/HybridPaymentHandler.kt +++ b/package/android/src/main/java/com/margelo/nitro/pay/HybridPaymentHandler.kt @@ -2,6 +2,8 @@ package com.margelo.nitro.pay import android.app.Activity import android.content.Intent +import android.os.Handler +import android.os.Looper import android.util.Log import com.facebook.react.bridge.ActivityEventListener import com.facebook.react.bridge.ReactApplicationContext @@ -17,6 +19,8 @@ import com.margelo.nitro.core.Promise import com.margelo.nitro.NitroModules import java.util.concurrent.TimeUnit +private typealias AuthorizeCallback = (PaymentToken) -> Promise> + /** * Hybrid implementation of PaymentHandler for Google Pay on Android */ @@ -28,6 +32,8 @@ class HybridPaymentHandler : HybridPaymentHandlerSpec(), ActivityEventListener { } private var paymentPromise: Promise? = null + private var onAuthorize: AuthorizeCallback? = null + private val mainHandler = Handler(Looper.getMainLooper()) private var currentEnvironment: Int = WalletConstants.ENVIRONMENT_TEST init { @@ -69,9 +75,13 @@ class HybridPaymentHandler : HybridPaymentHandlerSpec(), ActivityEventListener { } } - override fun startPayment(request: PaymentRequest): Promise { + override fun startPayment( + request: PaymentRequest, + options: StartPaymentOptions? + ): Promise { val promise = Promise() paymentPromise = promise + onAuthorize = options?.onAuthorize try { val environment = determineEnvironment(request.googlePayEnvironment) @@ -193,14 +203,76 @@ class HybridPaymentHandler : HybridPaymentHandlerSpec(), ActivityEventListener { private fun handlePaymentSuccess(paymentData: PaymentData) { Log.d(PaymentConstants.TAG_PAYMENT_HANDLER, "Payment successful") val result = PaymentMapper.mapPaymentDataToResult(paymentData) - paymentPromise?.resolve(result) - paymentPromise = null + val authorize = onAuthorize + val token = result.token + if (authorize == null || token == null) { + resolvePayment(result) + return + } + authorizePayment(authorize, token, result) } private fun handlePaymentError(errorMessage: String) { val result = PaymentMapper.createErrorResult(errorMessage) + resolvePayment(result) + } + + private fun resolvePayment(result: PaymentResult) { paymentPromise?.resolve(result) paymentPromise = null + onAuthorize = null + } + + /** + * Lets the merchant decide the result. Google Pay has already closed its + * sheet, so unlike iOS there is no UI to update. Runs on the main thread. + */ + private fun authorizePayment( + authorize: AuthorizeCallback, + token: PaymentToken, + authorizedResult: PaymentResult + ) { + // Take this payment's promise, so a later startPayment can't receive its result. + val promise = paymentPromise ?: return + paymentPromise = null + onAuthorize = null + + var isSettled = false + val settle = { result: PaymentResult -> + if (!isSettled) { + isSettled = true + promise.resolve(result) + } + } + // Nitro settles JS promises on the JS thread. + val settleOnMain = { result: PaymentResult -> + mainHandler.post { settle(result) } + } + val settleWithError = { error: Throwable -> + settleOnMain( + PaymentMapper.createErrorResult( + error.message ?: PaymentConstants.ERROR_AUTHORIZATION_FAILED + ) + ) + } + + mainHandler.postDelayed({ + settle(PaymentMapper.createErrorResult(PaymentConstants.ERROR_AUTHORIZATION_TIMED_OUT)) + }, PaymentConstants.AUTHORIZATION_TIMEOUT_MS) + + try { + authorize(token) + .then { authorization -> + authorization + .then { settleOnMain(PaymentMapper.applyAuthorization(it, authorizedResult)) } + .catch { settleWithError(it) } + } + .catch { settleWithError(it) } + } catch (e: Throwable) { + // Thrown when the JS runtime is gone. + Log.e(PaymentConstants.TAG_PAYMENT_HANDLER, "Failed to call onAuthorize", e) + settleWithError(e) + } } private fun logPaymentRequest(request: org.json.JSONObject, environment: Int) { diff --git a/package/android/src/main/java/com/margelo/nitro/pay/PaymentMapper.kt b/package/android/src/main/java/com/margelo/nitro/pay/PaymentMapper.kt index 28fea92..d4faa46 100644 --- a/package/android/src/main/java/com/margelo/nitro/pay/PaymentMapper.kt +++ b/package/android/src/main/java/com/margelo/nitro/pay/PaymentMapper.kt @@ -73,6 +73,21 @@ object PaymentMapper { } } + /** + * Applies the merchant's `onAuthorize` outcome to an authorized PaymentResult + */ + fun applyAuthorization( + authorization: PaymentAuthorizationResult, + authorizedResult: PaymentResult + ): PaymentResult { + return when (authorization.status) { + PaymentAuthorizationStatus.SUCCESS -> authorizedResult + PaymentAuthorizationStatus.FAILURE -> createErrorResult( + authorization.error ?: PaymentConstants.ERROR_AUTHORIZATION_FAILED + ) + } + } + /** * Creates an error PaymentResult */ diff --git a/package/ios/HybridPaymentHandler.swift b/package/ios/HybridPaymentHandler.swift index e35204f..f4864ba 100644 --- a/package/ios/HybridPaymentHandler.swift +++ b/package/ios/HybridPaymentHandler.swift @@ -11,8 +11,16 @@ private enum ErrorMessage { static let missingMerchantIdentifier = "No Apple Pay merchant identifier configured" static let unableToPresent = "Unable to present payment authorization" static let unableToCreate = "Unable to create payment authorization" + static let authorizationFailed = "Payment authorization failed" + static let authorizationTimedOut = "Payment authorization timed out" } +// PassKit fails the sheet on its own when the authorization completion is not +// called within about 30 seconds, so settle first with a definite result. +private let authorizationTimeout: TimeInterval = 25 + +private typealias AuthorizeCallback = (PaymentToken) -> Promise> + private enum BundleKey { static let applePayMerchantIdentifiers = "ReactNativePayApplePayMerchantIdentifiers" } @@ -183,17 +191,58 @@ private struct PaymentTokenConverter { } } +// MARK: - Payment Results + +private enum PaymentResultFactory { + + static func success(token: PaymentToken) -> PaymentResult { + return PaymentResult.init( + success: true, + transactionId: UUID().uuidString, + token: token, + error: nil + ) + } + + static func failure(_ error: String) -> PaymentResult { + return PaymentResult.init( + success: false, + transactionId: nil, + token: nil, + error: error + ) + } + + static func failure(_ error: Error) -> PaymentResult { + // Nitro reports JS rejections as `RuntimeError`, whose description is the message. + return failure(String(describing: error)) + } + + static func from(_ authorization: PaymentAuthorizationResult, token: PaymentToken) -> PaymentResult { + switch authorization.status { + case .success: + return success(token: token) + case .failure: + return failure(authorization.error ?? ErrorMessage.authorizationFailed) + } + } +} + // MARK: - Payment Delegate private class PaymentDelegate: NSObject, PKPaymentAuthorizationViewControllerDelegate { private weak var paymentHandler: HybridPaymentHandler? + private let onAuthorize: AuthorizeCallback? private var paymentAuthorized: Bool = false private var authorizedResult: PaymentResult? + private var isAuthorizing: Bool = false + private var finishedWhileAuthorizing: Bool = false private var presentationDate: Date? private let userDismissThreshold: TimeInterval = 0.75 - init(paymentHandler: HybridPaymentHandler) { + init(paymentHandler: HybridPaymentHandler, onAuthorize: AuthorizeCallback?) { self.paymentHandler = paymentHandler + self.onAuthorize = onAuthorize } func markPresented() { @@ -206,45 +255,74 @@ private class PaymentDelegate: NSObject, PKPaymentAuthorizationViewControllerDel handler completion: @escaping (PKPaymentAuthorizationResult) -> Void ) { paymentAuthorized = true - let paymentToken = PaymentTokenConverter.convert(payment.token) - authorizedResult = PaymentResult.init( - success: true, - transactionId: UUID().uuidString, - token: paymentToken, - error: nil - ) - // Keep a short processing delay so PassKit can show its success UI, - // but settle the Nitro promise only once from didFinish. - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + guard let onAuthorize else { + authorizedResult = PaymentResultFactory.success(token: paymentToken) completion(PKPaymentAuthorizationResult(status: .success, errors: nil)) + return + } + + // PassKit calls this again when the user retries after a failure, so + // each attempt completes its own handler exactly once. Runs on main. + var isSettled = false + isAuthorizing = true + let settle = { (result: PaymentResult) in + guard !isSettled else { return } + isSettled = true + self.isAuthorizing = false + self.authorizedResult = result + completion(PKPaymentAuthorizationResult( + status: result.success ? .success : .failure, + errors: nil + )) + if self.finishedWhileAuthorizing { + self.reportResult() + } + } + // Nitro settles JS promises on the JS thread. + let settleOnMain = { (result: PaymentResult) in + DispatchQueue.main.async { settle(result) } } + + DispatchQueue.main.asyncAfter(deadline: .now() + authorizationTimeout) { + settle(PaymentResultFactory.failure(ErrorMessage.authorizationTimedOut)) + } + + onAuthorize(paymentToken) + .then { authorization in + authorization + .then { settleOnMain(PaymentResultFactory.from($0, token: paymentToken)) } + .catch { settleOnMain(PaymentResultFactory.failure($0)) } + } + .catch { settleOnMain(PaymentResultFactory.failure($0)) } } func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) { controller.dismiss(animated: true) { - if self.paymentAuthorized, let authorizedResult = self.authorizedResult { - self.paymentHandler?.handlePaymentResult(authorizedResult) + // Report the merchant's outcome, not a cancellation, once it arrives. + if self.isAuthorizing { + self.finishedWhileAuthorizing = true return } + self.reportResult() + } + } - let errorMessage: String - if let presentationDate = self.presentationDate, - Date().timeIntervalSince(presentationDate) >= self.userDismissThreshold { - errorMessage = ErrorMessage.paymentCancelled - } else { - errorMessage = ErrorMessage.paymentDismissed - } + private func reportResult() { + if paymentAuthorized, let authorizedResult { + paymentHandler?.handlePaymentResult(authorizedResult) + return + } - let result = PaymentResult.init( - success: false, - transactionId: nil, - token: nil, - error: errorMessage - ) - self.paymentHandler?.handlePaymentResult(result) + let errorMessage: String + if let presentationDate, + Date().timeIntervalSince(presentationDate) >= userDismissThreshold { + errorMessage = ErrorMessage.paymentCancelled + } else { + errorMessage = ErrorMessage.paymentDismissed } + paymentHandler?.handlePaymentResult(PaymentResultFactory.failure(errorMessage)) } } @@ -268,11 +346,12 @@ class HybridPaymentHandler: HybridPaymentHandlerSpec { return PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: networks) } - func startPayment(request: PaymentRequest) throws -> Promise { + func startPayment(request: PaymentRequest, options: StartPaymentOptions?) throws -> Promise { let promise = Promise() + let onAuthorize = options?.onAuthorize DispatchQueue.main.async { - self.performPayment(request: request) { result in + self.performPayment(request: request, onAuthorize: onAuthorize) { result in promise.resolve(withResult: result) } } @@ -293,32 +372,36 @@ class HybridPaymentHandler: HybridPaymentHandlerSpec { // MARK: - Private Methods - private func performPayment(request: PaymentRequest, completion: @escaping (PaymentResult) -> Void) { + private func performPayment( + request: PaymentRequest, + onAuthorize: AuthorizeCallback?, + completion: @escaping (PaymentResult) -> Void + ) { // Reject overlaps before touching shared state so the in-flight // payment keeps its completion/delegate and settles correctly. guard paymentCompletion == nil else { - completion(createErrorResult(ErrorMessage.paymentInProgress)) + completion(PaymentResultFactory.failure(ErrorMessage.paymentInProgress)) return } guard let paymentRequest = PaymentRequestBuilder.build(from: request) else { - completion(createErrorResult(ErrorMessage.missingMerchantIdentifier)) + completion(PaymentResultFactory.failure(ErrorMessage.missingMerchantIdentifier)) return } paymentCompletion = completion currentPaymentRequest = paymentRequest - delegate = PaymentDelegate(paymentHandler: self) + delegate = PaymentDelegate(paymentHandler: self, onAuthorize: onAuthorize) guard let paymentAuthVC = PKPaymentAuthorizationViewController(paymentRequest: paymentRequest) else { - handlePaymentResult(createErrorResult(ErrorMessage.unableToCreate)) + handlePaymentResult(PaymentResultFactory.failure(ErrorMessage.unableToCreate)) return } paymentAuthVC.delegate = delegate guard let rootViewController = getRootViewController() else { - handlePaymentResult(createErrorResult(ErrorMessage.unableToPresent)) + handlePaymentResult(PaymentResultFactory.failure(ErrorMessage.unableToPresent)) return } @@ -327,15 +410,6 @@ class HybridPaymentHandler: HybridPaymentHandlerSpec { } } - private func createErrorResult(_ error: String) -> PaymentResult { - return PaymentResult.init( - success: false, - transactionId: nil, - token: nil, - error: error - ) - } - private func getRootViewController() -> UIViewController? { let scenes = UIApplication.shared.connectedScenes .compactMap { $0 as? UIWindowScene } diff --git a/package/nitrogen/generated/android/NitroPayOnLoad.cpp b/package/nitrogen/generated/android/NitroPayOnLoad.cpp index 2c3f069..1d1533d 100644 --- a/package/nitrogen/generated/android/NitroPayOnLoad.cpp +++ b/package/nitrogen/generated/android/NitroPayOnLoad.cpp @@ -19,6 +19,7 @@ #include "JFunc_void.hpp" #include "views/JHybridGooglePayButtonStateUpdater.hpp" #include "JHybridPaymentHandlerSpec.hpp" +#include "JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.hpp" #include namespace margelo::nitro::pay { @@ -55,6 +56,7 @@ void registerAllNatives() { margelo::nitro::pay::JFunc_void_cxx::registerNatives(); margelo::nitro::pay::views::JHybridGooglePayButtonStateUpdater::registerNatives(); margelo::nitro::pay::JHybridPaymentHandlerSpec::CxxPart::registerNatives(); + margelo::nitro::pay::JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken_cxx::registerNatives(); // Register Nitro Hybrid Objects HybridObjectRegistry::registerHybridObjectConstructor( diff --git a/package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.hpp b/package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.hpp new file mode 100644 index 0000000..b5bf7fc --- /dev/null +++ b/package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.hpp @@ -0,0 +1,156 @@ +/// +/// JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include + +#include "PaymentAuthorizationResult.hpp" +#include +#include "PaymentToken.hpp" +#include +#include +#include +#include "JPaymentAuthorizationResult.hpp" +#include "PaymentAuthorizationStatus.hpp" +#include "JPaymentAuthorizationStatus.hpp" +#include +#include +#include "JPaymentToken.hpp" +#include "PaymentMethod.hpp" +#include "JPaymentMethod.hpp" +#include "PaymentNetwork.hpp" +#include "JPaymentNetwork.hpp" +#include "PaymentMethodType.hpp" +#include "JPaymentMethodType.hpp" +#include "PKSecureElementPass.hpp" +#include "JPKSecureElementPass.hpp" +#include "PassActivationState.hpp" +#include "JPassActivationState.hpp" +#include "CNContact.hpp" +#include "JCNContact.hpp" +#include "CNContactType.hpp" +#include "JCNContactType.hpp" +#include "CNLabeledPhoneNumber.hpp" +#include +#include "JCNLabeledPhoneNumber.hpp" +#include "CNPhoneNumber.hpp" +#include "JCNPhoneNumber.hpp" +#include "CNLabeledEmailAddress.hpp" +#include "JCNLabeledEmailAddress.hpp" +#include "CNLabeledPostalAddress.hpp" +#include "JCNLabeledPostalAddress.hpp" +#include "CNPostalAddress.hpp" +#include "JCNPostalAddress.hpp" + +namespace margelo::nitro::pay { + + using namespace facebook; + + /** + * Represents the Java/Kotlin callback `(token: PaymentToken) -> Promise>`. + * This can be passed around between C++ and Java/Kotlin. + */ + struct JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken: public jni::JavaClass { + public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/pay/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken;"; + + public: + /** + * Invokes the function this `JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken` instance holds through JNI. + */ + std::shared_ptr>>> invoke(const PaymentToken& token) const { + static const auto method = javaClassStatic()->getMethod(jni::alias_ref /* token */)>("invoke"); + auto __result = method(self(), JPaymentToken::fromCpp(token)); + return [&]() { + auto __promise = Promise>>::create(); + __result->cthis()->addOnResolvedListener([=](const jni::alias_ref& __boxedResult) { + auto __result = jni::static_ref_cast(__boxedResult); + __promise->resolve([&]() { + auto __promise = Promise::create(); + __result->cthis()->addOnResolvedListener([=](const jni::alias_ref& __boxedResult) { + auto __result = jni::static_ref_cast(__boxedResult); + __promise->resolve(__result->toCpp()); + }); + __result->cthis()->addOnRejectedListener([=](const jni::alias_ref& __throwable) { + jni::JniException __jniError(__throwable); + __promise->reject(std::make_exception_ptr(__jniError)); + }); + return __promise; + }()); + }); + __result->cthis()->addOnRejectedListener([=](const jni::alias_ref& __throwable) { + jni::JniException __jniError(__throwable); + __promise->reject(std::make_exception_ptr(__jniError)); + }); + return __promise; + }(); + } + }; + + /** + * An implementation of Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken that is backed by a C++ implementation (using `std::function<...>`) + */ + class JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken_cxx final: public jni::HybridClass { + public: + static jni::local_ref fromCpp(const std::function>>>(const PaymentToken& /* token */)>& func) { + return JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken_cxx::newObjectCxxArgs(func); + } + + public: + /** + * Invokes the C++ `std::function<...>` this `JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken_cxx` instance holds. + */ + jni::local_ref invoke_cxx(jni::alias_ref token) { + std::shared_ptr>>> __result = _func(token->toCpp()); + return [&]() { + jni::local_ref __localPromise = JPromise::create(); + jni::global_ref __promise = jni::make_global(__localPromise); + __result->addOnResolvedListener([=](const std::shared_ptr>& __result) { + __promise->cthis()->resolve([&]() { + jni::local_ref __localPromise = JPromise::create(); + jni::global_ref __promise = jni::make_global(__localPromise); + __result->addOnResolvedListener([=](const PaymentAuthorizationResult& __result) { + __promise->cthis()->resolve(JPaymentAuthorizationResult::fromCpp(__result)); + }); + __result->addOnRejectedListener([=](const std::exception_ptr& __error) { + auto __jniError = jni::getJavaExceptionForCppException(__error); + __promise->cthis()->reject(__jniError); + }); + return __localPromise; + }()); + }); + __result->addOnRejectedListener([=](const std::exception_ptr& __error) { + auto __jniError = jni::getJavaExceptionForCppException(__error); + __promise->cthis()->reject(__jniError); + }); + return __localPromise; + }(); + } + + public: + [[nodiscard]] + inline const std::function>>>(const PaymentToken& /* token */)>& getFunction() const { + return _func; + } + + public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/pay/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken_cxx;"; + static void registerNatives() { + registerHybrid({makeNativeMethod("invoke_cxx", JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken_cxx::invoke_cxx)}); + } + + private: + explicit JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken_cxx(const std::function>>>(const PaymentToken& /* token */)>& func): _func(func) { } + + private: + friend HybridBase; + std::function>>>(const PaymentToken& /* token */)> _func; + }; + +} // namespace margelo::nitro::pay diff --git a/package/nitrogen/generated/android/c++/JHybridPaymentHandlerSpec.cpp b/package/nitrogen/generated/android/c++/JHybridPaymentHandlerSpec.cpp index 87054ba..d4f31b5 100644 --- a/package/nitrogen/generated/android/c++/JHybridPaymentHandlerSpec.cpp +++ b/package/nitrogen/generated/android/c++/JHybridPaymentHandlerSpec.cpp @@ -45,6 +45,12 @@ namespace margelo::nitro::pay { struct PaymentItem; } namespace margelo::nitro::pay { enum class PaymentItemType; } // Forward declaration of `GooglePayEnvironment` to properly resolve imports. namespace margelo::nitro::pay { enum class GooglePayEnvironment; } +// Forward declaration of `StartPaymentOptions` to properly resolve imports. +namespace margelo::nitro::pay { struct StartPaymentOptions; } +// Forward declaration of `PaymentAuthorizationResult` to properly resolve imports. +namespace margelo::nitro::pay { struct PaymentAuthorizationResult; } +// Forward declaration of `PaymentAuthorizationStatus` to properly resolve imports. +namespace margelo::nitro::pay { enum class PaymentAuthorizationStatus; } #include "PayServiceStatus.hpp" #include "JPayServiceStatus.hpp" @@ -89,6 +95,15 @@ namespace margelo::nitro::pay { enum class GooglePayEnvironment; } #include "JPaymentItemType.hpp" #include "GooglePayEnvironment.hpp" #include "JGooglePayEnvironment.hpp" +#include "StartPaymentOptions.hpp" +#include "JStartPaymentOptions.hpp" +#include "PaymentAuthorizationResult.hpp" +#include +#include "JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.hpp" +#include +#include "JPaymentAuthorizationResult.hpp" +#include "PaymentAuthorizationStatus.hpp" +#include "JPaymentAuthorizationStatus.hpp" namespace margelo::nitro::pay { @@ -128,9 +143,9 @@ namespace margelo::nitro::pay { auto __result = method(_javaPart); return __result->toCpp(); } - std::shared_ptr> JHybridPaymentHandlerSpec::startPayment(const PaymentRequest& request) { - static const auto method = _javaPart->javaClassStatic()->getMethod(jni::alias_ref /* request */)>("startPayment"); - auto __result = method(_javaPart, JPaymentRequest::fromCpp(request)); + std::shared_ptr> JHybridPaymentHandlerSpec::startPayment(const PaymentRequest& request, const std::optional& options) { + static const auto method = _javaPart->javaClassStatic()->getMethod(jni::alias_ref /* request */, jni::alias_ref /* options */)>("startPayment"); + auto __result = method(_javaPart, JPaymentRequest::fromCpp(request), options.has_value() ? JStartPaymentOptions::fromCpp(options.value()) : nullptr); return [&]() { auto __promise = Promise::create(); __result->cthis()->addOnResolvedListener([=](const jni::alias_ref& __boxedResult) { diff --git a/package/nitrogen/generated/android/c++/JHybridPaymentHandlerSpec.hpp b/package/nitrogen/generated/android/c++/JHybridPaymentHandlerSpec.hpp index d068b93..dfcb0c6 100644 --- a/package/nitrogen/generated/android/c++/JHybridPaymentHandlerSpec.hpp +++ b/package/nitrogen/generated/android/c++/JHybridPaymentHandlerSpec.hpp @@ -55,7 +55,7 @@ namespace margelo::nitro::pay { public: // Methods PayServiceStatus payServiceStatus() override; - std::shared_ptr> startPayment(const PaymentRequest& request) override; + std::shared_ptr> startPayment(const PaymentRequest& request, const std::optional& options) override; bool canMakePayments(const std::vector& usingNetworks) override; private: diff --git a/package/nitrogen/generated/android/c++/JPaymentAuthorizationResult.hpp b/package/nitrogen/generated/android/c++/JPaymentAuthorizationResult.hpp new file mode 100644 index 0000000..8853455 --- /dev/null +++ b/package/nitrogen/generated/android/c++/JPaymentAuthorizationResult.hpp @@ -0,0 +1,64 @@ +/// +/// JPaymentAuthorizationResult.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "PaymentAuthorizationResult.hpp" + +#include "JPaymentAuthorizationStatus.hpp" +#include "PaymentAuthorizationStatus.hpp" +#include +#include + +namespace margelo::nitro::pay { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "PaymentAuthorizationResult" and the Kotlin data class "PaymentAuthorizationResult". + */ + struct JPaymentAuthorizationResult final: public jni::JavaClass { + public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/pay/PaymentAuthorizationResult;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct PaymentAuthorizationResult by copying all values to C++. + */ + [[maybe_unused]] + [[nodiscard]] + PaymentAuthorizationResult toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldStatus = clazz->getField("status"); + jni::local_ref status = this->getFieldValue(fieldStatus); + static const auto fieldError = clazz->getField("error"); + jni::local_ref error = this->getFieldValue(fieldError); + return PaymentAuthorizationResult( + status->toCpp(), + error != nullptr ? std::make_optional(error->toStdString()) : std::nullopt + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const PaymentAuthorizationResult& value) { + using JSignature = JPaymentAuthorizationResult(jni::alias_ref, jni::alias_ref); + static const auto clazz = javaClassStatic(); + static const auto create = clazz->getStaticMethod("fromCpp"); + return create( + clazz, + JPaymentAuthorizationStatus::fromCpp(value.status), + value.error.has_value() ? jni::make_jstring(value.error.value()) : nullptr + ); + } + }; + +} // namespace margelo::nitro::pay diff --git a/package/nitrogen/generated/android/c++/JPaymentAuthorizationStatus.hpp b/package/nitrogen/generated/android/c++/JPaymentAuthorizationStatus.hpp new file mode 100644 index 0000000..7a36a00 --- /dev/null +++ b/package/nitrogen/generated/android/c++/JPaymentAuthorizationStatus.hpp @@ -0,0 +1,58 @@ +/// +/// JPaymentAuthorizationStatus.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "PaymentAuthorizationStatus.hpp" + +namespace margelo::nitro::pay { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "PaymentAuthorizationStatus" and the Kotlin enum "PaymentAuthorizationStatus". + */ + struct JPaymentAuthorizationStatus final: public jni::JavaClass { + public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/pay/PaymentAuthorizationStatus;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum PaymentAuthorizationStatus. + */ + [[maybe_unused]] + [[nodiscard]] + PaymentAuthorizationStatus toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(PaymentAuthorizationStatus value) { + static const auto clazz = javaClassStatic(); + switch (value) { + case PaymentAuthorizationStatus::SUCCESS: + static const auto fieldSUCCESS = clazz->getStaticField("SUCCESS"); + return clazz->getStaticFieldValue(fieldSUCCESS); + case PaymentAuthorizationStatus::FAILURE: + static const auto fieldFAILURE = clazz->getStaticField("FAILURE"); + return clazz->getStaticFieldValue(fieldFAILURE); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::pay diff --git a/package/nitrogen/generated/android/c++/JStartPaymentOptions.hpp b/package/nitrogen/generated/android/c++/JStartPaymentOptions.hpp new file mode 100644 index 0000000..0b01990 --- /dev/null +++ b/package/nitrogen/generated/android/c++/JStartPaymentOptions.hpp @@ -0,0 +1,102 @@ +/// +/// JStartPaymentOptions.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "StartPaymentOptions.hpp" + +#include "CNContact.hpp" +#include "CNContactType.hpp" +#include "CNLabeledEmailAddress.hpp" +#include "CNLabeledPhoneNumber.hpp" +#include "CNLabeledPostalAddress.hpp" +#include "CNPhoneNumber.hpp" +#include "CNPostalAddress.hpp" +#include "JCNContact.hpp" +#include "JCNContactType.hpp" +#include "JCNLabeledEmailAddress.hpp" +#include "JCNLabeledPhoneNumber.hpp" +#include "JCNLabeledPostalAddress.hpp" +#include "JCNPhoneNumber.hpp" +#include "JCNPostalAddress.hpp" +#include "JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.hpp" +#include "JPKSecureElementPass.hpp" +#include "JPassActivationState.hpp" +#include "JPaymentAuthorizationResult.hpp" +#include "JPaymentAuthorizationStatus.hpp" +#include "JPaymentMethod.hpp" +#include "JPaymentMethodType.hpp" +#include "JPaymentNetwork.hpp" +#include "JPaymentToken.hpp" +#include "PKSecureElementPass.hpp" +#include "PassActivationState.hpp" +#include "PaymentAuthorizationResult.hpp" +#include "PaymentAuthorizationStatus.hpp" +#include "PaymentMethod.hpp" +#include "PaymentMethodType.hpp" +#include "PaymentNetwork.hpp" +#include "PaymentToken.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace margelo::nitro::pay { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "StartPaymentOptions" and the Kotlin data class "StartPaymentOptions". + */ + struct JStartPaymentOptions final: public jni::JavaClass { + public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/pay/StartPaymentOptions;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct StartPaymentOptions by copying all values to C++. + */ + [[maybe_unused]] + [[nodiscard]] + StartPaymentOptions toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOnAuthorize = clazz->getField("onAuthorize"); + jni::local_ref onAuthorize = this->getFieldValue(fieldOnAuthorize); + return StartPaymentOptions( + onAuthorize != nullptr ? std::make_optional([&]() -> std::function>>>(const PaymentToken& /* token */)> { + if (onAuthorize->isInstanceOf(JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken_cxx::javaClassStatic())) [[likely]] { + auto downcast = jni::static_ref_cast(onAuthorize); + return downcast->cthis()->getFunction(); + } else { + auto onAuthorizeRef = jni::make_global(onAuthorize); + return JNICallable>>>(PaymentToken)>(std::move(onAuthorizeRef)); + } + }()) : std::nullopt + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const StartPaymentOptions& value) { + using JSignature = JStartPaymentOptions(jni::alias_ref); + static const auto clazz = javaClassStatic(); + static const auto create = clazz->getStaticMethod("fromCpp"); + return create( + clazz, + value.onAuthorize.has_value() ? JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken_cxx::fromCpp(value.onAuthorize.value()) : nullptr + ); + } + }; + +} // namespace margelo::nitro::pay diff --git a/package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.kt b/package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.kt new file mode 100644 index 0000000..4df9ef0 --- /dev/null +++ b/package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.kt @@ -0,0 +1,80 @@ +/// +/// Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.pay + +import androidx.annotation.Keep +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip +import dalvik.annotation.optimization.FastNative +import com.margelo.nitro.core.Promise + +/** + * Represents the JavaScript callback `(token: struct) => std::shared_ptr>>>`. + * This can be either implemented in C++ (in which case it might be a callback coming from JS), + * or in Kotlin/Java (in which case it is a native callback). + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType") +fun interface Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken: (PaymentToken) -> Promise> { + /** + * Call the given JS callback. + * @throws Throwable if the JS function itself throws an error, or if the JS function/runtime has already been deleted. + */ + @DoNotStrip + @Keep + override fun invoke(token: PaymentToken): Promise> +} + +/** + * Represents the JavaScript callback `(token: struct) => std::shared_ptr>>>`. + * This is implemented in C++, via a `std::function<...>`. + * The callback might be coming from JS. + */ +@DoNotStrip +@Keep +@Suppress( + "KotlinJniMissingFunction", "unused", + "RedundantSuppression", "RedundantUnitReturnType", "FunctionName", + "ConvertSecondaryConstructorToPrimary", "ClassName", "LocalVariableName", +) +class Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken_cxx: Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken { + @DoNotStrip + @Keep + private val mHybridData: HybridData + + @DoNotStrip + @Keep + private constructor(hybridData: HybridData) { + mHybridData = hybridData + } + + @DoNotStrip + @Keep + override fun invoke(token: PaymentToken): Promise> + = invoke_cxx(token) + + @FastNative + private external fun invoke_cxx(token: PaymentToken): Promise> +} + +/** + * Represents the JavaScript callback `(token: struct) => std::shared_ptr>>>`. + * This is implemented in Java/Kotlin, via a `(PaymentToken) -> Promise>`. + * The callback is always coming from native. + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType", "unused") +class Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken_java(private val function: (PaymentToken) -> Promise>): Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken { + @DoNotStrip + @Keep + override fun invoke(token: PaymentToken): Promise> { + return this.function(token) + } +} diff --git a/package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/HybridPaymentHandlerSpec.kt b/package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/HybridPaymentHandlerSpec.kt index 8e6b662..2c10b76 100644 --- a/package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/HybridPaymentHandlerSpec.kt +++ b/package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/HybridPaymentHandlerSpec.kt @@ -35,7 +35,7 @@ abstract class HybridPaymentHandlerSpec: HybridObject() { @DoNotStrip @Keep - abstract fun startPayment(request: PaymentRequest): Promise + abstract fun startPayment(request: PaymentRequest, options: StartPaymentOptions?): Promise @DoNotStrip @Keep diff --git a/package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/PaymentAuthorizationResult.kt b/package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/PaymentAuthorizationResult.kt new file mode 100644 index 0000000..ce9946c --- /dev/null +++ b/package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/PaymentAuthorizationResult.kt @@ -0,0 +1,56 @@ +/// +/// PaymentAuthorizationResult.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.pay + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import java.util.Objects + + +/** + * Represents the JavaScript object/struct "PaymentAuthorizationResult". + */ +@DoNotStrip +@Keep +data class PaymentAuthorizationResult( + @DoNotStrip + @Keep + val status: PaymentAuthorizationStatus, + @DoNotStrip + @Keep + val error: String? +) { + /* primary constructor */ + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is PaymentAuthorizationResult) return false + return Objects.deepEquals(this.status, other.status) + && Objects.deepEquals(this.error, other.error) + } + + override fun hashCode(): Int { + return arrayOf( + status, + error + ).contentDeepHashCode() + } + + companion object { + /** + * Constructor called from C++ + */ + @DoNotStrip + @Keep + @Suppress("unused") + @JvmStatic + private fun fromCpp(status: PaymentAuthorizationStatus, error: String?): PaymentAuthorizationResult { + return PaymentAuthorizationResult(status, error) + } + } +} diff --git a/package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/PaymentAuthorizationStatus.kt b/package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/PaymentAuthorizationStatus.kt new file mode 100644 index 0000000..2d9fd69 --- /dev/null +++ b/package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/PaymentAuthorizationStatus.kt @@ -0,0 +1,23 @@ +/// +/// PaymentAuthorizationStatus.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.pay + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "PaymentAuthorizationStatus". + */ +@DoNotStrip +@Keep +enum class PaymentAuthorizationStatus(@DoNotStrip @Keep val value: Int) { + SUCCESS(0), + FAILURE(1); + + companion object +} diff --git a/package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/StartPaymentOptions.kt b/package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/StartPaymentOptions.kt new file mode 100644 index 0000000..f96b2a8 --- /dev/null +++ b/package/nitrogen/generated/android/kotlin/com/margelo/nitro/pay/StartPaymentOptions.kt @@ -0,0 +1,55 @@ +/// +/// StartPaymentOptions.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.pay + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import java.util.Objects +import com.margelo.nitro.core.Promise + +/** + * Represents the JavaScript object/struct "StartPaymentOptions". + */ +@DoNotStrip +@Keep +data class StartPaymentOptions( + @DoNotStrip + @Keep + val onAuthorize: Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken? +) { + /** + * Create a new instance of StartPaymentOptions from Kotlin + */ + constructor(onAuthorize: ((token: PaymentToken) -> Promise>)?): + this(onAuthorize?.let { Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken_java(it) }) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is StartPaymentOptions) return false + return Objects.deepEquals(this.onAuthorize, other.onAuthorize) + } + + override fun hashCode(): Int { + return arrayOf( + onAuthorize + ).contentDeepHashCode() + } + + companion object { + /** + * Constructor called from C++ + */ + @DoNotStrip + @Keep + @Suppress("unused") + @JvmStatic + private fun fromCpp(onAuthorize: Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken?): StartPaymentOptions { + return StartPaymentOptions(onAuthorize) + } + } +} diff --git a/package/nitrogen/generated/ios/NitroPay-Swift-Cxx-Bridge.cpp b/package/nitrogen/generated/ios/NitroPay-Swift-Cxx-Bridge.cpp index a7535b4..d647a3b 100644 --- a/package/nitrogen/generated/ios/NitroPay-Swift-Cxx-Bridge.cpp +++ b/package/nitrogen/generated/ios/NitroPay-Swift-Cxx-Bridge.cpp @@ -55,6 +55,31 @@ namespace margelo::nitro::pay::bridge::swift { }; } + // pragma MARK: std::function + Func_void_PaymentAuthorizationResult create_Func_void_PaymentAuthorizationResult(void* NON_NULL swiftClosureWrapper) noexcept { + auto swiftClosure = NitroPay::Func_void_PaymentAuthorizationResult::fromUnsafe(swiftClosureWrapper); + return [swiftClosure = std::move(swiftClosure)](const PaymentAuthorizationResult& result) mutable -> void { + swiftClosure.call(result); + }; + } + + // pragma MARK: std::function>>>(const PaymentToken& /* token */)> + Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken create_Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken(void* NON_NULL swiftClosureWrapper) noexcept { + auto swiftClosure = NitroPay::Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken::fromUnsafe(swiftClosureWrapper); + return [swiftClosure = std::move(swiftClosure)](const PaymentToken& token) mutable -> std::shared_ptr>>> { + auto __result = swiftClosure.call(token); + return __result; + }; + } + + // pragma MARK: std::function>& /* result */)> + Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__ create_Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__(void* NON_NULL swiftClosureWrapper) noexcept { + auto swiftClosure = NitroPay::Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__::fromUnsafe(swiftClosureWrapper); + return [swiftClosure = std::move(swiftClosure)](const std::shared_ptr>& result) mutable -> void { + swiftClosure.call(result); + }; + } + // pragma MARK: std::shared_ptr std::shared_ptr create_std__shared_ptr_HybridPaymentHandlerSpec_(void* NON_NULL swiftUnsafePointer) noexcept { NitroPay::HybridPaymentHandlerSpec_cxx swiftPart = NitroPay::HybridPaymentHandlerSpec_cxx::fromUnsafe(swiftUnsafePointer); diff --git a/package/nitrogen/generated/ios/NitroPay-Swift-Cxx-Bridge.hpp b/package/nitrogen/generated/ios/NitroPay-Swift-Cxx-Bridge.hpp index 8f46d65..f1a8d48 100644 --- a/package/nitrogen/generated/ios/NitroPay-Swift-Cxx-Bridge.hpp +++ b/package/nitrogen/generated/ios/NitroPay-Swift-Cxx-Bridge.hpp @@ -34,6 +34,10 @@ namespace margelo::nitro::pay { struct PKSecureElementPass; } namespace margelo::nitro::pay { enum class PassActivationState; } // Forward declaration of `PayServiceStatus` to properly resolve imports. namespace margelo::nitro::pay { struct PayServiceStatus; } +// Forward declaration of `PaymentAuthorizationResult` to properly resolve imports. +namespace margelo::nitro::pay { struct PaymentAuthorizationResult; } +// Forward declaration of `PaymentAuthorizationStatus` to properly resolve imports. +namespace margelo::nitro::pay { enum class PaymentAuthorizationStatus; } // Forward declaration of `PaymentItemType` to properly resolve imports. namespace margelo::nitro::pay { enum class PaymentItemType; } // Forward declaration of `PaymentItem` to properly resolve imports. @@ -48,6 +52,8 @@ namespace margelo::nitro::pay { enum class PaymentNetwork; } namespace margelo::nitro::pay { struct PaymentResult; } // Forward declaration of `PaymentToken` to properly resolve imports. namespace margelo::nitro::pay { struct PaymentToken; } +// Forward declaration of `StartPaymentOptions` to properly resolve imports. +namespace margelo::nitro::pay { struct StartPaymentOptions; } // Forward declarations of Swift defined types // Forward declaration of `HybridApplePayButtonSpec_cxx` to properly resolve imports. @@ -69,6 +75,8 @@ namespace NitroPay { class HybridPaymentHandlerSpec_cxx; } #include "PKSecureElementPass.hpp" #include "PassActivationState.hpp" #include "PayServiceStatus.hpp" +#include "PaymentAuthorizationResult.hpp" +#include "PaymentAuthorizationStatus.hpp" #include "PaymentItem.hpp" #include "PaymentItemType.hpp" #include "PaymentMethod.hpp" @@ -76,6 +84,7 @@ namespace NitroPay { class HybridPaymentHandlerSpec_cxx; } #include "PaymentNetwork.hpp" #include "PaymentResult.hpp" #include "PaymentToken.hpp" +#include "StartPaymentOptions.hpp" #include #include #include @@ -372,6 +381,127 @@ namespace margelo::nitro::pay::bridge::swift { return optional.value(); } + // pragma MARK: std::shared_ptr> + /** + * Specialized version of `std::shared_ptr>`. + */ + using std__shared_ptr_Promise_PaymentAuthorizationResult__ = std::shared_ptr>; + inline std::shared_ptr> create_std__shared_ptr_Promise_PaymentAuthorizationResult__() noexcept { + return Promise::create(); + } + inline PromiseHolder wrap_std__shared_ptr_Promise_PaymentAuthorizationResult__(std::shared_ptr> promise) noexcept { + return PromiseHolder(std::move(promise)); + } + + // pragma MARK: std::function + /** + * Specialized version of `std::function`. + */ + using Func_void_PaymentAuthorizationResult = std::function; + /** + * Wrapper class for a `std::function`, this can be used from Swift. + */ + class Func_void_PaymentAuthorizationResult_Wrapper final { + public: + explicit Func_void_PaymentAuthorizationResult_Wrapper(std::function&& func): _function(std::make_unique>(std::move(func))) {} + inline void call(PaymentAuthorizationResult result) const noexcept { + _function->operator()(result); + } + private: + std::unique_ptr> _function; + } SWIFT_NONCOPYABLE; + Func_void_PaymentAuthorizationResult create_Func_void_PaymentAuthorizationResult(void* NON_NULL swiftClosureWrapper) noexcept; + inline Func_void_PaymentAuthorizationResult_Wrapper wrap_Func_void_PaymentAuthorizationResult(Func_void_PaymentAuthorizationResult value) noexcept { + return Func_void_PaymentAuthorizationResult_Wrapper(std::move(value)); + } + + // pragma MARK: std::function>>>(const PaymentToken& /* token */)> + /** + * Specialized version of `std::function>>>(const PaymentToken&)>`. + */ + using Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken = std::function>>>(const PaymentToken& /* token */)>; + /** + * Wrapper class for a `std::function>>>(const PaymentToken& / * token * /)>`, this can be used from Swift. + */ + class Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken_Wrapper final { + public: + explicit Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken_Wrapper(std::function>>>(const PaymentToken& /* token */)>&& func): _function(std::make_unique>>>(const PaymentToken& /* token */)>>(std::move(func))) {} + inline std::shared_ptr>>> call(PaymentToken token) const noexcept { + auto __result = _function->operator()(token); + return __result; + } + private: + std::unique_ptr>>>(const PaymentToken& /* token */)>> _function; + } SWIFT_NONCOPYABLE; + Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken create_Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken(void* NON_NULL swiftClosureWrapper) noexcept; + inline Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken_Wrapper wrap_Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken(Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken value) noexcept { + return Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken_Wrapper(std::move(value)); + } + + // pragma MARK: std::shared_ptr>>> + /** + * Specialized version of `std::shared_ptr>>>`. + */ + using std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult____ = std::shared_ptr>>>; + inline std::shared_ptr>>> create_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult____() noexcept { + return Promise>>::create(); + } + inline PromiseHolder>> wrap_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult____(std::shared_ptr>>> promise) noexcept { + return PromiseHolder>>(std::move(promise)); + } + + // pragma MARK: std::function>& /* result */)> + /** + * Specialized version of `std::function>&)>`. + */ + using Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__ = std::function>& /* result */)>; + /** + * Wrapper class for a `std::function>& / * result * /)>`, this can be used from Swift. + */ + class Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult___Wrapper final { + public: + explicit Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult___Wrapper(std::function>& /* result */)>&& func): _function(std::make_unique>& /* result */)>>(std::move(func))) {} + inline void call(std::shared_ptr> result) const noexcept { + _function->operator()(result); + } + private: + std::unique_ptr>& /* result */)>> _function; + } SWIFT_NONCOPYABLE; + Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__ create_Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__(void* NON_NULL swiftClosureWrapper) noexcept; + inline Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult___Wrapper wrap_Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__(Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__ value) noexcept { + return Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult___Wrapper(std::move(value)); + } + + // pragma MARK: std::optional>>>(const PaymentToken& /* token */)>> + /** + * Specialized version of `std::optional>>>(const PaymentToken& / * token * /)>>`. + */ + using std__optional_std__function_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____const_PaymentToken_____token______ = std::optional>>>(const PaymentToken& /* token */)>>; + inline std::optional>>>(const PaymentToken& /* token */)>> create_std__optional_std__function_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____const_PaymentToken_____token______(const std::function>>>(const PaymentToken& /* token */)>& value) noexcept { + return std::optional>>>(const PaymentToken& /* token */)>>(value); + } + inline bool has_value_std__optional_std__function_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____const_PaymentToken_____token______(const std::optional>>>(const PaymentToken& /* token */)>>& optional) noexcept { + return optional.has_value(); + } + inline std::function>>>(const PaymentToken& /* token */)> get_std__optional_std__function_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____const_PaymentToken_____token______(const std::optional>>>(const PaymentToken& /* token */)>>& optional) noexcept { + return optional.value(); + } + + // pragma MARK: std::optional + /** + * Specialized version of `std::optional`. + */ + using std__optional_StartPaymentOptions_ = std::optional; + inline std::optional create_std__optional_StartPaymentOptions_(const StartPaymentOptions& value) noexcept { + return std::optional(value); + } + inline bool has_value_std__optional_StartPaymentOptions_(const std::optional& optional) noexcept { + return optional.has_value(); + } + inline StartPaymentOptions get_std__optional_StartPaymentOptions_(const std::optional& optional) noexcept { + return optional.value(); + } + // pragma MARK: std::shared_ptr /** * Specialized version of `std::shared_ptr`. diff --git a/package/nitrogen/generated/ios/NitroPay-Swift-Cxx-Umbrella.hpp b/package/nitrogen/generated/ios/NitroPay-Swift-Cxx-Umbrella.hpp index a9a3ab3..2c5898d 100644 --- a/package/nitrogen/generated/ios/NitroPay-Swift-Cxx-Umbrella.hpp +++ b/package/nitrogen/generated/ios/NitroPay-Swift-Cxx-Umbrella.hpp @@ -38,6 +38,10 @@ namespace margelo::nitro::pay { struct PKSecureElementPass; } namespace margelo::nitro::pay { enum class PassActivationState; } // Forward declaration of `PayServiceStatus` to properly resolve imports. namespace margelo::nitro::pay { struct PayServiceStatus; } +// Forward declaration of `PaymentAuthorizationResult` to properly resolve imports. +namespace margelo::nitro::pay { struct PaymentAuthorizationResult; } +// Forward declaration of `PaymentAuthorizationStatus` to properly resolve imports. +namespace margelo::nitro::pay { enum class PaymentAuthorizationStatus; } // Forward declaration of `PaymentItemType` to properly resolve imports. namespace margelo::nitro::pay { enum class PaymentItemType; } // Forward declaration of `PaymentItem` to properly resolve imports. @@ -54,6 +58,8 @@ namespace margelo::nitro::pay { struct PaymentRequest; } namespace margelo::nitro::pay { struct PaymentResult; } // Forward declaration of `PaymentToken` to properly resolve imports. namespace margelo::nitro::pay { struct PaymentToken; } +// Forward declaration of `StartPaymentOptions` to properly resolve imports. +namespace margelo::nitro::pay { struct StartPaymentOptions; } // Include C++ defined types #include "ApplePayButtonStyle.hpp" @@ -71,6 +77,8 @@ namespace margelo::nitro::pay { struct PaymentToken; } #include "PKSecureElementPass.hpp" #include "PassActivationState.hpp" #include "PayServiceStatus.hpp" +#include "PaymentAuthorizationResult.hpp" +#include "PaymentAuthorizationStatus.hpp" #include "PaymentItem.hpp" #include "PaymentItemType.hpp" #include "PaymentMethod.hpp" @@ -79,6 +87,7 @@ namespace margelo::nitro::pay { struct PaymentToken; } #include "PaymentRequest.hpp" #include "PaymentResult.hpp" #include "PaymentToken.hpp" +#include "StartPaymentOptions.hpp" #include #include #include diff --git a/package/nitrogen/generated/ios/c++/HybridPaymentHandlerSpecSwift.hpp b/package/nitrogen/generated/ios/c++/HybridPaymentHandlerSpecSwift.hpp index 78e2206..d4122fa 100644 --- a/package/nitrogen/generated/ios/c++/HybridPaymentHandlerSpecSwift.hpp +++ b/package/nitrogen/generated/ios/c++/HybridPaymentHandlerSpecSwift.hpp @@ -50,6 +50,12 @@ namespace margelo::nitro::pay { struct PaymentItem; } namespace margelo::nitro::pay { enum class PaymentItemType; } // Forward declaration of `GooglePayEnvironment` to properly resolve imports. namespace margelo::nitro::pay { enum class GooglePayEnvironment; } +// Forward declaration of `StartPaymentOptions` to properly resolve imports. +namespace margelo::nitro::pay { struct StartPaymentOptions; } +// Forward declaration of `PaymentAuthorizationResult` to properly resolve imports. +namespace margelo::nitro::pay { struct PaymentAuthorizationResult; } +// Forward declaration of `PaymentAuthorizationStatus` to properly resolve imports. +namespace margelo::nitro::pay { enum class PaymentAuthorizationStatus; } #include "PayServiceStatus.hpp" #include "PaymentResult.hpp" @@ -74,6 +80,10 @@ namespace margelo::nitro::pay { enum class GooglePayEnvironment; } #include "PaymentItem.hpp" #include "PaymentItemType.hpp" #include "GooglePayEnvironment.hpp" +#include "StartPaymentOptions.hpp" +#include "PaymentAuthorizationResult.hpp" +#include +#include "PaymentAuthorizationStatus.hpp" #include "NitroPay-Swift-Cxx-Umbrella.hpp" @@ -133,8 +143,8 @@ namespace margelo::nitro::pay { auto __value = std::move(__result.value()); return __value; } - inline std::shared_ptr> startPayment(const PaymentRequest& request) override { - auto __result = _swiftPart.startPayment(std::forward(request)); + inline std::shared_ptr> startPayment(const PaymentRequest& request, const std::optional& options) override { + auto __result = _swiftPart.startPayment(std::forward(request), options); if (__result.hasError()) [[unlikely]] { std::rethrow_exception(__result.error()); } diff --git a/package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.swift b/package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.swift new file mode 100644 index 0000000..6afdbc2 --- /dev/null +++ b/package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.swift @@ -0,0 +1,61 @@ +/// +/// Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * Wraps a Swift `(_ token: PaymentToken) -> Promise>` as a class. + * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. + */ +public final class Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken { + public typealias bridge = margelo.nitro.pay.bridge.swift + + private let closure: (_ token: PaymentToken) -> Promise> + + public init(_ closure: @escaping (_ token: PaymentToken) -> Promise>) { + self.closure = closure + } + + @inline(__always) + public func call(token: PaymentToken) -> bridge.std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult____ { + let __result: Promise> = self.closure(token) + return { () -> bridge.std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult____ in + let __promise = bridge.create_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult____() + let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult____(__promise) + __result + .then({ __result in __promiseHolder.resolve({ () -> bridge.std__shared_ptr_Promise_PaymentAuthorizationResult__ in + let __promise = bridge.create_std__shared_ptr_Promise_PaymentAuthorizationResult__() + let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_PaymentAuthorizationResult__(__promise) + __result + .then({ __result in __promiseHolder.resolve(__result) }) + .catch({ __error in __promiseHolder.reject(__error.toCpp()) }) + return __promise + }()) }) + .catch({ __error in __promiseHolder.reject(__error.toCpp()) }) + return __promise + }() + } + + /** + * Casts this instance to a retained unsafe raw pointer. + * This acquires one additional strong reference on the object! + */ + @inline(__always) + public func toUnsafe() -> UnsafeMutableRawPointer { + return Unmanaged.passRetained(self).toOpaque() + } + + /** + * Casts an unsafe pointer to a `Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken`. + * The pointer has to be a retained opaque `Unmanaged`. + * This removes one strong reference from the object! + */ + @inline(__always) + public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken { + return Unmanaged.fromOpaque(pointer).takeRetainedValue() + } +} diff --git a/package/nitrogen/generated/ios/swift/Func_void_PaymentAuthorizationResult.swift b/package/nitrogen/generated/ios/swift/Func_void_PaymentAuthorizationResult.swift new file mode 100644 index 0000000..2ef68c1 --- /dev/null +++ b/package/nitrogen/generated/ios/swift/Func_void_PaymentAuthorizationResult.swift @@ -0,0 +1,46 @@ +/// +/// Func_void_PaymentAuthorizationResult.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * Wraps a Swift `(_ value: PaymentAuthorizationResult) -> Void` as a class. + * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. + */ +public final class Func_void_PaymentAuthorizationResult { + public typealias bridge = margelo.nitro.pay.bridge.swift + + private let closure: (_ value: PaymentAuthorizationResult) -> Void + + public init(_ closure: @escaping (_ value: PaymentAuthorizationResult) -> Void) { + self.closure = closure + } + + @inline(__always) + public func call(value: PaymentAuthorizationResult) -> Void { + self.closure(value) + } + + /** + * Casts this instance to a retained unsafe raw pointer. + * This acquires one additional strong reference on the object! + */ + @inline(__always) + public func toUnsafe() -> UnsafeMutableRawPointer { + return Unmanaged.passRetained(self).toOpaque() + } + + /** + * Casts an unsafe pointer to a `Func_void_PaymentAuthorizationResult`. + * The pointer has to be a retained opaque `Unmanaged`. + * This removes one strong reference from the object! + */ + @inline(__always) + public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_PaymentAuthorizationResult { + return Unmanaged.fromOpaque(pointer).takeRetainedValue() + } +} diff --git a/package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__.swift b/package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__.swift new file mode 100644 index 0000000..11a03a8 --- /dev/null +++ b/package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__.swift @@ -0,0 +1,66 @@ +/// +/// Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * Wraps a Swift `(_ value: Promise) -> Void` as a class. + * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. + */ +public final class Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__ { + public typealias bridge = margelo.nitro.pay.bridge.swift + + private let closure: (_ value: Promise) -> Void + + public init(_ closure: @escaping (_ value: Promise) -> Void) { + self.closure = closure + } + + @inline(__always) + public func call(value: bridge.std__shared_ptr_Promise_PaymentAuthorizationResult__) -> Void { + self.closure({ () -> Promise in + let __promise = Promise() + let __resolver = { (__result: PaymentAuthorizationResult) in + __promise.resolve(withResult: __result) + } + let __rejecter = { (__error: Error) in + __promise.reject(withError: __error) + } + let __resolverCpp = { () -> bridge.Func_void_PaymentAuthorizationResult in + let __closureWrapper = Func_void_PaymentAuthorizationResult(__resolver) + return bridge.create_Func_void_PaymentAuthorizationResult(__closureWrapper.toUnsafe()) + }() + let __rejecterCpp = { () -> bridge.Func_void_std__exception_ptr in + let __closureWrapper = Func_void_std__exception_ptr(__rejecter) + return bridge.create_Func_void_std__exception_ptr(__closureWrapper.toUnsafe()) + }() + let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_PaymentAuthorizationResult__(value) + __promiseHolder.addOnResolvedListener(__resolverCpp) + __promiseHolder.addOnRejectedListener(__rejecterCpp) + return __promise + }()) + } + + /** + * Casts this instance to a retained unsafe raw pointer. + * This acquires one additional strong reference on the object! + */ + @inline(__always) + public func toUnsafe() -> UnsafeMutableRawPointer { + return Unmanaged.passRetained(self).toOpaque() + } + + /** + * Casts an unsafe pointer to a `Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__`. + * The pointer has to be a retained opaque `Unmanaged`. + * This removes one strong reference from the object! + */ + @inline(__always) + public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__ { + return Unmanaged.fromOpaque(pointer).takeRetainedValue() + } +} diff --git a/package/nitrogen/generated/ios/swift/HybridPaymentHandlerSpec.swift b/package/nitrogen/generated/ios/swift/HybridPaymentHandlerSpec.swift index 219f1cf..5f7cdc1 100644 --- a/package/nitrogen/generated/ios/swift/HybridPaymentHandlerSpec.swift +++ b/package/nitrogen/generated/ios/swift/HybridPaymentHandlerSpec.swift @@ -14,7 +14,7 @@ public protocol HybridPaymentHandlerSpec_protocol: HybridObject { // Methods func payServiceStatus() throws -> PayServiceStatus - func startPayment(request: PaymentRequest) throws -> Promise + func startPayment(request: PaymentRequest, options: StartPaymentOptions?) throws -> Promise func canMakePayments(usingNetworks: [String]) throws -> Bool } diff --git a/package/nitrogen/generated/ios/swift/HybridPaymentHandlerSpec_cxx.swift b/package/nitrogen/generated/ios/swift/HybridPaymentHandlerSpec_cxx.swift index ba545bd..e93680e 100644 --- a/package/nitrogen/generated/ios/swift/HybridPaymentHandlerSpec_cxx.swift +++ b/package/nitrogen/generated/ios/swift/HybridPaymentHandlerSpec_cxx.swift @@ -137,9 +137,9 @@ open class HybridPaymentHandlerSpec_cxx { } @inline(__always) - public final func startPayment(request: PaymentRequest) -> bridge.Result_std__shared_ptr_Promise_PaymentResult___ { + public final func startPayment(request: PaymentRequest, options: bridge.std__optional_StartPaymentOptions_) -> bridge.Result_std__shared_ptr_Promise_PaymentResult___ { do { - let __result = try self.__implementation.startPayment(request: request) + let __result = try self.__implementation.startPayment(request: request, options: options.value) let __resultCpp = { () -> bridge.std__shared_ptr_Promise_PaymentResult__ in let __promise = bridge.create_std__shared_ptr_Promise_PaymentResult__() let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_PaymentResult__(__promise) diff --git a/package/nitrogen/generated/ios/swift/PaymentAuthorizationResult.swift b/package/nitrogen/generated/ios/swift/PaymentAuthorizationResult.swift new file mode 100644 index 0000000..b2d7c50 --- /dev/null +++ b/package/nitrogen/generated/ios/swift/PaymentAuthorizationResult.swift @@ -0,0 +1,47 @@ +/// +/// PaymentAuthorizationResult.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * Represents an instance of `PaymentAuthorizationResult`, backed by a C++ struct. + */ +public typealias PaymentAuthorizationResult = margelo.nitro.pay.PaymentAuthorizationResult + +public extension PaymentAuthorizationResult { + private typealias bridge = margelo.nitro.pay.bridge.swift + + /** + * Create a new instance of `PaymentAuthorizationResult`. + */ + init(status: PaymentAuthorizationStatus, error: String?) { + self.init(status, { () -> bridge.std__optional_std__string_ in + if let __unwrappedValue = error { + return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) + } else { + return .init() + } + }()) + } + + @inline(__always) + var status: PaymentAuthorizationStatus { + return self.__status + } + + @inline(__always) + var error: String? { + return { () -> String? in + if bridge.has_value_std__optional_std__string_(self.__error) { + let __unwrapped = bridge.get_std__optional_std__string_(self.__error) + return String(__unwrapped) + } else { + return nil + } + }() + } +} diff --git a/package/nitrogen/generated/ios/swift/PaymentAuthorizationStatus.swift b/package/nitrogen/generated/ios/swift/PaymentAuthorizationStatus.swift new file mode 100644 index 0000000..8f73a64 --- /dev/null +++ b/package/nitrogen/generated/ios/swift/PaymentAuthorizationStatus.swift @@ -0,0 +1,40 @@ +/// +/// PaymentAuthorizationStatus.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +/** + * Represents the JS union `PaymentAuthorizationStatus`, backed by a C++ enum. + */ +public typealias PaymentAuthorizationStatus = margelo.nitro.pay.PaymentAuthorizationStatus + +public extension PaymentAuthorizationStatus { + /** + * Get a PaymentAuthorizationStatus for the given String value, or + * return `nil` if the given value was invalid/unknown. + */ + init?(fromString string: String) { + switch string { + case "success": + self = .success + case "failure": + self = .failure + default: + return nil + } + } + + /** + * Get the String value this PaymentAuthorizationStatus represents. + */ + var stringValue: String { + switch self { + case .success: + return "success" + case .failure: + return "failure" + } + } +} diff --git a/package/nitrogen/generated/ios/swift/StartPaymentOptions.swift b/package/nitrogen/generated/ios/swift/StartPaymentOptions.swift new file mode 100644 index 0000000..21234fe --- /dev/null +++ b/package/nitrogen/generated/ios/swift/StartPaymentOptions.swift @@ -0,0 +1,71 @@ +/// +/// StartPaymentOptions.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * Represents an instance of `StartPaymentOptions`, backed by a C++ struct. + */ +public typealias StartPaymentOptions = margelo.nitro.pay.StartPaymentOptions + +public extension StartPaymentOptions { + private typealias bridge = margelo.nitro.pay.bridge.swift + + /** + * Create a new instance of `StartPaymentOptions`. + */ + init(onAuthorize: ((_ token: PaymentToken) -> Promise>)?) { + self.init({ () -> bridge.std__optional_std__function_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____const_PaymentToken_____token______ in + if let __unwrappedValue = onAuthorize { + return bridge.create_std__optional_std__function_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____const_PaymentToken_____token______({ () -> bridge.Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken in + let __closureWrapper = Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken(__unwrappedValue) + return bridge.create_Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken(__closureWrapper.toUnsafe()) + }()) + } else { + return .init() + } + }()) + } + + @inline(__always) + var onAuthorize: ((_ token: PaymentToken) -> Promise>)? { + return { () -> ((_ token: PaymentToken) -> Promise>)? in + if bridge.has_value_std__optional_std__function_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____const_PaymentToken_____token______(self.__onAuthorize) { + let __unwrapped = bridge.get_std__optional_std__function_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____const_PaymentToken_____token______(self.__onAuthorize) + return { () -> (PaymentToken) -> Promise> in + let __wrappedFunction = bridge.wrap_Func_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult_____PaymentToken(__unwrapped) + return { (__token: PaymentToken) -> Promise> in + let __result = __wrappedFunction.call(__token) + return { () -> Promise> in + let __promise = Promise>() + let __resolver = { (__result: Promise) in + __promise.resolve(withResult: __result) + } + let __rejecter = { (__error: Error) in + __promise.reject(withError: __error) + } + let __resolverCpp = { () -> bridge.Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__ in + let __closureWrapper = Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__(__resolver) + return bridge.create_Func_void_std__shared_ptr_Promise_PaymentAuthorizationResult__(__closureWrapper.toUnsafe()) + }() + let __rejecterCpp = { () -> bridge.Func_void_std__exception_ptr in + let __closureWrapper = Func_void_std__exception_ptr(__rejecter) + return bridge.create_Func_void_std__exception_ptr(__closureWrapper.toUnsafe()) + }() + let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_std__shared_ptr_Promise_PaymentAuthorizationResult____(__result) + __promiseHolder.addOnResolvedListener(__resolverCpp) + __promiseHolder.addOnRejectedListener(__rejecterCpp) + return __promise + }() + } + }() + } else { + return nil + } + }() + } +} diff --git a/package/nitrogen/generated/shared/c++/HybridPaymentHandlerSpec.hpp b/package/nitrogen/generated/shared/c++/HybridPaymentHandlerSpec.hpp index 1c2839d..dc156b9 100644 --- a/package/nitrogen/generated/shared/c++/HybridPaymentHandlerSpec.hpp +++ b/package/nitrogen/generated/shared/c++/HybridPaymentHandlerSpec.hpp @@ -19,11 +19,15 @@ namespace margelo::nitro::pay { struct PayServiceStatus; } namespace margelo::nitro::pay { struct PaymentResult; } // Forward declaration of `PaymentRequest` to properly resolve imports. namespace margelo::nitro::pay { struct PaymentRequest; } +// Forward declaration of `StartPaymentOptions` to properly resolve imports. +namespace margelo::nitro::pay { struct StartPaymentOptions; } #include "PayServiceStatus.hpp" #include "PaymentResult.hpp" #include #include "PaymentRequest.hpp" +#include "StartPaymentOptions.hpp" +#include #include #include @@ -59,7 +63,7 @@ namespace margelo::nitro::pay { public: // Methods virtual PayServiceStatus payServiceStatus() = 0; - virtual std::shared_ptr> startPayment(const PaymentRequest& request) = 0; + virtual std::shared_ptr> startPayment(const PaymentRequest& request, const std::optional& options) = 0; virtual bool canMakePayments(const std::vector& usingNetworks) = 0; protected: diff --git a/package/nitrogen/generated/shared/c++/PaymentAuthorizationResult.hpp b/package/nitrogen/generated/shared/c++/PaymentAuthorizationResult.hpp new file mode 100644 index 0000000..877dc30 --- /dev/null +++ b/package/nitrogen/generated/shared/c++/PaymentAuthorizationResult.hpp @@ -0,0 +1,90 @@ +/// +/// PaymentAuthorizationResult.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + +// Forward declaration of `PaymentAuthorizationStatus` to properly resolve imports. +namespace margelo::nitro::pay { enum class PaymentAuthorizationStatus; } + +#include "PaymentAuthorizationStatus.hpp" +#include +#include + +namespace margelo::nitro::pay { + + /** + * A struct which can be represented as a JavaScript object (PaymentAuthorizationResult). + */ + struct PaymentAuthorizationResult final { + public: + PaymentAuthorizationStatus status SWIFT_PRIVATE; + std::optional error SWIFT_PRIVATE; + + public: + PaymentAuthorizationResult() = default; + explicit PaymentAuthorizationResult(PaymentAuthorizationStatus status, std::optional error): status(status), error(error) {} + + public: + friend bool operator==(const PaymentAuthorizationResult& lhs, const PaymentAuthorizationResult& rhs) = default; + }; + +} // namespace margelo::nitro::pay + +namespace margelo::nitro { + + // C++ PaymentAuthorizationResult <> JS PaymentAuthorizationResult (object) + template <> + struct JSIConverter final { + static inline margelo::nitro::pay::PaymentAuthorizationResult fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { + jsi::Object obj = arg.asObject(runtime); + return margelo::nitro::pay::PaymentAuthorizationResult( + JSIConverter::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "status"))), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "error"))) + ); + } + static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::pay::PaymentAuthorizationResult& arg) { + jsi::Object obj(runtime); + obj.setProperty(runtime, PropNameIDCache::get(runtime, "status"), JSIConverter::toJSI(runtime, arg.status)); + obj.setProperty(runtime, PropNameIDCache::get(runtime, "error"), JSIConverter>::toJSI(runtime, arg.error)); + return obj; + } + static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { + if (!value.isObject()) { + return false; + } + jsi::Object obj = value.getObject(runtime); + if (!nitro::isPlainObject(runtime, obj)) { + return false; + } + if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "status")))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "error")))) return false; + return true; + } + }; + +} // namespace margelo::nitro diff --git a/package/nitrogen/generated/shared/c++/PaymentAuthorizationStatus.hpp b/package/nitrogen/generated/shared/c++/PaymentAuthorizationStatus.hpp new file mode 100644 index 0000000..360e358 --- /dev/null +++ b/package/nitrogen/generated/shared/c++/PaymentAuthorizationStatus.hpp @@ -0,0 +1,76 @@ +/// +/// PaymentAuthorizationStatus.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + +namespace margelo::nitro::pay { + + /** + * An enum which can be represented as a JavaScript union (PaymentAuthorizationStatus). + */ + enum class PaymentAuthorizationStatus { + SUCCESS SWIFT_NAME(success) = 0, + FAILURE SWIFT_NAME(failure) = 1, + } CLOSED_ENUM; + +} // namespace margelo::nitro::pay + +namespace margelo::nitro { + + // C++ PaymentAuthorizationStatus <> JS PaymentAuthorizationStatus (union) + template <> + struct JSIConverter final { + static inline margelo::nitro::pay::PaymentAuthorizationStatus fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { + std::string unionValue = JSIConverter::fromJSI(runtime, arg); + switch (hashString(unionValue.c_str(), unionValue.size())) { + case hashString("success"): return margelo::nitro::pay::PaymentAuthorizationStatus::SUCCESS; + case hashString("failure"): return margelo::nitro::pay::PaymentAuthorizationStatus::FAILURE; + default: [[unlikely]] + throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum PaymentAuthorizationStatus - invalid value!"); + } + } + static inline jsi::Value toJSI(jsi::Runtime& runtime, margelo::nitro::pay::PaymentAuthorizationStatus arg) { + switch (arg) { + case margelo::nitro::pay::PaymentAuthorizationStatus::SUCCESS: return JSIConverter::toJSI(runtime, "success"); + case margelo::nitro::pay::PaymentAuthorizationStatus::FAILURE: return JSIConverter::toJSI(runtime, "failure"); + default: [[unlikely]] + throw std::invalid_argument("Cannot convert PaymentAuthorizationStatus to JS - invalid value: " + + std::to_string(static_cast(arg)) + "!"); + } + } + static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { + if (!value.isString()) { + return false; + } + std::string unionValue = JSIConverter::fromJSI(runtime, value); + switch (hashString(unionValue.c_str(), unionValue.size())) { + case hashString("success"): + case hashString("failure"): + return true; + default: + return false; + } + } + }; + +} // namespace margelo::nitro diff --git a/package/nitrogen/generated/shared/c++/StartPaymentOptions.hpp b/package/nitrogen/generated/shared/c++/StartPaymentOptions.hpp new file mode 100644 index 0000000..92d795e --- /dev/null +++ b/package/nitrogen/generated/shared/c++/StartPaymentOptions.hpp @@ -0,0 +1,90 @@ +/// +/// StartPaymentOptions.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + +// Forward declaration of `PaymentAuthorizationResult` to properly resolve imports. +namespace margelo::nitro::pay { struct PaymentAuthorizationResult; } +// Forward declaration of `PaymentToken` to properly resolve imports. +namespace margelo::nitro::pay { struct PaymentToken; } + +#include "PaymentAuthorizationResult.hpp" +#include +#include "PaymentToken.hpp" +#include +#include + +namespace margelo::nitro::pay { + + /** + * A struct which can be represented as a JavaScript object (StartPaymentOptions). + */ + struct StartPaymentOptions final { + public: + std::optional>>>(const PaymentToken& /* token */)>> onAuthorize SWIFT_PRIVATE; + + public: + StartPaymentOptions() = default; + explicit StartPaymentOptions(std::optional>>>(const PaymentToken& /* token */)>> onAuthorize): onAuthorize(onAuthorize) {} + + public: + // StartPaymentOptions is not equatable because these properties are not equatable: onAuthorize + }; + +} // namespace margelo::nitro::pay + +namespace margelo::nitro { + + // C++ StartPaymentOptions <> JS StartPaymentOptions (object) + template <> + struct JSIConverter final { + static inline margelo::nitro::pay::StartPaymentOptions fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { + jsi::Object obj = arg.asObject(runtime); + return margelo::nitro::pay::StartPaymentOptions( + JSIConverter>>>(const margelo::nitro::pay::PaymentToken&)>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "onAuthorize"))) + ); + } + static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::pay::StartPaymentOptions& arg) { + jsi::Object obj(runtime); + obj.setProperty(runtime, PropNameIDCache::get(runtime, "onAuthorize"), JSIConverter>>>(const margelo::nitro::pay::PaymentToken&)>>>::toJSI(runtime, arg.onAuthorize)); + return obj; + } + static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { + if (!value.isObject()) { + return false; + } + jsi::Object obj = value.getObject(runtime); + if (!nitro::isPlainObject(runtime, obj)) { + return false; + } + if (!JSIConverter>>>(const margelo::nitro::pay::PaymentToken&)>>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "onAuthorize")))) return false; + return true; + } + }; + +} // namespace margelo::nitro diff --git a/package/src/hooks/__tests__/usePaymentCheckout.integration.test.ts b/package/src/hooks/__tests__/usePaymentCheckout.integration.test.ts index aae2439..8e8d4ec 100644 --- a/package/src/hooks/__tests__/usePaymentCheckout.integration.test.ts +++ b/package/src/hooks/__tests__/usePaymentCheckout.integration.test.ts @@ -207,6 +207,100 @@ describe('usePaymentCheckout integration', () => { ) }) + describe('onAuthorize', () => { + const token = { + paymentMethod: { type: 'credit' }, + transactionIdentifier: 'apple-tx', + paymentData: 'base64-token', + } + + // Mirrors the native handlers: the merchant's outcome decides the result. + const nativeWithAuthorization = async (_request: any, options?: any) => { + try { + const authorization = await options.onAuthorize(token) + return authorization.status === 'success' + ? { success: true, token } + : { + success: false, + error: authorization.error ?? 'Payment authorization failed', + } + } catch (err) { + return { success: false, error: (err as Error).message } + } + } + + const renderWithAuthorize = (onAuthorize: jest.Mock) => { + const rendered = renderHook(() => usePaymentCheckout({ onAuthorize })) + act(() => { + rendered.result.current.addItem('Order', 12) + }) + return rendered + } + + it('does not pass options when onAuthorize is not configured', async () => { + mockStartPayment.mockResolvedValueOnce({ success: true }) + const { result } = renderCheckout() + + act(() => { + result.current.addItem('Order', 12) + }) + await act(async () => { + await result.current.startPayment() + }) + + expect(mockStartPayment.mock.calls[0]).toHaveLength(1) + }) + + it('passes the token to onAuthorize and returns the token on success', async () => { + mockStartPayment.mockImplementationOnce(nativeWithAuthorization) + const onAuthorize = jest.fn().mockResolvedValue({ status: 'success' }) + const { result } = renderWithAuthorize(onAuthorize) + + let paymentResult: any = null + await act(async () => { + paymentResult = await result.current.startPayment() + }) + + expect(onAuthorize).toHaveBeenCalledWith(token) + expect(paymentResult).toEqual({ success: true, token }) + expect(result.current.error).toBeNull() + }) + + it('reports a failure from onAuthorize as an error', async () => { + mockStartPayment.mockImplementationOnce(nativeWithAuthorization) + const onAuthorize = jest + .fn() + .mockResolvedValue({ status: 'failure', error: 'Card declined' }) + const { result } = renderWithAuthorize(onAuthorize) + + let paymentResult: any = null + await act(async () => { + paymentResult = await result.current.startPayment() + }) + + expect(paymentResult).toEqual({ success: false, error: 'Card declined' }) + expect(result.current.error?.message).toBe('Card declined') + }) + + it('hands native a Promise even when onAuthorize throws synchronously', async () => { + mockStartPayment.mockImplementationOnce(nativeWithAuthorization) + const onAuthorize = jest.fn(() => { + throw new Error('Server unreachable') + }) + const { result } = renderWithAuthorize(onAuthorize) + + await act(async () => { + await result.current.startPayment() + }) + + const options = mockStartPayment.mock.calls[0]?.[1] + const returned = options.onAuthorize(token) + expect(returned).toBeInstanceOf(Promise) + await expect(returned).rejects.toThrow('Server unreachable') + expect(result.current.error?.message).toBe('Server unreachable') + }) + }) + it('uses generic error for non-Error thrown values', async () => { mockStartPayment.mockRejectedValueOnce('native crash') const { result } = renderCheckout() diff --git a/package/src/hooks/usePaymentCheckout.ts b/package/src/hooks/usePaymentCheckout.ts index 8e199f4..994a62b 100644 --- a/package/src/hooks/usePaymentCheckout.ts +++ b/package/src/hooks/usePaymentCheckout.ts @@ -10,6 +10,8 @@ import type { PaymentItem, PayServiceStatus, GooglePayEnvironment, + PaymentToken, + PaymentAuthorizationResult, } from '../types' import { createPaymentItem, @@ -79,6 +81,16 @@ export interface UsePaymentCheckoutConfig { * Merchant ID provided by your payment gateway for Google Pay tokenization. */ googlePayGatewayMerchantId?: string + + /** + * Called with the token after the user authorizes the payment. Charge it on + * your server and resolve with the outcome; `startPayment` then resolves + * with `success: false` and your `error` on `'failure'`. + * On iOS the Apple Pay sheet waits for it and shows the outcome. On Android + * it runs after the Google Pay sheet has closed. + * It must settle within 25 seconds, or the payment fails with a timeout. + */ + onAuthorize?: (token: PaymentToken) => Promise } export interface UsePaymentCheckoutReturn { @@ -280,9 +292,15 @@ export function usePaymentCheckout( setResult(null) setError(null) + const { onAuthorize } = config + try { - const paymentResult = - await HybridPaymentHandler.startPayment(paymentRequest) + const paymentResult = onAuthorize + ? await HybridPaymentHandler.startPayment(paymentRequest, { + // `async` turns a synchronous return or throw into the Promise native expects. + onAuthorize: async (token) => onAuthorize(token), + }) + : await HybridPaymentHandler.startPayment(paymentRequest) setResult(paymentResult) @@ -299,7 +317,7 @@ export function usePaymentCheckout( } finally { setIsProcessing(false) } - }, [items, paymentRequest]) + }, [config, items, paymentRequest]) const reset = useCallback(() => { setIsProcessing(false) diff --git a/package/src/specs/PaymentHandler.nitro.ts b/package/src/specs/PaymentHandler.nitro.ts index a8973db..7c634b3 100644 --- a/package/src/specs/PaymentHandler.nitro.ts +++ b/package/src/specs/PaymentHandler.nitro.ts @@ -3,11 +3,15 @@ import type { PayServiceStatus, PaymentRequest, PaymentResult, + StartPaymentOptions, } from '../types/Payment' export interface PaymentHandler extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> { payServiceStatus(): PayServiceStatus - startPayment(request: PaymentRequest): Promise + startPayment( + request: PaymentRequest, + options?: StartPaymentOptions + ): Promise canMakePayments(usingNetworks: string[]): boolean } diff --git a/package/src/types/Payment.ts b/package/src/types/Payment.ts index c1b923b..1f45d63 100644 --- a/package/src/types/Payment.ts +++ b/package/src/types/Payment.ts @@ -99,6 +99,30 @@ export interface PaymentResult { error?: string } +export type PaymentAuthorizationStatus = 'success' | 'failure' + +/** + * What your server decided about an authorized payment. Returned from + * `StartPaymentOptions.onAuthorize`. + */ +export interface PaymentAuthorizationResult { + status: PaymentAuthorizationStatus + // Reported as `PaymentResult.error` when `status` is `'failure'`. + error?: string +} + +export interface StartPaymentOptions { + /** + * Called with the token after the user authorizes the payment. Charge it on + * your server and resolve with the outcome. On iOS the Apple Pay sheet stays + * up until this settles and then shows success or failure. On Android it + * runs after the Google Pay sheet has closed. + * It must settle within 25 seconds, or the payment fails with a timeout and + * its later result is ignored. A rejection counts as `'failure'`. + */ + onAuthorize?: (token: PaymentToken) => Promise +} + export interface PayServiceStatus { canMakePayments: boolean canSetupCards: boolean From 9720cda2075801dea3a6b72e24ab79c9241ffec5 Mon Sep 17 00:00:00 2001 From: Jakub Kasprzyk Date: Tue, 20 Oct 2026 10:25:32 +0200 Subject: [PATCH 2/2] docs: clarify onAuthorize retries, idempotency and Android timing - On iOS, a retry after a failure or timeout calls onAuthorize again with a new token, so key idempotency on the order ID, not the token. - With onAuthorize, don't charge result.token again after startPayment. - Only iOS charges before the sheet closes; Android runs it afterwards. --- README.md | 3 ++- docs/docs/guides/manual-flow.md | 2 +- docs/docs/guides/server-processing.md | 5 +++-- docs/docs/payment-flow.md | 2 +- package/src/hooks/usePaymentCheckout.ts | 3 +++ package/src/types/Payment.ts | 3 +++ 6 files changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 43a4dca..5b90856 100644 --- a/README.md +++ b/README.md @@ -922,7 +922,8 @@ const result = await checkout.startPayment(); // result.success reflects your server's answer ``` -- `onAuthorize` must settle within 25 seconds. Otherwise the payment fails with "Payment authorization timed out", and its later result is ignored, so make your server charge idempotent. +- `onAuthorize` must settle within 25 seconds. Otherwise the payment fails with "Payment authorization timed out", and its later result is ignored. Your server may still have charged the card. +- **Retries on iOS:** after a failure or a timeout the user can retry from the same sheet, and `onAuthorize` runs again with a new token. Make your server charge idempotent per order ID, not per token. - A rejected promise counts as a failure. - **Android:** Google Pay closes its sheet before returning the token, so `onAuthorize` runs afterwards and only decides the `PaymentResult`. diff --git a/docs/docs/guides/manual-flow.md b/docs/docs/guides/manual-flow.md index 3c81186..f057d56 100644 --- a/docs/docs/guides/manual-flow.md +++ b/docs/docs/guides/manual-flow.md @@ -52,7 +52,7 @@ const handlePayment = async () => { } ``` -To charge the token before the Apple Pay sheet closes, pass `startPayment(paymentRequest, { onAuthorize })`. See [Charge before the sheet closes](/docs/guides/server-processing#charge-before-the-sheet-closes). +To charge the token before the Apple Pay sheet closes, pass `startPayment(paymentRequest, { onAuthorize })` and charge inside the callback instead of calling `sendTokenToServer` afterwards, so the token isn't charged twice. See [Charge before the sheet closes](/docs/guides/server-processing#charge-before-the-sheet-closes). ## 4. Render the button diff --git a/docs/docs/guides/server-processing.md b/docs/docs/guides/server-processing.md index 1242e3b..28b5baa 100644 --- a/docs/docs/guides/server-processing.md +++ b/docs/docs/guides/server-processing.md @@ -54,7 +54,8 @@ const { startPayment } = usePaymentCheckout({ }) const result = await startPayment() -// result.success is true only when onAuthorize returned 'success' +// result.success is true only when onAuthorize returned 'success'. +// The token is already charged, so don't send it to your server again. ``` With `HybridPaymentHandler`, pass the same callback as `startPayment(request, { onAuthorize })`. @@ -62,7 +63,7 @@ With `HybridPaymentHandler`, pass the same callback as `startPayment(request, { How it behaves: - **Failure:** `{ status: 'failure', error }` makes the sheet show the failure state. `startPayment` resolves with `success: false` and your `error`. A rejected promise is treated the same way. -- **Retries on iOS:** if the user retries from the sheet after a failure, `onAuthorize` runs again with the new token. The result reflects the last attempt. +- **Retries on iOS:** if the user retries from the sheet after a failure or a timeout, `onAuthorize` runs again with the new token. The result reflects the last attempt. A retry can start while a timed-out charge is still running, so key idempotency on your order ID, not on the token. - **Timeout:** `onAuthorize` must settle within 25 seconds (Apple Pay itself gives up after about 30). Otherwise the payment fails with "Payment authorization timed out" and the late result is ignored. Your server may still have charged the card, so make the charge idempotent and reconcile by order ID. - **Android:** Google Pay closes its sheet before it returns the token, and its basic flow has no step that waits for the merchant. `onAuthorize` still runs, with the same timeout, after the sheet closes, and its outcome decides the `PaymentResult`. The same callback works on both platforms. diff --git a/docs/docs/payment-flow.md b/docs/docs/payment-flow.md index 351e179..89aa8fb 100644 --- a/docs/docs/payment-flow.md +++ b/docs/docs/payment-flow.md @@ -41,7 +41,7 @@ sequenceDiagram ## PaymentResult outcomes -With [`onAuthorize`](/docs/guides/server-processing#charge-before-the-sheet-closes), your backend charges the token before the wallet sheet closes, and the result below already reflects the charge. +With [`onAuthorize`](/docs/guides/server-processing#charge-before-the-sheet-closes), your backend charges the token before `startPayment` resolves: on iOS while the Apple Pay sheet is still up, on Android after the Google Pay sheet has closed. The result below already reflects the charge, so don't send `result.token` to your backend again. ### 1) Success diff --git a/package/src/hooks/usePaymentCheckout.ts b/package/src/hooks/usePaymentCheckout.ts index 55b4ac7..2c6edad 100644 --- a/package/src/hooks/usePaymentCheckout.ts +++ b/package/src/hooks/usePaymentCheckout.ts @@ -85,6 +85,9 @@ export interface UsePaymentCheckoutConfig { * On iOS the Apple Pay sheet waits for it and shows the outcome. On Android * it runs after the Google Pay sheet has closed. * It must settle within 25 seconds, or the payment fails with a timeout. + * On iOS, after a `'failure'` or a timeout the user can retry from the same + * sheet, which calls this again with a new token. Make the charge idempotent + * per order ID, not per token. */ onAuthorize?: (token: PaymentToken) => Promise } diff --git a/package/src/types/Payment.ts b/package/src/types/Payment.ts index 1f45d63..a628f62 100644 --- a/package/src/types/Payment.ts +++ b/package/src/types/Payment.ts @@ -119,6 +119,9 @@ export interface StartPaymentOptions { * runs after the Google Pay sheet has closed. * It must settle within 25 seconds, or the payment fails with a timeout and * its later result is ignored. A rejection counts as `'failure'`. + * On iOS, after a `'failure'` or a timeout the user can retry from the same + * sheet, which calls this again with a new token. Make the charge idempotent + * per order ID, not per token. */ onAuthorize?: (token: PaymentToken) => Promise }