Skip to content

Commit

Permalink
support offline mode (#564)
Browse files Browse the repository at this point in the history
* support offline mode.

* update codes.

* support ios offline mode

* update code

* fix iOS delegate codes issues.

* update codes.

* debug offline mode and update.

* update codes.

* remove offline mode transaction limit.

* debug getOfflineStatus method.

* add DatabaseScreen.

* fix lint & debug on iOS.

* fix lint issue.

* fix issues mentioned in the comments.

* fix e2e test issue.

* update pods

* Trigger Build

* update pod shas

* rebuild detox

* delete rebuild

* rebuild detox again

---------

Co-authored-by: EricLin-BBpos <[email protected]>
Co-authored-by: Nazli Yurdakul <[email protected]>
  • Loading branch information
3 people authored Nov 27, 2023
1 parent ce3cb02 commit ffb7cae
Show file tree
Hide file tree
Showing 28 changed files with 2,362 additions and 2,197 deletions.
24 changes: 24 additions & 0 deletions android/src/main/java/com/stripeterminalreactnative/Mappers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import com.stripe.stripeterminal.external.models.ConnectionStatus
import com.stripe.stripeterminal.external.models.DeviceType
import com.stripe.stripeterminal.external.models.Location
import com.stripe.stripeterminal.external.models.LocationStatus
import com.stripe.stripeterminal.external.models.NetworkStatus
import com.stripe.stripeterminal.external.models.OfflineStatus
import com.stripe.stripeterminal.external.models.PaymentIntent
import com.stripe.stripeterminal.external.models.PaymentIntentStatus
import com.stripe.stripeterminal.external.models.PaymentMethod
Expand Down Expand Up @@ -487,3 +489,25 @@ fun mapFromReceiptDetails(receiptDetails: ReceiptDetails?): ReadableMap =
putString("tsi", receiptDetails?.tsi)
putString("tvr", receiptDetails?.tvr)
}

internal fun mapFromNetworkStatus(status: NetworkStatus): String {
return when (status) {
NetworkStatus.ONLINE -> "online"
NetworkStatus.OFFLINE -> "offline"
NetworkStatus.UNKNOWN -> "unknown"
}
}

fun mapFromOfflineStatus(offlineStatus: OfflineStatus): ReadableMap =
nativeMapOf {
putString("networkStatus", mapFromNetworkStatus(offlineStatus.sdk.networkStatus))
putInt("offlinePaymentsCount", offlineStatus.sdk.offlinePaymentsCount)

val map = nativeMapOf {
offlineStatus.sdk.offlinePaymentAmountsByCurrency.forEach {
putInt(it.key, it.value.toInt())
}
}
putMap("offlinePaymentAmountsByCurrency", map)
}

Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ enum class ReactNativeConstants(val listenerName: String) {
START_READER_RECONNECT("didStartReaderReconnect"),
READER_RECONNECT_SUCCEED("didSucceedReaderReconnect"),
READER_RECONNECT_FAIL("didFailReaderReconnect"),
CHANGE_OFFLINE_STATUS("didChangeOfflineStatus"),
FORWARD_PAYMENT_INTENT("didForwardPaymentIntent"),
REPORT_FORWARDING_ERROR("didReportForwardingError"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ import com.stripe.stripeterminal.external.models.CardPresentParameters
import com.stripe.stripeterminal.external.models.CardPresentRoutingOptionParameters
import com.stripe.stripeterminal.external.models.Cart
import com.stripe.stripeterminal.external.models.CollectConfiguration
import com.stripe.stripeterminal.external.models.CreateConfiguration
import com.stripe.stripeterminal.external.models.DiscoveryConfiguration
import com.stripe.stripeterminal.external.models.ListLocationsParameters
import com.stripe.stripeterminal.external.models.OfflineBehavior
import com.stripe.stripeterminal.external.models.PaymentIntent
import com.stripe.stripeterminal.external.models.PaymentIntentParameters
import com.stripe.stripeterminal.external.models.PaymentMethodOptionsParameters
Expand All @@ -46,6 +48,7 @@ import com.stripeterminalreactnative.ktx.connectReader
import com.stripeterminalreactnative.listener.RNBluetoothReaderListener
import com.stripeterminalreactnative.listener.RNDiscoveryListener
import com.stripeterminalreactnative.listener.RNHandoffReaderListener
import com.stripeterminalreactnative.listener.RNOfflineListener
import com.stripeterminalreactnative.listener.RNReaderReconnectionListener
import com.stripeterminalreactnative.listener.RNTerminalListener
import com.stripeterminalreactnative.listener.RNUsbReaderListener
Expand Down Expand Up @@ -91,6 +94,7 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :

override fun getName(): String = "StripeTerminalReactNative"

@OptIn(OfflineMode::class)
@ReactMethod
@Suppress("unused")
fun initialize(params: ReadableMap, promise: Promise) = withExceptionResolver(promise) {
Expand All @@ -101,7 +105,8 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
this.context.applicationContext,
mapToLogLevel(params.getString("logLevel")),
tokenProvider,
RNTerminalListener(context)
RNTerminalListener(context),
RNOfflineListener(context),
)
NativeTypeFactory.writableNativeMap()
} else {
Expand Down Expand Up @@ -323,6 +328,7 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
getBoolean(paymentMethodOptions, "requestIncrementalAuthorizationSupport")
val requestedPriority = paymentMethodOptions?.getString("requestedPriority")
val captureMethod = params.getString("captureMethod")
val offlineBehavior = params.getString("offlineBehavior")

val paymentMethodTypes = paymentMethods?.toArrayList()?.mapNotNull {
if (it is String) PaymentMethodType.valueOf(it.uppercase())
Expand Down Expand Up @@ -397,11 +403,20 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
}
}

val offlineBehaviorParam = offlineBehavior.let {
when (it) {
"prefer_online" -> OfflineBehavior.PREFER_ONLINE
"require_online" -> OfflineBehavior.REQUIRE_ONLINE
"force_offline" -> OfflineBehavior.FORCE_OFFLINE
else -> OfflineBehavior.PREFER_ONLINE
}
}

val uuid = UUID.randomUUID().toString()

terminal.createPaymentIntent(intentParams.build(), RNPaymentIntentCallback(promise, uuid) { pi ->
paymentIntents[uuid] = pi
})
}, CreateConfiguration(offlineBehaviorParam))
}

@OptIn(OfflineMode::class)
Expand Down Expand Up @@ -673,6 +688,40 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
terminal.confirmRefund(RNRefundCallback(promise))
}

@OptIn(OfflineMode::class)
@ReactMethod
@Suppress("unused")
fun getOfflineStatus(promise: Promise) {
promise.resolve(
nativeMapOf {
val sdkMap = nativeMapOf {
putInt("offlinePaymentsCount", terminal.offlineStatus.sdk.offlinePaymentsCount)

val map = nativeMapOf {
terminal.offlineStatus.sdk.offlinePaymentAmountsByCurrency.forEach {
putInt(it.key, it.value.toInt())
}
}
putMap("offlinePaymentAmountsByCurrency", map)
}

val readerMap = nativeMapOf {
putInt("offlinePaymentsCount", terminal.offlineStatus.reader?.offlinePaymentsCount?:0)

val map = nativeMapOf {
terminal.offlineStatus.reader?.offlinePaymentAmountsByCurrency?.forEach {
putInt(it.key, it.value.toInt())
}
}
putMap("offlinePaymentAmountsByCurrency", map)
}

putMap("sdk", sdkMap)
putMap("reader", readerMap)
}
)
}

private fun cancelOperation(
promise: Promise,
cancelable: Cancelable?,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.stripeterminalreactnative.listener

import com.facebook.react.bridge.ReactApplicationContext
import com.stripe.stripeterminal.external.OfflineMode
import com.stripe.stripeterminal.external.callable.OfflineListener
import com.stripe.stripeterminal.external.models.OfflineStatus
import com.stripe.stripeterminal.external.models.PaymentIntent
import com.stripe.stripeterminal.external.models.TerminalException
import com.stripeterminalreactnative.ReactExtensions.sendEvent
import com.stripeterminalreactnative.ReactNativeConstants
import com.stripeterminalreactnative.createError
import com.stripeterminalreactnative.mapFromOfflineStatus
import com.stripeterminalreactnative.mapFromPaymentIntent
import com.stripeterminalreactnative.nativeMapOf

@OptIn(OfflineMode::class)
class RNOfflineListener(
private val context: ReactApplicationContext,
): OfflineListener {
override fun onOfflineStatusChange(offlineStatus: OfflineStatus) {
context.sendEvent(ReactNativeConstants.CHANGE_OFFLINE_STATUS.listenerName) {
putMap("result", mapFromOfflineStatus(offlineStatus))
}
}

override fun onPaymentIntentForwarded(paymentIntent: PaymentIntent, e: TerminalException?) {
context.sendEvent(ReactNativeConstants.FORWARD_PAYMENT_INTENT.listenerName) {
putMap("result", mapFromPaymentIntent(paymentIntent, ""))
putMap("error", nativeMapOf {
putString("code", e?.errorCode.toString())
putString("message", e?.errorMessage)
})
}
}

override fun onForwardingFailure(e: TerminalException) {
context.sendEvent(ReactNativeConstants.REPORT_FORWARDING_ERROR.listenerName) {
putMap("result", createError(e))
}
}
}
1 change: 1 addition & 0 deletions bitrise.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ workflows:
# This is a terrible hack, as I haven't worked out how Bitrise's `pod install` step interacts with the rbenv set in this app. You definitely shouldn't copy this.
cd dev-app/ios && asdf install ruby 3.2.2 && bundle install && \
gem install cocoapods -v 1.14.2 && pod install && cd - && \
npm rebuild detox
echo "Checking for diffs in pod lockfile, if this fails please ensure all dependencies are up to date" && \
git diff --exit-code
title: Set up cocoapods
Expand Down
14 changes: 7 additions & 7 deletions dev-app/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -487,11 +487,11 @@ PODS:
- React-jsi (= 0.72.7)
- React-logger (= 0.72.7)
- React-perflogger (= 0.72.7)
- RNCAsyncStorage (1.19.5):
- RNCAsyncStorage (1.19.8):
- React-Core
- RNCPicker (2.5.1):
- React-Core
- RNGestureHandler (2.13.4):
- RNGestureHandler (2.14.0):
- RCT-Folly (= 2021.07.22.00)
- React-Core
- RNScreens (3.27.0):
Expand Down Expand Up @@ -689,9 +689,9 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/yoga"

SPEC CHECKSUMS:
boost: a7c83b31436843459a1961bfd74b96033dc77234
boost: 57d2868c099736d80fcd648bf211b4431e51a558
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: 5fbbff1d7734827299274638deb8ba3024f6c597
FBReactNativeSpec: 638095fe8a01506634d77b260ef8a322019ac671
Flipper: 6edb735e6c3e332975d1b17956bcc584eccf5818
Expand All @@ -703,7 +703,7 @@ SPEC CHECKSUMS:
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
FlipperKit: 2efad7007d6745a3f95e4034d547be637f89d3f6
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 476ee3e89abb49e07f822b48323c51c57124b572
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
hermes-engine: 9180d43df05c1ed658a87cc733dc3044cf90c00a
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
Expand Down Expand Up @@ -740,9 +740,9 @@ SPEC CHECKSUMS:
React-runtimescheduler: 7649c3b46c8dee1853691ecf60146a16ae59253c
React-utils: 56838edeaaf651220d1e53cd0b8934fb8ce68415
ReactCommon: 5f704096ccf7733b390f59043b6fa9cc180ee4f6
RNCAsyncStorage: f2974eca860c16a3e56eea5771fda8d12e2d2057
RNCAsyncStorage: 687bb9e85dd3d45b966662440dcfc0cd962347e6
RNCPicker: 529d564911e93598cc399b56cc0769ce3675f8c8
RNGestureHandler: 6e46dde1f87e5f018a54fe5d40cd0e0b942b49ee
RNGestureHandler: 32a01c29ecc9bb0b5bf7bc0a33547f61b4dc2741
RNScreens: 3c2d122f5e08c192e254c510b212306da97d2581
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
stripe-terminal-react-native: 61209e78f472f094096b940ddfa2316f90cbead0
Expand Down
3 changes: 2 additions & 1 deletion dev-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"react-native-gesture-handler": "^2.13.4",
"react-native-keyboard-aware-scroll-view": "^0.9.5",
"react-native-safe-area-context": "^4.7.4",
"react-native-screens": "^3.27.0"
"react-native-screens": "^3.27.0",
"react-native-root-toast":"^3.5.1"
},
"devDependencies": {
"@babel/cli": "^7.15.7",
Expand Down
6 changes: 6 additions & 0 deletions dev-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
import { Alert, LogBox } from 'react-native';

import { AppContext } from './AppContext';
import DatabaseScreen from './screens/DatabaseScreen';

export type RouteParamList = {
UpdateReader: {
Expand Down Expand Up @@ -214,6 +215,11 @@ export default function App() {
options={{ headerTitle: 'Merchant Select' }}
component={MerchantSelectScreen}
/>
<Stack.Screen
name="DatabaseScreen"
options={{ headerTitle: 'DatabaseScreen' }}
component={DatabaseScreen}
/>
<Stack.Screen
name="DiscoverReadersScreen"
options={{ headerTitle: 'Discovery' }}
Expand Down
1 change: 1 addition & 0 deletions dev-app/src/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export const colors = {
slate: '#0A2540',
gray: '#A3B3C1',
red: '#ff3a30',
green: '#00FF00',
};
Loading

0 comments on commit ffb7cae

Please sign in to comment.