Skip to content

Commit

Permalink
Handle other Android connection methods (stripe#111)
Browse files Browse the repository at this point in the history
* chore: handle other connection methods

* chore: add proper e2e tests

* fix: e2e tests

* fix: e2e test

* fix indentations

* fix methods params

* fix merge issues
  • Loading branch information
arekkubaczkowski authored Mar 22, 2022
1 parent 163e22e commit 18db810
Show file tree
Hide file tree
Showing 11 changed files with 478 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,13 @@ internal fun mapFromReaderDisplayMessage(message: ReaderDisplayMessage): String
}
}

internal fun mapFromReaderEvent(event: ReaderEvent): String {
return when (event) {
ReaderEvent.CARD_INSERTED -> "cardInserted"
ReaderEvent.CARD_REMOVED -> "cardRemoved"
}
}

internal fun mapFromCharge(reader: Charge): WritableMap = WritableNativeMap().apply {
putString("id", reader.id)
putString("status", reader.status)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,8 @@ import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEm
import com.stripe.stripeterminal.Terminal
import com.stripe.stripeterminal.TerminalApplicationDelegate.onCreate
import com.stripe.stripeterminal.TerminalApplicationDelegate.onTrimMemory
import com.stripe.stripeterminal.external.callable.BluetoothReaderListener
import com.stripe.stripeterminal.external.callable.Callback
import com.stripe.stripeterminal.external.callable.Cancelable
import com.stripe.stripeterminal.external.callable.DiscoveryListener
import com.stripe.stripeterminal.external.callable.LocationListCallback
import com.stripe.stripeterminal.external.callable.PaymentIntentCallback
import com.stripe.stripeterminal.external.callable.PaymentMethodCallback
import com.stripe.stripeterminal.external.callable.ReaderCallback
import com.stripe.stripeterminal.external.callable.RefundCallback
import com.stripe.stripeterminal.external.callable.SetupIntentCallback
import com.stripe.stripeterminal.external.callable.TerminalListener
import com.stripe.stripeterminal.external.models.Cart
import com.stripe.stripeterminal.external.models.ConnectionConfiguration
import com.stripe.stripeterminal.external.models.ConnectionStatus
import com.stripe.stripeterminal.external.models.DiscoveryConfiguration
import com.stripe.stripeterminal.external.models.ListLocationsParameters
import com.stripe.stripeterminal.external.models.Location
import com.stripe.stripeterminal.external.models.PaymentIntent
import com.stripe.stripeterminal.external.models.PaymentIntentParameters
import com.stripe.stripeterminal.external.models.PaymentMethod
import com.stripe.stripeterminal.external.models.PaymentStatus
import com.stripe.stripeterminal.external.models.ReadReusableCardParameters
import com.stripe.stripeterminal.external.models.Reader
import com.stripe.stripeterminal.external.models.ReaderDisplayMessage
import com.stripe.stripeterminal.external.models.ReaderInputOptions
import com.stripe.stripeterminal.external.models.ReaderSoftwareUpdate
import com.stripe.stripeterminal.external.models.Refund
import com.stripe.stripeterminal.external.models.RefundParameters
import com.stripe.stripeterminal.external.models.SetupIntent
import com.stripe.stripeterminal.external.models.SetupIntentCancellationParameters
import com.stripe.stripeterminal.external.models.SetupIntentParameters
import com.stripe.stripeterminal.external.models.SimulatorConfiguration
import com.stripe.stripeterminal.external.models.TerminalException
import com.stripe.stripeterminal.external.callable.*
import com.stripe.stripeterminal.external.models.*
import com.stripeterminalreactnative.ReactNativeConstants.CHANGE_CONNECTION_STATUS
import com.stripeterminalreactnative.ReactNativeConstants.CHANGE_PAYMENT_STATUS
import com.stripeterminalreactnative.ReactNativeConstants.FETCH_TOKEN_PROVIDER
Expand Down Expand Up @@ -251,9 +220,7 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
return
}


val simulated = getBoolean(params, "simulated")

val config = DiscoveryConfiguration(0, discoveryMethod, simulated)

discoverCancelable = Terminal.getInstance().discoverReaders(
Expand Down Expand Up @@ -488,6 +455,176 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
)
}

@ReactMethod
fun connectLocalMobileReader(params: ReadableMap, promise: Promise) {
val reader = getMapOr(params, "reader") ?: run {
promise.resolve(
createError(
TerminalException(
TerminalException.TerminalErrorCode.INVALID_REQUIRED_PARAMETER,
"You must provide a reader object"
)
)
)
return
}

val readerId = getStringOr(reader, "serialNumber") as String

val selectedReader = discoveredReadersList.find {
it.serialNumber == readerId
} ?: run {
promise.resolve(
createError(
TerminalException(
TerminalException.TerminalErrorCode.INVALID_REQUIRED_PARAMETER,
"Could not find reader with id $readerId"
)
)
)
return
}

val locationId = getStringOr(params, "locationId") ?: selectedReader.location?.id.orEmpty()

val connectionConfig = ConnectionConfiguration.LocalMobileConnectionConfiguration(
locationId
)

Terminal.getInstance().connectLocalMobileReader(
selectedReader,
connectionConfig,
object : ReaderCallback {
override fun onSuccess(reader: Reader) {
val result = WritableNativeMap()
result.putMap("reader", mapFromReader(reader))
promise.resolve(result)
}

override fun onFailure(e: TerminalException) {
promise.resolve(createError(e))
}
}
)
}

@ReactMethod
fun connectEmbeddedReader(params: ReadableMap, promise: Promise) {
val reader = getMapOr(params, "reader") ?: run {
promise.resolve(
createError(
TerminalException(
TerminalException.TerminalErrorCode.INVALID_REQUIRED_PARAMETER,
"You must provide a reader object"
)
)
)
return
}

val readerId = getStringOr(reader, "serialNumber") as String

val selectedReader = discoveredReadersList.find {
it.serialNumber == readerId
} ?: run {
promise.resolve(
createError(
TerminalException(
TerminalException.TerminalErrorCode.INVALID_REQUIRED_PARAMETER,
"Could not find reader with id $readerId"
)
)
)
return
}

val locationId = getStringOr(params, "locationId") ?: selectedReader.location?.id.orEmpty()

val connectionConfig = ConnectionConfiguration.EmbeddedConnectionConfiguration(
locationId
)

Terminal.getInstance().connectEmbeddedReader(
selectedReader,
connectionConfig,
object : ReaderCallback {
override fun onSuccess(reader: Reader) {
val result = WritableNativeMap()
result.putMap("reader", mapFromReader(reader))
promise.resolve(result)
}

override fun onFailure(e: TerminalException) {
promise.resolve(createError(e))
}
}
)
}

@ReactMethod
fun connectHandoffReader(params: ReadableMap, promise: Promise) {
val reader = getMapOr(params, "reader") ?: run {
promise.resolve(
createError(
TerminalException(
TerminalException.TerminalErrorCode.INVALID_REQUIRED_PARAMETER,
"You must provide a reader object"
)
)
)
return
}

val readerId = getStringOr(reader, "serialNumber") as String

val selectedReader = discoveredReadersList.find {
it.serialNumber == readerId
} ?: run {
promise.resolve(
createError(
TerminalException(
TerminalException.TerminalErrorCode.INVALID_REQUIRED_PARAMETER,
"Could not find reader with id $readerId"
)
)
)
return
}

val locationId = getStringOr(params, "locationId") ?: selectedReader.location?.id.orEmpty()

val connectionConfig = ConnectionConfiguration.HandoffConnectionConfiguration(
locationId
)

val listener: HandoffReaderListener = object : HandoffReaderListener {
override fun onReportReaderEvent(event: ReaderEvent) {
super.onReportReaderEvent(event)

val result = WritableNativeMap()
result.putString("event", mapFromReaderEvent(event))
sendEvent("didRequestReaderInput", result)
}
}

Terminal.getInstance().connectHandoffReader(
selectedReader,
connectionConfig,
listener,
object : ReaderCallback {
override fun onSuccess(reader: Reader) {
val result = WritableNativeMap()
result.putMap("reader", mapFromReader(reader))
promise.resolve(result)
}

override fun onFailure(e: TerminalException) {
promise.resolve(createError(e))
}
}
)
}

@ReactMethod
@Suppress("unused")
fun disconnectReader(promise: Promise) {
Expand Down
24 changes: 24 additions & 0 deletions e2e/app.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,37 @@ describe('Payments', () => {
});

it('Change discovery method to bluetooth proximity', async () => {
if (device.getPlatform() !== 'ios') {
return;
}
await changeDiscoveryMethod('Bluetooth Proximity');
});

it('Change discovery method to Internet', async () => {
await changeDiscoveryMethod('Internet');
});

it('Change discovery method to Embedded', async () => {
if (device.getPlatform() !== 'android') {
return;
}
await changeDiscoveryMethod('Embedded');
});

it('Change discovery method to LocalMobile', async () => {
if (device.getPlatform() !== 'android') {
return;
}
await changeDiscoveryMethod('Local mobile');
});

it('Change discovery method to Handoff', async () => {
if (device.getPlatform() !== 'android') {
return;
}
await changeDiscoveryMethod('Handoff');
});

// temporary skipped due to bug in stripe-termina-ios that connects the device despite an error.
//
it('Required update impossible due to low battery', async () => {
Expand Down
65 changes: 63 additions & 2 deletions example/src/screens/DiscoverReadersScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ export default function DiscoverReadersScreen() {
discoveredReaders,
connectInternetReader,
simulateReaderUpdate,
connectEmbeddedReader,
connectLocalMobileReader,
connectHandoffReader,
} = useStripeTerminal({
onFinishDiscoveringReaders: (finishError) => {
if (finishError) {
Expand Down Expand Up @@ -151,9 +154,19 @@ export default function DiscoverReadersScreen() {
discoveryMethod === 'bluetoothScan' ||
discoveryMethod === 'bluetoothProximity'
) {
const result = await handleConnectBluetoothReader(reader);
error = result.error;
const results = await handleConnectBluetoothReader(reader);
error = results.error;
} else if (discoveryMethod === 'localMobile') {
const results = await handleConnectLocalMobileReader(reader);
error = results.error;
} else if (discoveryMethod === 'handoff') {
const results = await handleConnectHandoffReader(reader);
error = results.error;
} else if (discoveryMethod === 'embedded') {
const results = await handleConnectEmbeddedReader(reader);
error = results.error;
}

if (error) {
setConnectingReader(undefined);
Alert.alert(error.code, error.message);
Expand All @@ -162,6 +175,54 @@ export default function DiscoverReadersScreen() {
}
};

const handleConnectEmbeddedReader = async (reader: Reader.Type) => {
setConnectingReader(reader);

const { reader: connectedReader, error } = await connectEmbeddedReader({
reader,
locationId: selectedLocation?.id,
});

if (error) {
console.log('connectEmbeddedReader error:', error);
} else {
console.log('Reader connected successfully', connectedReader);
}
return { error };
};

const handleConnectHandoffReader = async (reader: Reader.Type) => {
setConnectingReader(reader);

const { reader: connectedReader, error } = await connectHandoffReader({
reader,
locationId: selectedLocation?.id,
});

if (error) {
console.log('connectHandoffReader error:', error);
} else {
console.log('Reader connected successfully', connectedReader);
}
return { error };
};

const handleConnectLocalMobileReader = async (reader: Reader.Type) => {
setConnectingReader(reader);

const { reader: connectedReader, error } = await connectLocalMobileReader({
reader,
locationId: selectedLocation?.id,
});

if (error) {
console.log('connectLocalMobileReader error:', error);
} else {
console.log('Reader connected successfully', connectedReader);
}
return { error };
};

const handleConnectBluetoothReader = async (reader: Reader.Type) => {
setConnectingReader(reader);

Expand Down
Loading

0 comments on commit 18db810

Please sign in to comment.