Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use sdk_uuid for payment and setup intents #551

Merged
merged 12 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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