diff --git a/android/build.gradle b/android/build.gradle index 53e937bd..86f57f28 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -28,7 +28,6 @@ def getExtOrIntegerDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['StripeTerminalReactNative_' + name]).toInteger() } - def terminalAndroidSdkVersion = '4.0.0' def reactNativeSdkVersion = getVersionFromNpm() diff --git a/android/src/main/java/com/stripeterminalreactnative/StripeTerminalReactNativeModule.kt b/android/src/main/java/com/stripeterminalreactnative/StripeTerminalReactNativeModule.kt index 336829d7..43fd529b 100644 --- a/android/src/main/java/com/stripeterminalreactnative/StripeTerminalReactNativeModule.kt +++ b/android/src/main/java/com/stripeterminalreactnative/StripeTerminalReactNativeModule.kt @@ -95,6 +95,9 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) : private var cancelReaderConnectionCancellable: Cancelable? = null private var collectInputsCancelable: Cancelable? = null private var collectDataCancelable: Cancelable? = null + private var confirmPaymentIntentCancelable: Cancelable? = null + private var confirmSetupIntentCancelable: Cancelable? = null + private var confirmRefundCancelable: Cancelable? = null private var paymentIntents: HashMap = HashMap() private var setupIntents: HashMap = HashMap() @@ -150,19 +153,49 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) : @ReactMethod @Suppress("unused") fun cancelCollectPaymentMethod(promise: Promise) { - cancelOperation(promise, collectPaymentMethodCancelable, "collectPaymentMethod") + cancelOperation(promise, collectPaymentMethodCancelable, "collectPaymentMethod") { + collectPaymentMethodCancelable = null + } } @ReactMethod @Suppress("unused") fun cancelCollectSetupIntent(promise: Promise) { - cancelOperation(promise, collectSetupIntentCancelable, "collectSetupIntent") + cancelOperation(promise, collectSetupIntentCancelable, "collectSetupIntent") { + collectSetupIntentCancelable = null + } } @ReactMethod @Suppress("unused") fun cancelCollectRefundPaymentMethod(promise: Promise) { - cancelOperation(promise, collectRefundPaymentMethodCancelable, "collectRefundPaymentMethod") + cancelOperation(promise, collectRefundPaymentMethodCancelable, "collectRefundPaymentMethod") { + collectRefundPaymentMethodCancelable = null + } + } + + @ReactMethod + @Suppress("unused") + fun cancelConfirmPaymentIntent(promise: Promise) { + cancelOperation(promise, confirmPaymentIntentCancelable, "confirmPaymentIntent") { + confirmPaymentIntentCancelable = null + } + } + + @ReactMethod + @Suppress("unused") + fun cancelConfirmSetupIntent(promise: Promise) { + cancelOperation(promise, confirmSetupIntentCancelable, "confirmSetupIntent") { + confirmSetupIntentCancelable = null + } + } + + @ReactMethod + @Suppress("unused") + fun cancelConfirmRefund(promise: Promise) { + cancelOperation(promise, confirmRefundCancelable, "confirmRefund") { + confirmRefundCancelable = null + } } @ReactMethod @@ -345,9 +378,11 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) : DiscoveryMethod.TAP_TO_PAY -> { getBoolean(params, "autoReconnectOnUnexpectedDisconnect", true) } + DiscoveryMethod.BLUETOOTH_SCAN, DiscoveryMethod.USB -> { getBoolean(params, "autoReconnectOnUnexpectedDisconnect") } + else -> { false } @@ -393,7 +428,9 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) : @ReactMethod @Suppress("unused") fun cancelReaderReconnection(promise: Promise) { - cancelOperation(promise, cancelReaderConnectionCancellable, "readerReconnection") + cancelOperation(promise, cancelReaderConnectionCancellable, "readerReconnection") { + cancelReaderConnectionCancellable = null + } } @OptIn(OfflineMode::class) @@ -615,7 +652,7 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) : } val config = configBuilder.build() - terminal.confirmPaymentIntent( + confirmPaymentIntentCancelable = terminal.confirmPaymentIntent( paymentIntent, RNPaymentIntentCallback(promise, uuid) { paymentIntents.clear() @@ -722,7 +759,9 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) : @ReactMethod @Suppress("unused") fun cancelInstallingUpdate(promise: Promise) { - cancelOperation(promise, installUpdateCancelable, "installUpdate") + cancelOperation(promise, installUpdateCancelable, "installUpdate") { + installUpdateCancelable = null + } } @ReactMethod @@ -791,8 +830,7 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) : val setupIntent = requireParam(setupIntents[uuid]) { "No SetupIntent was found with the sdkUuid $uuid. The SetupIntent provided must be re-retrieved with retrieveSetupIntent or a new SetupIntent must be created with createSetupIntent." } - - terminal.confirmSetupIntent( + confirmSetupIntentCancelable = terminal.confirmSetupIntent( setupIntent, RNSetupIntentCallback(promise, uuid) { setupIntents[it.id.orEmpty()] = null @@ -885,7 +923,7 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) : @ReactMethod @Suppress("unused") fun confirmRefund(promise: Promise) { - terminal.confirmRefund(RNRefundCallback(promise)) + confirmRefundCancelable = terminal.confirmRefund(RNRefundCallback(promise)) } @OptIn(OfflineMode::class) @@ -1095,7 +1133,9 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) : @ReactMethod @Suppress("unused") fun cancelCollectInputs(promise: Promise) { - cancelOperation(promise, collectInputsCancelable, "collectInputs") + cancelOperation(promise, collectInputsCancelable, "collectInputs") { + collectInputsCancelable = null + } } @ReactMethod diff --git a/dev-app/src/screens/CollectCardPaymentScreen.tsx b/dev-app/src/screens/CollectCardPaymentScreen.tsx index f7f06434..c62a55fc 100644 --- a/dev-app/src/screens/CollectCardPaymentScreen.tsx +++ b/dev-app/src/screens/CollectCardPaymentScreen.tsx @@ -128,6 +128,7 @@ export default function CollectCardPaymentScreen() { confirmPaymentIntent, retrievePaymentIntent, cancelCollectPaymentMethod, + cancelConfirmPaymentIntent, setSimulatedCard, getOfflineStatus, } = useStripeTerminal({ @@ -449,6 +450,7 @@ export default function CollectCardPaymentScreen() { events: [ { name: 'Process', + onBack: cancelConfirmPaymentIntent, description: 'terminal.confirmPaymentIntent', metadata: { paymentIntentId: collectedPaymentIntent.id }, }, diff --git a/dev-app/src/screens/RefundPaymentScreen.tsx b/dev-app/src/screens/RefundPaymentScreen.tsx index a2a2d152..b27a446a 100644 --- a/dev-app/src/screens/RefundPaymentScreen.tsx +++ b/dev-app/src/screens/RefundPaymentScreen.tsx @@ -54,6 +54,7 @@ export default function RefundPaymentScreen() { collectRefundPaymentMethod, cancelCollectRefundPaymentMethod, confirmRefund, + cancelConfirmRefund, setSimulatedCard, } = useStripeTerminal({ onDidRequestReaderInput: (input) => { @@ -144,6 +145,7 @@ export default function RefundPaymentScreen() { events: [ { name: 'Processing', + onBack: cancelConfirmRefund, description: 'terminal.confirmRefund', metadata: _refundMetadata, }, diff --git a/dev-app/src/screens/SetupIntentScreen.tsx b/dev-app/src/screens/SetupIntentScreen.tsx index 1c407d48..3f5775d9 100644 --- a/dev-app/src/screens/SetupIntentScreen.tsx +++ b/dev-app/src/screens/SetupIntentScreen.tsx @@ -40,6 +40,7 @@ export default function SetupIntentScreen() { createSetupIntent, collectSetupIntentPaymentMethod, confirmSetupIntent, + cancelConfirmSetupIntent, retrieveSetupIntent, cancelCollectSetupIntent, } = useStripeTerminal({ @@ -75,6 +76,7 @@ export default function SetupIntentScreen() { events: [ { name: 'Process', + onBack: cancelConfirmSetupIntent, description: 'terminal.confirmSetupIntent', metadata: { setupIntentId: si.id }, }, @@ -113,7 +115,7 @@ export default function SetupIntentScreen() { }); } }, - [addLogs, confirmSetupIntent] + [addLogs, confirmSetupIntent, cancelConfirmSetupIntent] ); const _collectSetupMethod = async (si: SetupIntent.Type) => { diff --git a/ios/StripeTerminalReactNative.m b/ios/StripeTerminalReactNative.m index 65e73190..ce7e4a4c 100644 --- a/ios/StripeTerminalReactNative.m +++ b/ios/StripeTerminalReactNative.m @@ -184,6 +184,21 @@ @interface RCT_EXTERN_MODULE(StripeTerminalReactNative, RCTEventEmitter) rejecter: (RCTPromiseRejectBlock)reject ) +RCT_EXTERN_METHOD( + cancelConfirmPaymentIntent:(RCTPromiseResolveBlock)resolve + rejecter: (RCTPromiseRejectBlock)reject + ) + +RCT_EXTERN_METHOD( + cancelConfirmSetupIntent:(RCTPromiseResolveBlock)resolve + rejecter: (RCTPromiseRejectBlock)reject + ) + +RCT_EXTERN_METHOD( + cancelConfirmRefund:(RCTPromiseResolveBlock)resolve + rejecter: (RCTPromiseRejectBlock)reject + ) + RCT_EXTERN_METHOD( getLoggingToken:(RCTPromiseResolveBlock)resolve rejecter: (RCTPromiseRejectBlock)reject diff --git a/ios/StripeTerminalReactNative.swift b/ios/StripeTerminalReactNative.swift index a8ecbe93..aec34f87 100644 --- a/ios/StripeTerminalReactNative.swift +++ b/ios/StripeTerminalReactNative.swift @@ -57,6 +57,9 @@ class StripeTerminalReactNative: RCTEventEmitter, DiscoveryDelegate, MobileReade var readReusableCardCancelable: Cancelable? = nil var cancelReaderConnectionCancellable: Cancelable? = nil var collectInputsCancellable: Cancelable? = nil + var confirmPaymentIntentCancelable: Cancelable? = nil + var confirmSetupIntentCancelable: Cancelable? = nil + var confirmRefundCancelable: Cancelable? = nil var loggingToken: String? = nil func terminal(_ terminal: Terminal, didUpdateDiscoveredReaders readers: [Reader]) { @@ -132,6 +135,57 @@ class StripeTerminalReactNative: RCTEventEmitter, DiscoveryDelegate, MobileReade } } + @objc(cancelConfirmPaymentIntent:rejecter:) + func cancelConfirmPaymentIntent(resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + guard let cancelable = confirmPaymentIntentCancelable else { + resolve(Errors.createError(code: ErrorCode.cancelFailedAlreadyCompleted, message: "cancelConfirmPaymentIntent could not be canceled because the command has already been canceled or has completed.")) + return + } + cancelable.cancel() { error in + if let error = error as NSError? { + resolve(Errors.createError(nsError: error)) + } + else { + resolve([:]) + } + self.confirmPaymentIntentCancelable = nil + } + } + + @objc(cancelConfirmSetupIntent:rejecter:) + func cancelConfirmSetupIntent(resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + guard let cancelable = confirmSetupIntentCancelable else { + resolve(Errors.createError(code: ErrorCode.cancelFailedAlreadyCompleted, message: "cancelConfirmSetupIntent could not be canceled because the command has already been canceled or has completed.")) + return + } + cancelable.cancel() { error in + if let error = error as NSError? { + resolve(Errors.createError(nsError: error)) + } + else { + resolve([:]) + } + self.confirmSetupIntentCancelable = nil + } + } + + @objc(cancelConfirmRefund:rejecter:) + func cancelConfirmRefund(resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + guard let cancelable = confirmRefundCancelable else { + resolve(Errors.createError(code: ErrorCode.cancelFailedAlreadyCompleted, message: "cancelConfirmRefund could not be canceled because the command has already been canceled or has completed.")) + return + } + cancelable.cancel() { error in + if let error = error as NSError? { + resolve(Errors.createError(nsError: error)) + } + else { + resolve([:]) + } + self.confirmRefundCancelable = nil + } + } + @objc(cancelCollectSetupIntent:rejecter:) func cancelCollectSetupIntent(resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { guard let cancelable = collectSetupIntentCancelable else { @@ -628,7 +682,7 @@ class StripeTerminalReactNative: RCTEventEmitter, DiscoveryDelegate, MobileReade return } - Terminal.shared.confirmPaymentIntent(paymentIntent,confirmConfig: confirmConfig) { pi, error in + self.confirmPaymentIntentCancelable = Terminal.shared.confirmPaymentIntent(paymentIntent,confirmConfig: confirmConfig) { pi, error in if let error = error as NSError? { var result = Errors.createError(nsError: error) if let pi { @@ -871,8 +925,7 @@ class StripeTerminalReactNative: RCTEventEmitter, DiscoveryDelegate, MobileReade return } - - Terminal.shared.confirmSetupIntent(setupIntent) { si, collectError in + self.confirmSetupIntentCancelable = Terminal.shared.confirmSetupIntent(setupIntent) { si, collectError in if let error = collectError as NSError? { resolve(Errors.createError(nsError: error)) } else if let setupIntent = si { @@ -943,7 +996,7 @@ class StripeTerminalReactNative: RCTEventEmitter, DiscoveryDelegate, MobileReade @objc(confirmRefund:rejecter:) func confirmRefund(resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { - Terminal.shared.confirmRefund() { rf, error in + self.confirmRefundCancelable = Terminal.shared.confirmRefund() { rf, error in if let error = error as NSError? { resolve(Errors.createError(nsError: error)) } else { diff --git a/src/StripeTerminalSdk.tsx b/src/StripeTerminalSdk.tsx index 4f155888..959dabc0 100644 --- a/src/StripeTerminalSdk.tsx +++ b/src/StripeTerminalSdk.tsx @@ -131,6 +131,15 @@ export interface StripeTerminalSdkType { cancelCollectSetupIntent(): Promise<{ error?: StripeError; }>; + cancelConfirmPaymentIntent(): Promise<{ + error?: StripeError; + }>; + cancelConfirmSetupIntent(): Promise<{ + error?: StripeError; + }>; + cancelConfirmRefund(): Promise<{ + error?: StripeError; + }>; setSimulatedCard(cardNumber: string): Promise<{ error?: StripeError; }>; diff --git a/src/__tests__/__snapshots__/functions.test.ts.snap b/src/__tests__/__snapshots__/functions.test.ts.snap index 0fddbcc4..ec46c22f 100644 --- a/src/__tests__/__snapshots__/functions.test.ts.snap +++ b/src/__tests__/__snapshots__/functions.test.ts.snap @@ -6,6 +6,9 @@ Object { "cancelCollectPaymentMethod": [Function], "cancelCollectRefundPaymentMethod": [Function], "cancelCollectSetupIntent": [Function], + "cancelConfirmPaymentIntent": [Function], + "cancelConfirmRefund": [Function], + "cancelConfirmSetupIntent": [Function], "cancelDiscovering": [Function], "cancelInstallingUpdate": [Function], "cancelPaymentIntent": [Function], diff --git a/src/functions.ts b/src/functions.ts index 7c67ded0..7fe3881a 100644 --- a/src/functions.ts +++ b/src/functions.ts @@ -680,6 +680,51 @@ export async function cancelCollectSetupIntent(): Promise<{ }, 'cancelCollectSetupIntent')(); } +export async function cancelConfirmPaymentIntent(): Promise<{ + error?: StripeError; +}> { + return Logger.traceSdkMethod(async () => { + try { + await StripeTerminalSdk.cancelConfirmPaymentIntent(); + return {}; + } catch (error) { + return { + error: error as any, + }; + } + }, 'cancelConfirmPaymentIntent')(); +} + +export async function cancelConfirmSetupIntent(): Promise<{ + error?: StripeError; +}> { + return Logger.traceSdkMethod(async () => { + try { + await StripeTerminalSdk.cancelConfirmSetupIntent(); + return {}; + } catch (error) { + return { + error: error as any, + }; + } + }, 'cancelConfirmSetupIntent')(); +} + +export async function cancelConfirmRefund(): Promise<{ + error?: StripeError; +}> { + return Logger.traceSdkMethod(async () => { + try { + await StripeTerminalSdk.cancelConfirmRefund(); + return {}; + } catch (error) { + return { + error: error as any, + }; + } + }, 'cancelConfirmRefund')(); +} + export async function getOfflineStatus(): Promise { return Logger.traceSdkMethod(async () => { try { diff --git a/src/hooks/__tests__/__snapshots__/useStripeTerminal.test.tsx.snap b/src/hooks/__tests__/__snapshots__/useStripeTerminal.test.tsx.snap index 6cbdb382..31416762 100644 --- a/src/hooks/__tests__/__snapshots__/useStripeTerminal.test.tsx.snap +++ b/src/hooks/__tests__/__snapshots__/useStripeTerminal.test.tsx.snap @@ -7,6 +7,9 @@ Object { "cancelCollectPaymentMethod": [Function], "cancelCollectRefundPaymentMethod": [Function], "cancelCollectSetupIntent": [Function], + "cancelConfirmPaymentIntent": [Function], + "cancelConfirmRefund": [Function], + "cancelConfirmSetupIntent": [Function], "cancelDiscovering": [Function], "cancelInstallingUpdate": [Function], "cancelPaymentIntent": [Function], diff --git a/src/hooks/useStripeTerminal.tsx b/src/hooks/useStripeTerminal.tsx index 905e688c..3fa17b8e 100644 --- a/src/hooks/useStripeTerminal.tsx +++ b/src/hooks/useStripeTerminal.tsx @@ -52,6 +52,9 @@ import { clearCachedCredentials, cancelCollectPaymentMethod, cancelCollectSetupIntent, + cancelConfirmPaymentIntent, + cancelConfirmSetupIntent, + cancelConfirmRefund, setSimulatedCard, cancelCollectRefundPaymentMethod, getOfflineStatus, @@ -814,6 +817,48 @@ export function useStripeTerminal(props?: Props) { return response; }, [_isInitialized, setLoading]); + const _cancelConfirmPaymentIntent = useCallback(async () => { + if (!_isInitialized()) { + console.error(NOT_INITIALIZED_ERROR_MESSAGE); + throw Error(NOT_INITIALIZED_ERROR_MESSAGE); + } + setLoading(true); + + const response = await cancelConfirmPaymentIntent(); + + setLoading(false); + + return response; + }, [_isInitialized, setLoading]); + + const _cancelConfirmSetupIntent = useCallback(async () => { + if (!_isInitialized()) { + console.error(NOT_INITIALIZED_ERROR_MESSAGE); + throw Error(NOT_INITIALIZED_ERROR_MESSAGE); + } + setLoading(true); + + const response = await cancelConfirmSetupIntent(); + + setLoading(false); + + return response; + }, [_isInitialized, setLoading]); + + const _cancelConfirmRefund = useCallback(async () => { + if (!_isInitialized()) { + console.error(NOT_INITIALIZED_ERROR_MESSAGE); + throw Error(NOT_INITIALIZED_ERROR_MESSAGE); + } + setLoading(true); + + const response = await cancelConfirmRefund(); + + setLoading(false); + + return response; + }, [_isInitialized, setLoading]); + const _getOfflineStatus = useCallback(async () => { if (!_isInitialized()) { console.error(NOT_INITIALIZED_ERROR_MESSAGE); @@ -1005,6 +1050,9 @@ export function useStripeTerminal(props?: Props) { cancelCollectPaymentMethod: _cancelCollectPaymentMethod, cancelCollectRefundPaymentMethod: _cancelCollectRefundPaymentMethod, cancelCollectSetupIntent: _cancelCollectSetupIntent, + cancelConfirmPaymentIntent: _cancelConfirmPaymentIntent, + cancelConfirmSetupIntent: _cancelConfirmSetupIntent, + cancelConfirmRefund: _cancelConfirmRefund, setSimulatedCard: _setSimulatedCard, getOfflineStatus: _getOfflineStatus, getPaymentStatus: _getPaymentStatus,