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

fix: do not double-create Stripe token in Apple Pay flow #1243

Merged
merged 5 commits into from
Dec 19, 2022
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

## Unreleased

### Breaking changes

- `createPlatformPayPaymentMethod` no longer returns a `token` object. [#1236](https://github.com/stripe/stripe-react-native/issues/1236)
- If your integration depends on Stripe's Tokens API, please use `createPlatformPayToken`, which accepts identical arguments.

## Fixes

- Fixed an issue with `createPlatformPayPaymentMethod` on iOS where a "Canceled" error could be returned in production. [#1236](https://github.com/stripe/stripe-react-native/issues/1236)
- Fixed an issue where the `PlatformPayButton` with `type={PlatformPay.ButtonType.GooglePayMark}` would be unclickable. [#1236](https://github.com/stripe/stripe-react-native/issues/1236)

## 0.22.1 - 2022-12-07

## Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ class GooglePayRequestHelper {
val isPhoneNumberRequired = params?.getBooleanOr("isPhoneNumberRequired", false)
val isRequired = params?.getBooleanOr("isRequired", false)
val allowedCountryCodes = if (params?.hasKey("allowedCountryCodes") == true)
params.getArray("allowedCountryCodes") as Array<String> else Locale.getISOCountries()
params.getArray("allowedCountryCodes")?.toArrayList()?.toSet() as? Set<String> else null

return GooglePayJsonFactory.ShippingAddressParameters(
isRequired = isRequired ?: false,
allowedCountryCodes = allowedCountryCodes.toSet(),
allowedCountryCodes = allowedCountryCodes ?: Locale.getISOCountries().toSet(),
phoneNumberRequired = isPhoneNumberRequired ?: false
)
}
Expand Down Expand Up @@ -98,12 +98,16 @@ class GooglePayRequestHelper {
)
}

internal fun handleGooglePaymentMethodResult(resultCode: Int, data: Intent?, stripe: Stripe, promise: Promise) {
internal fun handleGooglePaymentMethodResult(resultCode: Int, data: Intent?, stripe: Stripe, forToken: Boolean, promise: Promise) {
when (resultCode) {
Activity.RESULT_OK -> {
data?.let { intent ->
PaymentData.getFromIntent(intent)?.let {
resolveWithPaymentMethodAndToken(it, stripe, promise)
if (forToken) {
resolveWithToken(it, promise)
} else {
resolveWithPaymentMethod(it, stripe, promise)
}
}
}
}
Expand All @@ -118,13 +122,9 @@ class GooglePayRequestHelper {
}
}

private fun resolveWithPaymentMethodAndToken(paymentData: PaymentData, stripe: Stripe, promise: Promise) {
private fun resolveWithPaymentMethod(paymentData: PaymentData, stripe: Stripe, promise: Promise) {
val paymentInformation = JSONObject(paymentData.toJson())
val googlePayResult = GooglePayResult.fromJson(paymentInformation)
val promiseResult = WritableNativeMap()
googlePayResult.token?.let {
promiseResult.putMap("token", mapFromToken(it))
}
stripe.createPaymentMethod(
PaymentMethodCreateParams.createFromGooglePay(paymentInformation),
callback = object : ApiResultCallback<PaymentMethod> {
Expand All @@ -139,6 +139,18 @@ class GooglePayRequestHelper {
}
)
}

private fun resolveWithToken(paymentData: PaymentData, promise: Promise) {
val paymentInformation = JSONObject(paymentData.toJson())
val googlePayResult = GooglePayResult.fromJson(paymentInformation)
val promiseResult = WritableNativeMap()
googlePayResult.token?.let {
promiseResult.putMap("token", mapFromToken(it))
promise.resolve(promiseResult)
} ?: run {
promise.resolve(createError("Failed", "Unexpected response from Google Pay. No token was found."))
}
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
private var confirmPromise: Promise? = null
private var confirmPaymentClientSecret: String? = null
private var createPlatformPayPaymentMethodPromise: Promise? = null
private var platformPayUsesDeprecatedTokenFlow = false

private var paymentSheetFragment: PaymentSheetFragment? = null
private var googlePayFragment: GooglePayFragment? = null
Expand All @@ -65,7 +66,7 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
when (requestCode) {
GooglePayRequestHelper.LOAD_PAYMENT_DATA_REQUEST_CODE -> {
createPlatformPayPaymentMethodPromise?.let {
GooglePayRequestHelper.handleGooglePaymentMethodResult(resultCode, data, stripe, it)
GooglePayRequestHelper.handleGooglePaymentMethodResult(resultCode, data, stripe, platformPayUsesDeprecatedTokenFlow, it)
createPlatformPayPaymentMethodPromise = null
} ?: run { Log.d("StripeReactNative", "No promise was found, Google Pay result went unhandled,") }
}
Expand Down Expand Up @@ -654,11 +655,12 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
}

@ReactMethod
fun createPlatformPayPaymentMethod(params: ReadableMap, promise: Promise) {
fun createPlatformPayPaymentMethod(params: ReadableMap, usesDeprecatedTokenFlow: Boolean, promise: Promise) {
val googlePayParams: ReadableMap = params.getMap("googlePay") ?: run {
promise.resolve(createError(GooglePayErrorType.Failed.toString(), "You must provide the `googlePay` parameter."))
return
}
platformPayUsesDeprecatedTokenFlow = usesDeprecatedTokenFlow
createPlatformPayPaymentMethodPromise = promise
getCurrentActivityOrResolveWithError(promise)?.let {
val request = GooglePayRequestHelper.createPaymentRequest(
Expand Down
2 changes: 0 additions & 2 deletions android/src/main/res/layout/googlepay_mark_button.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
android:focusable="true"
android:minWidth="1dp"
android:background="@drawable/googlepay_mark_background">

<ImageView
android:id="@+id/imageView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:cropToPadding="true"
Expand Down
3 changes: 2 additions & 1 deletion docs/Platform-Pay-Migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

- `confirmPlatformPaySetupIntent` if you were using them to confirm a setup intent
- `confirmPlatformPayPayment` if you were using them to confirm a payment intent
- `createPlatformPayPaymentMethod` if you were using them to create a payment method (this was impossible previously with Apple Pay, so it was only possible on Android). This method also now creates and returns the Stripe token object.
- `createPlatformPayPaymentMethod` if you were using them to create a payment method (this was impossible previously with Apple Pay, so it was only possible on Android).
- `createPlatformPayToken` if you are migrating from Tipsi Stripe and your payments code still uses the legacy Tokens API.

# `updateApplePaySummaryItems`

Expand Down
100 changes: 72 additions & 28 deletions example/src/screens/ApplePayScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default function ApplePayScreen() {
const [clientSecret, setClientSecret] = useState<String | null>(null);
const {
createPlatformPayPaymentMethod,
createPlatformPayToken,
isPlatformPaySupported,
confirmPlatformPayPayment,
} = usePlatformPay();
Expand Down Expand Up @@ -245,36 +246,57 @@ export default function ApplePayScreen() {
};

const createPaymentMethod = async () => {
const { paymentMethod, token, error } =
await createPlatformPayPaymentMethod({
applePay: {
cartItems: cart,
merchantCountryCode: 'US',
currencyCode: 'USD',
shippingMethods,
requiredShippingAddressFields: [
PlatformPay.ContactField.EmailAddress,
PlatformPay.ContactField.PhoneNumber,
PlatformPay.ContactField.PostalAddress,
PlatformPay.ContactField.Name,
],
requiredBillingContactFields: [
PlatformPay.ContactField.PostalAddress,
],
supportsCouponCode: true,
couponCode: '123',
shippingType: PlatformPay.ApplePayShippingType.StorePickup,
additionalEnabledNetworks: ['JCB'],
},
});
const { paymentMethod, error } = await createPlatformPayPaymentMethod({
applePay: {
cartItems: cart,
merchantCountryCode: 'US',
currencyCode: 'USD',
shippingMethods,
requiredShippingAddressFields: [
PlatformPay.ContactField.EmailAddress,
PlatformPay.ContactField.PhoneNumber,
PlatformPay.ContactField.PostalAddress,
PlatformPay.ContactField.Name,
],
requiredBillingContactFields: [PlatformPay.ContactField.PostalAddress],
supportsCouponCode: true,
couponCode: '123',
shippingType: PlatformPay.ApplePayShippingType.StorePickup,
additionalEnabledNetworks: ['JCB'],
},
});
if (error) {
Alert.alert(error.code, error.localizedMessage);
} else {
Alert.alert(
'Success',
'Check the logs for payment method and token details.'
);
Alert.alert('Success', 'Check the logs for payment method details.');
console.log(JSON.stringify(paymentMethod, null, 2));
}
};

const createToken = async () => {
const { token, error } = await createPlatformPayToken({
applePay: {
cartItems: cart,
merchantCountryCode: 'US',
currencyCode: 'USD',
shippingMethods,
requiredShippingAddressFields: [
PlatformPay.ContactField.EmailAddress,
PlatformPay.ContactField.PhoneNumber,
PlatformPay.ContactField.PostalAddress,
PlatformPay.ContactField.Name,
],
requiredBillingContactFields: [PlatformPay.ContactField.PostalAddress],
supportsCouponCode: true,
couponCode: '123',
shippingType: PlatformPay.ApplePayShippingType.StorePickup,
additionalEnabledNetworks: ['JCB'],
},
});
if (error) {
Alert.alert(error.code, error.localizedMessage);
} else {
Alert.alert('Success', 'Check the logs for token details.');
console.log(JSON.stringify(token, null, 2));
}
};
Expand Down Expand Up @@ -328,6 +350,28 @@ export default function ApplePayScreen() {
}}
/>

<PlatformPayButton
onPress={createToken}
type={PlatformPay.ButtonType.SetUp}
appearance={PlatformPay.ButtonStyle.Black}
borderRadius={4}
disabled={!isApplePaySupported}
style={styles.createPaymentMethodButton}
onCouponCodeEntered={couponCodeListener}
onShippingContactSelected={({ shippingContact }) => {
console.log(JSON.stringify(shippingContact, null, 2));
updatePlatformPaySheet({
applePay: { cartItems: cart, shippingMethods, errors: [] },
});
}}
onShippingMethodSelected={({ shippingMethod }) => {
console.log(JSON.stringify(shippingMethod, null, 2));
updatePlatformPaySheet({
applePay: { cartItems: cart, shippingMethods, errors: [] },
});
}}
/>

{showAddToWalletButton && (
<AddToWalletButton
androidAssetSource={{}}
Expand Down Expand Up @@ -361,13 +405,13 @@ const styles = StyleSheet.create({
payButton: {
width: '65%',
height: 50,
marginTop: 40,
marginTop: 20,
alignSelf: 'center',
},
createPaymentMethodButton: {
width: '65%',
height: 50,
marginTop: 40,
marginTop: 20,
alignSelf: 'center',
},
});
Loading