Skip to content

Commit

Permalink
Use sdk_uuid for payment and setup intents (#551)
Browse files Browse the repository at this point in the history
* pass pi for collect confirm

* cancel payment intent

* adnroid updates to use uuid

* Pass si for confirmSetupIntent、cancelSetupIntent

* android updates to use uuid for SetupIntent

* update Gemfile.

* update ruby

* fix issues of review

* fix param issue.

* improve the error messaging & remove cancelReadReusableCard.

* improve the error messaging.

* update error message

---------

Co-authored-by: EricLin-BBpos <[email protected]>
Co-authored-by: Ian Lin <[email protected]>
  • Loading branch information
3 people authored Oct 16, 2023
1 parent 5f02e26 commit f9aeda5
Show file tree
Hide file tree
Showing 24 changed files with 235 additions and 239 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -146,17 +146,18 @@ internal fun mapToDiscoveryMethod(method: String?): DiscoveryMethod? {
}
}

internal fun mapFromPaymentIntent(paymentIntent: PaymentIntent): ReadableMap = nativeMapOf {
internal fun mapFromPaymentIntent(paymentIntent: PaymentIntent, uuid: String): ReadableMap = nativeMapOf {
putInt("amount", paymentIntent.amount.toInt())
putString("currency", paymentIntent.currency)
putString("id", paymentIntent.id)
putString("description", paymentIntent.description)
putString("status", mapFromPaymentIntentStatus(paymentIntent.status))
putArray("charges", mapFromChargesList(paymentIntent.getCharges()))
putString("created", convertToUnixTimestamp(paymentIntent.created))
putString("sdk_uuid", uuid)
}

internal fun mapFromSetupIntent(setupIntent: SetupIntent): ReadableMap = nativeMapOf {
internal fun mapFromSetupIntent(setupIntent: SetupIntent, uuid: String): ReadableMap = nativeMapOf {
putString("created", convertToUnixTimestamp(setupIntent.created))
putString("id", setupIntent.id)
putString("status", mapFromSetupIntentStatus(setupIntent.status))
Expand All @@ -169,6 +170,7 @@ internal fun mapFromSetupIntent(setupIntent: SetupIntent): ReadableMap = nativeM
putString("onBehalfOfId", setupIntent.onBehalfOfId)
putString("paymentMethodId", setupIntent.paymentMethodId)
putString("singleUseMandateId", setupIntent.singleUseMandateId)
putString("sdk_uuid", uuid)
}

internal fun mapFromSetupAttempt(attempt: SetupAttempt?): ReadableMap? = attempt?.let {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.stripeterminalreactnative

import android.annotation.SuppressLint
import android.app.Application
import android.content.ComponentCallbacks2
import android.content.res.Configuration
Expand Down Expand Up @@ -51,6 +52,8 @@ import com.stripeterminalreactnative.listener.RNUsbReaderListener
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.UUID
import kotlin.collections.HashMap


class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
Expand All @@ -61,7 +64,6 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
private var collectRefundPaymentMethodCancelable: Cancelable? = null
private var collectSetupIntentCancelable: Cancelable? = null
private var installUpdateCancelable: Cancelable? = null
private var readReusableCardCancelable: Cancelable? = null
private var cancelReaderConnectionCancellable: Cancelable? = null

private var paymentIntents: HashMap<String, PaymentIntent?> = HashMap()
Expand Down Expand Up @@ -160,6 +162,7 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
promise.resolve(null)
}

@SuppressLint("MissingPermission")
@ReactMethod
@Suppress("unused")
fun discoverReaders(params: ReadableMap, promise: Promise) = withExceptionResolver(promise) {
Expand Down Expand Up @@ -396,8 +399,10 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
}
}

terminal.createPaymentIntent(intentParams.build(), RNPaymentIntentCallback(promise) { pi ->
(pi.id ?: pi.offlineDetails?.id)?.let { paymentIntents[it] = pi }
val uuid = UUID.randomUUID().toString()

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

Expand All @@ -406,11 +411,14 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
@Suppress("unused")
fun collectPaymentMethod(params: ReadableMap, promise: Promise) =
withExceptionResolver(promise) {
val paymentIntentId = requireParam(params.getString("paymentIntentId")) {
"You must provide a paymentIntentId"
val paymentIntentJson = requireParam(params.getMap("paymentIntent")) {
"You must provide a paymentIntent"
}
val uuid = requireParam(paymentIntentJson.getString("sdk_uuid")) {
"The PaymentIntent is missing sdk_uuid field. This method requires you to use the PaymentIntent that was returned from either createPaymentIntent or retrievePaymentIntent."
}
val paymentIntent = requireParam(paymentIntents[paymentIntentId]) {
"There is no associated paymentIntent with id $paymentIntentId"
val paymentIntent = requireParam(paymentIntents[uuid]) {
"No PaymentIntent was found with the sdk_uuid $uuid. The PaymentIntent provided must be re-retrieved with retrievePaymentIntent or a new PaymentIntent must be created with createPaymentIntent."
}

val configBuilder = CollectConfiguration.Builder()
Expand All @@ -432,8 +440,8 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :

collectPaymentMethodCancelable = terminal.collectPaymentMethod(
paymentIntent,
RNPaymentIntentCallback(promise) { pi ->
(pi.id ?: pi.offlineDetails?.id)?.let { paymentIntents[it] = pi }
RNPaymentIntentCallback(promise, uuid) { pi ->
paymentIntents[uuid] = pi
},
config
)
Expand All @@ -443,19 +451,23 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
@ReactMethod
@Suppress("unused")
fun retrievePaymentIntent(clientSecret: String, promise: Promise) {
terminal.retrievePaymentIntent(clientSecret, RNPaymentIntentCallback(promise) { pi ->
(pi.id ?: pi.offlineDetails?.id)?.let { paymentIntents[it] = pi }
val uuid = UUID.randomUUID().toString()
terminal.retrievePaymentIntent(clientSecret, RNPaymentIntentCallback(promise, uuid) { pi ->
paymentIntents[uuid] = pi
})
}

@ReactMethod
@Suppress("unused")
fun confirmPaymentIntent(paymentIntentId: String, promise: Promise) = withExceptionResolver(promise) {
val paymentIntent = requireParam(paymentIntents[paymentIntentId]) {
"There is no associated paymentIntent with id $paymentIntentId"
fun confirmPaymentIntent(paymentIntent: ReadableMap, promise: Promise) = withExceptionResolver(promise) {
val uuid = requireParam(paymentIntent.getString("sdk_uuid")) {
"The PaymentIntent is missing sdk_uuid field. This method requires you to use the PaymentIntent that was returned from either createPaymentIntent or retrievePaymentIntent."
}
val paymentIntent = requireParam(paymentIntents[uuid]) {
"No PaymentIntent was found with the sdk_uuid $uuid. The PaymentIntent provided must be re-retrieved with retrievePaymentIntent or a new PaymentIntent must be created with createPaymentIntent."
}

terminal.confirmPaymentIntent(paymentIntent, RNPaymentIntentCallback(promise) {
terminal.confirmPaymentIntent(paymentIntent, RNPaymentIntentCallback(promise, uuid) {
paymentIntents.clear()
})
}
Expand All @@ -478,55 +490,61 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
SetupIntentParameters.Builder().setCustomer(customerId).build()
} ?: SetupIntentParameters.NULL

terminal.createSetupIntent(intentParams, RNSetupIntentCallback(promise) {
setupIntents[it.id] = it
val uuid = UUID.randomUUID().toString()
terminal.createSetupIntent(intentParams, RNSetupIntentCallback(promise, uuid) {
setupIntents[uuid] = it
})
}

@ReactMethod
@Suppress("unused")
fun retrieveSetupIntent(clientSecret: String, promise: Promise) {
terminal.retrieveSetupIntent(clientSecret, RNSetupIntentCallback(promise) {
setupIntents[it.id] = it
val uuid = UUID.randomUUID().toString()
terminal.retrieveSetupIntent(clientSecret, RNSetupIntentCallback(promise, uuid) {
setupIntents[uuid] = it
})
}

@OptIn(OfflineMode::class)
@ReactMethod
@Suppress("unused")
fun cancelPaymentIntent(paymentIntentId: String, promise: Promise) =
fun cancelPaymentIntent(paymentIntent: ReadableMap, promise: Promise) =
withExceptionResolver(promise) {
val paymentIntent = requireParam(paymentIntents[paymentIntentId]) {
"There is no associated paymentIntent with id $paymentIntentId"
val uuid = requireParam(paymentIntent.getString("sdk_uuid")) {
"The PaymentIntent is missing sdk_uuid field. This method requires you to use the PaymentIntent that was returned from either createPaymentIntent or retrievePaymentIntent."
}
val paymentIntent = requireParam(paymentIntents[uuid]) {
"No PaymentIntent was found with the sdk_uuid $uuid. The PaymentIntent provided must be re-retrieved with retrievePaymentIntent or a new PaymentIntent must be created with createPaymentIntent."
}
terminal.cancelPaymentIntent(paymentIntent, RNPaymentIntentCallback(promise) { pi ->
(pi.id ?: pi.offlineDetails?.id)?.let { paymentIntents[it] = null }

terminal.cancelPaymentIntent(paymentIntent, RNPaymentIntentCallback(promise, uuid) {
paymentIntents[uuid] = null
})
}

@ReactMethod
@Suppress("unused")
fun cancelReadReusableCard(promise: Promise) {
cancelOperation(promise, readReusableCardCancelable, "readReusableCard")
}

@ReactMethod
@Suppress("unused")
fun collectSetupIntentPaymentMethod(params: ReadableMap, promise: Promise) =
withExceptionResolver(promise) {
val setupIntentId = params.getString("setupIntentId")
val setupIntentJson = requireParam(params.getMap("setupIntent")) {
"You must provide a setupIntent"
}
val uuid = requireParam(setupIntentJson.getString("sdk_uuid")) {
"The SetupIntent is missing sdk_uuid field. This method requires you to use the SetupIntent that was returned from either createPaymentIntent or retrievePaymentIntent."
}
val setupIntent = requireParam(setupIntents[uuid]) {
"No SetupIntent was found with the sdk_uuid $uuid. The SetupIntent provided must be re-retrieved with retrieveSetupIntent or a new SetupIntent must be created with createSetupIntent."
}

val customerConsentCollected = getBoolean(params, "customerConsentCollected")

val setupIntent = requireParam(setupIntents[setupIntentId]) {
"There is no created setupIntent with id $setupIntentId"
}
collectSetupIntentCancelable = terminal.collectSetupIntentPaymentMethod(
setupIntent,
customerConsentCollected,
SetupIntentConfiguration.Builder()
.setEnableCustomerCancellation(false)
.build(),
RNSetupIntentCallback(promise) { setupIntents[it.id] = it }
RNSetupIntentCallback(promise, uuid) { setupIntents[uuid] = it }
)
}

Expand Down Expand Up @@ -571,28 +589,34 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :

@ReactMethod
@Suppress("unused")
fun cancelSetupIntent(setupIntentId: String, promise: Promise) =
fun cancelSetupIntent(setupIntent: ReadableMap, promise: Promise) =
withExceptionResolver(promise) {
val setupIntent = requireParam(setupIntents[setupIntentId]) {
"There is no associated setupIntent with id $setupIntentId"
val uuid = requireParam(setupIntent.getString("sdk_uuid")) {
"The SetupIntent is missing sdk_uuid field. This method requires you to use the SetupIntent that was returned from either createPaymentIntent or retrievePaymentIntent."
}
val setupIntent = requireParam(setupIntents[uuid]) {
"No SetupIntent was found with the sdk_uuid $uuid. The SetupIntent provided must be re-retrieved with retrieveSetupIntent or a new SetupIntent must be created with createSetupIntent."
}

val params = SetupIntentCancellationParameters.Builder().build()

terminal.cancelSetupIntent(setupIntent, params, RNSetupIntentCallback(promise) {
terminal.cancelSetupIntent(setupIntent, params, RNSetupIntentCallback(promise, uuid) {
setupIntents[setupIntent.id] = null
})
}

@ReactMethod
@Suppress("unused")
fun confirmSetupIntent(setupIntentId: String, promise: Promise) =
fun confirmSetupIntent(setupIntent: ReadableMap, promise: Promise) =
withExceptionResolver(promise) {
val setupIntent = requireParam(setupIntents[setupIntentId]) {
"There is no associated setupIntent with id $setupIntentId"
val uuid = requireParam(setupIntent.getString("sdk_uuid")) {
"The SetupIntent is missing sdk_uuid field. This method requires you to use the SetupIntent that was returned from either createPaymentIntent or retrievePaymentIntent."
}
val setupIntent = requireParam(setupIntents[uuid]) {
"No SetupIntent was found with the sdk_uuid $uuid. The SetupIntent provided must be re-retrieved with retrieveSetupIntent or a new SetupIntent must be created with createSetupIntent."
}

terminal.confirmSetupIntent(setupIntent, RNSetupIntentCallback(promise) {
terminal.confirmSetupIntent(setupIntent, RNSetupIntentCallback(promise, uuid) {
setupIntents[it.id] = null
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import com.stripeterminalreactnative.nativeMapOf

class RNPaymentIntentCallback(
private val promise: Promise,
private val uuid: String,
private val onPaymentIntentSuccess: (PaymentIntent) -> Unit = {}
): PaymentIntentCallback {

override fun onSuccess(paymentIntent: PaymentIntent) {
onPaymentIntentSuccess(paymentIntent)
promise.resolve(nativeMapOf {
putMap("paymentIntent", mapFromPaymentIntent(paymentIntent))
putMap("paymentIntent", mapFromPaymentIntent(paymentIntent, uuid))
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import com.stripeterminalreactnative.nativeMapOf

class RNSetupIntentCallback(
private val promise: Promise,
private val uuid: String,
private val onSetupIntentSuccess: (SetupIntent) -> Unit = {}
): SetupIntentCallback {

override fun onSuccess(setupIntent: SetupIntent) {
onSetupIntentSuccess(setupIntent)
promise.resolve(nativeMapOf {
putMap("setupIntent", mapFromSetupIntent(setupIntent))
putMap("setupIntent", mapFromSetupIntent(setupIntent, uuid))
})
}

Expand Down
2 changes: 1 addition & 1 deletion bitrise.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ workflows:
- content: |-
set -e
# 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 2.7.4 && bundle install && \
cd dev-app/ios && asdf install ruby 3.2.2 && bundle install && \
gem install cocoapods -v 1.12.1 && pod install && cd - && \
echo "Checking for diffs in pod lockfile, if this fails please ensure all dependencies are up to date" && \
git diff --exit-code
Expand Down
2 changes: 1 addition & 1 deletion dev-app/.ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.7.4
3.2.2
3 changes: 2 additions & 1 deletion dev-app/Gemfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
source 'https://rubygems.org'
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby '2.7.4'
ruby '3.2.2'
gem 'cocoapods', '~> 1.11', '>= 1.12.1'
gem 'activesupport', '~> 7.0', '<= 7.0.8'
7 changes: 4 additions & 3 deletions dev-app/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ GEM
specs:
CFPropertyList (3.0.6)
rexml
activesupport (7.0.7.2)
activesupport (7.0.8)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
Expand Down Expand Up @@ -65,7 +65,7 @@ GEM
i18n (1.14.1)
concurrent-ruby (~> 1.0)
json (2.6.3)
minitest (5.19.0)
minitest (5.20.0)
molinillo (0.8.0)
nanaimo (0.3.0)
nap (1.1.0)
Expand All @@ -89,10 +89,11 @@ PLATFORMS
ruby

DEPENDENCIES
activesupport (~> 7.0, <= 7.0.8)
cocoapods (~> 1.11, >= 1.12.1)

RUBY VERSION
ruby 2.7.4p191
ruby 3.2.2p53

BUNDLED WITH
2.1.4
1 change: 0 additions & 1 deletion dev-app/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,6 @@ export class Api {
Authorization: `Bearer ${secretKey}`,
},
});

const data = await result.json();

if ('error' in data) {
Expand Down
10 changes: 5 additions & 5 deletions dev-app/src/screens/CollectCardPaymentScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,10 +254,10 @@ export default function CollectCardPaymentScreen() {
],
});

return await _collectPaymentMethod(paymentIntent.id);
return await _collectPaymentMethod(paymentIntent);
};

const _collectPaymentMethod = async (paymentIntentId: string) => {
const _collectPaymentMethod = async (pi: PaymentIntent.Type) => {
// @ts-ignore
setCancel((prev) => ({ ...prev, isDisabled: false }));
addLogs({
Expand All @@ -266,13 +266,13 @@ export default function CollectCardPaymentScreen() {
{
name: 'Collect',
description: 'terminal.collectPaymentMethod',
metadata: { paymentIntentId },
metadata: { paymentIntentId: pi.id },
onBack: cancelCollectPaymentMethod,
},
],
});
const { paymentIntent, error } = await collectPaymentMethod({
paymentIntentId: paymentIntentId,
paymentIntent: pi,
skipTipping: skipTipping,
tipEligibleAmount: tipEligibleAmount
? Number(tipEligibleAmount)
Expand Down Expand Up @@ -331,7 +331,7 @@ export default function CollectCardPaymentScreen() {
});

const { paymentIntent, error } = await confirmPaymentIntent(
collectedPaymentIntent.id
collectedPaymentIntent
);

if (error) {
Expand Down
Loading

0 comments on commit f9aeda5

Please sign in to comment.