diff --git a/android/build.gradle b/android/build.gradle index 1385afa28..450fb2d1f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -11,9 +11,15 @@ buildscript { classpath 'com.android.tools.build:gradle:7.2.2' // noinspection DifferentKotlinGradleVersion classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.diffplug.spotless:spotless-plugin-gradle:6.25.0' } } +if (project == rootProject) { + apply from: 'spotless.gradle' + return +} + apply plugin: 'com.android.library' apply plugin: 'kotlin-android' //apply plugin: 'kotlin-android-extensions' diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..e6441136f Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..e2847c820 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/gradlew b/android/gradlew new file mode 100755 index 000000000..b740cf133 --- /dev/null +++ b/android/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 000000000..7101f8e46 --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/spotless.gradle b/android/spotless.gradle new file mode 100644 index 000000000..c42d99949 --- /dev/null +++ b/android/spotless.gradle @@ -0,0 +1,19 @@ +apply plugin: 'com.diffplug.spotless' + +allprojects { + repositories { + google() + mavenCentral() + } +} + + +spotless { + kotlin { + target 'src/**/*.kt' + ktlint("1.5.0") + trimTrailingWhitespace() + indentWithSpaces() + endWithNewline() + } +} diff --git a/android/src/androidTest/java/com/reactnativestripesdk/addresssheet/AddressSheetViewTest.kt b/android/src/androidTest/java/com/reactnativestripesdk/addresssheet/AddressSheetViewTest.kt index e1ade698f..a30c1d031 100644 --- a/android/src/androidTest/java/com/reactnativestripesdk/addresssheet/AddressSheetViewTest.kt +++ b/android/src/androidTest/java/com/reactnativestripesdk/addresssheet/AddressSheetViewTest.kt @@ -15,9 +15,10 @@ import org.junit.Before import org.junit.Test class AddressSheetViewTest { - private val reactApplicationContext = ReactApplicationContext( - ApplicationProvider.getApplicationContext() - ) + private val reactApplicationContext = + ReactApplicationContext( + ApplicationProvider.getApplicationContext(), + ) private val testCity = "testCity" private val testCountry = "testCountry" private val testLine1 = "testLine1" @@ -28,15 +29,13 @@ class AddressSheetViewTest { private val testPhone = "testPhone" @Before - fun setup(){ + fun setup() { SoLoader.init(reactApplicationContext, false) } @Test fun buildAddressDetails_Default() { - val addressDetails = AddressSheetView.buildAddressDetails( - bundleOf() - ) + val addressDetails = AddressSheetView.buildAddressDetails(bundleOf()) Assert.assertNull(addressDetails.address) Assert.assertNull(addressDetails.name) Assert.assertFalse(addressDetails.isCheckboxSelected ?: false) @@ -45,17 +44,19 @@ class AddressSheetViewTest { @Test fun buildAddressDetails_Custom() { - val addressDetails = AddressSheetView.buildAddressDetails( - bundleOf( - "name" to testName, - "phone" to testPhone, - "isCheckboxSelected" to true, - "address" to bundleOf( - "city" to testCity, - "line1" to testLine1, + val addressDetails = + AddressSheetView.buildAddressDetails( + bundleOf( + "name" to testName, + "phone" to testPhone, + "isCheckboxSelected" to true, + "address" to + bundleOf( + "city" to testCity, + "line1" to testLine1, + ), ), ) - ) Assert.assertEquals(addressDetails.address?.city, testCity) Assert.assertEquals(addressDetails.address?.line1, testLine1) Assert.assertEquals(addressDetails.name, testName) @@ -65,9 +66,7 @@ class AddressSheetViewTest { @Test fun buildAddress_Default() { - val address = AddressSheetView.buildAddress( - bundleOf() - ) + val address = AddressSheetView.buildAddress(bundleOf()) Assert.assertNull(address?.city) Assert.assertNull(address?.country) Assert.assertNull(address?.state) @@ -78,16 +77,17 @@ class AddressSheetViewTest { @Test fun buildAddress_Custom() { - val address = AddressSheetView.buildAddress( - bundleOf( - "city" to testCity, - "line1" to testLine1, - "country" to testCountry, - "postalCode" to testPostalCode, - "line2" to testLine2, - "state" to testState, + val address = + AddressSheetView.buildAddress( + bundleOf( + "city" to testCity, + "line1" to testLine1, + "country" to testCountry, + "postalCode" to testPostalCode, + "line2" to testLine2, + "state" to testState, + ), ) - ) Assert.assertEquals(address?.city, testCity) Assert.assertEquals(address?.line1, testLine1) Assert.assertEquals(address?.line2, testLine2) @@ -100,22 +100,22 @@ class AddressSheetViewTest { fun getFieldConfiguration() { Assert.assertEquals( AddressSheetView.getFieldConfiguration("hidden"), - AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.HIDDEN + AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.HIDDEN, ) Assert.assertEquals( AddressSheetView.getFieldConfiguration("required"), - AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.REQUIRED + AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.REQUIRED, ) Assert.assertEquals( AddressSheetView.getFieldConfiguration("optional"), - AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.OPTIONAL + AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.OPTIONAL, ) Assert.assertEquals( AddressSheetView.getFieldConfiguration("anything"), - AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.HIDDEN + AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.HIDDEN, ) } @@ -125,7 +125,7 @@ class AddressSheetViewTest { Assert.assertEquals( result.phone, - AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.HIDDEN + AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.HIDDEN, ) Assert.assertNull(result.checkboxLabel) @@ -134,55 +134,59 @@ class AddressSheetViewTest { @Test fun buildAdditionalFieldsConfiguration_Custom() { val label = "custom label" - val params = WritableNativeMap().also { - it.putString("phoneNumber", "required") - it.putString("checkboxLabel", label) - } + val params = + WritableNativeMap().also { + it.putString("phoneNumber", "required") + it.putString("checkboxLabel", label) + } val received = AddressSheetView.buildAdditionalFieldsConfiguration(params) Assert.assertEquals( received.phone, - AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.REQUIRED + AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.REQUIRED, ) - Assert.assertEquals( - received.checkboxLabel, - label - ) + Assert.assertEquals(received.checkboxLabel, label) } @Test fun buildResult() { - val received = AddressSheetView.buildResult( - AddressDetails( - name = testName, - address = PaymentSheet.Address( - city = testCity, - state = testState, - country = testCountry, - line2 = testLine2, - line1 = testLine1, - postalCode = testPostalCode + val received = + AddressSheetView.buildResult( + AddressDetails( + name = testName, + address = + PaymentSheet.Address( + city = testCity, + state = testState, + country = testCountry, + line2 = testLine2, + line1 = testLine1, + postalCode = testPostalCode, + ), + phoneNumber = testPhone, + isCheckboxSelected = true, ), - phoneNumber = testPhone, - isCheckboxSelected = true ) - ) - val expected = WritableNativeMap().also { - it.putString("name", testName) - it.putMap("address", WritableNativeMap().also { - it.putString("city", testCity) - it.putString("country", testCountry) - it.putString("state", testState) - it.putString("line1", testLine1) - it.putString("line2", testLine2) - it.putString("postalCode", testPostalCode) - }) - it.putString("phone", testPhone) - it.putBoolean("isCheckboxSelected", true) - } + val expected = + WritableNativeMap().also { + it.putString("name", testName) + it.putMap( + "address", + WritableNativeMap().also { + it.putString("city", testCity) + it.putString("country", testCountry) + it.putString("state", testState) + it.putString("line1", testLine1) + it.putString("line2", testLine2) + it.putString("postalCode", testPostalCode) + }, + ) + it.putString("phone", testPhone) + it.putBoolean("isCheckboxSelected", true) + } Assert.assertEquals(expected, received) } diff --git a/android/src/androidTest/java/com/reactnativestripesdk/mappers/MappersTest.kt b/android/src/androidTest/java/com/reactnativestripesdk/mappers/MappersTest.kt index 78d5039f0..a9951fa82 100644 --- a/android/src/androidTest/java/com/reactnativestripesdk/mappers/MappersTest.kt +++ b/android/src/androidTest/java/com/reactnativestripesdk/mappers/MappersTest.kt @@ -11,23 +11,19 @@ import org.junit.Before import org.junit.Test class MappersTest { - private val reactApplicationContext = ReactApplicationContext( - ApplicationProvider.getApplicationContext() - ) + private val reactApplicationContext = + ReactApplicationContext( + ApplicationProvider.getApplicationContext(), + ) @Before - fun setup(){ + fun setup() { SoLoader.init(reactApplicationContext, false) } - @Test fun createCanAddCardResult_NoStatus() { - val result = createCanAddCardResult( - true, - null, - null - ) + val result = createCanAddCardResult(true, null, null) Assert.assertNotNull(result.getMap("details")) Assert.assertNull(result.getMap("details")?.getString("status")) Assert.assertNull(result.getMap("details")?.getMap("token")) @@ -36,11 +32,12 @@ class MappersTest { @Test fun createCanAddCardResult_WithToken() { - val result = createCanAddCardResult( - true, - "CARD_ALREADY_EXISTS", - WritableNativeMap().also { it.putString("key", "value") } - ) + val result = + createCanAddCardResult( + true, + "CARD_ALREADY_EXISTS", + WritableNativeMap().also { it.putString("key", "value") }, + ) Assert.assertTrue(result.getBoolean("canAddCard")) val details = result.getMap("details") Assert.assertEquals(details?.getString("status"), "CARD_ALREADY_EXISTS") @@ -49,11 +46,7 @@ class MappersTest { @Test fun createCanAddCardResult_UnsupportedDevice() { - val result = createCanAddCardResult( - false, - "UNSUPPORTED_DEVICE", - null - ) + val result = createCanAddCardResult(false, "UNSUPPORTED_DEVICE", null) Assert.assertFalse(result.getBoolean("canAddCard")) val details = result.getMap("details") Assert.assertEquals(details?.getString("status"), "UNSUPPORTED_DEVICE") @@ -62,15 +55,10 @@ class MappersTest { @Test fun createCanAddCardResult_MissingConfiguration() { - val result = createCanAddCardResult( - false, - "MISSING_CONFIGURATION", - null - ) + val result = createCanAddCardResult(false, "MISSING_CONFIGURATION", null) Assert.assertFalse(result.getBoolean("canAddCard")) val details = result.getMap("details") Assert.assertEquals(details?.getString("status"), "MISSING_CONFIGURATION") Assert.assertNull(details?.getMap("token")) } } - diff --git a/android/src/androidTest/java/com/reactnativestripesdk/paymentsheet/PaymentSheetFragmentTest.kt b/android/src/androidTest/java/com/reactnativestripesdk/paymentsheet/PaymentSheetFragmentTest.kt index 8108a6670..906246c21 100644 --- a/android/src/androidTest/java/com/reactnativestripesdk/paymentsheet/PaymentSheetFragmentTest.kt +++ b/android/src/androidTest/java/com/reactnativestripesdk/paymentsheet/PaymentSheetFragmentTest.kt @@ -8,49 +8,45 @@ import org.junit.Assert import org.junit.Test class PaymentSheetFragmentTest { - @Test fun buildGooglePayConfig() { - val config = PaymentSheetFragment.buildGooglePayConfig( - bundleOf( - "merchantCountryCode" to "US", - "currencyCode" to "USD", - "testEnv" to true, - "buttonType" to 4 + val config = + PaymentSheetFragment.buildGooglePayConfig( + bundleOf( + "merchantCountryCode" to "US", + "currencyCode" to "USD", + "testEnv" to true, + "buttonType" to 4, + ), ) - ) Assert.assertEquals( config, PaymentSheet.GooglePayConfiguration( environment = PaymentSheet.GooglePayConfiguration.Environment.Test, countryCode = "US", currencyCode = "USD", - buttonType = PaymentSheet.GooglePayConfiguration.ButtonType.Donate - ) + buttonType = PaymentSheet.GooglePayConfiguration.ButtonType.Donate, + ), ) } @Test fun buildGooglePayConfig_returnsNull() { - val config = PaymentSheetFragment.buildGooglePayConfig( - null - ) + val config = PaymentSheetFragment.buildGooglePayConfig(null) Assert.assertNull(config) } @Test fun buildGooglePayConfig_defaultsToCorrectValues() { - val config = PaymentSheetFragment.buildGooglePayConfig( - Bundle.EMPTY - ) + val config = PaymentSheetFragment.buildGooglePayConfig(Bundle.EMPTY) Assert.assertEquals( config, PaymentSheet.GooglePayConfiguration( environment = PaymentSheet.GooglePayConfiguration.Environment.Production, countryCode = "", currencyCode = "", - buttonType = PaymentSheet.GooglePayConfiguration.ButtonType.Pay - ) + buttonType = PaymentSheet.GooglePayConfiguration.ButtonType.Pay, + ), ) } } diff --git a/android/src/androidTest/java/com/reactnativestripesdk/pushprovisioning/PushProvisioningProxyTest.kt b/android/src/androidTest/java/com/reactnativestripesdk/pushprovisioning/PushProvisioningProxyTest.kt index 89aa9d70b..ec1c5fdf8 100644 --- a/android/src/androidTest/java/com/reactnativestripesdk/pushprovisioning/PushProvisioningProxyTest.kt +++ b/android/src/androidTest/java/com/reactnativestripesdk/pushprovisioning/PushProvisioningProxyTest.kt @@ -3,38 +3,33 @@ package com.reactnativestripesdk.pushprovisioning import android.content.Context import androidx.test.core.app.ApplicationProvider import com.facebook.react.bridge.ReactApplicationContext -import org.junit.Assert.* +import org.junit.Assert import org.junit.Test class PushProvisioningProxyTest { - private val reactApplicationContext = ReactApplicationContext( - ApplicationProvider.getApplicationContext() - ) + private val reactApplicationContext = + ReactApplicationContext( + ApplicationProvider.getApplicationContext(), + ) @Test fun getApiVersion() { /** * An empty string is returned because we do not include compileOnly dependencies in our tests. * Including the compileOnly dependency causes some linters to complain, and one single test - * isn't worth it. Once the push provisioning library is split into it's own SDK, we can - * add back this test. (it should equal "2019-09-09" if the dependency is included). + * isn't worth it. Once the push provisioning library is split into it's own SDK, we can add + * back this test. (it should equal "2019-09-09" if the dependency is included). */ - assertEquals( - "", - PushProvisioningProxy.getApiVersion() - ) + Assert.assertEquals("", PushProvisioningProxy.getApiVersion()) } @Test fun isNFCEnabled() { - assertEquals( - false, - PushProvisioningProxy.isNFCEnabled(reactApplicationContext) - ) + Assert.assertEquals(false, PushProvisioningProxy.isNFCEnabled(reactApplicationContext)) } @Test fun isTokenInWallet() { - assertEquals(TapAndPayProxy.isTokenInWallet({}, "4242"), false) + Assert.assertEquals(TapAndPayProxy.isTokenInWallet({}, "4242"), false) } } diff --git a/android/src/main/java/com/reactnativestripesdk/AuBECSDebitFormView.kt b/android/src/main/java/com/reactnativestripesdk/AuBECSDebitFormView.kt index c95177847..8b54c5614 100644 --- a/android/src/main/java/com/reactnativestripesdk/AuBECSDebitFormView.kt +++ b/android/src/main/java/com/reactnativestripesdk/AuBECSDebitFormView.kt @@ -17,9 +17,12 @@ import com.stripe.android.model.PaymentMethodCreateParams import com.stripe.android.view.BecsDebitWidget import com.stripe.android.view.StripeEditText -class AuBECSDebitFormView(private val context: ThemedReactContext) : FrameLayout(context) { +class AuBECSDebitFormView( + private val context: ThemedReactContext, +) : FrameLayout(context) { private lateinit var becsDebitWidget: BecsDebitWidget - private var mEventDispatcher: EventDispatcher? = context.getNativeModule(UIManagerModule::class.java)?.eventDispatcher + private var mEventDispatcher: EventDispatcher? = + context.getNativeModule(UIManagerModule::class.java)?.eventDispatcher private var formStyle: ReadableMap? = null fun setCompanyName(name: String?) { @@ -73,50 +76,44 @@ class AuBECSDebitFormView(private val context: ThemedReactContext) : FrameLayout (binding.nameEditText).textSize = it.toFloat() } - becsDebitWidget.background = MaterialShapeDrawable( - ShapeAppearanceModel() - .toBuilder() - .setAllCorners(CornerFamily.ROUNDED, (borderRadius * 2).toFloat()) - .build() - ).also { shape -> - shape.strokeWidth = 0.0f - shape.strokeColor = ColorStateList.valueOf(Color.parseColor("#000000")) - shape.fillColor = ColorStateList.valueOf(Color.parseColor("#FFFFFF")) - borderWidth?.let { - shape.strokeWidth = (it * 2).toFloat() - } - borderColor?.let { - shape.strokeColor = ColorStateList.valueOf(Color.parseColor(it)) - } - backgroundColor?.let { - shape.fillColor = ColorStateList.valueOf(Color.parseColor(it)) + becsDebitWidget.background = + MaterialShapeDrawable( + ShapeAppearanceModel() + .toBuilder() + .setAllCorners(CornerFamily.ROUNDED, (borderRadius * 2).toFloat()) + .build(), + ).also { shape -> + shape.strokeWidth = 0.0f + shape.strokeColor = ColorStateList.valueOf(Color.parseColor("#000000")) + shape.fillColor = ColorStateList.valueOf(Color.parseColor("#FFFFFF")) + borderWidth?.let { shape.strokeWidth = (it * 2).toFloat() } + borderColor?.let { shape.strokeColor = ColorStateList.valueOf(Color.parseColor(it)) } + backgroundColor?.let { + shape.fillColor = ColorStateList.valueOf(Color.parseColor(it)) + } } - } } - fun onFormChanged(params: PaymentMethodCreateParams) { val billingDetails = params.toParamMap()["billing_details"] as HashMap<*, *> val auBecsDebit = params.toParamMap()["au_becs_debit"] as HashMap<*, *> - val formDetails: MutableMap = mutableMapOf( - "accountNumber" to auBecsDebit["account_number"] as String, - "bsbNumber" to auBecsDebit["bsb_number"] as String, - "name" to billingDetails["name"] as String, - "email" to billingDetails["email"] as String - ) + val formDetails: MutableMap = + mutableMapOf( + "accountNumber" to auBecsDebit["account_number"] as String, + "bsbNumber" to auBecsDebit["bsb_number"] as String, + "name" to billingDetails["name"] as String, + "email" to billingDetails["email"] as String, + ) - mEventDispatcher?.dispatchEvent( - FormCompleteEvent(id, formDetails)) + mEventDispatcher?.dispatchEvent(FormCompleteEvent(id, formDetails)) } private fun setListeners() { becsDebitWidget.validParamsCallback = object : BecsDebitWidget.ValidParamsCallback { override fun onInputChanged(isValid: Boolean) { - becsDebitWidget.params?.let { params -> - onFormChanged(params) - } + becsDebitWidget.params?.let { params -> onFormChanged(params) } } } } diff --git a/android/src/main/java/com/reactnativestripesdk/AuBECSDebitFormViewManager.kt b/android/src/main/java/com/reactnativestripesdk/AuBECSDebitFormViewManager.kt index ea5a98bcf..44aa6b28a 100644 --- a/android/src/main/java/com/reactnativestripesdk/AuBECSDebitFormViewManager.kt +++ b/android/src/main/java/com/reactnativestripesdk/AuBECSDebitFormViewManager.kt @@ -9,22 +9,27 @@ import com.facebook.react.uimanager.annotations.ReactProp class AuBECSDebitFormViewManager : SimpleViewManager() { override fun getName() = "AuBECSDebitForm" - override fun getExportedCustomDirectEventTypeConstants(): MutableMap { - return MapBuilder.of( - FormCompleteEvent.EVENT_NAME, MapBuilder.of("registrationName", "onCompleteAction")) - } + override fun getExportedCustomDirectEventTypeConstants(): MutableMap = + MapBuilder.of( + FormCompleteEvent.EVENT_NAME, + MapBuilder.of("registrationName", "onCompleteAction"), + ) @ReactProp(name = "companyName") - fun setCompanyName(view: AuBECSDebitFormView, name: String?) { + fun setCompanyName( + view: AuBECSDebitFormView, + name: String?, + ) { view.setCompanyName(name) } @ReactProp(name = "formStyle") - fun setFormStyle(view: AuBECSDebitFormView, style: ReadableMap) { + fun setFormStyle( + view: AuBECSDebitFormView, + style: ReadableMap, + ) { view.setFormStyle(style) } - override fun createViewInstance(reactContext: ThemedReactContext): AuBECSDebitFormView { - return AuBECSDebitFormView(reactContext) - } + override fun createViewInstance(reactContext: ThemedReactContext): AuBECSDebitFormView = AuBECSDebitFormView(reactContext) } diff --git a/android/src/main/java/com/reactnativestripesdk/CardChangedEvent.kt b/android/src/main/java/com/reactnativestripesdk/CardChangedEvent.kt index d97252ba8..90f6564cc 100644 --- a/android/src/main/java/com/reactnativestripesdk/CardChangedEvent.kt +++ b/android/src/main/java/com/reactnativestripesdk/CardChangedEvent.kt @@ -5,51 +5,49 @@ import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.Event import com.facebook.react.uimanager.events.RCTEventEmitter -internal class CardChangedEvent constructor(viewTag: Int, private val cardDetails: MutableMap, private val postalCodeEnabled: Boolean, private val complete: Boolean, private val dangerouslyGetFullCardDetails: Boolean) : Event(viewTag) { - override fun getEventName(): String { - return EVENT_NAME - } +internal class CardChangedEvent + constructor( + viewTag: Int, + private val cardDetails: MutableMap, + private val postalCodeEnabled: Boolean, + private val complete: Boolean, + private val dangerouslyGetFullCardDetails: Boolean, + ) : Event(viewTag) { + override fun getEventName(): String = EVENT_NAME + + override fun dispatch(rctEventEmitter: RCTEventEmitter) { + rctEventEmitter.receiveEvent(viewTag, eventName, serializeEventData()) + } - override fun dispatch(rctEventEmitter: RCTEventEmitter) { - rctEventEmitter.receiveEvent(viewTag, eventName, serializeEventData()) - } + private fun serializeEventData(): WritableMap { + val eventData = Arguments.createMap() + eventData.putString("brand", cardDetails["brand"]?.toString()) + eventData.putString("last4", cardDetails["last4"]?.toString()) - private fun serializeEventData(): WritableMap { - val eventData = Arguments.createMap() - eventData.putString("brand", cardDetails["brand"]?.toString()) - eventData.putString("last4", cardDetails["last4"]?.toString()) + (cardDetails["expiryMonth"] as Int?)?.let { eventData.putInt("expiryMonth", it) } + ?: run { eventData.putNull("expiryMonth") } - (cardDetails["expiryMonth"] as Int?)?.let { - eventData.putInt("expiryMonth", it) - } ?: run { - eventData.putNull("expiryMonth") - } + (cardDetails["expiryYear"] as Int?)?.let { eventData.putInt("expiryYear", it) } + ?: run { eventData.putNull("expiryYear") } - (cardDetails["expiryYear"] as Int?)?.let { - eventData.putInt("expiryYear", it) - } ?: run { - eventData.putNull("expiryYear") - } + eventData.putBoolean("complete", complete) + eventData.putString("validNumber", cardDetails["validNumber"]?.toString()) + eventData.putString("validCVC", cardDetails["validCVC"]?.toString()) + eventData.putString("validExpiryDate", cardDetails["validExpiryDate"]?.toString()) - eventData.putBoolean("complete", complete) - eventData.putString("validNumber", cardDetails["validNumber"]?.toString()) - eventData.putString("validCVC", cardDetails["validCVC"]?.toString()) - eventData.putString("validExpiryDate", cardDetails["validExpiryDate"]?.toString()) + if (postalCodeEnabled) { + eventData.putString("postalCode", cardDetails["postalCode"]?.toString()) + } - if (postalCodeEnabled) { - eventData.putString("postalCode", cardDetails["postalCode"]?.toString()) - } + if (dangerouslyGetFullCardDetails) { + eventData.putString("number", cardDetails["number"]?.toString()?.replace(" ", "")) + eventData.putString("cvc", cardDetails["cvc"]?.toString()) + } - if (dangerouslyGetFullCardDetails) { - eventData.putString("number", cardDetails["number"]?.toString()?.replace(" ", "")) - eventData.putString("cvc", cardDetails["cvc"]?.toString()) + return eventData } - return eventData - } - - companion object { - const val EVENT_NAME = "onCardChange" + companion object { + const val EVENT_NAME = "onCardChange" + } } - -} diff --git a/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt b/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt index 2ac656f66..79e4ed8e5 100644 --- a/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt +++ b/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt @@ -19,8 +19,13 @@ import com.facebook.react.views.text.ReactTypefaceUtils import com.google.android.material.shape.CornerFamily import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.ShapeAppearanceModel -import com.reactnativestripesdk.utils.* +import com.reactnativestripesdk.utils.PostalCodeUtilities +import com.reactnativestripesdk.utils.getIntOrNull +import com.reactnativestripesdk.utils.getValOr +import com.reactnativestripesdk.utils.hideSoftKeyboard import com.reactnativestripesdk.utils.mapCardBrand +import com.reactnativestripesdk.utils.mapToPreferredNetworks +import com.reactnativestripesdk.utils.showSoftKeyboard import com.stripe.android.core.model.CountryCode import com.stripe.android.core.model.CountryUtils import com.stripe.android.databinding.StripeCardInputWidgetBinding @@ -31,13 +36,26 @@ import com.stripe.android.view.CardInputWidget import com.stripe.android.view.CardValidCallback import com.stripe.android.view.StripeEditText -class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { +class CardFieldView( + context: ThemedReactContext, +) : FrameLayout(context) { private var mCardWidget: CardInputWidget = CardInputWidget(context) private val cardInputWidgetBinding = StripeCardInputWidgetBinding.bind(mCardWidget) - val cardDetails: MutableMap = mutableMapOf("brand" to "", "last4" to "", "expiryMonth" to null, "expiryYear" to null, "postalCode" to "", "validNumber" to "Unknown", "validCVC" to "Unknown", "validExpiryDate" to "Unknown") + val cardDetails: MutableMap = + mutableMapOf( + "brand" to "", + "last4" to "", + "expiryMonth" to null, + "expiryYear" to null, + "postalCode" to "", + "validNumber" to "Unknown", + "validCVC" to "Unknown", + "validExpiryDate" to "Unknown", + ) var cardParams: PaymentMethodCreateParams.Card? = null var cardAddress: Address? = null - private var mEventDispatcher: EventDispatcher? = context.getNativeModule(UIManagerModule::class.java)?.eventDispatcher + private var mEventDispatcher: EventDispatcher? = + context.getNativeModule(UIManagerModule::class.java)?.eventDispatcher private var dangerouslyGetFullCardDetails: Boolean = false private var currentFocusedField: String? = null private var isCardValid = false @@ -81,8 +99,7 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { } private fun onChangeFocus() { - mEventDispatcher?.dispatchEvent( - CardFocusEvent(id, currentFocusedField)) + mEventDispatcher?.dispatchEvent(CardFocusEvent(id, currentFocusedField)) } fun setCardStyle(value: ReadableMap) { @@ -96,12 +113,13 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { val placeholderColor = getValOr(value, "placeholderColor", null) val textErrorColor = getValOr(value, "textErrorColor", null) val cursorColor = getValOr(value, "cursorColor", null) - val bindings = setOf( - cardInputWidgetBinding.cardNumberEditText, - cardInputWidgetBinding.cvcEditText, - cardInputWidgetBinding.expiryDateEditText, - cardInputWidgetBinding.postalCodeEditText - ) + val bindings = + setOf( + cardInputWidgetBinding.cardNumberEditText, + cardInputWidgetBinding.cvcEditText, + cardInputWidgetBinding.expiryDateEditText, + cardInputWidgetBinding.postalCodeEditText, + ) textColor?.let { for (editTextBinding in bindings) { @@ -127,7 +145,14 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { fontFamily?.let { for (editTextBinding in bindings) { // Load custom font from assets, and fallback to default system font - editTextBinding.typeface = ReactTypefaceUtils.applyStyles(null, -1, -1, it.takeIf { it.isNotEmpty() }, context.assets) + editTextBinding.typeface = + ReactTypefaceUtils.applyStyles( + null, + -1, + -1, + it.takeIf { it.isNotEmpty() }, + context.assets, + ) } } cursorColor?.let { @@ -144,38 +169,34 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { } mCardWidget.setPadding(20, 0, 20, 0) - mCardWidget.background = MaterialShapeDrawable( - ShapeAppearanceModel() - .toBuilder() - .setAllCorners(CornerFamily.ROUNDED, PixelUtil.toPixelFromDIP(borderRadius.toDouble())) - .build() - ).also { shape -> - shape.strokeWidth = 0.0f - shape.strokeColor = ColorStateList.valueOf(Color.parseColor("#000000")) - shape.fillColor = ColorStateList.valueOf(Color.parseColor("#FFFFFF")) - borderWidth?.let { - shape.strokeWidth = PixelUtil.toPixelFromDIP(it.toDouble()) - } - borderColor?.let { - shape.strokeColor = ColorStateList.valueOf(Color.parseColor(it)) - } - backgroundColor?.let { - shape.fillColor = ColorStateList.valueOf(Color.parseColor(it)) + mCardWidget.background = + MaterialShapeDrawable( + ShapeAppearanceModel() + .toBuilder() + .setAllCorners( + CornerFamily.ROUNDED, + PixelUtil.toPixelFromDIP(borderRadius.toDouble()), + ).build(), + ).also { shape -> + shape.strokeWidth = 0.0f + shape.strokeColor = ColorStateList.valueOf(Color.parseColor("#000000")) + shape.fillColor = ColorStateList.valueOf(Color.parseColor("#FFFFFF")) + borderWidth?.let { shape.strokeWidth = PixelUtil.toPixelFromDIP(it.toDouble()) } + borderColor?.let { shape.strokeColor = ColorStateList.valueOf(Color.parseColor(it)) } + backgroundColor?.let { + shape.fillColor = ColorStateList.valueOf(Color.parseColor(it)) + } } - } } private fun setCardBrandTint(color: Int) { try { - cardInputWidgetBinding.cardBrandView::class.java + cardInputWidgetBinding.cardBrandView::class + .java .getDeclaredMethod("setTintColorInt\$payments_core_release", Int::class.java) - .let { - it(cardInputWidgetBinding.cardBrandView, color) - } + .let { it(cardInputWidgetBinding.cardBrandView, color) } } catch (e: Exception) { - Log.e( - "StripeReactNative", - "Unable to set card brand tint color: " + e.message) + Log.e("StripeReactNative", "Unable to set card brand tint color: " + e.message) } } @@ -185,18 +206,10 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { val cvcPlaceholder = getValOr(value, "cvc", null) val postalCodePlaceholder = getValOr(value, "postalCode", null) - numberPlaceholder?.let { - cardInputWidgetBinding.cardNumberEditText.hint = it - } - expirationPlaceholder?.let { - cardInputWidgetBinding.expiryDateEditText.hint = it - } - cvcPlaceholder?.let { - mCardWidget.setCvcLabel(it) - } - postalCodePlaceholder?.let { - cardInputWidgetBinding.postalCodeEditText.hint = it - } + numberPlaceholder?.let { cardInputWidgetBinding.cardNumberEditText.hint = it } + expirationPlaceholder?.let { cardInputWidgetBinding.expiryDateEditText.hint = it } + cvcPlaceholder?.let { mCardWidget.setCvcLabel(it) } + postalCodePlaceholder?.let { cardInputWidgetBinding.postalCodeEditText.hint = it } } fun setDangerouslyGetFullCardDetails(isEnabled: Boolean) { @@ -224,46 +237,54 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { } /** - * We can reliable assume that setPostalCodeEnabled is called before - * setCountryCode because of the order of the props in CardField.tsx + * We can reliable assume that setPostalCodeEnabled is called before setCountryCode because of the + * order of the props in CardField.tsx */ @SuppressLint("RestrictedApi") fun setCountryCode(countryString: String?) { if (mCardWidget.postalCodeEnabled) { - val countryCode = CountryCode.create(value = countryString ?: LocaleListCompat.getAdjustedDefault()[0]?.country ?: "US") + val countryCode = + CountryCode.create( + value = countryString ?: LocaleListCompat.getAdjustedDefault()[0]?.country ?: "US", + ) mCardWidget.postalCodeRequired = CountryUtils.doesCountryUsePostalCode(countryCode) setPostalCodeFilter(countryCode) } } - fun getValue(): MutableMap { - return cardDetails - } + fun getValue(): MutableMap = cardDetails private fun onValidCardChange() { mCardWidget.paymentMethodCard?.let { cardParams = it - cardAddress = Address.Builder() - .setPostalCode(cardDetails["postalCode"] as String?) - .build() - } ?: run { - cardParams = null - cardAddress = null + cardAddress = Address.Builder().setPostalCode(cardDetails["postalCode"] as String?).build() } + ?: run { + cardParams = null + cardAddress = null + } mCardWidget.cardParams?.let { cardDetails["brand"] = mapCardBrand(it.brand) cardDetails["last4"] = it.last4 - } ?: run { - cardDetails["brand"] = null - cardDetails["last4"] = null } + ?: run { + cardDetails["brand"] = null + cardDetails["last4"] = null + } sendCardDetailsEvent() } private fun sendCardDetailsEvent() { mEventDispatcher?.dispatchEvent( - CardChangedEvent(id, cardDetails, mCardWidget.postalCodeEnabled, isCardValid, dangerouslyGetFullCardDetails)) + CardChangedEvent( + id, + cardDetails, + mCardWidget.postalCodeEnabled, + isCardValid, + dangerouslyGetFullCardDetails, + ), + ) } private fun setListeners() { @@ -286,17 +307,29 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { mCardWidget.setCardValidCallback { isValid, invalidFields -> isCardValid = isValid - fun getCardValidationState(field: CardValidCallback.Fields, editTextField: StripeEditText): String { + + fun getCardValidationState( + field: CardValidCallback.Fields, + editTextField: StripeEditText, + ): String { if (invalidFields.contains(field)) { - return if (editTextField.shouldShowError) "Invalid" - else "Incomplete" + return if (editTextField.shouldShowError) "Invalid" else "Incomplete" } return "Valid" } - cardDetails["validNumber"] = getCardValidationState(CardValidCallback.Fields.Number, cardInputWidgetBinding.cardNumberEditText) - cardDetails["validCVC"] = getCardValidationState(CardValidCallback.Fields.Cvc, cardInputWidgetBinding.cvcEditText) - cardDetails["validExpiryDate"] = getCardValidationState(CardValidCallback.Fields.Expiry, cardInputWidgetBinding.expiryDateEditText) + cardDetails["validNumber"] = + getCardValidationState( + CardValidCallback.Fields.Number, + cardInputWidgetBinding.cardNumberEditText, + ) + cardDetails["validCVC"] = + getCardValidationState(CardValidCallback.Fields.Cvc, cardInputWidgetBinding.cvcEditText) + cardDetails["validExpiryDate"] = + getCardValidationState( + CardValidCallback.Fields.Expiry, + cardInputWidgetBinding.expiryDateEditText, + ) cardDetails["brand"] = mapCardBrand(cardInputWidgetBinding.cardNumberEditText.cardBrand) if (isValid) { @@ -308,69 +341,139 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { } } - mCardWidget.setCardInputListener(object : CardInputListener { - override fun onCardComplete() {} - override fun onExpirationComplete() {} - override fun onCvcComplete() {} - override fun onPostalCodeComplete() {} - override fun onFocusChange(focusField: CardInputListener.FocusField) {} - }) - - mCardWidget.setExpiryDateTextWatcher(object : TextWatcher { - override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} - override fun afterTextChanged(p0: Editable?) {} - override fun onTextChanged(var1: CharSequence?, var2: Int, var3: Int, var4: Int) { - val splitText = var1.toString().split("/") - cardDetails["expiryMonth"] = splitText[0].toIntOrNull() - - if (splitText.size == 2) { - cardDetails["expiryYear"] = var1.toString().split("/")[1].toIntOrNull() + mCardWidget.setCardInputListener( + object : CardInputListener { + override fun onCardComplete() {} + + override fun onExpirationComplete() {} + + override fun onCvcComplete() {} + + override fun onPostalCodeComplete() {} + + override fun onFocusChange(focusField: CardInputListener.FocusField) {} + }, + ) + + mCardWidget.setExpiryDateTextWatcher( + object : TextWatcher { + override fun beforeTextChanged( + p0: CharSequence?, + p1: Int, + p2: Int, + p3: Int, + ) {} + + override fun afterTextChanged(p0: Editable?) {} + + override fun onTextChanged( + var1: CharSequence?, + var2: Int, + var3: Int, + var4: Int, + ) { + val splitText = var1.toString().split("/") + cardDetails["expiryMonth"] = splitText[0].toIntOrNull() + + if (splitText.size == 2) { + cardDetails["expiryYear"] = var1.toString().split("/")[1].toIntOrNull() + } } - } - }) + }, + ) - mCardWidget.setPostalCodeTextWatcher(object : TextWatcher { - override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} - override fun afterTextChanged(p0: Editable?) {} - override fun onTextChanged(var1: CharSequence?, var2: Int, var3: Int, var4: Int) { - cardDetails["postalCode"] = var1.toString() - } - }) - - mCardWidget.setCardNumberTextWatcher(object : TextWatcher { - override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} - override fun afterTextChanged(p0: Editable?) {} - override fun onTextChanged(var1: CharSequence?, var2: Int, var3: Int, var4: Int) { - if (dangerouslyGetFullCardDetails) { - cardDetails["number"] = var1.toString().replace(" ", "") + mCardWidget.setPostalCodeTextWatcher( + object : TextWatcher { + override fun beforeTextChanged( + p0: CharSequence?, + p1: Int, + p2: Int, + p3: Int, + ) {} + + override fun afterTextChanged(p0: Editable?) {} + + override fun onTextChanged( + var1: CharSequence?, + var2: Int, + var3: Int, + var4: Int, + ) { + cardDetails["postalCode"] = var1.toString() } - } - }) - - mCardWidget.setCvcNumberTextWatcher(object : TextWatcher { - override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} - override fun afterTextChanged(p0: Editable?) {} - override fun onTextChanged(var1: CharSequence?, var2: Int, var3: Int, var4: Int) { - if (dangerouslyGetFullCardDetails) { - cardDetails["cvc"] = var1.toString() + }, + ) + + mCardWidget.setCardNumberTextWatcher( + object : TextWatcher { + override fun beforeTextChanged( + p0: CharSequence?, + p1: Int, + p2: Int, + p3: Int, + ) {} + + override fun afterTextChanged(p0: Editable?) {} + + override fun onTextChanged( + var1: CharSequence?, + var2: Int, + var3: Int, + var4: Int, + ) { + if (dangerouslyGetFullCardDetails) { + cardDetails["number"] = var1.toString().replace(" ", "") + } } - } - }) + }, + ) + + mCardWidget.setCvcNumberTextWatcher( + object : TextWatcher { + override fun beforeTextChanged( + p0: CharSequence?, + p1: Int, + p2: Int, + p3: Int, + ) {} + + override fun afterTextChanged(p0: Editable?) {} + + override fun onTextChanged( + var1: CharSequence?, + var2: Int, + var3: Int, + var4: Int, + ) { + if (dangerouslyGetFullCardDetails) { + cardDetails["cvc"] = var1.toString() + } + } + }, + ) } private fun setPostalCodeFilter(countryCode: CountryCode) { - cardInputWidgetBinding.postalCodeEditText.filters = arrayOf( - *cardInputWidgetBinding.postalCodeEditText.filters, - createPostalCodeInputFilter(countryCode) - ) + cardInputWidgetBinding.postalCodeEditText.filters = + arrayOf( + *cardInputWidgetBinding.postalCodeEditText.filters, + createPostalCodeInputFilter(countryCode), + ) } @SuppressLint("RestrictedApi") private fun createPostalCodeInputFilter(countryCode: CountryCode): InputFilter { return InputFilter { charSequence, start, end, _, _, _ -> for (i in start until end) { - val isValidCharacter = (countryCode == CountryCode.US && PostalCodeUtilities.isValidUsPostalCodeCharacter(charSequence[i])) || - (countryCode != CountryCode.US && PostalCodeUtilities.isValidGlobalPostalCodeCharacter(charSequence[i])) + val isValidCharacter = + ( + countryCode == CountryCode.US && + PostalCodeUtilities.isValidUsPostalCodeCharacter(charSequence[i]) + ) || + ( + countryCode != CountryCode.US && + PostalCodeUtilities.isValidGlobalPostalCodeCharacter(charSequence[i]) + ) if (!isValidCharacter) { return@InputFilter "" } @@ -384,10 +487,12 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { post(mLayoutRunnable) } - private val mLayoutRunnable = Runnable { - measure( - MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)) - layout(left, top, right, bottom) - } + private val mLayoutRunnable = + Runnable { + measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY), + ) + layout(left, top, right, bottom) + } } diff --git a/android/src/main/java/com/reactnativestripesdk/CardFieldViewManager.kt b/android/src/main/java/com/reactnativestripesdk/CardFieldViewManager.kt index 6575ca5d1..02db56975 100644 --- a/android/src/main/java/com/reactnativestripesdk/CardFieldViewManager.kt +++ b/android/src/main/java/com/reactnativestripesdk/CardFieldViewManager.kt @@ -12,13 +12,19 @@ class CardFieldViewManager : SimpleViewManager() { private var reactContextRef: ThemedReactContext? = null - override fun getExportedCustomDirectEventTypeConstants(): MutableMap { - return MapBuilder.of( - CardFocusEvent.EVENT_NAME, MapBuilder.of("registrationName", "onFocusChange"), - CardChangedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onCardChange")) - } - - override fun receiveCommand(root: CardFieldView, commandId: String?, args: ReadableArray?) { + override fun getExportedCustomDirectEventTypeConstants(): MutableMap = + MapBuilder.of( + CardFocusEvent.EVENT_NAME, + MapBuilder.of("registrationName", "onFocusChange"), + CardChangedEvent.EVENT_NAME, + MapBuilder.of("registrationName", "onCardChange"), + ) + + override fun receiveCommand( + root: CardFieldView, + commandId: String?, + args: ReadableArray?, + ) { when (commandId) { "focus" -> root.requestFocusFromJS() "blur" -> root.requestBlurFromJS() @@ -27,53 +33,81 @@ class CardFieldViewManager : SimpleViewManager() { } @ReactProp(name = "dangerouslyGetFullCardDetails") - fun setDangerouslyGetFullCardDetails(view: CardFieldView, dangerouslyGetFullCardDetails: Boolean = false) { + fun setDangerouslyGetFullCardDetails( + view: CardFieldView, + dangerouslyGetFullCardDetails: Boolean = false, + ) { view.setDangerouslyGetFullCardDetails(dangerouslyGetFullCardDetails) } @ReactProp(name = "postalCodeEnabled") - fun setPostalCodeEnabled(view: CardFieldView, postalCodeEnabled: Boolean = true) { + fun setPostalCodeEnabled( + view: CardFieldView, + postalCodeEnabled: Boolean = true, + ) { view.setPostalCodeEnabled(postalCodeEnabled) } @ReactProp(name = "autofocus") - fun setAutofocus(view: CardFieldView, autofocus: Boolean = false) { + fun setAutofocus( + view: CardFieldView, + autofocus: Boolean = false, + ) { view.setAutofocus(autofocus) } @ReactProp(name = "cardStyle") - fun setCardStyle(view: CardFieldView, cardStyle: ReadableMap) { + fun setCardStyle( + view: CardFieldView, + cardStyle: ReadableMap, + ) { view.setCardStyle(cardStyle) } @ReactProp(name = "countryCode") - fun setCountryCode(view: CardFieldView, countryCode: String?) { + fun setCountryCode( + view: CardFieldView, + countryCode: String?, + ) { view.setCountryCode(countryCode) } @ReactProp(name = "onBehalfOf") - fun setOnBehalfOf(view: CardFieldView, onBehalfOf: String?) { + fun setOnBehalfOf( + view: CardFieldView, + onBehalfOf: String?, + ) { view.setOnBehalfOf(onBehalfOf) } @ReactProp(name = "placeholders") - fun setPlaceHolders(view: CardFieldView, placeholders: ReadableMap) { + fun setPlaceHolders( + view: CardFieldView, + placeholders: ReadableMap, + ) { view.setPlaceHolders(placeholders) } @ReactProp(name = "disabled") - fun setDisabled(view: CardFieldView, isDisabled: Boolean) { + fun setDisabled( + view: CardFieldView, + isDisabled: Boolean, + ) { view.setDisabled(isDisabled) } @ReactProp(name = "preferredNetworks") - fun setPreferredNetworks(view: CardFieldView, preferredNetworks: ReadableArray?) { + fun setPreferredNetworks( + view: CardFieldView, + preferredNetworks: ReadableArray?, + ) { val networks = preferredNetworks?.toArrayList()?.filterIsInstance()?.let { ArrayList(it) } view.setPreferredNetworks(networks) } override fun createViewInstance(reactContext: ThemedReactContext): CardFieldView { - val stripeSdkModule: StripeSdkModule? = reactContext.getNativeModule(StripeSdkModule::class.java) + val stripeSdkModule: StripeSdkModule? = + reactContext.getNativeModule(StripeSdkModule::class.java) val view = CardFieldView(reactContext) reactContextRef = reactContext @@ -85,7 +119,8 @@ class CardFieldViewManager : SimpleViewManager() { override fun onDropViewInstance(view: CardFieldView) { super.onDropViewInstance(view) - val stripeSdkModule: StripeSdkModule? = reactContextRef?.getNativeModule(StripeSdkModule::class.java) + val stripeSdkModule: StripeSdkModule? = + reactContextRef?.getNativeModule(StripeSdkModule::class.java) stripeSdkModule?.cardFieldView = null reactContextRef = null } diff --git a/android/src/main/java/com/reactnativestripesdk/CardFocusEvent.kt b/android/src/main/java/com/reactnativestripesdk/CardFocusEvent.kt index 84ee559ed..c61929a66 100644 --- a/android/src/main/java/com/reactnativestripesdk/CardFocusEvent.kt +++ b/android/src/main/java/com/reactnativestripesdk/CardFocusEvent.kt @@ -1,13 +1,15 @@ package com.reactnativestripesdk + import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.Event import com.facebook.react.uimanager.events.RCTEventEmitter -internal class CardFocusEvent constructor(viewTag: Int, private val focusField: String?) : Event(viewTag) { - override fun getEventName(): String { - return EVENT_NAME - } +internal class CardFocusEvent constructor( + viewTag: Int, + private val focusField: String?, +) : Event(viewTag) { + override fun getEventName(): String = EVENT_NAME override fun dispatch(rctEventEmitter: RCTEventEmitter) { rctEventEmitter.receiveEvent(viewTag, eventName, serializeEventData()) @@ -23,5 +25,4 @@ internal class CardFocusEvent constructor(viewTag: Int, private val focusField: companion object { const val EVENT_NAME = "topFocusChange" } - } diff --git a/android/src/main/java/com/reactnativestripesdk/CardFormCompleteEvent.kt b/android/src/main/java/com/reactnativestripesdk/CardFormCompleteEvent.kt index e648035bf..3895e4035 100644 --- a/android/src/main/java/com/reactnativestripesdk/CardFormCompleteEvent.kt +++ b/android/src/main/java/com/reactnativestripesdk/CardFormCompleteEvent.kt @@ -5,39 +5,42 @@ import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.Event import com.facebook.react.uimanager.events.RCTEventEmitter -internal class CardFormCompleteEvent constructor(viewTag: Int, private val cardDetails: MutableMap?, private val complete: Boolean, private val dangerouslyGetFullCardDetails: Boolean) : Event(viewTag) { - override fun getEventName(): String { - return EVENT_NAME - } - - override fun dispatch(rctEventEmitter: RCTEventEmitter) { - rctEventEmitter.receiveEvent(viewTag, eventName, serializeEventData()) - } +internal class CardFormCompleteEvent + constructor( + viewTag: Int, + private val cardDetails: MutableMap?, + private val complete: Boolean, + private val dangerouslyGetFullCardDetails: Boolean, + ) : Event(viewTag) { + override fun getEventName(): String = EVENT_NAME + + override fun dispatch(rctEventEmitter: RCTEventEmitter) { + rctEventEmitter.receiveEvent(viewTag, eventName, serializeEventData()) + } - private fun serializeEventData(): WritableMap { - val eventData = Arguments.createMap() + private fun serializeEventData(): WritableMap { + val eventData = Arguments.createMap() + + if (cardDetails == null) { + return eventData + } + eventData.putString("brand", cardDetails["brand"]?.toString()) + eventData.putString("last4", cardDetails["last4"]?.toString()) + eventData.putString("country", cardDetails["country"]?.toString()) + eventData.putInt("expiryMonth", cardDetails["expiryMonth"] as Int) + eventData.putInt("expiryYear", cardDetails["expiryYear"] as Int) + eventData.putBoolean("complete", complete) + eventData.putString("postalCode", cardDetails["postalCode"]?.toString()) + + if (dangerouslyGetFullCardDetails) { + eventData.putString("number", cardDetails["number"]?.toString()?.replace(" ", "")) + eventData.putString("cvc", cardDetails["cvc"]?.toString()) + } - if (cardDetails == null) { return eventData } - eventData.putString("brand", cardDetails["brand"]?.toString()) - eventData.putString("last4", cardDetails["last4"]?.toString()) - eventData.putString("country", cardDetails["country"]?.toString()) - eventData.putInt("expiryMonth", cardDetails["expiryMonth"] as Int) - eventData.putInt("expiryYear", cardDetails["expiryYear"] as Int) - eventData.putBoolean("complete", complete) - eventData.putString("postalCode", cardDetails["postalCode"]?.toString()) - - if (dangerouslyGetFullCardDetails) { - eventData.putString("number", cardDetails["number"]?.toString()?.replace(" ", "")) - eventData.putString("cvc", cardDetails["cvc"]?.toString()) - } - return eventData - } - - companion object { - const val EVENT_NAME = "onFormComplete" + companion object { + const val EVENT_NAME = "onFormComplete" + } } - -} diff --git a/android/src/main/java/com/reactnativestripesdk/CardFormView.kt b/android/src/main/java/com/reactnativestripesdk/CardFormView.kt index e7a23949a..db59cb856 100644 --- a/android/src/main/java/com/reactnativestripesdk/CardFormView.kt +++ b/android/src/main/java/com/reactnativestripesdk/CardFormView.kt @@ -18,30 +18,41 @@ import com.facebook.react.views.text.ReactTypefaceUtils import com.google.android.material.shape.CornerFamily import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.ShapeAppearanceModel -import com.reactnativestripesdk.utils.* +import com.reactnativestripesdk.utils.PostalCodeUtilities +import com.reactnativestripesdk.utils.getIntOrNull +import com.reactnativestripesdk.utils.getValOr +import com.reactnativestripesdk.utils.hideSoftKeyboard import com.reactnativestripesdk.utils.mapCardBrand +import com.reactnativestripesdk.utils.mapToPreferredNetworks +import com.reactnativestripesdk.utils.showSoftKeyboard import com.stripe.android.core.model.CountryCode -import com.stripe.android.databinding.StripeCardMultilineWidgetBinding import com.stripe.android.databinding.StripeCardFormViewBinding +import com.stripe.android.databinding.StripeCardMultilineWidgetBinding import com.stripe.android.model.Address import com.stripe.android.model.PaymentMethodCreateParams import com.stripe.android.view.CardFormView import com.stripe.android.view.CardInputListener -class CardFormView(context: ThemedReactContext) : FrameLayout(context) { - private var cardForm: CardFormView = CardFormView(context, null, com.stripe.android.R.style.StripeCardFormView_Borderless) - private var mEventDispatcher: EventDispatcher? = context.getNativeModule(UIManagerModule::class.java)?.eventDispatcher +class CardFormView( + context: ThemedReactContext, +) : FrameLayout(context) { + private var cardForm: CardFormView = + CardFormView(context, null, com.stripe.android.R.style.StripeCardFormView_Borderless) + private var mEventDispatcher: EventDispatcher? = + context.getNativeModule(UIManagerModule::class.java)?.eventDispatcher private var dangerouslyGetFullCardDetails: Boolean = false private var currentFocusedField: String? = null var cardParams: PaymentMethodCreateParams.Card? = null var cardAddress: Address? = null private val cardFormViewBinding = StripeCardFormViewBinding.bind(cardForm) - private val multilineWidgetBinding = StripeCardMultilineWidgetBinding.bind(cardFormViewBinding.cardMultilineWidget) + private val multilineWidgetBinding = + StripeCardMultilineWidgetBinding.bind(cardFormViewBinding.cardMultilineWidget) init { cardFormViewBinding.cardMultilineWidgetContainer.isFocusable = true cardFormViewBinding.cardMultilineWidgetContainer.isFocusableInTouchMode = true - (cardFormViewBinding.cardMultilineWidgetContainer.layoutParams as MarginLayoutParams).setMargins(0) + (cardFormViewBinding.cardMultilineWidgetContainer.layoutParams as MarginLayoutParams) + .setMargins(0) addView(cardForm) setListeners() @@ -82,18 +93,10 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) { val cvcPlaceholder = getValOr(value, "cvc", null) val postalCodePlaceholder = getValOr(value, "postalCode", null) - numberPlaceholder?.let { - multilineWidgetBinding.tlCardNumber.hint = it - } - expirationPlaceholder?.let { - multilineWidgetBinding.tlExpiry.hint = it - } - cvcPlaceholder?.let { - multilineWidgetBinding.tlCvc.hint = it - } - postalCodePlaceholder?.let { - cardFormViewBinding.postalCodeContainer.hint = it - } + numberPlaceholder?.let { multilineWidgetBinding.tlCardNumber.hint = it } + expirationPlaceholder?.let { multilineWidgetBinding.tlExpiry.hint = it } + cvcPlaceholder?.let { multilineWidgetBinding.tlCvc.hint = it } + postalCodePlaceholder?.let { cardFormViewBinding.postalCodeContainer.hint = it } } fun setAutofocus(value: Boolean) { @@ -124,8 +127,7 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) { } private fun onChangeFocus() { - mEventDispatcher?.dispatchEvent( - CardFocusEvent(id, currentFocusedField)) + mEventDispatcher?.dispatchEvent(CardFocusEvent(id, currentFocusedField)) } @SuppressLint("RestrictedApi") @@ -141,18 +143,20 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) { val textErrorColor = getValOr(value, "textErrorColor", null) val cursorColor = getValOr(value, "cursorColor", null) - val editTextBindings = setOf( - cardFormViewBinding.cardMultilineWidget.cardNumberEditText, - cardFormViewBinding.cardMultilineWidget.cvcEditText, - cardFormViewBinding.cardMultilineWidget.expiryDateEditText, - cardFormViewBinding.postalCode - ) - val placeholderTextBindings = setOf( - multilineWidgetBinding.tlExpiry, - multilineWidgetBinding.tlCardNumber, - multilineWidgetBinding.tlCvc, - cardFormViewBinding.postalCodeContainer, - ) + val editTextBindings = + setOf( + cardFormViewBinding.cardMultilineWidget.cardNumberEditText, + cardFormViewBinding.cardMultilineWidget.cvcEditText, + cardFormViewBinding.cardMultilineWidget.expiryDateEditText, + cardFormViewBinding.postalCode, + ) + val placeholderTextBindings = + setOf( + multilineWidgetBinding.tlExpiry, + multilineWidgetBinding.tlCardNumber, + multilineWidgetBinding.tlCvc, + cardFormViewBinding.postalCodeContainer, + ) textColor?.let { for (binding in editTextBindings) { @@ -178,7 +182,14 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) { } fontFamily?.let { // Load custom font from assets, and fallback to default system font - val typeface = ReactTypefaceUtils.applyStyles(null, -1, -1, it.takeIf { it.isNotEmpty() }, context.assets) + val typeface = + ReactTypefaceUtils.applyStyles( + null, + -1, + -1, + it.takeIf { it.isNotEmpty() }, + context.assets, + ) for (binding in editTextBindings) { binding.typeface = typeface } @@ -202,25 +213,24 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) { } } - cardFormViewBinding.cardMultilineWidgetContainer.background = MaterialShapeDrawable( - ShapeAppearanceModel() - .toBuilder() - .setAllCorners(CornerFamily.ROUNDED, PixelUtil.toPixelFromDIP(borderRadius.toDouble())) - .build() - ).also { shape -> - shape.strokeWidth = 0.0f - shape.strokeColor = ColorStateList.valueOf(Color.parseColor("#000000")) - shape.fillColor = ColorStateList.valueOf(Color.parseColor("#FFFFFF")) - borderWidth?.let { - shape.strokeWidth = PixelUtil.toPixelFromDIP(it.toDouble()) - } - borderColor?.let { - shape.strokeColor = ColorStateList.valueOf(Color.parseColor(it)) - } - backgroundColor?.let { - shape.fillColor = ColorStateList.valueOf(Color.parseColor(it)) + cardFormViewBinding.cardMultilineWidgetContainer.background = + MaterialShapeDrawable( + ShapeAppearanceModel() + .toBuilder() + .setAllCorners( + CornerFamily.ROUNDED, + PixelUtil.toPixelFromDIP(borderRadius.toDouble()), + ).build(), + ).also { shape -> + shape.strokeWidth = 0.0f + shape.strokeColor = ColorStateList.valueOf(Color.parseColor("#000000")) + shape.fillColor = ColorStateList.valueOf(Color.parseColor("#FFFFFF")) + borderWidth?.let { shape.strokeWidth = PixelUtil.toPixelFromDIP(it.toDouble()) } + borderColor?.let { shape.strokeColor = ColorStateList.valueOf(Color.parseColor(it)) } + backgroundColor?.let { + shape.fillColor = ColorStateList.valueOf(Color.parseColor(it)) + } } - } } fun setDangerouslyGetFullCardDetails(isEnabled: Boolean) { @@ -232,14 +242,15 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) { if (isValid) { cardForm.cardParams?.let { val cardParamsMap = it.toParamMap()["card"] as HashMap<*, *> - val cardDetails: MutableMap = mutableMapOf( - "expiryMonth" to cardParamsMap["exp_month"] as Int, - "expiryYear" to cardParamsMap["exp_year"] as Int, - "last4" to it.last4, - "brand" to mapCardBrand(it.brand), - "postalCode" to (it.address?.postalCode ?: ""), - "country" to (it.address?.country ?: "") - ) + val cardDetails: MutableMap = + mutableMapOf( + "expiryMonth" to cardParamsMap["exp_month"] as Int, + "expiryYear" to cardParamsMap["exp_year"] as Int, + "last4" to it.last4, + "brand" to mapCardBrand(it.brand), + "postalCode" to (it.address?.postalCode ?: ""), + "country" to (it.address?.country ?: ""), + ) if (dangerouslyGetFullCardDetails) { cardDetails["number"] = cardParamsMap["number"] as String @@ -247,20 +258,26 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) { } mEventDispatcher?.dispatchEvent( - CardFormCompleteEvent(id, cardDetails, isValid, dangerouslyGetFullCardDetails)) + CardFormCompleteEvent(id, cardDetails, isValid, dangerouslyGetFullCardDetails), + ) - cardAddress = Address.Builder() - .setPostalCode(it.address?.postalCode) - .setCountry(it.address?.country) - .build() + cardAddress = + Address + .Builder() + .setPostalCode(it.address?.postalCode) + .setCountry(it.address?.country) + .build() - cardFormViewBinding.cardMultilineWidget.paymentMethodCard?.let { params -> cardParams = params } + cardFormViewBinding.cardMultilineWidget.paymentMethodCard?.let { params -> + cardParams = params + } } } else { cardParams = null cardAddress = null mEventDispatcher?.dispatchEvent( - CardFormCompleteEvent(id, null, isValid, dangerouslyGetFullCardDetails)) + CardFormCompleteEvent(id, null, isValid, dangerouslyGetFullCardDetails), + ) } } @@ -269,29 +286,34 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) { val expiryEditText = multilineWidgetBinding.etExpiry val postalCodeEditText = cardFormViewBinding.postalCode - cardNumberEditText.onFocusChangeListener = OnFocusChangeListener { _, hasFocus -> - currentFocusedField = if (hasFocus) CardInputListener.FocusField.CardNumber.toString() else null - onChangeFocus() - } - cvcEditText.onFocusChangeListener = OnFocusChangeListener { _, hasFocus -> - currentFocusedField = if (hasFocus) CardInputListener.FocusField.Cvc.toString() else null - onChangeFocus() - } - expiryEditText.onFocusChangeListener = OnFocusChangeListener { _, hasFocus -> - currentFocusedField = if (hasFocus) CardInputListener.FocusField.ExpiryDate.toString() else null - onChangeFocus() - } - postalCodeEditText.onFocusChangeListener = OnFocusChangeListener { _, hasFocus -> - currentFocusedField = if (hasFocus) CardInputListener.FocusField.PostalCode.toString() else null - onChangeFocus() - } + cardNumberEditText.onFocusChangeListener = + OnFocusChangeListener { _, hasFocus -> + currentFocusedField = + if (hasFocus) CardInputListener.FocusField.CardNumber.toString() else null + onChangeFocus() + } + cvcEditText.onFocusChangeListener = + OnFocusChangeListener { _, hasFocus -> + currentFocusedField = if (hasFocus) CardInputListener.FocusField.Cvc.toString() else null + onChangeFocus() + } + expiryEditText.onFocusChangeListener = + OnFocusChangeListener { _, hasFocus -> + currentFocusedField = + if (hasFocus) CardInputListener.FocusField.ExpiryDate.toString() else null + onChangeFocus() + } + postalCodeEditText.onFocusChangeListener = + OnFocusChangeListener { _, hasFocus -> + currentFocusedField = + if (hasFocus) CardInputListener.FocusField.PostalCode.toString() else null + onChangeFocus() + } } private fun setPostalCodeFilter() { - cardFormViewBinding.postalCode.filters = arrayOf( - *cardFormViewBinding.postalCode.filters, - createPostalCodeInputFilter() - ) + cardFormViewBinding.postalCode.filters = + arrayOf(*cardFormViewBinding.postalCode.filters, createPostalCodeInputFilter()) } @SuppressLint("RestrictedApi") @@ -316,10 +338,12 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) { post(mLayoutRunnable) } - private val mLayoutRunnable = Runnable { - measure( - MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)) - layout(left, top, right, bottom) - } + private val mLayoutRunnable = + Runnable { + measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY), + ) + layout(left, top, right, bottom) + } } diff --git a/android/src/main/java/com/reactnativestripesdk/CardFormViewManager.kt b/android/src/main/java/com/reactnativestripesdk/CardFormViewManager.kt index 0b0141807..991deb20f 100644 --- a/android/src/main/java/com/reactnativestripesdk/CardFormViewManager.kt +++ b/android/src/main/java/com/reactnativestripesdk/CardFormViewManager.kt @@ -12,13 +12,19 @@ class CardFormViewManager : SimpleViewManager() { private var reactContextRef: ThemedReactContext? = null - override fun getExportedCustomDirectEventTypeConstants(): MutableMap { - return MapBuilder.of( - CardFocusEvent.EVENT_NAME, MapBuilder.of("registrationName", "onFocusChange"), - CardFormCompleteEvent.EVENT_NAME, MapBuilder.of("registrationName", "onFormComplete")) - } - - override fun receiveCommand(root: CardFormView, commandId: String?, args: ReadableArray?) { + override fun getExportedCustomDirectEventTypeConstants(): MutableMap = + MapBuilder.of( + CardFocusEvent.EVENT_NAME, + MapBuilder.of("registrationName", "onFocusChange"), + CardFormCompleteEvent.EVENT_NAME, + MapBuilder.of("registrationName", "onFormComplete"), + ) + + override fun receiveCommand( + root: CardFormView, + commandId: String?, + args: ReadableArray?, + ) { when (commandId) { "focus" -> root.requestFocusFromJS() "blur" -> root.requestBlurFromJS() @@ -27,48 +33,73 @@ class CardFormViewManager : SimpleViewManager() { } @ReactProp(name = "dangerouslyGetFullCardDetails") - fun setDangerouslyGetFullCardDetails(view: CardFormView, dangerouslyGetFullCardDetails: Boolean = false) { + fun setDangerouslyGetFullCardDetails( + view: CardFormView, + dangerouslyGetFullCardDetails: Boolean = false, + ) { view.setDangerouslyGetFullCardDetails(dangerouslyGetFullCardDetails) } @ReactProp(name = "postalCodeEnabled") - fun setPostalCodeEnabled(view: CardFormView, postalCodeEnabled: Boolean = false) { + fun setPostalCodeEnabled( + view: CardFormView, + postalCodeEnabled: Boolean = false, + ) { view.setPostalCodeEnabled(postalCodeEnabled) } - @ReactProp(name = "placeholders") - fun setPlaceHolders(view: CardFormView, placeholders: ReadableMap) { - view.setPlaceHolders(placeholders) - } + @ReactProp(name = "placeholders") + fun setPlaceHolders( + view: CardFormView, + placeholders: ReadableMap, + ) { + view.setPlaceHolders(placeholders) + } @ReactProp(name = "autofocus") - fun setAutofocus(view: CardFormView, autofocus: Boolean = false) { + fun setAutofocus( + view: CardFormView, + autofocus: Boolean = false, + ) { view.setAutofocus(autofocus) } @ReactProp(name = "cardStyle") - fun setCardStyle(view: CardFormView, cardStyle: ReadableMap) { + fun setCardStyle( + view: CardFormView, + cardStyle: ReadableMap, + ) { view.setCardStyle(cardStyle) } @ReactProp(name = "defaultValues") - fun setDefaultValues(view: CardFormView, defaults: ReadableMap) { + fun setDefaultValues( + view: CardFormView, + defaults: ReadableMap, + ) { view.setDefaultValues(defaults) } @ReactProp(name = "disabled") - fun setDisabled(view: CardFormView, isDisabled: Boolean) { + fun setDisabled( + view: CardFormView, + isDisabled: Boolean, + ) { view.setDisabled(isDisabled) } @ReactProp(name = "preferredNetworks") - fun setPreferredNetworks(view: CardFormView, preferredNetworks: ReadableArray?) { + fun setPreferredNetworks( + view: CardFormView, + preferredNetworks: ReadableArray?, + ) { val networks = preferredNetworks?.toArrayList()?.filterIsInstance()?.let { ArrayList(it) } view.setPreferredNetworks(networks) } override fun createViewInstance(reactContext: ThemedReactContext): CardFormView { - val stripeSdkModule: StripeSdkModule? = reactContext.getNativeModule(StripeSdkModule::class.java) + val stripeSdkModule: StripeSdkModule? = + reactContext.getNativeModule(StripeSdkModule::class.java) val view = CardFormView(reactContext) reactContextRef = reactContext @@ -80,7 +111,8 @@ class CardFormViewManager : SimpleViewManager() { override fun onDropViewInstance(view: CardFormView) { super.onDropViewInstance(view) - val stripeSdkModule: StripeSdkModule? = reactContextRef?.getNativeModule(StripeSdkModule::class.java) + val stripeSdkModule: StripeSdkModule? = + reactContextRef?.getNativeModule(StripeSdkModule::class.java) stripeSdkModule?.cardFormView = null reactContextRef = null } diff --git a/android/src/main/java/com/reactnativestripesdk/CollectBankAccountLauncherFragment.kt b/android/src/main/java/com/reactnativestripesdk/CollectBankAccountLauncherFragment.kt index 0139f3b62..f1d3097cb 100644 --- a/android/src/main/java/com/reactnativestripesdk/CollectBankAccountLauncherFragment.kt +++ b/android/src/main/java/com/reactnativestripesdk/CollectBankAccountLauncherFragment.kt @@ -5,16 +5,16 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout -import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext -import com.reactnativestripesdk.utils.* +import com.reactnativestripesdk.utils.ErrorType import com.reactnativestripesdk.utils.createError import com.reactnativestripesdk.utils.createResult +import com.reactnativestripesdk.utils.mapFromFinancialConnectionsEvent import com.reactnativestripesdk.utils.mapFromPaymentIntentResult import com.reactnativestripesdk.utils.mapFromSetupIntentResult +import com.reactnativestripesdk.utils.removeFragment import com.stripe.android.financialconnections.FinancialConnections import com.stripe.android.model.PaymentIntent import com.stripe.android.model.SetupIntent @@ -29,8 +29,8 @@ class CollectBankAccountLauncherFragment( private val stripeAccountId: String?, private val clientSecret: String, private val isPaymentIntent: Boolean, - private val collectParams: CollectBankAccountConfiguration.USBankAccount, - private val promise: Promise + private val collectParams: CollectBankAccountConfiguration.USBankAccount, + private val promise: Promise, ) : Fragment() { private lateinit var collectBankAccountLauncher: CollectBankAccountLauncher @@ -46,33 +46,35 @@ class CollectBankAccountLauncherFragment( } } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { collectBankAccountLauncher = createBankAccountLauncher() - return FrameLayout(requireActivity()).also { - it.visibility = View.GONE - } + return FrameLayout(requireActivity()).also { it.visibility = View.GONE } } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated( - view, - savedInstanceState) + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) if (isPaymentIntent) { collectBankAccountLauncher.presentWithPaymentIntent( publishableKey, stripeAccountId, clientSecret, - collectParams + collectParams, ) } else { collectBankAccountLauncher.presentWithSetupIntent( publishableKey, stripeAccountId, clientSecret, - collectParams + collectParams, ) } } @@ -84,24 +86,32 @@ class CollectBankAccountLauncherFragment( FinancialConnections.clearEventListener() } - private fun createBankAccountLauncher(): CollectBankAccountLauncher { - return CollectBankAccountLauncher.create(this) { result -> + private fun createBankAccountLauncher(): CollectBankAccountLauncher = + CollectBankAccountLauncher.create(this) { result -> when (result) { is CollectBankAccountResult.Completed -> { val intent = result.response.intent if (intent.status === StripeIntent.Status.RequiresPaymentMethod) { - promise.resolve(createError(ErrorType.Canceled.toString(), "Bank account collection was canceled.")) + promise.resolve( + createError(ErrorType.Canceled.toString(), "Bank account collection was canceled."), + ) } else if (intent.status === StripeIntent.Status.RequiresConfirmation) { promise.resolve( - if (isPaymentIntent) - createResult("paymentIntent", mapFromPaymentIntentResult(intent as PaymentIntent)) - else + if (isPaymentIntent) { + createResult( + "paymentIntent", + mapFromPaymentIntentResult(intent as PaymentIntent), + ) + } else { createResult("setupIntent", mapFromSetupIntentResult(intent as SetupIntent)) + }, ) } } is CollectBankAccountResult.Cancelled -> { - promise.resolve(createError(ErrorType.Canceled.toString(), "Bank account collection was canceled.")) + promise.resolve( + createError(ErrorType.Canceled.toString(), "Bank account collection was canceled."), + ) } is CollectBankAccountResult.Failed -> { promise.resolve(createError(ErrorType.Failed.toString(), result.error)) @@ -109,7 +119,6 @@ class CollectBankAccountLauncherFragment( } removeFragment(context) } - } companion object { internal const val TAG = "collect_bank_account_launcher_fragment" diff --git a/android/src/main/java/com/reactnativestripesdk/FinancialConnectionsSheetFragment.kt b/android/src/main/java/com/reactnativestripesdk/FinancialConnectionsSheetFragment.kt index 3beae8c8e..aaafa1a95 100644 --- a/android/src/main/java/com/reactnativestripesdk/FinancialConnectionsSheetFragment.kt +++ b/android/src/main/java/com/reactnativestripesdk/FinancialConnectionsSheetFragment.kt @@ -7,20 +7,32 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity -import com.facebook.react.bridge.* -import com.reactnativestripesdk.utils.* +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.WritableArray +import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.WritableNativeMap +import com.reactnativestripesdk.utils.ErrorType import com.reactnativestripesdk.utils.createError import com.reactnativestripesdk.utils.createMissingActivityError +import com.reactnativestripesdk.utils.mapFromFinancialConnectionsEvent import com.reactnativestripesdk.utils.mapFromToken import com.stripe.android.financialconnections.FinancialConnections import com.stripe.android.financialconnections.FinancialConnectionsSheet import com.stripe.android.financialconnections.FinancialConnectionsSheetForTokenResult import com.stripe.android.financialconnections.FinancialConnectionsSheetResult -import com.stripe.android.financialconnections.model.* +import com.stripe.android.financialconnections.model.Balance +import com.stripe.android.financialconnections.model.BalanceRefresh +import com.stripe.android.financialconnections.model.FinancialConnectionsAccount +import com.stripe.android.financialconnections.model.FinancialConnectionsAccountList +import com.stripe.android.financialconnections.model.FinancialConnectionsSession class FinancialConnectionsSheetFragment : Fragment() { enum class Mode { - ForToken, ForSession + ForToken, + ForSession, } private lateinit var promise: Promise @@ -40,30 +52,28 @@ class FinancialConnectionsSheetFragment : Fragment() { } } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View { - return FrameLayout(requireActivity()).also { - it.visibility = View.GONE - } - } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View = FrameLayout(requireActivity()).also { it.visibility = View.GONE } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { when (mode) { Mode.ForToken -> { - FinancialConnectionsSheet.createForBankAccountToken( - this, - ::onFinancialConnectionsSheetForTokenResult - ).present( - configuration = configuration - ) + FinancialConnectionsSheet + .createForBankAccountToken( + this, + ::onFinancialConnectionsSheetForTokenResult, + ).present(configuration = configuration) } Mode.ForSession -> { - FinancialConnectionsSheet.create( - this, - ::onFinancialConnectionsSheetForDataResult - ).present( - configuration = configuration - ) + FinancialConnectionsSheet + .create(this, ::onFinancialConnectionsSheetForDataResult) + .present(configuration = configuration) } } } @@ -76,75 +86,86 @@ class FinancialConnectionsSheetFragment : Fragment() { } private fun onFinancialConnectionsSheetForTokenResult(result: FinancialConnectionsSheetForTokenResult) { - when(result) { + when (result) { is FinancialConnectionsSheetForTokenResult.Canceled -> { - promise.resolve( - createError(ErrorType.Canceled.toString(), "The flow has been canceled") - ) + promise.resolve(createError(ErrorType.Canceled.toString(), "The flow has been canceled")) } is FinancialConnectionsSheetForTokenResult.Failed -> { - promise.resolve( - createError(ErrorType.Failed.toString(), result.error) - ) + promise.resolve(createError(ErrorType.Failed.toString(), result.error)) } is FinancialConnectionsSheetForTokenResult.Completed -> { promise.resolve(createTokenResult(result)) - (context.currentActivity as? FragmentActivity)?.supportFragmentManager?.beginTransaction()?.remove(this)?.commitAllowingStateLoss() + (context.currentActivity as? FragmentActivity) + ?.supportFragmentManager + ?.beginTransaction() + ?.remove(this) + ?.commitAllowingStateLoss() } } } private fun onFinancialConnectionsSheetForDataResult(result: FinancialConnectionsSheetResult) { - when(result) { + when (result) { is FinancialConnectionsSheetResult.Canceled -> { - promise.resolve( - createError(ErrorType.Canceled.toString(), "The flow has been canceled") - ) + promise.resolve(createError(ErrorType.Canceled.toString(), "The flow has been canceled")) } is FinancialConnectionsSheetResult.Failed -> { - promise.resolve( - createError(ErrorType.Failed.toString(), result.error) - ) + promise.resolve(createError(ErrorType.Failed.toString(), result.error)) } is FinancialConnectionsSheetResult.Completed -> { promise.resolve( - WritableNativeMap().also { - it.putMap("session", mapFromSession(result.financialConnectionsSession)) - } + WritableNativeMap().also { + it.putMap("session", mapFromSession(result.financialConnectionsSession)) + }, ) - (context.currentActivity as? FragmentActivity)?.supportFragmentManager?.beginTransaction()?.remove(this)?.commitAllowingStateLoss() + (context.currentActivity as? FragmentActivity) + ?.supportFragmentManager + ?.beginTransaction() + ?.remove(this) + ?.commitAllowingStateLoss() } } } - fun presentFinancialConnectionsSheet(clientSecret: String, mode: Mode, publishableKey: String, stripeAccountId: String?, promise: Promise, context: ReactApplicationContext) { + fun presentFinancialConnectionsSheet( + clientSecret: String, + mode: Mode, + publishableKey: String, + stripeAccountId: String?, + promise: Promise, + context: ReactApplicationContext, + ) { this.promise = promise this.context = context this.mode = mode - this.configuration = FinancialConnectionsSheet.Configuration( - financialConnectionsSessionClientSecret = clientSecret, - publishableKey = publishableKey, - stripeAccountId = stripeAccountId, - ) + this.configuration = + FinancialConnectionsSheet.Configuration( + financialConnectionsSessionClientSecret = clientSecret, + publishableKey = publishableKey, + stripeAccountId = stripeAccountId, + ) (context.currentActivity as? FragmentActivity)?.let { attemptToCleanupPreviousFragment(it) commitFragmentAndStartFlow(it) - } ?: run { - promise.resolve(createMissingActivityError()) - return } + ?: run { + promise.resolve(createMissingActivityError()) + return + } } private fun attemptToCleanupPreviousFragment(currentActivity: FragmentActivity) { - currentActivity.supportFragmentManager.beginTransaction() + currentActivity.supportFragmentManager + .beginTransaction() .remove(this) .commitAllowingStateLoss() } private fun commitFragmentAndStartFlow(currentActivity: FragmentActivity) { try { - currentActivity.supportFragmentManager.beginTransaction() + currentActivity.supportFragmentManager + .beginTransaction() .add(this, TAG) .commit() } catch (error: IllegalStateException) { @@ -155,12 +176,11 @@ class FinancialConnectionsSheetFragment : Fragment() { companion object { internal const val TAG = "financial_connections_sheet_launch_fragment" - private fun createTokenResult(result: FinancialConnectionsSheetForTokenResult.Completed): WritableMap { - return WritableNativeMap().also { + private fun createTokenResult(result: FinancialConnectionsSheetForTokenResult.Completed): WritableMap = + WritableNativeMap().also { it.putMap("session", mapFromSession(result.financialConnectionsSession)) it.putMap("token", mapFromToken(result.token)) } - } private fun mapFromSession(financialConnectionsSession: FinancialConnectionsSession): WritableMap { val session = WritableNativeMap() @@ -186,8 +206,19 @@ class FinancialConnectionsSheetFragment : Fragment() { map.putMap("balanceRefresh", mapFromAccountBalanceRefresh(account.balanceRefresh)) map.putString("category", mapFromCategory(account.category)) map.putString("subcategory", mapFromSubcategory(account.subcategory)) - map.putArray("permissions", (account.permissions?.map { permission -> mapFromPermission(permission) })?.toReadableArray()) - map.putArray("supportedPaymentMethodTypes", (account.supportedPaymentMethodTypes.map { type -> mapFromSupportedPaymentMethodTypes(type) }).toReadableArray()) + map.putArray( + "permissions", + (account.permissions?.map { permission -> mapFromPermission(permission) }) + ?.toReadableArray(), + ) + map.putArray( + "supportedPaymentMethodTypes", + ( + account.supportedPaymentMethodTypes.map { type -> + mapFromSupportedPaymentMethodTypes(type) + } + ).toReadableArray(), + ) results.pushMap(map) } return results @@ -212,8 +243,8 @@ class FinancialConnectionsSheetFragment : Fragment() { return map } - private fun mapFromCashAvailable(balance: Balance): WritableNativeMap { - return WritableNativeMap().also { cashMap -> + private fun mapFromCashAvailable(balance: Balance): WritableNativeMap = + WritableNativeMap().also { cashMap -> WritableNativeMap().also { availableMap -> balance.cash?.available?.entries?.let { entries -> for (entry in entries) { @@ -223,10 +254,9 @@ class FinancialConnectionsSheetFragment : Fragment() { cashMap.putMap("available", availableMap) } } - } - private fun mapFromCreditUsed(balance: Balance): WritableNativeMap { - return WritableNativeMap().also { creditMap -> + private fun mapFromCreditUsed(balance: Balance): WritableNativeMap = + WritableNativeMap().also { creditMap -> WritableNativeMap().also { usedMap -> balance.credit?.used?.entries?.let { entries -> for (entry in entries) { @@ -236,7 +266,6 @@ class FinancialConnectionsSheetFragment : Fragment() { creditMap.putMap("used", usedMap) } } - } private fun mapFromAccountBalanceRefresh(balanceRefresh: BalanceRefresh?): WritableMap? { if (balanceRefresh == null) { @@ -248,27 +277,25 @@ class FinancialConnectionsSheetFragment : Fragment() { return map } - private fun mapFromStatus(status: FinancialConnectionsAccount.Status): String { - return when (status) { + private fun mapFromStatus(status: FinancialConnectionsAccount.Status): String = + when (status) { FinancialConnectionsAccount.Status.ACTIVE -> "active" FinancialConnectionsAccount.Status.DISCONNECTED -> "disconnected" FinancialConnectionsAccount.Status.INACTIVE -> "inactive" FinancialConnectionsAccount.Status.UNKNOWN -> "unparsable" } - } - private fun mapFromCategory(category: FinancialConnectionsAccount.Category): String { - return when (category) { + private fun mapFromCategory(category: FinancialConnectionsAccount.Category): String = + when (category) { FinancialConnectionsAccount.Category.CASH -> "cash" FinancialConnectionsAccount.Category.CREDIT -> "credit" FinancialConnectionsAccount.Category.INVESTMENT -> "investment" FinancialConnectionsAccount.Category.OTHER -> "other" FinancialConnectionsAccount.Category.UNKNOWN -> "unparsable" } - } - private fun mapFromSubcategory(subcategory: FinancialConnectionsAccount.Subcategory): String { - return when (subcategory) { + private fun mapFromSubcategory(subcategory: FinancialConnectionsAccount.Subcategory): String = + when (subcategory) { FinancialConnectionsAccount.Subcategory.CHECKING -> "checking" FinancialConnectionsAccount.Subcategory.CREDIT_CARD -> "creditCard" FinancialConnectionsAccount.Subcategory.LINE_OF_CREDIT -> "lineOfCredit" @@ -277,10 +304,9 @@ class FinancialConnectionsSheetFragment : Fragment() { FinancialConnectionsAccount.Subcategory.SAVINGS -> "savings" FinancialConnectionsAccount.Subcategory.UNKNOWN -> "unparsable" } - } - private fun mapFromPermission(permission: FinancialConnectionsAccount.Permissions): String { - return when (permission) { + private fun mapFromPermission(permission: FinancialConnectionsAccount.Permissions): String = + when (permission) { FinancialConnectionsAccount.Permissions.PAYMENT_METHOD -> "paymentMethod" FinancialConnectionsAccount.Permissions.BALANCES -> "balances" FinancialConnectionsAccount.Permissions.OWNERSHIP -> "ownership" @@ -289,33 +315,29 @@ class FinancialConnectionsSheetFragment : Fragment() { FinancialConnectionsAccount.Permissions.UNKNOWN -> "unparsable" FinancialConnectionsAccount.Permissions.ACCOUNT_NUMBERS -> "accountNumbers" } - } - private fun mapFromSupportedPaymentMethodTypes(type: FinancialConnectionsAccount.SupportedPaymentMethodTypes): String { - return when (type) { + private fun mapFromSupportedPaymentMethodTypes(type: FinancialConnectionsAccount.SupportedPaymentMethodTypes): String = + when (type) { FinancialConnectionsAccount.SupportedPaymentMethodTypes.US_BANK_ACCOUNT -> "usBankAccount" FinancialConnectionsAccount.SupportedPaymentMethodTypes.LINK -> "link" FinancialConnectionsAccount.SupportedPaymentMethodTypes.UNKNOWN -> "unparsable" } - } - private fun mapFromBalanceType(type: Balance.Type): String { - return when (type) { + private fun mapFromBalanceType(type: Balance.Type): String = + when (type) { Balance.Type.CASH -> "cash" Balance.Type.CREDIT -> "credit" Balance.Type.UNKNOWN -> "unparsable" } - } - private fun mapFromBalanceRefreshStatus(status: BalanceRefresh.BalanceRefreshStatus?): String { - return when (status) { + private fun mapFromBalanceRefreshStatus(status: BalanceRefresh.BalanceRefreshStatus?): String = + when (status) { BalanceRefresh.BalanceRefreshStatus.SUCCEEDED -> "succeeded" BalanceRefresh.BalanceRefreshStatus.FAILED -> "failed" BalanceRefresh.BalanceRefreshStatus.PENDING -> "pending" BalanceRefresh.BalanceRefreshStatus.UNKNOWN -> "unparsable" null -> "null" } - } } } diff --git a/android/src/main/java/com/reactnativestripesdk/FormCompleteEvent.kt b/android/src/main/java/com/reactnativestripesdk/FormCompleteEvent.kt index b03acb07a..0821b436a 100644 --- a/android/src/main/java/com/reactnativestripesdk/FormCompleteEvent.kt +++ b/android/src/main/java/com/reactnativestripesdk/FormCompleteEvent.kt @@ -1,29 +1,32 @@ package com.reactnativestripesdk + import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.Event import com.facebook.react.uimanager.events.RCTEventEmitter -internal class FormCompleteEvent constructor(viewTag: Int, private val formDetails: MutableMap) : Event(viewTag) { - override fun getEventName(): String { - return EVENT_NAME - } +internal class FormCompleteEvent + constructor( + viewTag: Int, + private val formDetails: MutableMap, + ) : Event(viewTag) { + override fun getEventName(): String = EVENT_NAME - override fun dispatch(rctEventEmitter: RCTEventEmitter) { - rctEventEmitter.receiveEvent(viewTag, eventName, serializeEventData()) - } + override fun dispatch(rctEventEmitter: RCTEventEmitter) { + rctEventEmitter.receiveEvent(viewTag, eventName, serializeEventData()) + } - private fun serializeEventData(): WritableMap { - val eventData = Arguments.createMap() - eventData.putString("accountNumber", formDetails["accountNumber"].toString()) - eventData.putString("bsbNumber", formDetails["bsbNumber"].toString()) - eventData.putString("email", formDetails["email"].toString()) - eventData.putString("name", formDetails["name"].toString()) + private fun serializeEventData(): WritableMap { + val eventData = Arguments.createMap() + eventData.putString("accountNumber", formDetails["accountNumber"].toString()) + eventData.putString("bsbNumber", formDetails["bsbNumber"].toString()) + eventData.putString("email", formDetails["email"].toString()) + eventData.putString("name", formDetails["name"].toString()) - return eventData - } + return eventData + } - companion object { - const val EVENT_NAME = "onCompleteAction" + companion object { + const val EVENT_NAME = "onCompleteAction" + } } -} diff --git a/android/src/main/java/com/reactnativestripesdk/GooglePayButtonManager.kt b/android/src/main/java/com/reactnativestripesdk/GooglePayButtonManager.kt index db558c4a2..5b375a8a2 100644 --- a/android/src/main/java/com/reactnativestripesdk/GooglePayButtonManager.kt +++ b/android/src/main/java/com/reactnativestripesdk/GooglePayButtonManager.kt @@ -5,9 +5,7 @@ import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.annotations.ReactProp class GooglePayButtonManager : SimpleViewManager() { - override fun getName(): String { - return REACT_CLASS - } + override fun getName(): String = REACT_CLASS override fun onAfterUpdateTransaction(view: GooglePayButtonView) { super.onAfterUpdateTransaction(view) @@ -16,23 +14,30 @@ class GooglePayButtonManager : SimpleViewManager() { } @ReactProp(name = "type") - fun type(view: GooglePayButtonView, buttonType: Int) { + fun type( + view: GooglePayButtonView, + buttonType: Int, + ) { view.setType(buttonType) } @ReactProp(name = "appearance") - fun appearance(view: GooglePayButtonView, appearance: Int) { + fun appearance( + view: GooglePayButtonView, + appearance: Int, + ) { view.setAppearance(appearance) } @ReactProp(name = "borderRadius") - fun borderRadius(view: GooglePayButtonView, borderRadius: Int) { + fun borderRadius( + view: GooglePayButtonView, + borderRadius: Int, + ) { view.setBorderRadius(borderRadius) } - override fun createViewInstance(reactContext: ThemedReactContext): GooglePayButtonView { - return GooglePayButtonView(reactContext) - } + override fun createViewInstance(reactContext: ThemedReactContext): GooglePayButtonView = GooglePayButtonView(reactContext) companion object { const val REACT_CLASS = "GooglePayButton" diff --git a/android/src/main/java/com/reactnativestripesdk/GooglePayButtonView.kt b/android/src/main/java/com/reactnativestripesdk/GooglePayButtonView.kt index fcdfe0cd0..20d6d6c5a 100644 --- a/android/src/main/java/com/reactnativestripesdk/GooglePayButtonView.kt +++ b/android/src/main/java/com/reactnativestripesdk/GooglePayButtonView.kt @@ -13,7 +13,9 @@ import com.google.android.gms.wallet.button.PayButton import com.stripe.android.GooglePayJsonFactory import org.json.JSONArray -class GooglePayButtonView(private val context: ThemedReactContext) : FrameLayout(context) { +class GooglePayButtonView( + private val context: ThemedReactContext, +) : FrameLayout(context) { private var type: Int? = null private var appearance: Int? = null private var borderRadius: Int = 4 // Matches the default on iOS's ApplePayButton @@ -29,48 +31,44 @@ class GooglePayButtonView(private val context: ThemedReactContext) : FrameLayout } private fun configureGooglePayButton(): PayButton { - val googlePayButton = PayButton( - context - ) + val googlePayButton = PayButton(context) googlePayButton.initialize(buildButtonOptions()) googlePayButton.setOnClickListener { _ -> // Call the Javascript TouchableOpacity parent where the onClick handler is set - (this.parent as? View)?.performClick() ?: run { - Log.e("StripeReactNative", "Unable to find parent of GooglePayButtonView.") - } - }; + (this.parent as? View)?.performClick() + ?: run { Log.e("StripeReactNative", "Unable to find parent of GooglePayButtonView.") } + } return googlePayButton } @SuppressLint("RestrictedApi") private fun buildButtonOptions(): ButtonOptions { - val allowedPaymentMethods = JSONArray().put( - GooglePayJsonFactory(context).createCardPaymentMethod( - billingAddressParameters = null, - allowCreditCards = null - ) - ).toString() + val allowedPaymentMethods = + JSONArray() + .put( + GooglePayJsonFactory(context) + .createCardPaymentMethod( + billingAddressParameters = null, + allowCreditCards = null, + ), + ).toString() - val options = ButtonOptions.newBuilder() - .setAllowedPaymentMethods(allowedPaymentMethods) + val options = ButtonOptions.newBuilder().setAllowedPaymentMethods(allowedPaymentMethods) - getButtonType()?.let { - options.setButtonType(it) - } + getButtonType()?.let { options.setButtonType(it) } - getButtonTheme()?.let { - options.setButtonTheme(it) - } + getButtonTheme()?.let { options.setButtonTheme(it) } options.setCornerRadius(PixelUtil.toPixelFromDIP(this.borderRadius.toDouble()).toInt()) return options.build() } - private fun getButtonType(): Int? { - return when (this.type) { + private fun getButtonType(): Int? = + when (this.type) { 0, - 1 -> ButtonType.BUY + 1, + -> ButtonType.BUY 6 -> ButtonType.BOOK 5 -> ButtonType.CHECKOUT 4 -> ButtonType.DONATE @@ -80,28 +78,29 @@ class GooglePayButtonView(private val context: ThemedReactContext) : FrameLayout 1001 -> ButtonType.PLAIN else -> null } - } - private fun getButtonTheme(): Int? { - return when (this.appearance) { - 0, 1 -> ButtonTheme.LIGHT + private fun getButtonTheme(): Int? = + when (this.appearance) { + 0, + 1, + -> ButtonTheme.LIGHT 2 -> ButtonTheme.DARK else -> null } - } override fun requestLayout() { super.requestLayout() post(mLayoutRunnable) } - private val mLayoutRunnable = Runnable { - measure( - MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)) - button?.layout(left, top, right, bottom) - - } + private val mLayoutRunnable = + Runnable { + measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY), + ) + button?.layout(left, top, right, bottom) + } fun setType(type: Int) { this.type = type diff --git a/android/src/main/java/com/reactnativestripesdk/GooglePayLauncherFragment.kt b/android/src/main/java/com/reactnativestripesdk/GooglePayLauncherFragment.kt index 1c670981a..f5a46ea52 100644 --- a/android/src/main/java/com/reactnativestripesdk/GooglePayLauncherFragment.kt +++ b/android/src/main/java/com/reactnativestripesdk/GooglePayLauncherFragment.kt @@ -7,16 +7,22 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity -import com.facebook.react.bridge.* -import com.reactnativestripesdk.utils.* +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.WritableMap +import com.reactnativestripesdk.utils.ErrorType +import com.reactnativestripesdk.utils.GooglePayErrorType import com.reactnativestripesdk.utils.createError import com.reactnativestripesdk.utils.createMissingActivityError +import com.reactnativestripesdk.utils.getBooleanOr +import com.reactnativestripesdk.utils.getIntOrNull import com.stripe.android.googlepaylauncher.GooglePayEnvironment import com.stripe.android.googlepaylauncher.GooglePayLauncher class GooglePayLauncherFragment : Fragment() { enum class Mode { - ForSetup, ForPayment + ForSetup, + ForPayment, } private lateinit var launcher: GooglePayLauncher @@ -28,64 +34,81 @@ class GooglePayLauncherFragment : Fragment() { private var label: String? = null private lateinit var callback: (result: GooglePayLauncher.Result?, error: WritableMap?) -> Unit - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View { - return FrameLayout(requireActivity()).also { - it.visibility = View.GONE - } - } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View = FrameLayout(requireActivity()).also { it.visibility = View.GONE } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - launcher = GooglePayLauncher( - fragment = this, - config = configuration, - readyCallback = ::onGooglePayReady, - resultCallback = ::onGooglePayResult - ) + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + launcher = + GooglePayLauncher( + fragment = this, + config = configuration, + readyCallback = ::onGooglePayReady, + resultCallback = ::onGooglePayResult, + ) } - fun presentGooglePaySheet(clientSecret: String, mode: Mode, googlePayParams: ReadableMap, context: ReactApplicationContext, callback: (GooglePayLauncher.Result?, error: WritableMap?) -> Unit) { + fun presentGooglePaySheet( + clientSecret: String, + mode: Mode, + googlePayParams: ReadableMap, + context: ReactApplicationContext, + callback: (GooglePayLauncher.Result?, error: WritableMap?) -> Unit, + ) { this.clientSecret = clientSecret this.mode = mode this.callback = callback this.currencyCode = googlePayParams.getString("currencyCode") ?: "USD" this.amount = getIntOrNull(googlePayParams, "amount") this.label = googlePayParams.getString("label") - this.configuration = GooglePayLauncher.Config( - environment = if (googlePayParams.getBoolean("testEnv")) GooglePayEnvironment.Test else GooglePayEnvironment.Production, - merchantCountryCode = googlePayParams.getString("merchantCountryCode").orEmpty(), - merchantName = googlePayParams.getString("merchantName").orEmpty(), - isEmailRequired = googlePayParams.getBooleanOr("isEmailRequired", false), - billingAddressConfig = buildBillingAddressParameters(googlePayParams.getMap("billingAddressConfig")), - existingPaymentMethodRequired = googlePayParams.getBooleanOr("existingPaymentMethodRequired", false), - allowCreditCards = googlePayParams.getBooleanOr("allowCreditCards", true), - ) + this.configuration = + GooglePayLauncher.Config( + environment = + if (googlePayParams.getBoolean("testEnv")) { + GooglePayEnvironment.Test + } else { + GooglePayEnvironment.Production + }, + merchantCountryCode = googlePayParams.getString("merchantCountryCode").orEmpty(), + merchantName = googlePayParams.getString("merchantName").orEmpty(), + isEmailRequired = googlePayParams.getBooleanOr("isEmailRequired", false), + billingAddressConfig = + buildBillingAddressParameters(googlePayParams.getMap("billingAddressConfig")), + existingPaymentMethodRequired = + googlePayParams.getBooleanOr("existingPaymentMethodRequired", false), + allowCreditCards = googlePayParams.getBooleanOr("allowCreditCards", true), + ) (context.currentActivity as? FragmentActivity)?.let { attemptToCleanupPreviousFragment(it) commitFragmentAndStartFlow(it) - } ?: run { - callback(null, createMissingActivityError()) - return } + ?: run { + callback(null, createMissingActivityError()) + return + } } private fun attemptToCleanupPreviousFragment(currentActivity: FragmentActivity) { - currentActivity.supportFragmentManager.beginTransaction() + currentActivity.supportFragmentManager + .beginTransaction() .remove(this) .commitAllowingStateLoss() } private fun commitFragmentAndStartFlow(currentActivity: FragmentActivity) { try { - currentActivity.supportFragmentManager.beginTransaction() + currentActivity.supportFragmentManager + .beginTransaction() .add(this, TAG) .commit() } catch (error: IllegalStateException) { - callback( - null, - createError(ErrorType.Failed.toString(), error.message) - ) + callback(null, createError(ErrorType.Failed.toString(), error.message)) } } @@ -104,8 +127,8 @@ class GooglePayLauncherFragment : Fragment() { null, createError( GooglePayErrorType.Failed.toString(), - "Google Pay is not available on this device. You can use isPlatformPaySupported to preemptively check for Google Pay support." - ) + "Google Pay is not available on this device. You can use isPlatformPaySupported to preemptively check for Google Pay support.", + ), ) } } @@ -120,16 +143,17 @@ class GooglePayLauncherFragment : Fragment() { private fun buildBillingAddressParameters(params: ReadableMap?): GooglePayLauncher.BillingAddressConfig { val isRequired = params?.getBooleanOr("isRequired", false) val isPhoneNumberRequired = params?.getBooleanOr("isPhoneNumberRequired", false) - val format = when (params?.getString("format").orEmpty()) { - "FULL" -> GooglePayLauncher.BillingAddressConfig.Format.Full - "MIN" -> GooglePayLauncher.BillingAddressConfig.Format.Min - else -> GooglePayLauncher.BillingAddressConfig.Format.Min - } + val format = + when (params?.getString("format").orEmpty()) { + "FULL" -> GooglePayLauncher.BillingAddressConfig.Format.Full + "MIN" -> GooglePayLauncher.BillingAddressConfig.Format.Min + else -> GooglePayLauncher.BillingAddressConfig.Format.Min + } return GooglePayLauncher.BillingAddressConfig( isRequired = isRequired ?: false, format = format, - isPhoneNumberRequired = isPhoneNumberRequired ?: false + isPhoneNumberRequired = isPhoneNumberRequired ?: false, ) } } diff --git a/android/src/main/java/com/reactnativestripesdk/GooglePayPaymentMethodLauncherFragment.kt b/android/src/main/java/com/reactnativestripesdk/GooglePayPaymentMethodLauncherFragment.kt index 6c5ebe49f..5eb0e75ee 100644 --- a/android/src/main/java/com/reactnativestripesdk/GooglePayPaymentMethodLauncherFragment.kt +++ b/android/src/main/java/com/reactnativestripesdk/GooglePayPaymentMethodLauncherFragment.kt @@ -16,30 +16,35 @@ class GooglePayPaymentMethodLauncherFragment( private val context: ReactApplicationContext, private val isTestEnv: Boolean, private val paymentMethodRequired: Boolean, - private val promise: Promise - ) : Fragment() { - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View { - return FrameLayout(requireActivity()).also { - it.visibility = View.GONE - } - } + private val promise: Promise, +) : Fragment() { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View = FrameLayout(requireActivity()).also { it.visibility = View.GONE } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) GooglePayPaymentMethodLauncher( this, - config = GooglePayPaymentMethodLauncher.Config( - environment = if (isTestEnv) GooglePayEnvironment.Test else GooglePayEnvironment.Production, - existingPaymentMethodRequired = paymentMethodRequired, - merchantCountryCode = "", // Unnecessary since all we are checking for is Google Pay availability - merchantName = "", // Same as above - ), + config = + GooglePayPaymentMethodLauncher.Config( + environment = + if (isTestEnv) GooglePayEnvironment.Test else GooglePayEnvironment.Production, + existingPaymentMethodRequired = paymentMethodRequired, + merchantCountryCode = + "", // Unnecessary since all we are checking for is Google Pay availability + merchantName = "", // Same as above + ), readyCallback = { promise.resolve(it) removeFragment(context) }, - resultCallback = {} + resultCallback = {}, ) } diff --git a/android/src/main/java/com/reactnativestripesdk/GooglePayRequestHelper.kt b/android/src/main/java/com/reactnativestripesdk/GooglePayRequestHelper.kt index dcbb738ca..54a7e9b44 100644 --- a/android/src/main/java/com/reactnativestripesdk/GooglePayRequestHelper.kt +++ b/android/src/main/java/com/reactnativestripesdk/GooglePayRequestHelper.kt @@ -7,10 +7,16 @@ import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.WritableNativeMap import com.google.android.gms.tasks.Task -import com.google.android.gms.wallet.* -import com.reactnativestripesdk.utils.* +import com.google.android.gms.wallet.AutoResolveHelper +import com.google.android.gms.wallet.PaymentData +import com.google.android.gms.wallet.PaymentDataRequest +import com.google.android.gms.wallet.Wallet +import com.google.android.gms.wallet.WalletConstants +import com.reactnativestripesdk.utils.ErrorType import com.reactnativestripesdk.utils.createError +import com.reactnativestripesdk.utils.getBooleanOr import com.reactnativestripesdk.utils.mapFromPaymentMethod +import com.reactnativestripesdk.utils.mapFromShippingContact import com.reactnativestripesdk.utils.mapFromToken import com.stripe.android.ApiResultCallback import com.stripe.android.GooglePayJsonFactory @@ -19,60 +25,82 @@ import com.stripe.android.model.GooglePayResult import com.stripe.android.model.PaymentMethod import com.stripe.android.model.PaymentMethodCreateParams import org.json.JSONObject -import java.util.* +import java.util.Locale class GooglePayRequestHelper { companion object { internal const val LOAD_PAYMENT_DATA_REQUEST_CODE = 414243 - internal fun createPaymentRequest(activity: FragmentActivity, factory: GooglePayJsonFactory, googlePayParams: ReadableMap): Task { + internal fun createPaymentRequest( + activity: FragmentActivity, + factory: GooglePayJsonFactory, + googlePayParams: ReadableMap, + ): Task { val transactionInfo = buildTransactionInfo(googlePayParams) - val merchantInfo = GooglePayJsonFactory.MerchantInfo(googlePayParams.getString("merchantName").orEmpty()) - val billingAddressParameters = buildBillingAddressParameters(googlePayParams.getMap("billingAddressConfig")) - val shippingAddressParameters = buildShippingAddressParameters(googlePayParams.getMap("shippingAddressConfig")) - - val request = factory.createPaymentDataRequest( - transactionInfo = transactionInfo, - merchantInfo = merchantInfo, - billingAddressParameters = billingAddressParameters, - shippingAddressParameters = shippingAddressParameters, - isEmailRequired = googlePayParams.getBooleanOr("isEmailRequired", false), - allowCreditCards = googlePayParams.getBooleanOr("allowCreditCards", true) - ) - - val walletOptions = Wallet.WalletOptions.Builder() - .setEnvironment(if (googlePayParams.getBoolean("testEnv")) WalletConstants.ENVIRONMENT_TEST else WalletConstants.ENVIRONMENT_PRODUCTION) - .build() - return Wallet.getPaymentsClient(activity, walletOptions).loadPaymentData(PaymentDataRequest.fromJson(request.toString())) + val merchantInfo = + GooglePayJsonFactory.MerchantInfo(googlePayParams.getString("merchantName").orEmpty()) + val billingAddressParameters = + buildBillingAddressParameters(googlePayParams.getMap("billingAddressConfig")) + val shippingAddressParameters = + buildShippingAddressParameters(googlePayParams.getMap("shippingAddressConfig")) + + val request = + factory.createPaymentDataRequest( + transactionInfo = transactionInfo, + merchantInfo = merchantInfo, + billingAddressParameters = billingAddressParameters, + shippingAddressParameters = shippingAddressParameters, + isEmailRequired = googlePayParams.getBooleanOr("isEmailRequired", false), + allowCreditCards = googlePayParams.getBooleanOr("allowCreditCards", true), + ) + + val walletOptions = + Wallet.WalletOptions + .Builder() + .setEnvironment( + if (googlePayParams.getBoolean("testEnv")) { + WalletConstants.ENVIRONMENT_TEST + } else { + WalletConstants.ENVIRONMENT_PRODUCTION + }, + ).build() + return Wallet + .getPaymentsClient(activity, walletOptions) + .loadPaymentData(PaymentDataRequest.fromJson(request.toString())) } @Suppress("UNCHECKED_CAST") private fun buildShippingAddressParameters(params: ReadableMap?): GooglePayJsonFactory.ShippingAddressParameters { val isPhoneNumberRequired = params?.getBooleanOr("isPhoneNumberRequired", false) val isRequired = params?.getBooleanOr("isRequired", false) - val allowedCountryCodes = if (params?.hasKey("allowedCountryCodes") == true) - params.getArray("allowedCountryCodes")?.toArrayList()?.toSet() as? Set else null + val allowedCountryCodes = + if (params?.hasKey("allowedCountryCodes") == true) { + params.getArray("allowedCountryCodes")?.toArrayList()?.toSet() as? Set + } else { + null + } return GooglePayJsonFactory.ShippingAddressParameters( isRequired = isRequired ?: false, allowedCountryCodes = allowedCountryCodes ?: Locale.getISOCountries().toSet(), - phoneNumberRequired = isPhoneNumberRequired ?: false + phoneNumberRequired = isPhoneNumberRequired ?: false, ) } private fun buildBillingAddressParameters(params: ReadableMap?): GooglePayJsonFactory.BillingAddressParameters { val isRequired = params?.getBooleanOr("isRequired", false) val isPhoneNumberRequired = params?.getBooleanOr("isPhoneNumberRequired", false) - val format = when (params?.getString("format").orEmpty()) { - "FULL" -> GooglePayJsonFactory.BillingAddressParameters.Format.Full - "MIN" -> GooglePayJsonFactory.BillingAddressParameters.Format.Min - else -> GooglePayJsonFactory.BillingAddressParameters.Format.Min - } + val format = + when (params?.getString("format").orEmpty()) { + "FULL" -> GooglePayJsonFactory.BillingAddressParameters.Format.Full + "MIN" -> GooglePayJsonFactory.BillingAddressParameters.Format.Min + else -> GooglePayJsonFactory.BillingAddressParameters.Format.Min + } return GooglePayJsonFactory.BillingAddressParameters( isRequired = isRequired ?: false, format = format, - isPhoneNumberRequired = isPhoneNumberRequired ?: false + isPhoneNumberRequired = isPhoneNumberRequired ?: false, ) } @@ -88,19 +116,24 @@ class GooglePayRequestHelper { countryCode = countryCode, totalPrice = amount, totalPriceLabel = label, - checkoutOption = GooglePayJsonFactory.TransactionInfo.CheckoutOption.Default + checkoutOption = GooglePayJsonFactory.TransactionInfo.CheckoutOption.Default, ) } - internal fun createPaymentMethod(request: Task, activity: FragmentActivity) { - AutoResolveHelper.resolveTask( - request, - activity, - LOAD_PAYMENT_DATA_REQUEST_CODE - ) + internal fun createPaymentMethod( + request: Task, + activity: FragmentActivity, + ) { + AutoResolveHelper.resolveTask(request, activity, LOAD_PAYMENT_DATA_REQUEST_CODE) } - internal fun handleGooglePaymentMethodResult(resultCode: Int, data: Intent?, stripe: Stripe, forToken: Boolean, promise: Promise) { + internal fun handleGooglePaymentMethodResult( + resultCode: Int, + data: Intent?, + stripe: Stripe, + forToken: Boolean, + promise: Promise, + ) { when (resultCode) { Activity.RESULT_OK -> { data?.let { intent -> @@ -114,7 +147,9 @@ class GooglePayRequestHelper { } } Activity.RESULT_CANCELED -> { - promise.resolve(createError(ErrorType.Canceled.toString(), "The payment has been canceled")) + promise.resolve( + createError(ErrorType.Canceled.toString(), "The payment has been canceled"), + ) } AutoResolveHelper.RESULT_ERROR -> { AutoResolveHelper.getStatusFromIntent(data)?.let { @@ -124,30 +159,38 @@ class GooglePayRequestHelper { } } - private fun resolveWithPaymentMethod(paymentData: PaymentData, stripe: Stripe, promise: Promise) { + private fun resolveWithPaymentMethod( + paymentData: PaymentData, + stripe: Stripe, + promise: Promise, + ) { val paymentInformation = JSONObject(paymentData.toJson()) val promiseResult = WritableNativeMap() stripe.createPaymentMethod( PaymentMethodCreateParams.createFromGooglePay(paymentInformation), - callback = object : ApiResultCallback { - override fun onError(e: Exception) { - promise.resolve(createError("Failed", e)) - } + callback = + object : ApiResultCallback { + override fun onError(e: Exception) { + promise.resolve(createError("Failed", e)) + } - override fun onSuccess(result: PaymentMethod) { - promiseResult.putMap("paymentMethod", mapFromPaymentMethod(result)) - GooglePayResult.fromJson(paymentInformation).let { - if (it.shippingInformation != null) { - promiseResult.putMap("shippingContact", mapFromShippingContact(it)) + override fun onSuccess(result: PaymentMethod) { + promiseResult.putMap("paymentMethod", mapFromPaymentMethod(result)) + GooglePayResult.fromJson(paymentInformation).let { + if (it.shippingInformation != null) { + promiseResult.putMap("shippingContact", mapFromShippingContact(it)) + } } + promise.resolve(promiseResult) } - promise.resolve(promiseResult) - } - } + }, ) } - private fun resolveWithToken(paymentData: PaymentData, promise: Promise) { + private fun resolveWithToken( + paymentData: PaymentData, + promise: Promise, + ) { val paymentInformation = JSONObject(paymentData.toJson()) val googlePayResult = GooglePayResult.fromJson(paymentInformation) val promiseResult = WritableNativeMap() @@ -157,10 +200,12 @@ class GooglePayRequestHelper { promiseResult.putMap("shippingContact", mapFromShippingContact(googlePayResult)) } promise.resolve(promiseResult) - } ?: run { - promise.resolve(createError("Failed", "Unexpected response from Google Pay. No token was found.")) } + ?: run { + promise.resolve( + createError("Failed", "Unexpected response from Google Pay. No token was found."), + ) + } } } } - diff --git a/android/src/main/java/com/reactnativestripesdk/PaymentLauncherFragment.kt b/android/src/main/java/com/reactnativestripesdk/PaymentLauncherFragment.kt index 520434ff0..dfbe72c42 100644 --- a/android/src/main/java/com/reactnativestripesdk/PaymentLauncherFragment.kt +++ b/android/src/main/java/com/reactnativestripesdk/PaymentLauncherFragment.kt @@ -9,18 +9,26 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext -import com.reactnativestripesdk.utils.* +import com.reactnativestripesdk.utils.ConfirmPaymentErrorType +import com.reactnativestripesdk.utils.ConfirmSetupIntentErrorType +import com.reactnativestripesdk.utils.ErrorType import com.reactnativestripesdk.utils.createError import com.reactnativestripesdk.utils.createMissingActivityError +import com.reactnativestripesdk.utils.createResult +import com.reactnativestripesdk.utils.mapFromPaymentIntentResult +import com.reactnativestripesdk.utils.mapFromSetupIntentResult +import com.reactnativestripesdk.utils.removeFragment import com.stripe.android.ApiResultCallback import com.stripe.android.Stripe -import com.stripe.android.model.* +import com.stripe.android.model.ConfirmPaymentIntentParams +import com.stripe.android.model.ConfirmSetupIntentParams +import com.stripe.android.model.PaymentIntent +import com.stripe.android.model.SetupIntent +import com.stripe.android.model.StripeIntent import com.stripe.android.payments.paymentlauncher.PaymentLauncher import com.stripe.android.payments.paymentlauncher.PaymentResult -/** - * Instances of this class should only be initialized with the companion's helper methods. - */ +/** Instances of this class should only be initialized with the companion's helper methods. */ class PaymentLauncherFragment( private val context: ReactApplicationContext, private val stripe: Stripe, @@ -41,113 +49,123 @@ class PaymentLauncherFragment( private lateinit var paymentLauncher: PaymentLauncher companion object { - /** - * Helper-constructor used for confirming payment intents - */ - fun forPayment(context: ReactApplicationContext, - stripe: Stripe, - publishableKey: String, - stripeAccountId: String?, - promise: Promise, - paymentIntentClientSecret: String, - confirmPaymentParams: ConfirmPaymentIntentParams): PaymentLauncherFragment { - val paymentLauncherFragment = PaymentLauncherFragment( - context, - stripe, - publishableKey, - stripeAccountId, - promise, - paymentIntentClientSecret = paymentIntentClientSecret, - confirmPaymentParams = confirmPaymentParams - ) + /** Helper-constructor used for confirming payment intents */ + fun forPayment( + context: ReactApplicationContext, + stripe: Stripe, + publishableKey: String, + stripeAccountId: String?, + promise: Promise, + paymentIntentClientSecret: String, + confirmPaymentParams: ConfirmPaymentIntentParams, + ): PaymentLauncherFragment { + val paymentLauncherFragment = + PaymentLauncherFragment( + context, + stripe, + publishableKey, + stripeAccountId, + promise, + paymentIntentClientSecret = paymentIntentClientSecret, + confirmPaymentParams = confirmPaymentParams, + ) addFragment(paymentLauncherFragment, context, promise) return paymentLauncherFragment } - /** - * Helper-constructor used for confirming setup intents - */ - fun forSetup(context: ReactApplicationContext, - stripe: Stripe, - publishableKey: String, - stripeAccountId: String?, - promise: Promise, - setupIntentClientSecret: String, - confirmSetupParams: ConfirmSetupIntentParams): PaymentLauncherFragment { - val paymentLauncherFragment = PaymentLauncherFragment( - context, - stripe, - publishableKey, - stripeAccountId, - promise, - setupIntentClientSecret = setupIntentClientSecret, - confirmSetupParams = confirmSetupParams - ) + /** Helper-constructor used for confirming setup intents */ + fun forSetup( + context: ReactApplicationContext, + stripe: Stripe, + publishableKey: String, + stripeAccountId: String?, + promise: Promise, + setupIntentClientSecret: String, + confirmSetupParams: ConfirmSetupIntentParams, + ): PaymentLauncherFragment { + val paymentLauncherFragment = + PaymentLauncherFragment( + context, + stripe, + publishableKey, + stripeAccountId, + promise, + setupIntentClientSecret = setupIntentClientSecret, + confirmSetupParams = confirmSetupParams, + ) addFragment(paymentLauncherFragment, context, promise) return paymentLauncherFragment } - /** - * Helper-constructor used for handling the next action on a payment intent - */ - fun forNextActionPayment(context: ReactApplicationContext, - stripe: Stripe, - publishableKey: String, - stripeAccountId: String?, - promise: Promise, - handleNextActionPaymentIntentClientSecret: String): PaymentLauncherFragment { - val paymentLauncherFragment = PaymentLauncherFragment( - context, - stripe, - publishableKey, - stripeAccountId, - promise, - handleNextActionPaymentIntentClientSecret = handleNextActionPaymentIntentClientSecret, - ) + /** Helper-constructor used for handling the next action on a payment intent */ + fun forNextActionPayment( + context: ReactApplicationContext, + stripe: Stripe, + publishableKey: String, + stripeAccountId: String?, + promise: Promise, + handleNextActionPaymentIntentClientSecret: String, + ): PaymentLauncherFragment { + val paymentLauncherFragment = + PaymentLauncherFragment( + context, + stripe, + publishableKey, + stripeAccountId, + promise, + handleNextActionPaymentIntentClientSecret = handleNextActionPaymentIntentClientSecret, + ) addFragment(paymentLauncherFragment, context, promise) return paymentLauncherFragment } - /** - * Helper-constructor used for handling the next action on a setup intent - */ - fun forNextActionSetup(context: ReactApplicationContext, - stripe: Stripe, - publishableKey: String, - stripeAccountId: String?, - promise: Promise, - handleNextActionSetupIntentClientSecret: String): PaymentLauncherFragment { - val paymentLauncherFragment = PaymentLauncherFragment( - context, - stripe, - publishableKey, - stripeAccountId, - promise, - handleNextActionSetupIntentClientSecret = handleNextActionSetupIntentClientSecret, - ) + /** Helper-constructor used for handling the next action on a setup intent */ + fun forNextActionSetup( + context: ReactApplicationContext, + stripe: Stripe, + publishableKey: String, + stripeAccountId: String?, + promise: Promise, + handleNextActionSetupIntentClientSecret: String, + ): PaymentLauncherFragment { + val paymentLauncherFragment = + PaymentLauncherFragment( + context, + stripe, + publishableKey, + stripeAccountId, + promise, + handleNextActionSetupIntentClientSecret = handleNextActionSetupIntentClientSecret, + ) addFragment(paymentLauncherFragment, context, promise) return paymentLauncherFragment } - private fun addFragment(fragment: PaymentLauncherFragment, context: ReactApplicationContext, promise: Promise) { + private fun addFragment( + fragment: PaymentLauncherFragment, + context: ReactApplicationContext, + promise: Promise, + ) { (context.currentActivity as? FragmentActivity)?.let { try { - it.supportFragmentManager.beginTransaction() + it.supportFragmentManager + .beginTransaction() .add(fragment, TAG) .commit() } catch (error: IllegalStateException) { promise.resolve(createError(ErrorType.Failed.toString(), error.message)) } - } ?: run { - promise.resolve(createMissingActivityError()) - } + } ?: run { promise.resolve(createMissingActivityError()) } } internal const val TAG = "payment_launcher_fragment" } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { paymentLauncher = createPaymentLauncher() if (paymentIntentClientSecret != null && confirmPaymentParams != null) { paymentLauncher.confirm(confirmPaymentParams) @@ -158,15 +176,15 @@ class PaymentLauncherFragment( } else if (handleNextActionSetupIntentClientSecret != null) { paymentLauncher.handleNextActionForSetupIntent(handleNextActionSetupIntentClientSecret) } else { - throw Exception("Invalid parameters provided to PaymentLauncher. Ensure that you are providing the correct client secret and setup params (if necessary).") - } - return FrameLayout(requireActivity()).also { - it.visibility = View.GONE + throw Exception( + "Invalid parameters provided to PaymentLauncher. Ensure that you are providing the correct client secret and setup params (if necessary).", + ) } + return FrameLayout(requireActivity()).also { it.visibility = View.GONE } } - private fun createPaymentLauncher(): PaymentLauncher { - return PaymentLauncher.create(this, publishableKey, stripeAccountId) { paymentResult -> + private fun createPaymentLauncher(): PaymentLauncher = + PaymentLauncher.create(this, publishableKey, stripeAccountId) { paymentResult -> when (paymentResult) { is PaymentResult.Completed -> { if (paymentIntentClientSecret != null) { @@ -186,106 +204,167 @@ class PaymentLauncherFragment( removeFragment(context) } is PaymentResult.Failed -> { - promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), paymentResult.throwable)) + promise.resolve( + createError(ConfirmPaymentErrorType.Failed.toString(), paymentResult.throwable), + ) removeFragment(context) } } } - } - private fun retrieveSetupIntent(clientSecret: String, stripeAccountId: String?) { - stripe.retrieveSetupIntent(clientSecret, stripeAccountId, expand = listOf("payment_method"), object : ApiResultCallback { - override fun onError(e: Exception) { - promise.resolve(createError(ConfirmSetupIntentErrorType.Failed.toString(), e)) - removeFragment(context) - } + private fun retrieveSetupIntent( + clientSecret: String, + stripeAccountId: String?, + ) { + stripe.retrieveSetupIntent( + clientSecret, + stripeAccountId, + expand = listOf("payment_method"), + object : ApiResultCallback { + override fun onError(e: Exception) { + promise.resolve(createError(ConfirmSetupIntentErrorType.Failed.toString(), e)) + removeFragment(context) + } - override fun onSuccess(result: SetupIntent) { - when (result.status) { - StripeIntent.Status.Succeeded, - StripeIntent.Status.Processing, - StripeIntent.Status.RequiresConfirmation, - StripeIntent.Status.RequiresCapture -> { - promise.resolve(createResult("setupIntent", mapFromSetupIntentResult(result))) - } - StripeIntent.Status.RequiresAction -> { - if (isNextActionSuccessState(result.nextActionType)) { + override fun onSuccess(result: SetupIntent) { + when (result.status) { + StripeIntent.Status.Succeeded, + StripeIntent.Status.Processing, + StripeIntent.Status.RequiresConfirmation, + StripeIntent.Status.RequiresCapture, + -> { promise.resolve(createResult("setupIntent", mapFromSetupIntentResult(result))) - } else { - (result.lastSetupError)?.let { - promise.resolve(createError(ConfirmSetupIntentErrorType.Canceled.toString(), it)) - } ?: run { - promise.resolve(createError(ConfirmSetupIntentErrorType.Canceled.toString(), "Setup has been canceled")) + } + StripeIntent.Status.RequiresAction -> { + if (isNextActionSuccessState(result.nextActionType)) { + promise.resolve(createResult("setupIntent", mapFromSetupIntentResult(result))) + } else { + (result.lastSetupError)?.let { + promise.resolve( + createError(ConfirmSetupIntentErrorType.Canceled.toString(), it), + ) + } + ?: run { + promise.resolve( + createError( + ConfirmSetupIntentErrorType.Canceled.toString(), + "Setup has been canceled", + ), + ) + } } } + StripeIntent.Status.RequiresPaymentMethod -> { + promise.resolve( + createError( + ConfirmSetupIntentErrorType.Failed.toString(), + result.lastSetupError, + ), + ) + } + StripeIntent.Status.Canceled -> { + promise.resolve( + createError( + ConfirmSetupIntentErrorType.Canceled.toString(), + result.lastSetupError, + ), + ) + } + else -> { + promise.resolve( + createError( + ConfirmSetupIntentErrorType.Unknown.toString(), + "unhandled error: ${result.status}", + ), + ) + } } - StripeIntent.Status.RequiresPaymentMethod -> { - promise.resolve(createError(ConfirmSetupIntentErrorType.Failed.toString(), result.lastSetupError)) - } - StripeIntent.Status.Canceled -> { - promise.resolve(createError(ConfirmSetupIntentErrorType.Canceled.toString(), result.lastSetupError)) - } - else -> { - promise.resolve(createError(ConfirmSetupIntentErrorType.Unknown.toString(), "unhandled error: ${result.status}")) - } + removeFragment(context) } - removeFragment(context) - } - }) + }, + ) } - private fun retrievePaymentIntent(clientSecret: String, stripeAccountId: String?) { - stripe.retrievePaymentIntent(clientSecret, stripeAccountId, expand = listOf("payment_method"), object : ApiResultCallback { - override fun onError(e: Exception) { - promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), e)) - removeFragment(context) - } + private fun retrievePaymentIntent( + clientSecret: String, + stripeAccountId: String?, + ) { + stripe.retrievePaymentIntent( + clientSecret, + stripeAccountId, + expand = listOf("payment_method"), + object : ApiResultCallback { + override fun onError(e: Exception) { + promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), e)) + removeFragment(context) + } - override fun onSuccess(result: PaymentIntent) { - when (result.status) { - StripeIntent.Status.Succeeded, - StripeIntent.Status.Processing, - StripeIntent.Status.RequiresConfirmation, - StripeIntent.Status.RequiresCapture -> { - promise.resolve(createResult("paymentIntent", mapFromPaymentIntentResult(result))) - } - StripeIntent.Status.RequiresAction -> { - if (isNextActionSuccessState(result.nextActionType)) { + override fun onSuccess(result: PaymentIntent) { + when (result.status) { + StripeIntent.Status.Succeeded, + StripeIntent.Status.Processing, + StripeIntent.Status.RequiresConfirmation, + StripeIntent.Status.RequiresCapture, + -> { promise.resolve(createResult("paymentIntent", mapFromPaymentIntentResult(result))) - } else { - (result.lastPaymentError)?.let { - promise.resolve(createError(ConfirmPaymentErrorType.Canceled.toString(), it)) - } ?: run { - promise.resolve(createError(ConfirmPaymentErrorType.Canceled.toString(), "The payment has been canceled")) + } + StripeIntent.Status.RequiresAction -> { + if (isNextActionSuccessState(result.nextActionType)) { + promise.resolve(createResult("paymentIntent", mapFromPaymentIntentResult(result))) + } else { + (result.lastPaymentError)?.let { + promise.resolve(createError(ConfirmPaymentErrorType.Canceled.toString(), it)) + } + ?: run { + promise.resolve( + createError( + ConfirmPaymentErrorType.Canceled.toString(), + "The payment has been canceled", + ), + ) + } } } + StripeIntent.Status.RequiresPaymentMethod -> { + promise.resolve( + createError(ConfirmPaymentErrorType.Failed.toString(), result.lastPaymentError), + ) + } + StripeIntent.Status.Canceled -> { + promise.resolve( + createError( + ConfirmPaymentErrorType.Canceled.toString(), + result.lastPaymentError, + ), + ) + } + else -> { + promise.resolve( + createError( + ConfirmPaymentErrorType.Unknown.toString(), + "unhandled error: ${result.status}", + ), + ) + } } - StripeIntent.Status.RequiresPaymentMethod -> { - promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), result.lastPaymentError)) - } - StripeIntent.Status.Canceled -> { - promise.resolve(createError(ConfirmPaymentErrorType.Canceled.toString(), result.lastPaymentError)) - } - else -> { - promise.resolve(createError(ConfirmPaymentErrorType.Unknown.toString(), "unhandled error: ${result.status}")) - } + removeFragment(context) } - removeFragment(context) - } - }) + }, + ) } /** - * Check if paymentIntent.nextAction is out-of-band, such as voucher-based or waiting - * on customer verification. If it is, then being in this state is considered "successful". + * Check if paymentIntent.nextAction is out-of-band, such as voucher-based or waiting on customer + * verification. If it is, then being in this state is considered "successful". */ - private fun isNextActionSuccessState(nextAction: StripeIntent.NextActionType?): Boolean { - return when (nextAction) { + private fun isNextActionSuccessState(nextAction: StripeIntent.NextActionType?): Boolean = + when (nextAction) { StripeIntent.NextActionType.DisplayOxxoDetails, StripeIntent.NextActionType.DisplayBoletoDetails, StripeIntent.NextActionType.DisplayKonbiniDetails, StripeIntent.NextActionType.VerifyWithMicrodeposits, - StripeIntent.NextActionType.DisplayMultibancoDetails -> true + StripeIntent.NextActionType.DisplayMultibancoDetails, + -> true StripeIntent.NextActionType.RedirectToUrl, StripeIntent.NextActionType.UseStripeSdk, StripeIntent.NextActionType.AlipayRedirect, @@ -294,8 +373,7 @@ class PaymentLauncherFragment( StripeIntent.NextActionType.UpiAwaitNotification, StripeIntent.NextActionType.CashAppRedirect, StripeIntent.NextActionType.SwishRedirect, - null, -> false + null, + -> false } - } } - diff --git a/android/src/main/java/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.kt b/android/src/main/java/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.kt index 23ab6b775..443e0098f 100644 --- a/android/src/main/java/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.kt +++ b/android/src/main/java/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.kt @@ -1,12 +1,21 @@ package com.reactnativestripesdk import com.facebook.react.bridge.ReadableMap -import com.reactnativestripesdk.utils.* +import com.reactnativestripesdk.utils.getBooleanOrFalse +import com.reactnativestripesdk.utils.getMapOrNull +import com.reactnativestripesdk.utils.getValOr import com.reactnativestripesdk.utils.mapToBillingDetails import com.reactnativestripesdk.utils.mapToMetadata +import com.reactnativestripesdk.utils.mapToPaymentIntentFutureUsage import com.reactnativestripesdk.utils.mapToUSBankAccountHolderType import com.reactnativestripesdk.utils.mapToUSBankAccountType -import com.stripe.android.model.* +import com.stripe.android.model.ConfirmPaymentIntentParams +import com.stripe.android.model.ConfirmSetupIntentParams +import com.stripe.android.model.ConfirmStripeIntentParams +import com.stripe.android.model.MandateDataParams +import com.stripe.android.model.PaymentMethod +import com.stripe.android.model.PaymentMethodCreateParams +import com.stripe.android.model.PaymentMethodOptionsParams class PaymentMethodCreateParamsFactory( private val paymentMethodData: ReadableMap?, @@ -14,10 +23,11 @@ class PaymentMethodCreateParamsFactory( private val cardFieldView: CardFieldView?, private val cardFormView: CardFormView?, ) { - private val billingDetailsParams = mapToBillingDetails( - getMapOrNull(paymentMethodData, "billingDetails"), - cardFieldView?.cardAddress ?: cardFormView?.cardAddress - ) + private val billingDetailsParams = + mapToBillingDetails( + getMapOrNull(paymentMethodData, "billingDetails"), + cardFieldView?.cardAddress ?: cardFormView?.cardAddress, + ) private val metadataParams: Map? = mapToMetadata(getMapOrNull(paymentMethodData, "metadata")) @@ -62,25 +72,25 @@ class PaymentMethodCreateParamsFactory( return PaymentMethodCreateParams.create( ideal = idealParams, billingDetails = billingDetailsParams, - metadata = metadataParams + metadata = metadataParams, ) } @Throws(PaymentMethodCreateParamsException::class) - private fun createAlipayParams(): PaymentMethodCreateParams { - return PaymentMethodCreateParams.createAlipay() - } + private fun createAlipayParams(): PaymentMethodCreateParams = PaymentMethodCreateParams.createAlipay() @Throws(PaymentMethodCreateParamsException::class) private fun createSofortParams(): PaymentMethodCreateParams { - val country = getValOr(paymentMethodData, "country", null) ?: run { - throw PaymentMethodCreateParamsException("You must provide bank account country") - } + val country = + getValOr(paymentMethodData, "country", null) + ?: run { + throw PaymentMethodCreateParamsException("You must provide bank account country") + } return PaymentMethodCreateParams.create( PaymentMethodCreateParams.Sofort(country = country), billingDetailsParams, - metadata = metadataParams + metadata = metadataParams, ) } @@ -89,7 +99,7 @@ class PaymentMethodCreateParamsFactory( billingDetailsParams?.let { return PaymentMethodCreateParams.createBancontact( billingDetails = it, - metadata = metadataParams + metadata = metadataParams, ) } @@ -99,14 +109,14 @@ class PaymentMethodCreateParamsFactory( @Throws(PaymentMethodCreateParamsException::class) private fun createSepaParams(): PaymentMethodCreateParams { billingDetailsParams?.let { - val iban = getValOr(paymentMethodData, "iban", null) ?: run { - throw PaymentMethodCreateParamsException("You must provide IBAN") - } + val iban = + getValOr(paymentMethodData, "iban", null) + ?: run { throw PaymentMethodCreateParamsException("You must provide IBAN") } return PaymentMethodCreateParams.create( sepaDebit = PaymentMethodCreateParams.SepaDebit(iban), billingDetails = it, - metadata = metadataParams + metadata = metadataParams, ) } @@ -116,10 +126,7 @@ class PaymentMethodCreateParamsFactory( @Throws(PaymentMethodCreateParamsException::class) private fun createOXXOParams(): PaymentMethodCreateParams { billingDetailsParams?.let { - return PaymentMethodCreateParams.createOxxo( - billingDetails = it, - metadata = metadataParams - ) + return PaymentMethodCreateParams.createOxxo(billingDetails = it, metadata = metadataParams) } throw PaymentMethodCreateParamsException("You must provide billing details") @@ -128,10 +135,7 @@ class PaymentMethodCreateParamsFactory( @Throws(PaymentMethodCreateParamsException::class) private fun createGiropayParams(): PaymentMethodCreateParams { billingDetailsParams?.let { - return PaymentMethodCreateParams.createGiropay( - billingDetails = it, - metadata = metadataParams - ) + return PaymentMethodCreateParams.createGiropay(billingDetails = it, metadata = metadataParams) } throw PaymentMethodCreateParamsException("You must provide billing details") @@ -140,10 +144,7 @@ class PaymentMethodCreateParamsFactory( @Throws(PaymentMethodCreateParamsException::class) private fun createEPSParams(): PaymentMethodCreateParams { billingDetailsParams?.let { - return PaymentMethodCreateParams.createEps( - billingDetails = it, - metadata = metadataParams - ) + return PaymentMethodCreateParams.createEps(billingDetails = it, metadata = metadataParams) } throw PaymentMethodCreateParamsException("You must provide billing details") @@ -158,10 +159,7 @@ class PaymentMethodCreateParamsFactory( @Throws(PaymentMethodCreateParamsException::class) private fun createP24Params(): PaymentMethodCreateParams { billingDetailsParams?.let { - return PaymentMethodCreateParams.createP24( - billingDetails = it, - metadata = metadataParams - ) + return PaymentMethodCreateParams.createP24(billingDetails = it, metadata = metadataParams) } throw PaymentMethodCreateParamsException("You must provide billing details") @@ -172,7 +170,7 @@ class PaymentMethodCreateParamsFactory( val bank = getBooleanOrFalse(paymentMethodData, "testOfflineBank").let { "test_offline_bank" } return PaymentMethodCreateParams.create( PaymentMethodCreateParams.Fpx(bank), - metadata = metadataParams + metadata = metadataParams, ) } @@ -181,7 +179,7 @@ class PaymentMethodCreateParamsFactory( billingDetailsParams?.let { return PaymentMethodCreateParams.createAfterpayClearpay( billingDetails = it, - metadata = metadataParams + metadata = metadataParams, ) } @@ -190,27 +188,30 @@ class PaymentMethodCreateParamsFactory( @Throws(PaymentMethodCreateParamsException::class) private fun createAuBecsDebitParams(): PaymentMethodCreateParams { - val formDetails = getMapOrNull(paymentMethodData, "formDetails") ?: run { - throw PaymentMethodCreateParamsException("You must provide form details") - } + val formDetails = + getMapOrNull(paymentMethodData, "formDetails") + ?: run { throw PaymentMethodCreateParamsException("You must provide form details") } val bsbNumber = getValOr(formDetails, "bsbNumber") as String val accountNumber = getValOr(formDetails, "accountNumber") as String val name = getValOr(formDetails, "name") as String val email = getValOr(formDetails, "email") as String - val billingDetails = PaymentMethod.BillingDetails.Builder() - .setName(name) - .setEmail(email) - .build() + val billingDetails = + PaymentMethod.BillingDetails + .Builder() + .setName(name) + .setEmail(email) + .build() return PaymentMethodCreateParams.create( - auBecsDebit = PaymentMethodCreateParams.AuBecsDebit( - bsbNumber = bsbNumber, - accountNumber = accountNumber - ), + auBecsDebit = + PaymentMethodCreateParams.AuBecsDebit( + bsbNumber = bsbNumber, + accountNumber = accountNumber, + ), billingDetails = billingDetails, - metadata = metadataParams + metadata = metadataParams, ) } @@ -220,60 +221,52 @@ class PaymentMethodCreateParamsFactory( billingDetailsParams.address?.country.isNullOrBlank() || billingDetailsParams.email.isNullOrBlank() ) { - throw PaymentMethodCreateParamsException("Klarna requires that you provide the following billing details: email, country") + throw PaymentMethodCreateParamsException( + "Klarna requires that you provide the following billing details: email, country", + ) } return PaymentMethodCreateParams.createKlarna( billingDetails = billingDetailsParams, - metadata = metadataParams + metadata = metadataParams, ) } @Throws(PaymentMethodCreateParamsException::class) - private fun createPayPalParams(): PaymentMethodCreateParams { - return PaymentMethodCreateParams.createPayPal( - metadata = metadataParams - ) - } + private fun createPayPalParams(): PaymentMethodCreateParams = PaymentMethodCreateParams.createPayPal(metadata = metadataParams) @Throws(PaymentMethodCreateParamsException::class) - private fun createAffirmParams(): PaymentMethodCreateParams { - return PaymentMethodCreateParams.createAffirm( + private fun createAffirmParams(): PaymentMethodCreateParams = + PaymentMethodCreateParams.createAffirm( billingDetails = billingDetailsParams, - metadata = metadataParams + metadata = metadataParams, ) - } @Throws(PaymentMethodCreateParamsException::class) - private fun createCashAppParams(): PaymentMethodCreateParams { - return PaymentMethodCreateParams.createCashAppPay( + private fun createCashAppParams(): PaymentMethodCreateParams = + PaymentMethodCreateParams.createCashAppPay( billingDetails = billingDetailsParams, - metadata = metadataParams + metadata = metadataParams, ) - } @Throws(PaymentMethodCreateParamsException::class) - private fun createRevolutPayParams(): PaymentMethodCreateParams { - return PaymentMethodCreateParams.createRevolutPay( + private fun createRevolutPayParams(): PaymentMethodCreateParams = + PaymentMethodCreateParams.createRevolutPay( billingDetails = billingDetailsParams, - metadata = metadataParams + metadata = metadataParams, ) - } @Throws(PaymentMethodCreateParamsException::class) fun createParams( clientSecret: String, paymentMethodType: PaymentMethod.Type?, - isPaymentIntent: Boolean + isPaymentIntent: Boolean, ): ConfirmStripeIntentParams { try { return when (paymentMethodType) { PaymentMethod.Type.Card -> createCardStripeIntentParams(clientSecret, isPaymentIntent) - PaymentMethod.Type.USBankAccount -> createUSBankAccountStripeIntentParams( - clientSecret, - isPaymentIntent - ) - + PaymentMethod.Type.USBankAccount -> + createUSBankAccountStripeIntentParams(clientSecret, isPaymentIntent) PaymentMethod.Type.Affirm -> createAffirmStripeIntentParams(clientSecret, isPaymentIntent) PaymentMethod.Type.Ideal, PaymentMethod.Type.Alipay, @@ -291,31 +284,26 @@ class PaymentMethodCreateParamsFactory( PaymentMethod.Type.Klarna, PaymentMethod.Type.PayPal, PaymentMethod.Type.CashAppPay, - PaymentMethod.Type.RevolutPay -> { + PaymentMethod.Type.RevolutPay, + -> { val params = createPaymentMethodParams(paymentMethodType) return if (isPaymentIntent) { - ConfirmPaymentIntentParams - .createWithPaymentMethodCreateParams( - paymentMethodCreateParams = params, - clientSecret = clientSecret, - setupFutureUsage = mapToPaymentIntentFutureUsage( - getValOr( - options, - "setupFutureUsage" - ) - ), - mandateData = buildMandateDataParams() - ) + ConfirmPaymentIntentParams.createWithPaymentMethodCreateParams( + paymentMethodCreateParams = params, + clientSecret = clientSecret, + setupFutureUsage = + mapToPaymentIntentFutureUsage(getValOr(options, "setupFutureUsage")), + mandateData = buildMandateDataParams(), + ) } else { ConfirmSetupIntentParams.create( paymentMethodCreateParams = params, clientSecret = clientSecret, - mandateData = buildMandateDataParams() + mandateData = buildMandateDataParams(), ) } } - null -> ConfirmPaymentIntentParams.create(clientSecret) else -> { throw Exception("This paymentMethodType is not supported yet") @@ -345,7 +333,7 @@ class PaymentMethodCreateParamsFactory( @Throws(PaymentMethodCreateParamsException::class) private fun createCardStripeIntentParams( clientSecret: String, - isPaymentIntent: Boolean + isPaymentIntent: Boolean, ): ConfirmStripeIntentParams { val paymentMethodId = getValOr(paymentMethodData, "paymentMethodId", null) val setupFutureUsage = mapToPaymentIntentFutureUsage(getValOr(options, "setupFutureUsage")) @@ -356,51 +344,50 @@ class PaymentMethodCreateParamsFactory( if (cvc != null) PaymentMethodOptionsParams.Card(cvc) else null return ( - if (isPaymentIntent) + if (isPaymentIntent) { ConfirmPaymentIntentParams.createWithPaymentMethodId( paymentMethodId, paymentMethodOptions = paymentMethodOptionParams, clientSecret = clientSecret, - setupFutureUsage = setupFutureUsage - ) - else - ConfirmSetupIntentParams.create( - paymentMethodId, - clientSecret + setupFutureUsage = setupFutureUsage, ) - ) + } else { + ConfirmSetupIntentParams.create(paymentMethodId, clientSecret) + } + ) } else { val paymentMethodCreateParams = createCardPaymentMethodParams() return ( - if (isPaymentIntent) - ConfirmPaymentIntentParams - .createWithPaymentMethodCreateParams( - paymentMethodCreateParams, - clientSecret, - setupFutureUsage = setupFutureUsage - ) - else - ConfirmSetupIntentParams - .create(paymentMethodCreateParams, clientSecret) - ) + if (isPaymentIntent) { + ConfirmPaymentIntentParams.createWithPaymentMethodCreateParams( + paymentMethodCreateParams, + clientSecret, + setupFutureUsage = setupFutureUsage, + ) + } else { + ConfirmSetupIntentParams.create(paymentMethodCreateParams, clientSecret) + } + ) } } @Throws(PaymentMethodCreateParamsException::class) private fun createUSBankAccountStripeIntentParams( clientSecret: String, - isPaymentIntent: Boolean + isPaymentIntent: Boolean, ): ConfirmStripeIntentParams { // If payment method data is supplied, assume they are passing in the bank details manually paymentMethodData?.let { if (billingDetailsParams?.name.isNullOrBlank()) { - throw PaymentMethodCreateParamsException("When creating a US bank account payment method, you must provide the following billing details: name") + throw PaymentMethodCreateParamsException( + "When creating a US bank account payment method, you must provide the following billing details: name", + ) } return if (isPaymentIntent) { ConfirmPaymentIntentParams.createWithPaymentMethodCreateParams( paymentMethodCreateParams = createUSBankAccountParams(paymentMethodData), clientSecret, - setupFutureUsage = mapToPaymentIntentFutureUsage(getValOr(options, "setupFutureUsage")) + setupFutureUsage = mapToPaymentIntentFutureUsage(getValOr(options, "setupFutureUsage")), ) } else { ConfirmSetupIntentParams.create( @@ -408,27 +395,27 @@ class PaymentMethodCreateParamsFactory( clientSecret = clientSecret, ) } - } ?: run { - // Payment method is assumed to be already attached through via collectBankAccount - return if (isPaymentIntent) { - ConfirmPaymentIntentParams.create( - clientSecret = clientSecret, - paymentMethodType = PaymentMethod.Type.USBankAccount, - - ) - } else { - ConfirmSetupIntentParams.create( - clientSecret = clientSecret, - paymentMethodType = PaymentMethod.Type.USBankAccount - ) - } } + ?: run { + // Payment method is assumed to be already attached through via collectBankAccount + return if (isPaymentIntent) { + ConfirmPaymentIntentParams.create( + clientSecret = clientSecret, + paymentMethodType = PaymentMethod.Type.USBankAccount, + ) + } else { + ConfirmSetupIntentParams.create( + clientSecret = clientSecret, + paymentMethodType = PaymentMethod.Type.USBankAccount, + ) + } + } } @Throws(PaymentMethodCreateParamsException::class) private fun createAffirmStripeIntentParams( clientSecret: String, - isPaymentIntent: Boolean + isPaymentIntent: Boolean, ): ConfirmStripeIntentParams { if (!isPaymentIntent) { throw PaymentMethodCreateParamsException("Affirm is not yet supported through SetupIntents.") @@ -436,13 +423,12 @@ class PaymentMethodCreateParamsFactory( val params = createAffirmParams() - return ConfirmPaymentIntentParams - .createWithPaymentMethodCreateParams( - paymentMethodCreateParams = params, - clientSecret = clientSecret, - setupFutureUsage = mapToPaymentIntentFutureUsage(getValOr(options, "setupFutureUsage")), - mandateData = buildMandateDataParams(), - ) + return ConfirmPaymentIntentParams.createWithPaymentMethodCreateParams( + paymentMethodCreateParams = params, + clientSecret = clientSecret, + setupFutureUsage = mapToPaymentIntentFutureUsage(getValOr(options, "setupFutureUsage")), + mandateData = buildMandateDataParams(), + ) } @Throws(PaymentMethodCreateParamsException::class) @@ -451,34 +437,27 @@ class PaymentMethodCreateParamsFactory( val routingNumber = getValOr(params, "routingNumber", null) if (accountNumber.isNullOrBlank()) { - throw PaymentMethodCreateParamsException("When creating a US bank account payment method, you must provide the bank account number") + throw PaymentMethodCreateParamsException( + "When creating a US bank account payment method, you must provide the bank account number", + ) } else if (routingNumber.isNullOrBlank()) { - throw PaymentMethodCreateParamsException("When creating a US bank account payment method, you must provide the bank routing number") + throw PaymentMethodCreateParamsException( + "When creating a US bank account payment method, you must provide the bank routing number", + ) } - val usBankAccount = PaymentMethodCreateParams.USBankAccount( - accountNumber, - routingNumber, - mapToUSBankAccountType( - getValOr( - params, - "accountType", - null - ) - ), - mapToUSBankAccountHolderType( - getValOr( - params, - "accountHolderType", - null - ) + val usBankAccount = + PaymentMethodCreateParams.USBankAccount( + accountNumber, + routingNumber, + mapToUSBankAccountType(getValOr(params, "accountType", null)), + mapToUSBankAccountHolderType(getValOr(params, "accountHolderType", null)), ) - ) return PaymentMethodCreateParams.Companion.create( usBankAccount, billingDetailsParams, - metadataParams + metadataParams, ) } @@ -490,7 +469,7 @@ class PaymentMethodCreateParamsFactory( MandateDataParams.Type.Online( ipAddress = getValOr(onlineParams, "ipAddress", "") ?: "", userAgent = getValOr(onlineParams, "userAgent", "") ?: "", - ) + ), ) } } @@ -499,4 +478,6 @@ class PaymentMethodCreateParamsFactory( } } -class PaymentMethodCreateParamsException(message: String) : Exception(message) +class PaymentMethodCreateParamsException( + message: String, +) : Exception(message) diff --git a/android/src/main/java/com/reactnativestripesdk/PaymentSheetAppearance.kt b/android/src/main/java/com/reactnativestripesdk/PaymentSheetAppearance.kt index cd470e2c9..7e983caca 100644 --- a/android/src/main/java/com/reactnativestripesdk/PaymentSheetAppearance.kt +++ b/android/src/main/java/com/reactnativestripesdk/PaymentSheetAppearance.kt @@ -3,11 +3,13 @@ package com.reactnativestripesdk import android.content.Context import android.graphics.Color import android.os.Bundle -import com.facebook.react.bridge.ReactContext import com.reactnativestripesdk.utils.PaymentSheetAppearanceException import com.stripe.android.paymentsheet.PaymentSheet -fun buildPaymentSheetAppearance(userParams: Bundle?, context: Context): PaymentSheet.Appearance { +fun buildPaymentSheetAppearance( + userParams: Bundle?, + context: Context, +): PaymentSheet.Appearance { val colorParams = userParams?.getBundle(PaymentSheetAppearanceKeys.COLORS) val lightColorParams = colorParams?.getBundle(PaymentSheetAppearanceKeys.LIGHT) ?: colorParams val darkColorParams = colorParams?.getBundle(PaymentSheetAppearanceKeys.DARK) ?: colorParams @@ -17,58 +19,138 @@ fun buildPaymentSheetAppearance(userParams: Bundle?, context: Context): PaymentS colorsLight = buildColors(lightColorParams, PaymentSheet.Colors.defaultLight), colorsDark = buildColors(darkColorParams, PaymentSheet.Colors.defaultDark), shapes = buildShapes(userParams?.getBundle(PaymentSheetAppearanceKeys.SHAPES)), - primaryButton = buildPrimaryButton(userParams?.getBundle(PaymentSheetAppearanceKeys.PRIMARY_BUTTON), context) + primaryButton = + buildPrimaryButton( + userParams?.getBundle(PaymentSheetAppearanceKeys.PRIMARY_BUTTON), + context, + ), ) } -private fun buildTypography(fontParams: Bundle?, context: Context): PaymentSheet.Typography { +private fun buildTypography( + fontParams: Bundle?, + context: Context, +): PaymentSheet.Typography { val scale = getDoubleOrNull(fontParams, PaymentSheetAppearanceKeys.SCALE) - val resId = getFontResId(fontParams, PaymentSheetAppearanceKeys.FAMILY, PaymentSheet.Typography.default.fontResId, context) + val resId = + getFontResId( + fontParams, + PaymentSheetAppearanceKeys.FAMILY, + PaymentSheet.Typography.default.fontResId, + context, + ) return PaymentSheet.Typography.default.copy( sizeScaleFactor = scale?.toFloat() ?: PaymentSheet.Typography.default.sizeScaleFactor, - fontResId = resId + fontResId = resId, ) } @Throws(PaymentSheetAppearanceException::class) -private fun colorFromHexOrDefault(hexString: String?, default: Int): Int { - return hexString?.trim()?.replace("#","")?.let { +private fun colorFromHexOrDefault( + hexString: String?, + default: Int, +): Int { + return hexString?.trim()?.replace("#", "")?.let { if (it.length == 6 || it.length == 8) { return Color.parseColor("#$it") - } else throw PaymentSheetAppearanceException("Failed to set Payment Sheet appearance. Expected hex string of length 6 or 8, but received: $it") - } ?: run { - return default + } else { + throw PaymentSheetAppearanceException( + "Failed to set Payment Sheet appearance. Expected hex string of length 6 or 8, but received: $it", + ) + } } + ?: run { + return default + } } -private fun buildColors(colorParams: Bundle?, default: PaymentSheet.Colors): PaymentSheet.Colors { +private fun buildColors( + colorParams: Bundle?, + default: PaymentSheet.Colors, +): PaymentSheet.Colors { if (colorParams == null) { return default } return default.copy( - primary = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.PRIMARY), default.primary), - surface = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.BACKGROUND), default.surface), - component = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.COMPONENT_BACKGROUND), default.component), - componentBorder = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.COMPONENT_BORDER), default.componentBorder), - componentDivider = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.COMPONENT_DIVIDER), default.componentDivider), - onComponent = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.COMPONENT_TEXT), default.onComponent), - onSurface = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.PRIMARY_TEXT), default.onSurface), - subtitle = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.SECONDARY_TEXT), default.subtitle), - placeholderText = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.PLACEHOLDER_TEXT), default.placeholderText), - appBarIcon = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.ICON), default.appBarIcon), - error = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.ERROR), default.error), + primary = + colorFromHexOrDefault( + colorParams.getString(PaymentSheetAppearanceKeys.PRIMARY), + default.primary, + ), + surface = + colorFromHexOrDefault( + colorParams.getString(PaymentSheetAppearanceKeys.BACKGROUND), + default.surface, + ), + component = + colorFromHexOrDefault( + colorParams.getString(PaymentSheetAppearanceKeys.COMPONENT_BACKGROUND), + default.component, + ), + componentBorder = + colorFromHexOrDefault( + colorParams.getString(PaymentSheetAppearanceKeys.COMPONENT_BORDER), + default.componentBorder, + ), + componentDivider = + colorFromHexOrDefault( + colorParams.getString(PaymentSheetAppearanceKeys.COMPONENT_DIVIDER), + default.componentDivider, + ), + onComponent = + colorFromHexOrDefault( + colorParams.getString(PaymentSheetAppearanceKeys.COMPONENT_TEXT), + default.onComponent, + ), + onSurface = + colorFromHexOrDefault( + colorParams.getString(PaymentSheetAppearanceKeys.PRIMARY_TEXT), + default.onSurface, + ), + subtitle = + colorFromHexOrDefault( + colorParams.getString(PaymentSheetAppearanceKeys.SECONDARY_TEXT), + default.subtitle, + ), + placeholderText = + colorFromHexOrDefault( + colorParams.getString(PaymentSheetAppearanceKeys.PLACEHOLDER_TEXT), + default.placeholderText, + ), + appBarIcon = + colorFromHexOrDefault( + colorParams.getString(PaymentSheetAppearanceKeys.ICON), + default.appBarIcon, + ), + error = + colorFromHexOrDefault( + colorParams.getString(PaymentSheetAppearanceKeys.ERROR), + default.error, + ), ) } -private fun buildShapes(shapeParams: Bundle?): PaymentSheet.Shapes { - return PaymentSheet.Shapes.default.copy( - cornerRadiusDp = getFloatOr(shapeParams, PaymentSheetAppearanceKeys.BORDER_RADIUS, PaymentSheet.Shapes.default.cornerRadiusDp), - borderStrokeWidthDp = getFloatOr(shapeParams, PaymentSheetAppearanceKeys.BORDER_WIDTH, PaymentSheet.Shapes.default.borderStrokeWidthDp) +private fun buildShapes(shapeParams: Bundle?): PaymentSheet.Shapes = + PaymentSheet.Shapes.default.copy( + cornerRadiusDp = + getFloatOr( + shapeParams, + PaymentSheetAppearanceKeys.BORDER_RADIUS, + PaymentSheet.Shapes.default.cornerRadiusDp, + ), + borderStrokeWidthDp = + getFloatOr( + shapeParams, + PaymentSheetAppearanceKeys.BORDER_WIDTH, + PaymentSheet.Shapes.default.borderStrokeWidthDp, + ), ) -} -private fun buildPrimaryButton(params: Bundle?, context: Context): PaymentSheet.PrimaryButton { +private fun buildPrimaryButton( + params: Bundle?, + context: Context, +): PaymentSheet.PrimaryButton { if (params == null) { return PaymentSheet.PrimaryButton() } @@ -80,34 +162,61 @@ private fun buildPrimaryButton(params: Bundle?, context: Context): PaymentSheet. val darkColorParams = colorParams.getBundle(PaymentSheetAppearanceKeys.DARK) ?: colorParams return PaymentSheet.PrimaryButton( - colorsLight = buildPrimaryButtonColors(lightColorParams, PaymentSheet.PrimaryButtonColors.defaultLight), - colorsDark = buildPrimaryButtonColors(darkColorParams, PaymentSheet.PrimaryButtonColors.defaultDark), - shape = PaymentSheet.PrimaryButtonShape( - cornerRadiusDp = getFloatOrNull(shapeParams, PaymentSheetAppearanceKeys.BORDER_RADIUS), - borderStrokeWidthDp = getFloatOrNull(shapeParams, PaymentSheetAppearanceKeys.BORDER_WIDTH), - ), - typography = PaymentSheet.PrimaryButtonTypography( - fontResId = getFontResId(fontParams, PaymentSheetAppearanceKeys.FAMILY, null, context) - ) + colorsLight = + buildPrimaryButtonColors(lightColorParams, PaymentSheet.PrimaryButtonColors.defaultLight), + colorsDark = + buildPrimaryButtonColors(darkColorParams, PaymentSheet.PrimaryButtonColors.defaultDark), + shape = + PaymentSheet.PrimaryButtonShape( + cornerRadiusDp = + getFloatOrNull(shapeParams, PaymentSheetAppearanceKeys.BORDER_RADIUS), + borderStrokeWidthDp = + getFloatOrNull(shapeParams, PaymentSheetAppearanceKeys.BORDER_WIDTH), + ), + typography = + PaymentSheet.PrimaryButtonTypography( + fontResId = + getFontResId(fontParams, PaymentSheetAppearanceKeys.FAMILY, null, context), + ), ) } @Throws(PaymentSheetAppearanceException::class) -private fun buildPrimaryButtonColors(colorParams: Bundle, default: PaymentSheet.PrimaryButtonColors): PaymentSheet.PrimaryButtonColors { - return PaymentSheet.PrimaryButtonColors( - background = colorParams.getString(PaymentSheetAppearanceKeys.BACKGROUND)?.trim()?.replace("#", "")?.let { - if (it.length == 6 || it.length == 8) { - Color.parseColor("#$it") - } else throw PaymentSheetAppearanceException("Failed to set Payment Sheet appearance. Expected hex string of length 6 or 8, but received: $it") - } ?: run { - null - }, - onBackground = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.TEXT), default.onBackground), - border = colorFromHexOrDefault(colorParams.getString(PaymentSheetAppearanceKeys.BORDER), default.border), +private fun buildPrimaryButtonColors( + colorParams: Bundle, + default: PaymentSheet.PrimaryButtonColors, +): PaymentSheet.PrimaryButtonColors = + PaymentSheet.PrimaryButtonColors( + background = + colorParams + .getString(PaymentSheetAppearanceKeys.BACKGROUND) + ?.trim() + ?.replace("#", "") + ?.let { + if (it.length == 6 || it.length == 8) { + Color.parseColor("#$it") + } else { + throw PaymentSheetAppearanceException( + "Failed to set Payment Sheet appearance. Expected hex string of length 6 or 8, but received: $it", + ) + } + } ?: run { null }, + onBackground = + colorFromHexOrDefault( + colorParams.getString(PaymentSheetAppearanceKeys.TEXT), + default.onBackground, + ), + border = + colorFromHexOrDefault( + colorParams.getString(PaymentSheetAppearanceKeys.BORDER), + default.border, + ), ) -} -private fun getDoubleOrNull(bundle: Bundle?, key: String): Double? { +private fun getDoubleOrNull( + bundle: Bundle?, + key: String, +): Double? { if (bundle?.containsKey(key) == true) { val valueOfUnknownType = bundle.get(key) if (valueOfUnknownType is Double) { @@ -122,7 +231,11 @@ private fun getDoubleOrNull(bundle: Bundle?, key: String): Double? { return null } -private fun getFloatOr(bundle: Bundle?, key: String, defaultValue: Float): Float { +private fun getFloatOr( + bundle: Bundle?, + key: String, + defaultValue: Float, +): Float { if (bundle?.containsKey(key) == true) { val valueOfUnknownType = bundle.get(key) if (valueOfUnknownType is Float) { @@ -137,7 +250,10 @@ private fun getFloatOr(bundle: Bundle?, key: String, defaultValue: Float): Float return defaultValue } -private fun getFloatOrNull(bundle: Bundle?, key: String): Float? { +private fun getFloatOrNull( + bundle: Bundle?, + key: String, +): Float? { if (bundle?.containsKey(key) == true) { val valueOfUnknownType = bundle.get(key) if (valueOfUnknownType is Float) { @@ -153,17 +269,25 @@ private fun getFloatOrNull(bundle: Bundle?, key: String): Float? { } @Throws(PaymentSheetAppearanceException::class) -private fun getFontResId(bundle: Bundle?, key: String, defaultValue: Int?, context: Context): Int? { +private fun getFontResId( + bundle: Bundle?, + key: String, + defaultValue: Int?, + context: Context, +): Int? { val fontErrorPrefix = "Encountered an error when setting a custom font:" if (bundle?.containsKey(key) != true) { return defaultValue } - val fontFileName = bundle.getString(key) - ?: throw PaymentSheetAppearanceException("$fontErrorPrefix expected String for font.$key, but received null.") + val fontFileName = + bundle.getString(key) + ?: throw PaymentSheetAppearanceException( + "$fontErrorPrefix expected String for font.$key, but received null.", + ) if (Regex("[^a-z0-9]").containsMatchIn(fontFileName)) { throw PaymentSheetAppearanceException( - "$fontErrorPrefix appearance.font.$key should only contain lowercase alphanumeric characters on Android, but received '$fontFileName'. This value must match the filename in android/app/src/main/res/font" + "$fontErrorPrefix appearance.font.$key should only contain lowercase alphanumeric characters on Android, but received '$fontFileName'. This value must match the filename in android/app/src/main/res/font", ) } diff --git a/android/src/main/java/com/reactnativestripesdk/PaymentSheetFragment.kt b/android/src/main/java/com/reactnativestripesdk/PaymentSheetFragment.kt index cbc6e0486..63cc8fd16 100644 --- a/android/src/main/java/com/reactnativestripesdk/PaymentSheetFragment.kt +++ b/android/src/main/java/com/reactnativestripesdk/PaymentSheetFragment.kt @@ -18,13 +18,31 @@ import android.widget.FrameLayout import androidx.appcompat.content.res.AppCompatResources import androidx.core.graphics.drawable.DrawableCompat import androidx.fragment.app.Fragment -import com.facebook.react.bridge.* +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.WritableNativeMap import com.reactnativestripesdk.addresssheet.AddressSheetView -import com.reactnativestripesdk.utils.* +import com.reactnativestripesdk.utils.ErrorType +import com.reactnativestripesdk.utils.PaymentSheetAppearanceException +import com.reactnativestripesdk.utils.PaymentSheetErrorType +import com.reactnativestripesdk.utils.PaymentSheetException import com.reactnativestripesdk.utils.createError import com.reactnativestripesdk.utils.createResult +import com.reactnativestripesdk.utils.mapFromPaymentMethod +import com.reactnativestripesdk.utils.mapToPreferredNetworks +import com.reactnativestripesdk.utils.removeFragment import com.stripe.android.ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi -import com.stripe.android.paymentsheet.* +import com.stripe.android.paymentsheet.CreateIntentCallback +import com.stripe.android.paymentsheet.CreateIntentResult +import com.stripe.android.paymentsheet.ExperimentalCustomerSessionApi +import com.stripe.android.paymentsheet.ExperimentalPaymentMethodLayoutApi +import com.stripe.android.paymentsheet.PaymentOptionCallback +import com.stripe.android.paymentsheet.PaymentSheet +import com.stripe.android.paymentsheet.PaymentSheetResult +import com.stripe.android.paymentsheet.PaymentSheetResultCallback import kotlinx.coroutines.CompletableDeferred import java.io.ByteArrayOutputStream import kotlin.Exception @@ -32,7 +50,7 @@ import kotlin.Exception @OptIn(ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi::class) class PaymentSheetFragment( private val context: ReactApplicationContext, - private val initPromise: Promise + private val initPromise: Promise, ) : Fragment() { private var paymentSheet: PaymentSheet? = null private var flowController: PaymentSheet.FlowController? = null @@ -48,19 +66,20 @@ class PaymentSheetFragment( override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - return FrameLayout(requireActivity()).also { - it.visibility = View.GONE - } - } + savedInstanceState: Bundle?, + ): View = FrameLayout(requireActivity()).also { it.visibility = View.GONE } @OptIn(ExperimentalPaymentMethodLayoutApi::class) - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) val merchantDisplayName = arguments?.getString("merchantDisplayName").orEmpty() if (merchantDisplayName.isEmpty()) { - initPromise.resolve(createError(ErrorType.Failed.toString(), "merchantDisplayName cannot be empty or null.")) + initPromise.resolve( + createError(ErrorType.Failed.toString(), "merchantDisplayName cannot be empty or null."), + ) return } val primaryButtonLabel = arguments?.getString("primaryButtonLabel") @@ -69,238 +88,288 @@ class PaymentSheetFragment( val billingDetailsBundle = arguments?.getBundle("defaultBillingDetails") val billingConfigParams = arguments?.getBundle("billingDetailsCollectionConfiguration") val paymentMethodOrder = arguments?.getStringArrayList("paymentMethodOrder") - val allowsRemovalOfLastSavedPaymentMethod = arguments?.getBoolean("allowsRemovalOfLastSavedPaymentMethod", true) ?: true + val allowsRemovalOfLastSavedPaymentMethod = + arguments?.getBoolean("allowsRemovalOfLastSavedPaymentMethod", true) ?: true paymentIntentClientSecret = arguments?.getString("paymentIntentClientSecret").orEmpty() setupIntentClientSecret = arguments?.getString("setupIntentClientSecret").orEmpty() - intentConfiguration = try { - buildIntentConfiguration(arguments?.getBundle("intentConfiguration")) - } catch (error: PaymentSheetException) { - initPromise.resolve(createError(ErrorType.Failed.toString(), error)) - return - } - val appearance = try { - buildPaymentSheetAppearance(arguments?.getBundle("appearance"), context) - } catch (error: PaymentSheetAppearanceException) { - initPromise.resolve(createError(ErrorType.Failed.toString(), error)) - return - } + intentConfiguration = + try { + buildIntentConfiguration(arguments?.getBundle("intentConfiguration")) + } catch (error: PaymentSheetException) { + initPromise.resolve(createError(ErrorType.Failed.toString(), error)) + return + } + val appearance = + try { + buildPaymentSheetAppearance(arguments?.getBundle("appearance"), context) + } catch (error: PaymentSheetAppearanceException) { + initPromise.resolve(createError(ErrorType.Failed.toString(), error)) + return + } - val customerConfiguration = try { - buildCustomerConfiguration(arguments) - } catch (error: PaymentSheetException) { - initPromise.resolve(createError(ErrorType.Failed.toString(), error)) - return - } + val customerConfiguration = + try { + buildCustomerConfiguration(arguments) + } catch (error: PaymentSheetException) { + initPromise.resolve(createError(ErrorType.Failed.toString(), error)) + return + } - val shippingDetails = arguments?.getBundle("defaultShippingDetails")?.let { - AddressSheetView.buildAddressDetails(it) - } + val shippingDetails = + arguments?.getBundle("defaultShippingDetails")?.let { + AddressSheetView.buildAddressDetails(it) + } + + val paymentOptionCallback = + PaymentOptionCallback { paymentOption -> + val result = + paymentOption?.let { + val bitmap = getBitmapFromVectorDrawable(context, it.drawableResourceId) + val imageString = getBase64FromBitmap(bitmap) + val option: WritableMap = WritableNativeMap() + option.putString("label", it.label) + option.putString("image", imageString) + createResult("paymentOption", option) + } + ?: run { + if (paymentSheetTimedOut) { + paymentSheetTimedOut = false + createError(PaymentSheetErrorType.Timeout.toString(), "The payment has timed out") + } else { + createError( + PaymentSheetErrorType.Canceled.toString(), + "The payment option selection flow has been canceled", + ) + } + } + presentPromise?.resolve(result) + } - val paymentOptionCallback = PaymentOptionCallback { paymentOption -> - val result = paymentOption?.let { - val bitmap = getBitmapFromVectorDrawable(context, it.drawableResourceId) - val imageString = getBase64FromBitmap(bitmap) - val option: WritableMap = WritableNativeMap() - option.putString("label", it.label) - option.putString("image", imageString) - createResult("paymentOption", option) - } ?: run { + val paymentResultCallback = + PaymentSheetResultCallback { paymentResult -> if (paymentSheetTimedOut) { paymentSheetTimedOut = false - createError(PaymentSheetErrorType.Timeout.toString(), "The payment has timed out") + resolvePaymentResult( + createError(PaymentSheetErrorType.Timeout.toString(), "The payment has timed out"), + ) } else { - createError(PaymentSheetErrorType.Canceled.toString(), "The payment option selection flow has been canceled") - } - } - presentPromise?.resolve(result) - } - - val paymentResultCallback = PaymentSheetResultCallback { paymentResult -> - if (paymentSheetTimedOut) { - paymentSheetTimedOut = false - resolvePaymentResult(createError(PaymentSheetErrorType.Timeout.toString(), "The payment has timed out")) - } else { - when (paymentResult) { - is PaymentSheetResult.Canceled -> { - resolvePaymentResult(createError(PaymentSheetErrorType.Canceled.toString(), "The payment flow has been canceled")) - } - is PaymentSheetResult.Failed -> { - resolvePaymentResult(createError(PaymentSheetErrorType.Failed.toString(), paymentResult.error)) - } - is PaymentSheetResult.Completed -> { - resolvePaymentResult(WritableNativeMap()) - // Remove the fragment now, we can be sure it won't be needed again if an intent is successful - removeFragment(context) - paymentSheet = null - flowController = null + when (paymentResult) { + is PaymentSheetResult.Canceled -> { + resolvePaymentResult( + createError( + PaymentSheetErrorType.Canceled.toString(), + "The payment flow has been canceled", + ), + ) + } + is PaymentSheetResult.Failed -> { + resolvePaymentResult( + createError(PaymentSheetErrorType.Failed.toString(), paymentResult.error), + ) + } + is PaymentSheetResult.Completed -> { + resolvePaymentResult(WritableNativeMap()) + // Remove the fragment now, we can be sure it won't be needed again if an intent is + // successful + removeFragment(context) + paymentSheet = null + flowController = null + } } } } - } - val createIntentCallback = CreateIntentCallback { paymentMethod, shouldSavePaymentMethod -> - val stripeSdkModule: StripeSdkModule? = context.getNativeModule(StripeSdkModule::class.java) - if (stripeSdkModule == null || stripeSdkModule.eventListenerCount == 0) { - return@CreateIntentCallback CreateIntentResult.Failure( - cause = Exception("Tried to call confirmHandler, but no callback was found. Please file an issue: https://github.com/stripe/stripe-react-native/issues"), - displayMessage = "An unexpected error occurred" + val createIntentCallback = + CreateIntentCallback { paymentMethod, shouldSavePaymentMethod -> + val stripeSdkModule: StripeSdkModule? = context.getNativeModule(StripeSdkModule::class.java) + if (stripeSdkModule == null || stripeSdkModule.eventListenerCount == 0) { + return@CreateIntentCallback CreateIntentResult.Failure( + cause = + Exception( + "Tried to call confirmHandler, but no callback was found. Please file an issue: https://github.com/stripe/stripe-react-native/issues", + ), + displayMessage = "An unexpected error occurred", ) - } - val params = Arguments.createMap().apply { - putMap("paymentMethod", mapFromPaymentMethod(paymentMethod)) - putBoolean("shouldSavePaymentMethod", shouldSavePaymentMethod) - } + } + val params = + Arguments.createMap().apply { + putMap("paymentMethod", mapFromPaymentMethod(paymentMethod)) + putBoolean("shouldSavePaymentMethod", shouldSavePaymentMethod) + } - stripeSdkModule.sendEvent(context, "onConfirmHandlerCallback", params) + stripeSdkModule.sendEvent(context, "onConfirmHandlerCallback", params) - val resultFromJavascript = paymentSheetIntentCreationCallback.await() - // reset the completable - paymentSheetIntentCreationCallback = CompletableDeferred() + val resultFromJavascript = paymentSheetIntentCreationCallback.await() + // reset the completable + paymentSheetIntentCreationCallback = CompletableDeferred() - return@CreateIntentCallback resultFromJavascript.getString("clientSecret")?.let { - CreateIntentResult.Success(clientSecret = it) - } ?: run { - val errorMap = resultFromJavascript.getMap("error") - CreateIntentResult.Failure( - cause = Exception(errorMap?.getString("message")), - displayMessage = errorMap?.getString("localizedMessage") - ) + return@CreateIntentCallback resultFromJavascript.getString("clientSecret")?.let { + CreateIntentResult.Success(clientSecret = it) + } + ?: run { + val errorMap = resultFromJavascript.getMap("error") + CreateIntentResult.Failure( + cause = Exception(errorMap?.getString("message")), + displayMessage = errorMap?.getString("localizedMessage"), + ) + } } - } - val billingDetailsConfig = PaymentSheet.BillingDetailsCollectionConfiguration( - name = mapToCollectionMode(billingConfigParams?.getString("name")), - phone = mapToCollectionMode(billingConfigParams?.getString("phone")), - email = mapToCollectionMode(billingConfigParams?.getString("email")), - address = mapToAddressCollectionMode(billingConfigParams?.getString("address")), - attachDefaultsToPaymentMethod = billingConfigParams?.getBoolean("attachDefaultsToPaymentMethod") - ?: false - ) + val billingDetailsConfig = + PaymentSheet.BillingDetailsCollectionConfiguration( + name = mapToCollectionMode(billingConfigParams?.getString("name")), + phone = mapToCollectionMode(billingConfigParams?.getString("phone")), + email = mapToCollectionMode(billingConfigParams?.getString("email")), + address = mapToAddressCollectionMode(billingConfigParams?.getString("address")), + attachDefaultsToPaymentMethod = + billingConfigParams?.getBoolean("attachDefaultsToPaymentMethod") ?: false, + ) var defaultBillingDetails: PaymentSheet.BillingDetails? = null if (billingDetailsBundle != null) { val addressBundle = billingDetailsBundle.getBundle("address") - val address = PaymentSheet.Address( - addressBundle?.getString("city"), - addressBundle?.getString("country"), - addressBundle?.getString("line1"), - addressBundle?.getString("line2"), - addressBundle?.getString("postalCode"), - addressBundle?.getString("state")) - defaultBillingDetails = PaymentSheet.BillingDetails( - address, - billingDetailsBundle.getString("email"), - billingDetailsBundle.getString("name"), - billingDetailsBundle.getString("phone")) - } - val configurationBuilder = PaymentSheet.Configuration.Builder(merchantDisplayName) - .allowsDelayedPaymentMethods(allowsDelayedPaymentMethods ?: false) - .defaultBillingDetails(defaultBillingDetails) - .customer(customerConfiguration) - .googlePay(googlePayConfig) - .appearance(appearance) - .shippingDetails(shippingDetails) - .billingDetailsCollectionConfiguration(billingDetailsConfig) - .preferredNetworks(mapToPreferredNetworks(arguments?.getIntegerArrayList("preferredNetworks"))) - .allowsRemovalOfLastSavedPaymentMethod(allowsRemovalOfLastSavedPaymentMethod) - primaryButtonLabel?.let { - configurationBuilder.primaryButtonLabel(it) - } - paymentMethodOrder?.let { - configurationBuilder.paymentMethodOrder(it) + val address = + PaymentSheet.Address( + addressBundle?.getString("city"), + addressBundle?.getString("country"), + addressBundle?.getString("line1"), + addressBundle?.getString("line2"), + addressBundle?.getString("postalCode"), + addressBundle?.getString("state"), + ) + defaultBillingDetails = + PaymentSheet.BillingDetails( + address, + billingDetailsBundle.getString("email"), + billingDetailsBundle.getString("name"), + billingDetailsBundle.getString("phone"), + ) } + val configurationBuilder = + PaymentSheet.Configuration + .Builder(merchantDisplayName) + .allowsDelayedPaymentMethods(allowsDelayedPaymentMethods ?: false) + .defaultBillingDetails(defaultBillingDetails) + .customer(customerConfiguration) + .googlePay(googlePayConfig) + .appearance(appearance) + .shippingDetails(shippingDetails) + .billingDetailsCollectionConfiguration(billingDetailsConfig) + .preferredNetworks( + mapToPreferredNetworks(arguments?.getIntegerArrayList("preferredNetworks")), + ).allowsRemovalOfLastSavedPaymentMethod(allowsRemovalOfLastSavedPaymentMethod) + primaryButtonLabel?.let { configurationBuilder.primaryButtonLabel(it) } + paymentMethodOrder?.let { configurationBuilder.paymentMethodOrder(it) } configurationBuilder.paymentMethodLayout( - mapToPaymentMethodLayout(arguments?.getString("paymentMethodLayout")) + mapToPaymentMethodLayout(arguments?.getString("paymentMethodLayout")), ) paymentSheetConfiguration = configurationBuilder.build() if (arguments?.getBoolean("customFlow") == true) { - flowController = if (intentConfiguration != null) { - PaymentSheet.FlowController.create( - this, - paymentOptionCallback = paymentOptionCallback, - createIntentCallback = createIntentCallback, - paymentResultCallback = paymentResultCallback - ) - } else { - PaymentSheet.FlowController.create( - this, - paymentOptionCallback = paymentOptionCallback, - paymentResultCallback = paymentResultCallback - ) - } + flowController = + if (intentConfiguration != null) { + PaymentSheet.FlowController.create( + this, + paymentOptionCallback = paymentOptionCallback, + createIntentCallback = createIntentCallback, + paymentResultCallback = paymentResultCallback, + ) + } else { + PaymentSheet.FlowController.create( + this, + paymentOptionCallback = paymentOptionCallback, + paymentResultCallback = paymentResultCallback, + ) + } configureFlowController() } else { - paymentSheet = if (intentConfiguration != null) { - PaymentSheet( - this, - createIntentCallback = createIntentCallback, - paymentResultCallback = paymentResultCallback - ) - } else { - PaymentSheet( - this, - callback = paymentResultCallback - ) - } + paymentSheet = + if (intentConfiguration != null) { + PaymentSheet( + this, + createIntentCallback = createIntentCallback, + paymentResultCallback = paymentResultCallback, + ) + } else { + PaymentSheet(this, callback = paymentResultCallback) + } initPromise.resolve(WritableNativeMap()) } } fun present(promise: Promise) { this.presentPromise = promise - if(paymentSheet != null) { + if (paymentSheet != null) { if (!paymentIntentClientSecret.isNullOrEmpty()) { - paymentSheet?.presentWithPaymentIntent(paymentIntentClientSecret!!, paymentSheetConfiguration) + paymentSheet?.presentWithPaymentIntent( + paymentIntentClientSecret!!, + paymentSheetConfiguration, + ) } else if (!setupIntentClientSecret.isNullOrEmpty()) { paymentSheet?.presentWithSetupIntent(setupIntentClientSecret!!, paymentSheetConfiguration) } else if (intentConfiguration != null) { paymentSheet?.presentWithIntentConfiguration( intentConfiguration = intentConfiguration!!, - configuration = paymentSheetConfiguration + configuration = paymentSheetConfiguration, ) } - } else if(flowController != null) { + } else if (flowController != null) { flowController?.presentPaymentOptions() } else { promise.resolve(createMissingInitError()) } } - fun presentWithTimeout(timeout: Long, promise: Promise) { + fun presentWithTimeout( + timeout: Long, + promise: Promise, + ) { var paymentSheetActivity: Activity? = null - val activityLifecycleCallbacks = object : Application.ActivityLifecycleCallbacks { - override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { - paymentSheetActivity = activity - } + val activityLifecycleCallbacks = + object : Application.ActivityLifecycleCallbacks { + override fun onActivityCreated( + activity: Activity, + savedInstanceState: Bundle?, + ) { + paymentSheetActivity = activity + } - override fun onActivityStarted(activity: Activity) {} + override fun onActivityStarted(activity: Activity) {} - override fun onActivityResumed(activity: Activity) {} + override fun onActivityResumed(activity: Activity) {} - override fun onActivityPaused(activity: Activity) {} + override fun onActivityPaused(activity: Activity) {} - override fun onActivityStopped(activity: Activity) {} + override fun onActivityStopped(activity: Activity) {} - override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} + override fun onActivitySaveInstanceState( + activity: Activity, + outState: Bundle, + ) {} - override fun onActivityDestroyed(activity: Activity) { - paymentSheetActivity = null - context.currentActivity?.application?.unregisterActivityLifecycleCallbacks(this) + override fun onActivityDestroyed(activity: Activity) { + paymentSheetActivity = null + context.currentActivity?.application?.unregisterActivityLifecycleCallbacks(this) + } } - } - Handler(Looper.getMainLooper()).postDelayed({ - paymentSheetActivity?.let { - it.finish() - paymentSheetTimedOut = true - } - }, timeout) + Handler(Looper.getMainLooper()) + .postDelayed( + { + paymentSheetActivity?.let { + it.finish() + paymentSheetTimedOut = true + } + }, + timeout, + ) - context.currentActivity?.application?.registerActivityLifecycleCallbacks(activityLifecycleCallbacks) + context.currentActivity + ?.application + ?.registerActivityLifecycleCallbacks(activityLifecycleCallbacks) this.present(promise) } @@ -311,40 +380,45 @@ class PaymentSheetFragment( } private fun configureFlowController() { - val onFlowControllerConfigure = PaymentSheet.FlowController.ConfigCallback { _, _ -> - val result = flowController?.getPaymentOption()?.let { - val bitmap = getBitmapFromVectorDrawable(context, it.drawableResourceId) - val imageString = getBase64FromBitmap(bitmap) - val option: WritableMap = WritableNativeMap() - option.putString("label", it.label) - option.putString("image", imageString) - createResult("paymentOption", option) - } ?: run { - WritableNativeMap() + val onFlowControllerConfigure = + PaymentSheet.FlowController.ConfigCallback { _, _ -> + val result = + flowController?.getPaymentOption()?.let { + val bitmap = getBitmapFromVectorDrawable(context, it.drawableResourceId) + val imageString = getBase64FromBitmap(bitmap) + val option: WritableMap = WritableNativeMap() + option.putString("label", it.label) + option.putString("image", imageString) + createResult("paymentOption", option) + } ?: run { WritableNativeMap() } + initPromise.resolve(result) } - initPromise.resolve(result) - } if (!paymentIntentClientSecret.isNullOrEmpty()) { flowController?.configureWithPaymentIntent( paymentIntentClientSecret = paymentIntentClientSecret!!, configuration = paymentSheetConfiguration, - callback = onFlowControllerConfigure + callback = onFlowControllerConfigure, ) } else if (!setupIntentClientSecret.isNullOrEmpty()) { flowController?.configureWithSetupIntent( setupIntentClientSecret = setupIntentClientSecret!!, configuration = paymentSheetConfiguration, - callback = onFlowControllerConfigure + callback = onFlowControllerConfigure, ) } else if (intentConfiguration != null) { flowController?.configureWithIntentConfiguration( intentConfiguration = intentConfiguration!!, configuration = paymentSheetConfiguration, - callback = onFlowControllerConfigure + callback = onFlowControllerConfigure, ) } else { - initPromise.resolve(createError(ErrorType.Failed.toString(), "One of `paymentIntentClientSecret`, `setupIntentClientSecret`, or `intentConfiguration` is required")) + initPromise.resolve( + createError( + ErrorType.Failed.toString(), + "One of `paymentIntentClientSecret`, `setupIntentClientSecret`, or `intentConfiguration` is required", + ), + ) return } } @@ -353,28 +427,29 @@ class PaymentSheetFragment( confirmPromise?.let { it.resolve(map) confirmPromise = null - } ?: run { - presentPromise?.resolve(map) - } + } ?: run { presentPromise?.resolve(map) } } companion object { internal const val TAG = "payment_sheet_launch_fragment" - private val mapIntToButtonType = mapOf( - 1 to PaymentSheet.GooglePayConfiguration.ButtonType.Buy, - 6 to PaymentSheet.GooglePayConfiguration.ButtonType.Book, - 5 to PaymentSheet.GooglePayConfiguration.ButtonType.Checkout, - 4 to PaymentSheet.GooglePayConfiguration.ButtonType.Donate, - 11 to PaymentSheet.GooglePayConfiguration.ButtonType.Order, - 1000 to PaymentSheet.GooglePayConfiguration.ButtonType.Pay, - 7 to PaymentSheet.GooglePayConfiguration.ButtonType.Subscribe, - 1001 to PaymentSheet.GooglePayConfiguration.ButtonType.Plain, - ) + private val mapIntToButtonType = + mapOf( + 1 to PaymentSheet.GooglePayConfiguration.ButtonType.Buy, + 6 to PaymentSheet.GooglePayConfiguration.ButtonType.Book, + 5 to PaymentSheet.GooglePayConfiguration.ButtonType.Checkout, + 4 to PaymentSheet.GooglePayConfiguration.ButtonType.Donate, + 11 to PaymentSheet.GooglePayConfiguration.ButtonType.Order, + 1000 to PaymentSheet.GooglePayConfiguration.ButtonType.Pay, + 7 to PaymentSheet.GooglePayConfiguration.ButtonType.Subscribe, + 1001 to PaymentSheet.GooglePayConfiguration.ButtonType.Plain, + ) - internal fun createMissingInitError(): WritableMap { - return createError(PaymentSheetErrorType.Failed.toString(), "No payment sheet has been initialized yet. You must call `initPaymentSheet` before `presentPaymentSheet`.") - } + internal fun createMissingInitError(): WritableMap = + createError( + PaymentSheetErrorType.Failed.toString(), + "No payment sheet has been initialized yet. You must call `initPaymentSheet` before `presentPaymentSheet`.", + ) internal fun buildGooglePayConfig(params: Bundle?): PaymentSheet.GooglePayConfiguration? { if (params == null) { @@ -386,16 +461,22 @@ class PaymentSheetFragment( val testEnv = params.getBoolean("testEnv") val amount = params.getString("amount")?.toLongOrNull() val label = params.getString("label") - val buttonType = mapIntToButtonType.get(params.getInt("buttonType")) ?: PaymentSheet.GooglePayConfiguration.ButtonType.Pay - + val buttonType = + mapIntToButtonType.get(params.getInt("buttonType")) + ?: PaymentSheet.GooglePayConfiguration.ButtonType.Pay return PaymentSheet.GooglePayConfiguration( - environment = if (testEnv) PaymentSheet.GooglePayConfiguration.Environment.Test else PaymentSheet.GooglePayConfiguration.Environment.Production, + environment = + if (testEnv) { + PaymentSheet.GooglePayConfiguration.Environment.Test + } else { + PaymentSheet.GooglePayConfiguration.Environment.Production + }, countryCode = countryCode, currencyCode = currencyCode, amount = amount, label = label, - buttonType = buttonType + buttonType = buttonType, ) } @@ -404,18 +485,26 @@ class PaymentSheetFragment( if (intentConfigurationParams == null) { return null } - val modeParams = intentConfigurationParams.getBundle("mode") - ?: throw PaymentSheetException("If `intentConfiguration` is provided, `intentConfiguration.mode` is required") + val modeParams = + intentConfigurationParams.getBundle("mode") + ?: throw PaymentSheetException( + "If `intentConfiguration` is provided, `intentConfiguration.mode` is required", + ) return PaymentSheet.IntentConfiguration( mode = buildIntentConfigurationMode(modeParams), - paymentMethodTypes = intentConfigurationParams.getStringArrayList("paymentMethodTypes")?.toList() ?: emptyList(), + paymentMethodTypes = + intentConfigurationParams.getStringArrayList("paymentMethodTypes")?.toList() + ?: emptyList(), ) } private fun buildIntentConfigurationMode(modeParams: Bundle): PaymentSheet.IntentConfiguration.Mode { - val currencyCode = modeParams.getString("currencyCode") - ?: throw PaymentSheetException("You must provide a value to intentConfiguration.mode.currencyCode") + val currencyCode = + modeParams.getString("currencyCode") + ?: throw PaymentSheetException( + "You must provide a value to intentConfiguration.mode.currencyCode", + ) return if (modeParams.containsKey("amount")) { PaymentSheet.IntentConfiguration.Mode.Payment( @@ -425,11 +514,14 @@ class PaymentSheetFragment( captureMethod = mapToCaptureMethod(modeParams.getString("captureMethod")), ) } else { - val setupFutureUsage = mapToSetupFutureUse(modeParams.getString("setupFutureUsage")) - ?: throw PaymentSheetException("You must provide a value to intentConfiguration.mode.setupFutureUsage") + val setupFutureUsage = + mapToSetupFutureUse(modeParams.getString("setupFutureUsage")) + ?: throw PaymentSheetException( + "You must provide a value to intentConfiguration.mode.setupFutureUsage", + ) PaymentSheet.IntentConfiguration.Mode.Setup( currency = currencyCode, - setupFutureUse = setupFutureUsage + setupFutureUse = setupFutureUsage, ) } } @@ -440,25 +532,33 @@ class PaymentSheetFragment( val customerId = bundle?.getString("customerId").orEmpty() val customerEphemeralKeySecret = bundle?.getString("customerEphemeralKeySecret").orEmpty() val customerSessionClientSecret = bundle?.getString("customerSessionClientSecret").orEmpty() - return if (customerSessionClientSecret.isNotEmpty() && customerEphemeralKeySecret.isNotEmpty()) { - throw PaymentSheetException("`customerEphemeralKeySecret` and `customerSessionClientSecret` cannot both be set") + return if (customerSessionClientSecret.isNotEmpty() && + customerEphemeralKeySecret.isNotEmpty() + ) { + throw PaymentSheetException( + "`customerEphemeralKeySecret` and `customerSessionClientSecret` cannot both be set", + ) } else if (customerId.isNotEmpty() && customerSessionClientSecret.isNotEmpty()) { PaymentSheet.CustomerConfiguration.createWithCustomerSession( id = customerId, - clientSecret = customerSessionClientSecret + clientSecret = customerSessionClientSecret, ) - } - else if (customerId.isNotEmpty() && customerEphemeralKeySecret.isNotEmpty()) { + } else if (customerId.isNotEmpty() && customerEphemeralKeySecret.isNotEmpty()) { PaymentSheet.CustomerConfiguration( id = customerId, - ephemeralKeySecret = customerEphemeralKeySecret + ephemeralKeySecret = customerEphemeralKeySecret, ) - } else null + } else { + null + } } } } -fun getBitmapFromVectorDrawable(context: Context?, drawableId: Int): Bitmap? { +fun getBitmapFromVectorDrawable( + context: Context?, + drawableId: Int, +): Bitmap? { val drawable = AppCompatResources.getDrawable(context!!, drawableId) ?: return null return getBitmapFromDrawable(drawable) } @@ -468,7 +568,12 @@ fun getBitmapFromDrawable(drawable: Drawable): Bitmap? { if (drawableCompat.intrinsicWidth <= 0 || drawableCompat.intrinsicHeight <= 0) { return null } - val bitmap = Bitmap.createBitmap(drawableCompat.intrinsicWidth, drawableCompat.intrinsicHeight, Bitmap.Config.ARGB_8888) + val bitmap = + Bitmap.createBitmap( + drawableCompat.intrinsicWidth, + drawableCompat.intrinsicHeight, + Bitmap.Config.ARGB_8888, + ) bitmap.eraseColor(Color.WHITE) val canvas = Canvas(bitmap) drawable.setBounds(0, 0, canvas.width, canvas.height) @@ -486,45 +591,41 @@ fun getBase64FromBitmap(bitmap: Bitmap?): String? { return Base64.encodeToString(imageBytes, Base64.DEFAULT) } -fun mapToCollectionMode(str: String?): PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode { - return when (str) { +fun mapToCollectionMode(str: String?): PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode = + when (str) { "automatic" -> PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode.Automatic "never" -> PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode.Never "always" -> PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode.Always else -> PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode.Automatic } -} -fun mapToPaymentMethodLayout(str: String?): PaymentSheet.PaymentMethodLayout { - return when (str) { +fun mapToPaymentMethodLayout(str: String?): PaymentSheet.PaymentMethodLayout = + when (str) { "Horizontal" -> PaymentSheet.PaymentMethodLayout.Horizontal "Vertical" -> PaymentSheet.PaymentMethodLayout.Vertical else -> PaymentSheet.PaymentMethodLayout.Automatic } -} -fun mapToAddressCollectionMode(str: String?): PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode { - return when (str) { - "automatic" -> PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode.Automatic +fun mapToAddressCollectionMode(str: String?): PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode = + when (str) { + "automatic" -> + PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode.Automatic "never" -> PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode.Never "full" -> PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode.Full else -> PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode.Automatic } -} -fun mapToSetupFutureUse(type: String?): PaymentSheet.IntentConfiguration.SetupFutureUse? { - return when (type) { - "OffSession" -> PaymentSheet.IntentConfiguration.SetupFutureUse.OffSession - "OnSession" -> PaymentSheet.IntentConfiguration.SetupFutureUse.OnSession - else -> null +fun mapToSetupFutureUse(type: String?): PaymentSheet.IntentConfiguration.SetupFutureUse? = + when (type) { + "OffSession" -> PaymentSheet.IntentConfiguration.SetupFutureUse.OffSession + "OnSession" -> PaymentSheet.IntentConfiguration.SetupFutureUse.OnSession + else -> null } -} -fun mapToCaptureMethod(type: String?): PaymentSheet.IntentConfiguration.CaptureMethod { - return when (type) { - "Automatic" -> PaymentSheet.IntentConfiguration.CaptureMethod.Automatic - "Manual" -> PaymentSheet.IntentConfiguration.CaptureMethod.Manual - "AutomaticAsync" -> PaymentSheet.IntentConfiguration.CaptureMethod.AutomaticAsync - else -> PaymentSheet.IntentConfiguration.CaptureMethod.Automatic +fun mapToCaptureMethod(type: String?): PaymentSheet.IntentConfiguration.CaptureMethod = + when (type) { + "Automatic" -> PaymentSheet.IntentConfiguration.CaptureMethod.Automatic + "Manual" -> PaymentSheet.IntentConfiguration.CaptureMethod.Manual + "AutomaticAsync" -> PaymentSheet.IntentConfiguration.CaptureMethod.AutomaticAsync + else -> PaymentSheet.IntentConfiguration.CaptureMethod.Automatic } -} diff --git a/android/src/main/java/com/reactnativestripesdk/StripeContainerManager.kt b/android/src/main/java/com/reactnativestripesdk/StripeContainerManager.kt index 8d9efcef3..889eae16a 100644 --- a/android/src/main/java/com/reactnativestripesdk/StripeContainerManager.kt +++ b/android/src/main/java/com/reactnativestripesdk/StripeContainerManager.kt @@ -8,11 +8,12 @@ class StripeContainerManager : ViewGroupManager() { override fun getName() = "StripeContainer" @ReactProp(name = "keyboardShouldPersistTaps") - fun setKeyboardShouldPersistTaps(view: StripeContainerView, keyboardShouldPersistTaps: Boolean) { + fun setKeyboardShouldPersistTaps( + view: StripeContainerView, + keyboardShouldPersistTaps: Boolean, + ) { view.setKeyboardShouldPersistTaps(keyboardShouldPersistTaps) } - override fun createViewInstance(reactContext: ThemedReactContext): StripeContainerView { - return StripeContainerView(reactContext) - } + override fun createViewInstance(reactContext: ThemedReactContext): StripeContainerView = StripeContainerView(reactContext) } diff --git a/android/src/main/java/com/reactnativestripesdk/StripeContainerView.kt b/android/src/main/java/com/reactnativestripesdk/StripeContainerView.kt index f7bf35fcb..b796519be 100644 --- a/android/src/main/java/com/reactnativestripesdk/StripeContainerView.kt +++ b/android/src/main/java/com/reactnativestripesdk/StripeContainerView.kt @@ -8,7 +8,9 @@ import android.widget.EditText import android.widget.FrameLayout import com.facebook.react.uimanager.ThemedReactContext -class StripeContainerView(private val context: ThemedReactContext) : FrameLayout(context) { +class StripeContainerView( + private val context: ThemedReactContext, +) : FrameLayout(context) { private var keyboardShouldPersistTapsValue: Boolean = true init { diff --git a/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt b/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt index cd594fc02..a94d857dc 100644 --- a/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +++ b/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt @@ -2,20 +2,63 @@ package com.reactnativestripesdk import android.app.Activity import android.content.Intent -import android.os.Parcelable import android.util.Log import androidx.fragment.app.FragmentActivity -import com.facebook.react.bridge.* +import com.facebook.react.bridge.BaseActivityEventListener +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.WritableNativeMap import com.facebook.react.module.annotations.ReactModule import com.facebook.react.modules.core.DeviceEventManagerModule import com.reactnativestripesdk.addresssheet.AddressLauncherFragment import com.reactnativestripesdk.pushprovisioning.PushProvisioningProxy -import com.reactnativestripesdk.utils.* -import com.stripe.android.* +import com.reactnativestripesdk.utils.ConfirmPaymentErrorType +import com.reactnativestripesdk.utils.CreateTokenErrorType +import com.reactnativestripesdk.utils.ErrorType +import com.reactnativestripesdk.utils.GooglePayErrorType +import com.reactnativestripesdk.utils.createCanAddCardResult +import com.reactnativestripesdk.utils.createError +import com.reactnativestripesdk.utils.createMissingActivityError +import com.reactnativestripesdk.utils.createMissingInitError +import com.reactnativestripesdk.utils.createResult +import com.reactnativestripesdk.utils.getBooleanOr +import com.reactnativestripesdk.utils.getBooleanOrFalse +import com.reactnativestripesdk.utils.getMapOrNull +import com.reactnativestripesdk.utils.getValOr +import com.reactnativestripesdk.utils.mapFromPaymentIntentResult +import com.reactnativestripesdk.utils.mapFromPaymentMethod +import com.reactnativestripesdk.utils.mapFromSetupIntentResult +import com.reactnativestripesdk.utils.mapFromToken +import com.reactnativestripesdk.utils.mapToAddress +import com.reactnativestripesdk.utils.mapToBankAccountType +import com.reactnativestripesdk.utils.mapToPaymentMethodType +import com.reactnativestripesdk.utils.mapToReturnURL +import com.reactnativestripesdk.utils.mapToShippingDetails +import com.reactnativestripesdk.utils.mapToUICustomization +import com.reactnativestripesdk.utils.removeFragment +import com.reactnativestripesdk.utils.toBundleObject +import com.stripe.android.ApiResultCallback +import com.stripe.android.GooglePayJsonFactory +import com.stripe.android.PaymentAuthConfig +import com.stripe.android.PaymentConfiguration +import com.stripe.android.Stripe import com.stripe.android.core.ApiVersion import com.stripe.android.core.AppInfo import com.stripe.android.googlepaylauncher.GooglePayLauncher -import com.stripe.android.model.* +import com.stripe.android.model.BankAccountTokenParams +import com.stripe.android.model.CardParams +import com.stripe.android.model.ConfirmPaymentIntentParams +import com.stripe.android.model.ConfirmSetupIntentParams +import com.stripe.android.model.PaymentIntent +import com.stripe.android.model.PaymentMethod +import com.stripe.android.model.SetupIntent +import com.stripe.android.model.Token import com.stripe.android.payments.bankaccount.CollectBankAccountConfiguration import com.stripe.android.paymentsheet.PaymentSheet import kotlinx.coroutines.CoroutineScope @@ -23,12 +66,11 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.json.JSONObject - @ReactModule(name = StripeSdkModule.NAME) -class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { - override fun getName(): String { - return "StripeSdk" - } +class StripeSdkModule( + reactContext: ReactApplicationContext, +) : ReactContextBaseJavaModule(reactContext) { + override fun getName(): String = "StripeSdk" var cardFieldView: CardFieldView? = null var cardFormView: CardFormView? = null @@ -38,8 +80,6 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ private var stripeAccountId: String? = null private var urlScheme: String? = null - private var confirmPromise: Promise? = null - private var confirmPaymentClientSecret: String? = null private var createPlatformPayPaymentMethodPromise: Promise? = null private var platformPayUsesDeprecatedTokenFlow = false @@ -54,40 +94,63 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ // If you create a new Fragment, you must put the tag here, otherwise result callbacks for that // Fragment will not work on RN < 0.65 private val allStripeFragmentTags: List - get() = listOf( - PaymentSheetFragment.TAG, - PaymentLauncherFragment.TAG, - CollectBankAccountLauncherFragment.TAG, - FinancialConnectionsSheetFragment.TAG, - AddressLauncherFragment.TAG, - GooglePayLauncherFragment.TAG, - CustomerSheetFragment.TAG - ) + get() = + listOf( + PaymentSheetFragment.TAG, + PaymentLauncherFragment.TAG, + CollectBankAccountLauncherFragment.TAG, + FinancialConnectionsSheetFragment.TAG, + AddressLauncherFragment.TAG, + GooglePayLauncherFragment.TAG, + CustomerSheetFragment.TAG, + ) - private val mActivityEventListener = object : BaseActivityEventListener() { - override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) { - if (::stripe.isInitialized) { - when (requestCode) { - GooglePayRequestHelper.LOAD_PAYMENT_DATA_REQUEST_CODE -> { - createPlatformPayPaymentMethodPromise?.let { - GooglePayRequestHelper.handleGooglePaymentMethodResult(resultCode, data, stripe, platformPayUsesDeprecatedTokenFlow, it) - createPlatformPayPaymentMethodPromise = null - } ?: run { Log.d("StripeReactNative", "No promise was found, Google Pay result went unhandled,") } - } - else -> { - dispatchActivityResultsToFragments(requestCode, resultCode, data) + private val mActivityEventListener = + object : BaseActivityEventListener() { + override fun onActivityResult( + activity: Activity, + requestCode: Int, + resultCode: Int, + data: Intent?, + ) { + if (::stripe.isInitialized) { + when (requestCode) { + GooglePayRequestHelper.LOAD_PAYMENT_DATA_REQUEST_CODE -> { + createPlatformPayPaymentMethodPromise?.let { + GooglePayRequestHelper.handleGooglePaymentMethodResult( + resultCode, + data, + stripe, + platformPayUsesDeprecatedTokenFlow, + it, + ) + createPlatformPayPaymentMethodPromise = null + } + ?: run { + Log.d( + "StripeReactNative", + "No promise was found, Google Pay result went unhandled,", + ) + } + } + else -> { + dispatchActivityResultsToFragments(requestCode, resultCode, data) + } } } } } - } init { reactContext.addActivityEventListener(mActivityEventListener) } // Necessary on older versions of React Native (~0.65 and below) - private fun dispatchActivityResultsToFragments(requestCode: Int, resultCode: Int, data: Intent?) { + private fun dispatchActivityResultsToFragments( + requestCode: Int, + resultCode: Int, + data: Intent?, + ) { getCurrentActivityOrResolveWithError(null)?.supportFragmentManager?.let { fragmentManager -> for (tag in allStripeFragmentTags) { fragmentManager.findFragmentByTag(tag)?.let { @@ -103,26 +166,27 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ val uiCustomization = mapToUICustomization(params) PaymentAuthConfig.init( - PaymentAuthConfig.Builder() - .set3ds2Config( - stripe3dsConfigBuilder - .setUiCustomization(uiCustomization) - .build() - ) - .build() + PaymentAuthConfig + .Builder() + .set3ds2Config(stripe3dsConfigBuilder.setUiCustomization(uiCustomization).build()) + .build(), ) } override fun getConstants(): MutableMap = hashMapOf( - "API_VERSIONS" to hashMapOf( - "CORE" to ApiVersion.API_VERSION_CODE, - "ISSUING" to PushProvisioningProxy.getApiVersion(), - ) + "API_VERSIONS" to + hashMapOf( + "CORE" to ApiVersion.API_VERSION_CODE, + "ISSUING" to PushProvisioningProxy.getApiVersion(), + ), ) @ReactMethod - fun initialise(params: ReadableMap, promise: Promise) { + fun initialise( + params: ReadableMap, + promise: Promise, + ) { val publishableKey = getValOr(params, "publishableKey", null) as String val appInfo = getMapOrNull(params, "appInfo") as ReadableMap this.stripeAccountId = getValOr(params, "stripeAccountId", null) @@ -130,9 +194,7 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ val setReturnUrlSchemeOnAndroid = getBooleanOrFalse(params, "setReturnUrlSchemeOnAndroid") this.urlScheme = if (setReturnUrlSchemeOnAndroid) urlScheme else null - getMapOrNull(params, "threeDSecureParams")?.let { - configure3dSecure(it) - } + getMapOrNull(params, "threeDSecureParams")?.let { configure3dSecure(it) } this.publishableKey = publishableKey AddressLauncherFragment.publishableKey = publishableKey @@ -150,15 +212,20 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ } @ReactMethod - fun initPaymentSheet(params: ReadableMap, promise: Promise) { + fun initPaymentSheet( + params: ReadableMap, + promise: Promise, + ) { getCurrentActivityOrResolveWithError(promise)?.let { activity -> paymentSheetFragment?.removeFragment(reactApplicationContext) - paymentSheetFragment = PaymentSheetFragment(reactApplicationContext, promise).also { - val bundle = toBundleObject(params) - it.arguments = bundle - } + paymentSheetFragment = + PaymentSheetFragment(reactApplicationContext, promise).also { + val bundle = toBundleObject(params) + it.arguments = bundle + } try { - activity.supportFragmentManager.beginTransaction() + activity.supportFragmentManager + .beginTransaction() .add(paymentSheetFragment!!, PaymentSheetFragment.TAG) .commit() } catch (error: IllegalStateException) { @@ -168,7 +235,10 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ } @ReactMethod - fun presentPaymentSheet(options: ReadableMap, promise: Promise) { + fun presentPaymentSheet( + options: ReadableMap, + promise: Promise, + ) { if (paymentSheetFragment == null) { promise.resolve(PaymentSheetFragment.createMissingInitError()) return @@ -176,9 +246,7 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ val timeoutKey = "timeout" if (options.hasKey(timeoutKey)) { - paymentSheetFragment?.presentWithTimeout( - options.getInt(timeoutKey).toLong(), promise - ) + paymentSheetFragment?.presentWithTimeout(options.getInt(timeoutKey).toLong(), promise) } else { paymentSheetFragment?.present(promise) } @@ -201,7 +269,10 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ } @ReactMethod - fun intentCreationCallback(params: ReadableMap, promise: Promise) { + fun intentCreationCallback( + params: ReadableMap, + promise: Promise, + ) { if (paymentSheetFragment == null) { promise.resolve(PaymentSheetFragment.createMissingInitError()) return @@ -211,27 +282,40 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ } @ReactMethod - fun createPaymentMethod(data: ReadableMap, options: ReadableMap, promise: Promise) { - val paymentMethodType = getValOr(data, "paymentMethodType")?.let { mapToPaymentMethodType(it) } ?: run { - promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), "You must provide paymentMethodType")) - return - } + fun createPaymentMethod( + data: ReadableMap, + options: ReadableMap, + promise: Promise, + ) { + val paymentMethodType = + getValOr(data, "paymentMethodType")?.let { mapToPaymentMethodType(it) } + ?: run { + promise.resolve( + createError( + ConfirmPaymentErrorType.Failed.toString(), + "You must provide paymentMethodType", + ), + ) + return + } val paymentMethodData = getMapOrNull(data, "paymentMethodData") - val factory = PaymentMethodCreateParamsFactory(paymentMethodData, options, cardFieldView, cardFormView) + val factory = + PaymentMethodCreateParamsFactory(paymentMethodData, options, cardFieldView, cardFormView) try { val paymentMethodCreateParams = factory.createPaymentMethodParams(paymentMethodType) stripe.createPaymentMethod( paymentMethodCreateParams, - callback = object : ApiResultCallback { - override fun onError(e: Exception) { - promise.resolve(createError("Failed", e)) - } + callback = + object : ApiResultCallback { + override fun onError(e: Exception) { + promise.resolve(createError("Failed", e)) + } - override fun onSuccess(result: PaymentMethod) { - val paymentMethodMap: WritableMap = mapFromPaymentMethod(result) - promise.resolve(createResult("paymentMethod", paymentMethodMap)) - } - } + override fun onSuccess(result: PaymentMethod) { + val paymentMethodMap: WritableMap = mapFromPaymentMethod(result) + promise.resolve(createResult("paymentMethod", paymentMethodMap)) + } + }, ) } catch (error: PaymentMethodCreateParamsException) { promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), error)) @@ -239,10 +323,15 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ } @ReactMethod - fun createToken(params: ReadableMap, promise: Promise) { + fun createToken( + params: ReadableMap, + promise: Promise, + ) { val type = getValOr(params, "type", null) if (type == null) { - promise.resolve(createError(CreateTokenErrorType.Failed.toString(), "type parameter is required")) + promise.resolve( + createError(CreateTokenErrorType.Failed.toString(), "type parameter is required"), + ) return } @@ -257,12 +346,17 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ createTokenFromPii(params, promise) } else -> { - promise.resolve(createError(CreateTokenErrorType.Failed.toString(), "$type type is not supported yet")) + promise.resolve( + createError(CreateTokenErrorType.Failed.toString(), "$type type is not supported yet"), + ) } } } - private fun createTokenFromPii(params: ReadableMap, promise: Promise) { + private fun createTokenFromPii( + params: ReadableMap, + promise: Promise, + ) { getValOr(params, "personalId", null)?.let { CoroutineScope(Dispatchers.IO).launch { runCatching { @@ -272,12 +366,21 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ promise.resolve(createError(CreateTokenErrorType.Failed.toString(), it.message)) } } - } ?: run { - promise.resolve(createError(CreateTokenErrorType.Failed.toString(), "personalId parameter is required")) } + ?: run { + promise.resolve( + createError( + CreateTokenErrorType.Failed.toString(), + "personalId parameter is required", + ), + ) + } } - private fun createTokenFromBankAccount(params: ReadableMap, promise: Promise) { + private fun createTokenFromBankAccount( + params: ReadableMap, + promise: Promise, + ) { val accountHolderName = getValOr(params, "accountHolderName", null) val accountHolderType = getValOr(params, "accountHolderType", null) val accountNumber = getValOr(params, "accountNumber", null) @@ -285,14 +388,15 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ val currency = getValOr(params, "currency", null) val routingNumber = getValOr(params, "routingNumber", null) - val bankAccountParams = BankAccountTokenParams( - country = country!!, - currency = currency!!, - accountNumber = accountNumber!!, - accountHolderName = accountHolderName, - routingNumber = routingNumber, - accountHolderType = mapToBankAccountType(accountHolderType) - ) + val bankAccountParams = + BankAccountTokenParams( + country = country!!, + currency = currency!!, + accountNumber = accountNumber!!, + accountHolderName = accountHolderName, + routingNumber = routingNumber, + accountHolderType = mapToBankAccountType(accountHolderType), + ) CoroutineScope(Dispatchers.IO).launch { runCatching { val token = stripe.createBankAccountToken(bankAccountParams, null, stripeAccountId) @@ -301,34 +405,38 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ promise.resolve(createError(CreateTokenErrorType.Failed.toString(), it.message)) } } - } - private fun createTokenFromCard(params: ReadableMap, promise: Promise) { - val cardParamsMap = (cardFieldView?.cardParams ?: cardFormView?.cardParams)?.toParamMap() - ?: run { - promise.resolve(createError(CreateTokenErrorType.Failed.toString(), "Card details not complete")) - return - } + private fun createTokenFromCard( + params: ReadableMap, + promise: Promise, + ) { + val cardParamsMap = + (cardFieldView?.cardParams ?: cardFormView?.cardParams)?.toParamMap() + ?: run { + promise.resolve( + createError(CreateTokenErrorType.Failed.toString(), "Card details not complete"), + ) + return + } val cardAddress = cardFieldView?.cardAddress ?: cardFormView?.cardAddress val address = getMapOrNull(params, "address") - val cardParams = CardParams( - number = cardParamsMap["number"] as String, - expMonth = cardParamsMap["exp_month"] as Int, - expYear = cardParamsMap["exp_year"] as Int, - cvc = cardParamsMap["cvc"] as String, - address = mapToAddress(address, cardAddress), - name = getValOr(params, "name", null), - currency = getValOr(params, "currency", null), - ) + val cardParams = + CardParams( + number = cardParamsMap["number"] as String, + expMonth = cardParamsMap["exp_month"] as Int, + expYear = cardParamsMap["exp_year"] as Int, + cvc = cardParamsMap["cvc"] as String, + address = mapToAddress(address, cardAddress), + name = getValOr(params, "name", null), + currency = getValOr(params, "currency", null), + ) CoroutineScope(Dispatchers.IO).launch { try { - val token = stripe.createCardToken( - cardParams = cardParams, - stripeAccountId = stripeAccountId - ) + val token = + stripe.createCardToken(cardParams = cardParams, stripeAccountId = stripeAccountId) promise.resolve(createResult("token", mapFromToken(token))) } catch (e: Exception) { promise.resolve(createError(CreateTokenErrorType.Failed.toString(), e.message)) @@ -337,112 +445,144 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ } @ReactMethod - fun createTokenForCVCUpdate(cvc: String, promise: Promise) { + fun createTokenForCVCUpdate( + cvc: String, + promise: Promise, + ) { stripe.createCvcUpdateToken( cvc, - callback = object : ApiResultCallback { - override fun onSuccess(result: Token) { - val tokenId = result.id - val res = WritableNativeMap() - res.putString("tokenId", tokenId) - promise.resolve(res) - } + callback = + object : ApiResultCallback { + override fun onSuccess(result: Token) { + val tokenId = result.id + val res = WritableNativeMap() + res.putString("tokenId", tokenId) + promise.resolve(res) + } - override fun onError(e: Exception) { - promise.resolve(createError("Failed", e)) - } - } + override fun onError(e: Exception) { + promise.resolve(createError("Failed", e)) + } + }, ) } @ReactMethod - fun handleNextAction(paymentIntentClientSecret: String, promise: Promise) { - paymentLauncherFragment = PaymentLauncherFragment.forNextActionPayment( - context = reactApplicationContext, - stripe, - publishableKey, - stripeAccountId, - promise, - paymentIntentClientSecret - ) + fun handleNextAction( + paymentIntentClientSecret: String, + promise: Promise, + ) { + paymentLauncherFragment = + PaymentLauncherFragment.forNextActionPayment( + context = reactApplicationContext, + stripe, + publishableKey, + stripeAccountId, + promise, + paymentIntentClientSecret, + ) } @ReactMethod - fun handleNextActionForSetup(setupIntentClientSecret: String, promise: Promise) { - paymentLauncherFragment = PaymentLauncherFragment.forNextActionSetup( - context = reactApplicationContext, - stripe, - publishableKey, - stripeAccountId, - promise, - setupIntentClientSecret - ) + fun handleNextActionForSetup( + setupIntentClientSecret: String, + promise: Promise, + ) { + paymentLauncherFragment = + PaymentLauncherFragment.forNextActionSetup( + context = reactApplicationContext, + stripe, + publishableKey, + stripeAccountId, + promise, + setupIntentClientSecret, + ) } -// TODO: Uncomment when WeChat is re-enabled in stripe-ios -// private fun payWithWeChatPay(paymentIntentClientSecret: String, appId: String) { -// val activity = currentActivity as ComponentActivity -// -// activity.lifecycleScope.launch { -// stripe.createPaymentMethod(PaymentMethodCreateParams.createWeChatPay()).id?.let { paymentMethodId -> -// val confirmPaymentIntentParams = -// ConfirmPaymentIntentParams.createWithPaymentMethodId( -// paymentMethodId = paymentMethodId, -// clientSecret = paymentIntentClientSecret, -// paymentMethodOptions = PaymentMethodOptionsParams.WeChatPay( -// appId -// ) -// ) -// paymentLauncherFragment.paymentLauncher.confirm(confirmPaymentIntentParams) -// } -// } -// } - - @ReactMethod - fun confirmPayment(paymentIntentClientSecret: String, params: ReadableMap?, options: ReadableMap, promise: Promise) { + // TODO: Uncomment when WeChat is re-enabled in stripe-ios + // private fun payWithWeChatPay(paymentIntentClientSecret: String, appId: String) { + // val activity = currentActivity as ComponentActivity + // + // activity.lifecycleScope.launch { + // stripe.createPaymentMethod(PaymentMethodCreateParams.createWeChatPay()).id?.let { + // paymentMethodId -> + // val confirmPaymentIntentParams = + // ConfirmPaymentIntentParams.createWithPaymentMethodId( + // paymentMethodId = paymentMethodId, + // clientSecret = paymentIntentClientSecret, + // paymentMethodOptions = PaymentMethodOptionsParams.WeChatPay( + // appId + // ) + // ) + // paymentLauncherFragment.paymentLauncher.confirm(confirmPaymentIntentParams) + // } + // } + // } + + @ReactMethod + fun confirmPayment( + paymentIntentClientSecret: String, + params: ReadableMap?, + options: ReadableMap, + promise: Promise, + ) { val paymentMethodData = getMapOrNull(params, "paymentMethodData") - val paymentMethodType = if (params != null) - mapToPaymentMethodType(params.getString("paymentMethodType")) ?: run { - promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), "You must provide paymentMethodType")) - return + val paymentMethodType = + if (params != null) { + mapToPaymentMethodType(params.getString("paymentMethodType")) + ?: run { + promise.resolve( + createError( + ConfirmPaymentErrorType.Failed.toString(), + "You must provide paymentMethodType", + ), + ) + return + } + } else { + null // Expect that payment method was attached on the server } - else - null // Expect that payment method was attached on the server -// if (paymentMethodType == PaymentMethod.Type.WeChatPay) { -// val appId = getValOr(params, "appId") ?: run { -// promise.resolve(createError("Failed", "You must provide appId")) -// return -// } -// payWithWeChatPay(paymentIntentClientSecret, appId, promise) -// -// return -// } + // if (paymentMethodType == PaymentMethod.Type.WeChatPay) { + // val appId = getValOr(params, "appId") ?: run { + // promise.resolve(createError("Failed", "You must provide appId")) + // return + // } + // payWithWeChatPay(paymentIntentClientSecret, appId, promise) + // + // return + // } - val factory = PaymentMethodCreateParamsFactory(paymentMethodData, options, cardFieldView, cardFormView) + val factory = + PaymentMethodCreateParamsFactory(paymentMethodData, options, cardFieldView, cardFormView) try { - val confirmParams = factory.createParams(paymentIntentClientSecret, paymentMethodType, isPaymentIntent = true) as ConfirmPaymentIntentParams - urlScheme?.let { - confirmParams.returnUrl = mapToReturnURL(urlScheme) - } - confirmParams.shipping = mapToShippingDetails(getMapOrNull(paymentMethodData, "shippingDetails")) - paymentLauncherFragment = PaymentLauncherFragment.forPayment( - context = reactApplicationContext, - stripe, - publishableKey, - stripeAccountId, - promise, - paymentIntentClientSecret, - confirmParams - ) + val confirmParams = + factory.createParams(paymentIntentClientSecret, paymentMethodType, isPaymentIntent = true) + as ConfirmPaymentIntentParams + urlScheme?.let { confirmParams.returnUrl = mapToReturnURL(urlScheme) } + confirmParams.shipping = + mapToShippingDetails(getMapOrNull(paymentMethodData, "shippingDetails")) + paymentLauncherFragment = + PaymentLauncherFragment.forPayment( + context = reactApplicationContext, + stripe, + publishableKey, + stripeAccountId, + promise, + paymentIntentClientSecret, + confirmParams, + ) } catch (error: PaymentMethodCreateParamsException) { promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), error)) } } @ReactMethod - fun retrievePaymentIntent(clientSecret: String, promise: Promise) { + fun retrievePaymentIntent( + clientSecret: String, + promise: Promise, + ) { CoroutineScope(Dispatchers.IO).launch { val paymentIntent = stripe.retrievePaymentIntentSynchronous(clientSecret) promise.resolve(createResult("paymentIntent", mapFromPaymentIntentResult(paymentIntent))) @@ -450,7 +590,10 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ } @ReactMethod - fun retrieveSetupIntent(clientSecret: String, promise: Promise) { + fun retrieveSetupIntent( + clientSecret: String, + promise: Promise, + ) { CoroutineScope(Dispatchers.IO).launch { val setupIntent = stripe.retrieveSetupIntentSynchronous(clientSecret) promise.resolve(createResult("setupIntent", mapFromSetupIntentResult(setupIntent))) @@ -458,46 +601,70 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ } @ReactMethod - fun confirmSetupIntent(setupIntentClientSecret: String, params: ReadableMap, options: ReadableMap, promise: Promise) { - val paymentMethodType = getValOr(params, "paymentMethodType")?.let { mapToPaymentMethodType(it) } ?: run { - promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), "You must provide paymentMethodType")) - return - } + fun confirmSetupIntent( + setupIntentClientSecret: String, + params: ReadableMap, + options: ReadableMap, + promise: Promise, + ) { + val paymentMethodType = + getValOr(params, "paymentMethodType")?.let { mapToPaymentMethodType(it) } + ?: run { + promise.resolve( + createError( + ConfirmPaymentErrorType.Failed.toString(), + "You must provide paymentMethodType", + ), + ) + return + } - val factory = PaymentMethodCreateParamsFactory(getMapOrNull(params, "paymentMethodData"), options, cardFieldView, cardFormView) + val factory = + PaymentMethodCreateParamsFactory( + getMapOrNull(params, "paymentMethodData"), + options, + cardFieldView, + cardFormView, + ) try { - val confirmParams = factory.createParams(setupIntentClientSecret, paymentMethodType, isPaymentIntent = false) as ConfirmSetupIntentParams - urlScheme?.let { - confirmParams.returnUrl = mapToReturnURL(urlScheme) - } - paymentLauncherFragment = PaymentLauncherFragment.forSetup( - context = reactApplicationContext, - stripe, - publishableKey, - stripeAccountId, - promise, - setupIntentClientSecret, - confirmParams - ) + val confirmParams = + factory.createParams(setupIntentClientSecret, paymentMethodType, isPaymentIntent = false) + as ConfirmSetupIntentParams + urlScheme?.let { confirmParams.returnUrl = mapToReturnURL(urlScheme) } + paymentLauncherFragment = + PaymentLauncherFragment.forSetup( + context = reactApplicationContext, + stripe, + publishableKey, + stripeAccountId, + promise, + setupIntentClientSecret, + confirmParams, + ) } catch (error: PaymentMethodCreateParamsException) { promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), error)) } } @ReactMethod - fun isPlatformPaySupported(params: ReadableMap?, promise: Promise) { + fun isPlatformPaySupported( + params: ReadableMap?, + promise: Promise, + ) { val googlePayParams = params?.getMap("googlePay") - val fragment = GooglePayPaymentMethodLauncherFragment( - reactApplicationContext, - getBooleanOrFalse(googlePayParams, "testEnv"), - getBooleanOrFalse(googlePayParams, "existingPaymentMethodRequired"), - promise - ) + val fragment = + GooglePayPaymentMethodLauncherFragment( + reactApplicationContext, + getBooleanOrFalse(googlePayParams, "testEnv"), + getBooleanOrFalse(googlePayParams, "existingPaymentMethodRequired"), + promise, + ) getCurrentActivityOrResolveWithError(promise)?.let { try { - it.supportFragmentManager.beginTransaction() + it.supportFragmentManager + .beginTransaction() .add(fragment, GooglePayPaymentMethodLauncherFragment.TAG) .commit() } catch (error: IllegalStateException) { @@ -507,23 +674,39 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ } @ReactMethod - fun confirmPlatformPay(clientSecret: String, params: ReadableMap, isPaymentIntent: Boolean, promise: Promise) { + fun confirmPlatformPay( + clientSecret: String, + params: ReadableMap, + isPaymentIntent: Boolean, + promise: Promise, + ) { if (!::stripe.isInitialized) { promise.resolve(createMissingInitError()) return } - val googlePayParams: ReadableMap = params.getMap("googlePay") ?: run { - promise.resolve(createError(GooglePayErrorType.Failed.toString(), "You must provide the `googlePay` parameter.")) - return - } + val googlePayParams: ReadableMap = + params.getMap("googlePay") + ?: run { + promise.resolve( + createError( + GooglePayErrorType.Failed.toString(), + "You must provide the `googlePay` parameter.", + ), + ) + return + } GooglePayLauncherFragment().also { it.presentGooglePaySheet( clientSecret, - if (isPaymentIntent) GooglePayLauncherFragment.Mode.ForPayment else GooglePayLauncherFragment.Mode.ForSetup, + if (isPaymentIntent) { + GooglePayLauncherFragment.Mode.ForPayment + } else { + GooglePayLauncherFragment.Mode.ForSetup + }, googlePayParams, - reactApplicationContext + reactApplicationContext, ) { launcherResult, errorMap -> if (errorMap != null) { promise.resolve(errorMap) @@ -531,30 +714,53 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ when (launcherResult) { GooglePayLauncher.Result.Completed -> { if (isPaymentIntent) { - stripe.retrievePaymentIntent(clientSecret, stripeAccountId, expand = listOf("payment_method"), object : ApiResultCallback { - override fun onError(e: Exception) { - promise.resolve(createResult("paymentIntent", WritableNativeMap())) - } - override fun onSuccess(result: PaymentIntent) { - promise.resolve(createResult("paymentIntent", mapFromPaymentIntentResult(result))) - } - }) + stripe.retrievePaymentIntent( + clientSecret, + stripeAccountId, + expand = listOf("payment_method"), + object : ApiResultCallback { + override fun onError(e: Exception) { + promise.resolve(createResult("paymentIntent", WritableNativeMap())) + } + + override fun onSuccess(result: PaymentIntent) { + promise.resolve( + createResult("paymentIntent", mapFromPaymentIntentResult(result)), + ) + } + }, + ) } else { - stripe.retrieveSetupIntent(clientSecret, stripeAccountId, expand = listOf("payment_method"), object : ApiResultCallback { - override fun onError(e: Exception) { - promise.resolve(createResult("setupIntent", WritableNativeMap())) - } - override fun onSuccess(result: SetupIntent) { - promise.resolve(createResult("setupIntent", mapFromSetupIntentResult(result))) - } - }) + stripe.retrieveSetupIntent( + clientSecret, + stripeAccountId, + expand = listOf("payment_method"), + object : ApiResultCallback { + override fun onError(e: Exception) { + promise.resolve(createResult("setupIntent", WritableNativeMap())) + } + + override fun onSuccess(result: SetupIntent) { + promise.resolve( + createResult("setupIntent", mapFromSetupIntentResult(result)), + ) + } + }, + ) } } GooglePayLauncher.Result.Canceled -> { - promise.resolve(createError(GooglePayErrorType.Canceled.toString(), "Google Pay has been canceled")) + promise.resolve( + createError( + GooglePayErrorType.Canceled.toString(), + "Google Pay has been canceled", + ), + ) } is GooglePayLauncher.Result.Failed -> { - promise.resolve(createError(GooglePayErrorType.Failed.toString(), launcherResult.error)) + promise.resolve( + createError(GooglePayErrorType.Failed.toString(), launcherResult.error), + ) } } } @@ -563,73 +769,109 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ } @ReactMethod - 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 - } + 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( - it, - GooglePayJsonFactory(reactApplicationContext), - googlePayParams - ) + val request = + GooglePayRequestHelper.createPaymentRequest( + it, + GooglePayJsonFactory(reactApplicationContext), + googlePayParams, + ) GooglePayRequestHelper.createPaymentMethod(request, it) } } @ReactMethod - fun canAddCardToWallet(params: ReadableMap, promise: Promise) { - val last4 = getValOr(params, "cardLastFour", null) ?: run { - promise.resolve(createError("Failed", "You must provide cardLastFour")) - return - } + fun canAddCardToWallet( + params: ReadableMap, + promise: Promise, + ) { + val last4 = + getValOr(params, "cardLastFour", null) + ?: run { + promise.resolve(createError("Failed", "You must provide cardLastFour")) + return + } - if (params.getBooleanOr("supportsTapToPay", true) && !PushProvisioningProxy.isNFCEnabled(reactApplicationContext)) { + if (params.getBooleanOr("supportsTapToPay", true) && + !PushProvisioningProxy.isNFCEnabled(reactApplicationContext) + ) { promise.resolve(createCanAddCardResult(false, "UNSUPPORTED_DEVICE")) return } getCurrentActivityOrResolveWithError(promise)?.let { PushProvisioningProxy.isCardInWallet(it, last4) { isCardInWallet, token, error -> - val result = error?.let { - createCanAddCardResult(false, "MISSING_CONFIGURATION", null) - } ?: run { - val status = if (isCardInWallet) "CARD_ALREADY_EXISTS" else null - createCanAddCardResult(!isCardInWallet, status, token) - } + val result = + error?.let { createCanAddCardResult(false, "MISSING_CONFIGURATION", null) } + ?: run { + val status = if (isCardInWallet) "CARD_ALREADY_EXISTS" else null + createCanAddCardResult(!isCardInWallet, status, token) + } promise.resolve(result) } } } @ReactMethod - fun isCardInWallet(params: ReadableMap, promise: Promise) { - val last4 = getValOr(params, "cardLastFour", null) ?: run { - promise.resolve(createError("Failed", "You must provide cardLastFour")) - return - } + fun isCardInWallet( + params: ReadableMap, + promise: Promise, + ) { + val last4 = + getValOr(params, "cardLastFour", null) + ?: run { + promise.resolve(createError("Failed", "You must provide cardLastFour")) + return + } getCurrentActivityOrResolveWithError(promise)?.let { PushProvisioningProxy.isCardInWallet(it, last4) { isCardInWallet, token, error -> - val result: WritableMap = error ?: run { - val map = WritableNativeMap() - map.putBoolean("isInWallet", isCardInWallet) - map.putMap("token", token) - map - } + val result: WritableMap = + error + ?: run { + val map = WritableNativeMap() + map.putBoolean("isInWallet", isCardInWallet) + map.putMap("token", token) + map + } promise.resolve(result) } } } @ReactMethod - fun collectBankAccount(isPaymentIntent: Boolean, clientSecret: String, params: ReadableMap, promise: Promise) { + fun collectBankAccount( + isPaymentIntent: Boolean, + clientSecret: String, + params: ReadableMap, + promise: Promise, + ) { val paymentMethodData = getMapOrNull(params, "paymentMethodData") val paymentMethodType = mapToPaymentMethodType(getValOr(params, "paymentMethodType", null)) if (paymentMethodType != PaymentMethod.Type.USBankAccount) { - promise.resolve(createError(ErrorType.Failed.toString(), "collectBankAccount currently only accepts the USBankAccount payment method type.")) + promise.resolve( + createError( + ErrorType.Failed.toString(), + "collectBankAccount currently only accepts the USBankAccount payment method type.", + ), + ) return } @@ -637,27 +879,32 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ val name = billingDetails?.getString("name") if (name.isNullOrEmpty()) { - promise.resolve(createError(ErrorType.Failed.toString(), "You must provide a name when collecting US bank account details.")) + promise.resolve( + createError( + ErrorType.Failed.toString(), + "You must provide a name when collecting US bank account details.", + ), + ) return } - val collectParams = CollectBankAccountConfiguration.USBankAccount( - name, - billingDetails.getString("email") - ) + val collectParams = + CollectBankAccountConfiguration.USBankAccount(name, billingDetails.getString("email")) - collectBankAccountLauncherFragment = CollectBankAccountLauncherFragment( - reactApplicationContext, - publishableKey, - stripeAccountId, - clientSecret, - isPaymentIntent, - collectParams, - promise - ) + collectBankAccountLauncherFragment = + CollectBankAccountLauncherFragment( + reactApplicationContext, + publishableKey, + stripeAccountId, + clientSecret, + isPaymentIntent, + collectParams, + promise, + ) getCurrentActivityOrResolveWithError(promise)?.let { try { - it.supportFragmentManager.beginTransaction() + it.supportFragmentManager + .beginTransaction() .add(collectBankAccountLauncherFragment!!, "collect_bank_account_launcher_fragment") .commit() } catch (error: IllegalStateException) { @@ -667,38 +914,57 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ } @ReactMethod - fun verifyMicrodeposits(isPaymentIntent: Boolean, clientSecret: String, params: ReadableMap, promise: Promise) { + fun verifyMicrodeposits( + isPaymentIntent: Boolean, + clientSecret: String, + params: ReadableMap, + promise: Promise, + ) { val amounts = params.getArray("amounts") val descriptorCode = params.getString("descriptorCode") - if ((amounts != null && descriptorCode != null) || (amounts == null && descriptorCode == null)) { - promise.resolve(createError(ErrorType.Failed.toString(), "You must provide either amounts OR descriptorCode, not both.")) + if ((amounts != null && descriptorCode != null) || + (amounts == null && descriptorCode == null) + ) { + promise.resolve( + createError( + ErrorType.Failed.toString(), + "You must provide either amounts OR descriptorCode, not both.", + ), + ) return } - val paymentCallback = object : ApiResultCallback { - override fun onError(e: Exception) { - promise.resolve(createError(ErrorType.Failed.toString(), e)) - } + val paymentCallback = + object : ApiResultCallback { + override fun onError(e: Exception) { + promise.resolve(createError(ErrorType.Failed.toString(), e)) + } - override fun onSuccess(result: PaymentIntent) { - promise.resolve(createResult("paymentIntent", mapFromPaymentIntentResult(result))) + override fun onSuccess(result: PaymentIntent) { + promise.resolve(createResult("paymentIntent", mapFromPaymentIntentResult(result))) + } } - } - val setupCallback = object : ApiResultCallback { - override fun onError(e: Exception) { - promise.resolve(createError(ErrorType.Failed.toString(), e)) - } + val setupCallback = + object : ApiResultCallback { + override fun onError(e: Exception) { + promise.resolve(createError(ErrorType.Failed.toString(), e)) + } - override fun onSuccess(result: SetupIntent) { - promise.resolve(createResult("setupIntent", mapFromSetupIntentResult(result))) + override fun onSuccess(result: SetupIntent) { + promise.resolve(createResult("setupIntent", mapFromSetupIntentResult(result))) + } } - } amounts?.let { if (it.size() != 2) { - promise.resolve(createError(ErrorType.Failed.toString(), "Expected 2 integers in the amounts array, but received ${it.size()}")) + promise.resolve( + createError( + ErrorType.Failed.toString(), + "Expected 2 integers in the amounts array, but received ${it.size()}", + ), + ) return } @@ -707,57 +973,74 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ clientSecret, it.getInt(0), it.getInt(1), - paymentCallback + paymentCallback, ) } else { stripe.verifySetupIntentWithMicrodeposits( clientSecret, it.getInt(0), it.getInt(1), - setupCallback - ) - } - } ?: descriptorCode?.let { - if (isPaymentIntent) { - stripe.verifyPaymentIntentWithMicrodeposits( - clientSecret, - it, - paymentCallback - ) - } else { - stripe.verifySetupIntentWithMicrodeposits( - clientSecret, - it, - setupCallback + setupCallback, ) } } + ?: descriptorCode?.let { + if (isPaymentIntent) { + stripe.verifyPaymentIntentWithMicrodeposits(clientSecret, it, paymentCallback) + } else { + stripe.verifySetupIntentWithMicrodeposits(clientSecret, it, setupCallback) + } + } } @ReactMethod - fun collectBankAccountToken(clientSecret: String, promise: Promise) { + fun collectBankAccountToken( + clientSecret: String, + promise: Promise, + ) { if (!::stripe.isInitialized) { promise.resolve(createMissingInitError()) return } FinancialConnectionsSheetFragment().also { - it.presentFinancialConnectionsSheet(clientSecret, FinancialConnectionsSheetFragment.Mode.ForToken, publishableKey, stripeAccountId, promise, reactApplicationContext) + it.presentFinancialConnectionsSheet( + clientSecret, + FinancialConnectionsSheetFragment.Mode.ForToken, + publishableKey, + stripeAccountId, + promise, + reactApplicationContext, + ) } } @ReactMethod - fun collectFinancialConnectionsAccounts(clientSecret: String, promise: Promise) { + fun collectFinancialConnectionsAccounts( + clientSecret: String, + promise: Promise, + ) { if (!::stripe.isInitialized) { promise.resolve(createMissingInitError()) return } FinancialConnectionsSheetFragment().also { - it.presentFinancialConnectionsSheet(clientSecret, FinancialConnectionsSheetFragment.Mode.ForSession, publishableKey, stripeAccountId, promise, reactApplicationContext) + it.presentFinancialConnectionsSheet( + clientSecret, + FinancialConnectionsSheetFragment.Mode.ForSession, + publishableKey, + stripeAccountId, + promise, + reactApplicationContext, + ) } } @ReactMethod - fun initCustomerSheet(params: ReadableMap, customerAdapterOverrides: ReadableMap, promise: Promise) { + fun initCustomerSheet( + params: ReadableMap, + customerAdapterOverrides: ReadableMap, + promise: Promise, + ) { if (!::stripe.isInitialized) { promise.resolve(createMissingInitError()) return @@ -765,15 +1048,17 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ getCurrentActivityOrResolveWithError(promise)?.let { activity -> customerSheetFragment?.removeFragment(reactApplicationContext) - customerSheetFragment = CustomerSheetFragment().also { - it.context = reactApplicationContext - it.initPromise = promise - val bundle = toBundleObject(params) - bundle.putBundle("customerAdapter", toBundleObject(customerAdapterOverrides)) - it.arguments = bundle - } + customerSheetFragment = + CustomerSheetFragment().also { + it.context = reactApplicationContext + it.initPromise = promise + val bundle = toBundleObject(params) + bundle.putBundle("customerAdapter", toBundleObject(customerAdapterOverrides)) + it.arguments = bundle + } try { - activity.supportFragmentManager.beginTransaction() + activity.supportFragmentManager + .beginTransaction() .add(customerSheetFragment!!, CustomerSheetFragment.TAG) .commit() } catch (error: IllegalStateException) { @@ -783,99 +1068,133 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ } @ReactMethod - fun presentCustomerSheet(params: ReadableMap, promise: Promise) { + fun presentCustomerSheet( + params: ReadableMap, + promise: Promise, + ) { var timeout: Long? = null if (params.hasKey("timeout")) { timeout = params.getInt("timeout").toLong() } - customerSheetFragment?.present(timeout, promise) ?: run { - promise.resolve(CustomerSheetFragment.createMissingInitError()) - } + customerSheetFragment?.present(timeout, promise) + ?: run { promise.resolve(CustomerSheetFragment.createMissingInitError()) } } @ReactMethod fun retrieveCustomerSheetPaymentOptionSelection(promise: Promise) { - customerSheetFragment?.retrievePaymentOptionSelection(promise) ?: run { - promise.resolve(CustomerSheetFragment.createMissingInitError()) - } + customerSheetFragment?.retrievePaymentOptionSelection(promise) + ?: run { promise.resolve(CustomerSheetFragment.createMissingInitError()) } } @ReactMethod - fun customerAdapterFetchPaymentMethodsCallback(paymentMethodJsonObjects: ReadableArray, promise: Promise) { + fun customerAdapterFetchPaymentMethodsCallback( + paymentMethodJsonObjects: ReadableArray, + promise: Promise, + ) { customerSheetFragment?.let { fragment -> val paymentMethods = mutableListOf() for (paymentMethodJson in paymentMethodJsonObjects.toArrayList()) { PaymentMethod.fromJson(JSONObject((paymentMethodJson as HashMap<*, *>)))?.let { paymentMethods.add(it) - } ?: run { - Log.e("StripeReactNative", "There was an error converting Payment Method JSON to a Stripe Payment Method") } + ?: run { + Log.e( + "StripeReactNative", + "There was an error converting Payment Method JSON to a Stripe Payment Method", + ) + } } fragment.customerAdapter?.fetchPaymentMethodsCallback?.complete(paymentMethods) - } ?: run { - promise.resolve(CustomerSheetFragment.createMissingInitError()) - return } + ?: run { + promise.resolve(CustomerSheetFragment.createMissingInitError()) + return + } } @ReactMethod - fun customerAdapterAttachPaymentMethodCallback(paymentMethodJson: ReadableMap, promise: Promise) { + fun customerAdapterAttachPaymentMethodCallback( + paymentMethodJson: ReadableMap, + promise: Promise, + ) { customerSheetFragment?.let { - val paymentMethod = PaymentMethod.fromJson(JSONObject(paymentMethodJson.toHashMap() as HashMap<*, *>)) + val paymentMethod = + PaymentMethod.fromJson(JSONObject(paymentMethodJson.toHashMap() as HashMap<*, *>)) if (paymentMethod == null) { - Log.e("StripeReactNative", "There was an error converting Payment Method JSON to a Stripe Payment Method") + Log.e( + "StripeReactNative", + "There was an error converting Payment Method JSON to a Stripe Payment Method", + ) return } it.customerAdapter?.attachPaymentMethodCallback?.complete(paymentMethod) - } ?: run { - promise.resolve(CustomerSheetFragment.createMissingInitError()) - return } + ?: run { + promise.resolve(CustomerSheetFragment.createMissingInitError()) + return + } } @ReactMethod - fun customerAdapterDetachPaymentMethodCallback(paymentMethodJson: ReadableMap, promise: Promise) { + fun customerAdapterDetachPaymentMethodCallback( + paymentMethodJson: ReadableMap, + promise: Promise, + ) { customerSheetFragment?.let { - val paymentMethod = PaymentMethod.fromJson(JSONObject(paymentMethodJson.toHashMap() as HashMap<*, *>)) + val paymentMethod = + PaymentMethod.fromJson(JSONObject(paymentMethodJson.toHashMap() as HashMap<*, *>)) if (paymentMethod == null) { - Log.e("StripeReactNative", "There was an error converting Payment Method JSON to a Stripe Payment Method") + Log.e( + "StripeReactNative", + "There was an error converting Payment Method JSON to a Stripe Payment Method", + ) return } it.customerAdapter?.detachPaymentMethodCallback?.complete(paymentMethod) - } ?: run { - promise.resolve(CustomerSheetFragment.createMissingInitError()) - return } + ?: run { + promise.resolve(CustomerSheetFragment.createMissingInitError()) + return + } } @ReactMethod fun customerAdapterSetSelectedPaymentOptionCallback(promise: Promise) { customerSheetFragment?.let { it.customerAdapter?.setSelectedPaymentOptionCallback?.complete(Unit) - } ?: run { - promise.resolve(CustomerSheetFragment.createMissingInitError()) - return } + ?: run { + promise.resolve(CustomerSheetFragment.createMissingInitError()) + return + } } @ReactMethod - fun customerAdapterFetchSelectedPaymentOptionCallback(paymentOption: String?, promise: Promise) { + fun customerAdapterFetchSelectedPaymentOptionCallback( + paymentOption: String?, + promise: Promise, + ) { customerSheetFragment?.let { it.customerAdapter?.fetchSelectedPaymentOptionCallback?.complete(paymentOption) - } ?: run { - promise.resolve(CustomerSheetFragment.createMissingInitError()) - return } + ?: run { + promise.resolve(CustomerSheetFragment.createMissingInitError()) + return + } } @ReactMethod - fun customerAdapterSetupIntentClientSecretForCustomerAttachCallback(clientSecret: String, promise: Promise) { + fun customerAdapterSetupIntentClientSecretForCustomerAttachCallback( + clientSecret: String, + promise: Promise, + ) { customerSheetFragment?.let { it.customerAdapter?.setupIntentClientSecretForCustomerAttachCallback?.complete(clientSecret) - } ?: run { - promise.resolve(CustomerSheetFragment.createMissingInitError()) - return } + ?: run { + promise.resolve(CustomerSheetFragment.createMissingInitError()) + return + } } @ReactMethod @@ -891,7 +1210,11 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ } } - internal fun sendEvent(reactContext: ReactContext, eventName: String, params: WritableMap) { + internal fun sendEvent( + reactContext: ReactContext, + eventName: String, + params: WritableMap, + ) { reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(eventName, params) diff --git a/android/src/main/java/com/reactnativestripesdk/StripeSdkPackage.kt b/android/src/main/java/com/reactnativestripesdk/StripeSdkPackage.kt index 979e586b3..ffb7987d4 100644 --- a/android/src/main/java/com/reactnativestripesdk/StripeSdkPackage.kt +++ b/android/src/main/java/com/reactnativestripesdk/StripeSdkPackage.kt @@ -8,19 +8,17 @@ import com.reactnativestripesdk.addresssheet.AddressSheetViewManager import com.reactnativestripesdk.pushprovisioning.AddToWalletButtonManager class StripeSdkPackage : ReactPackage { - override fun createNativeModules(reactContext: ReactApplicationContext): List { - return listOf(StripeSdkModule(reactContext)) - } + override fun createNativeModules(reactContext: ReactApplicationContext): List = + listOf(StripeSdkModule(reactContext)) - override fun createViewManagers(reactContext: ReactApplicationContext): List> { - return listOf>( - CardFieldViewManager(), - AuBECSDebitFormViewManager(), - StripeContainerManager(), - CardFormViewManager(), - GooglePayButtonManager(), - AddToWalletButtonManager(reactContext), - AddressSheetViewManager() - ) - } + override fun createViewManagers(reactContext: ReactApplicationContext): List> = + listOf>( + CardFieldViewManager(), + AuBECSDebitFormViewManager(), + StripeContainerManager(), + CardFormViewManager(), + GooglePayButtonManager(), + AddToWalletButtonManager(reactContext), + AddressSheetViewManager(), + ) } diff --git a/android/src/main/java/com/reactnativestripesdk/addresssheet/AddressLauncherFragment.kt b/android/src/main/java/com/reactnativestripesdk/addresssheet/AddressLauncherFragment.kt index 25171ac49..7502cd2e6 100644 --- a/android/src/main/java/com/reactnativestripesdk/addresssheet/AddressLauncherFragment.kt +++ b/android/src/main/java/com/reactnativestripesdk/addresssheet/AddressLauncherFragment.kt @@ -26,28 +26,31 @@ class AddressLauncherFragment : Fragment() { private var configuration = AddressLauncher.Configuration() private var callback: ((error: WritableMap?, address: AddressDetails?) -> Unit)? = null - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View { - return FrameLayout(requireActivity()).also { - it.visibility = View.GONE - } - } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View = FrameLayout(requireActivity()).also { it.visibility = View.GONE } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { publishableKey?.let { publishableKey -> - addressLauncher = AddressLauncher(this, - ::onAddressLauncherResult).also { - it.present( - publishableKey = publishableKey, - configuration = configuration + addressLauncher = + AddressLauncher(this, ::onAddressLauncherResult).also { + it.present(publishableKey = publishableKey, configuration = configuration) + } + } + ?: run { + callback?.invoke( + createError( + ErrorType.Failed.toString(), + "No publishable key set. Stripe has not been initialized. Initialize Stripe in your app with the StripeProvider component or the initStripe method.", + ), + null, ) } - } ?: run { - callback?.invoke( - createError(ErrorType.Failed.toString(), "No publishable key set. Stripe has not been initialized. Initialize Stripe in your app with the StripeProvider component or the initStripe method."), - null - ) - } } private fun onAddressLauncherResult(result: AddressLauncherResult) { @@ -55,14 +58,11 @@ class AddressLauncherFragment : Fragment() { is AddressLauncherResult.Canceled -> { callback?.invoke( createError(ErrorType.Canceled.toString(), "The flow has been canceled."), - null + null, ) } is AddressLauncherResult.Succeeded -> { - callback?.invoke( - null, - result.address - ) + callback?.invoke(null, result.address) } } } @@ -77,17 +77,19 @@ class AddressLauncherFragment : Fragment() { googlePlacesApiKey: String?, autocompleteCountries: Set, additionalFields: AddressLauncher.AdditionalFieldsConfiguration?, - callback: ((error: WritableMap?, address: AddressDetails?) -> Unit)) { - configuration = AddressLauncher.Configuration( - appearance = appearance, - address = defaultAddress, - allowedCountries = allowedCountries, - buttonTitle = buttonTitle, - additionalFields = additionalFields, - title = title, - googlePlacesApiKey = googlePlacesApiKey, - autocompleteCountries = autocompleteCountries, - ) + callback: ((error: WritableMap?, address: AddressDetails?) -> Unit), + ) { + configuration = + AddressLauncher.Configuration( + appearance = appearance, + address = defaultAddress, + allowedCountries = allowedCountries, + buttonTitle = buttonTitle, + additionalFields = additionalFields, + title = title, + googlePlacesApiKey = googlePlacesApiKey, + autocompleteCountries = autocompleteCountries, + ) this.callback = callback (context.currentActivity as? FragmentActivity)?.let { attemptToCleanupPreviousFragment(it) @@ -96,16 +98,19 @@ class AddressLauncherFragment : Fragment() { } private fun attemptToCleanupPreviousFragment(currentActivity: FragmentActivity) { - currentActivity.supportFragmentManager.beginTransaction() + currentActivity.supportFragmentManager + .beginTransaction() .remove(this) .commitAllowingStateLoss() } private fun commitFragmentAndStartFlow(currentActivity: FragmentActivity) { try { - currentActivity.supportFragmentManager.beginTransaction() + currentActivity.supportFragmentManager + .beginTransaction() .add(this, TAG) .commit() - } catch (_: IllegalStateException) {} + } catch (_: IllegalStateException) { + } } } diff --git a/android/src/main/java/com/reactnativestripesdk/addresssheet/AddressSheetEvent.kt b/android/src/main/java/com/reactnativestripesdk/addresssheet/AddressSheetEvent.kt index 216005dc2..00259a089 100644 --- a/android/src/main/java/com/reactnativestripesdk/addresssheet/AddressSheetEvent.kt +++ b/android/src/main/java/com/reactnativestripesdk/addresssheet/AddressSheetEvent.kt @@ -4,25 +4,29 @@ import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.Event import com.facebook.react.uimanager.events.RCTEventEmitter -internal class AddressSheetEvent constructor(viewTag: Int, private val eventType: EventType, private val eventMap: WritableMap?) : Event(viewTag) { - enum class EventType { - OnSubmit, - OnError - } - - override fun dispatch(rctEventEmitter: RCTEventEmitter) { - rctEventEmitter.receiveEvent(viewTag, eventName, eventMap) - } +internal class AddressSheetEvent + constructor( + viewTag: Int, + private val eventType: EventType, + private val eventMap: WritableMap?, + ) : Event(viewTag) { + enum class EventType { + OnSubmit, + OnError, + } - companion object { - const val ON_SUBMIT = "onSubmitAction" - const val ON_ERROR = "onErrorAction" - } + override fun dispatch(rctEventEmitter: RCTEventEmitter) { + rctEventEmitter.receiveEvent(viewTag, eventName, eventMap) + } - override fun getEventName(): String { - return when (eventType) { - EventType.OnSubmit -> ON_SUBMIT - EventType.OnError -> ON_ERROR + companion object { + const val ON_SUBMIT = "onSubmitAction" + const val ON_ERROR = "onErrorAction" } + + override fun getEventName(): String = + when (eventType) { + EventType.OnSubmit -> ON_SUBMIT + EventType.OnError -> ON_ERROR + } } -} diff --git a/android/src/main/java/com/reactnativestripesdk/addresssheet/AddressSheetView.kt b/android/src/main/java/com/reactnativestripesdk/addresssheet/AddressSheetView.kt index ac5b03ddb..5fb1e4d16 100644 --- a/android/src/main/java/com/reactnativestripesdk/addresssheet/AddressSheetView.kt +++ b/android/src/main/java/com/reactnativestripesdk/addresssheet/AddressSheetView.kt @@ -3,7 +3,6 @@ package com.reactnativestripesdk.addresssheet import android.os.Bundle import android.util.Log import android.widget.FrameLayout -import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.WritableMap import com.facebook.react.bridge.WritableNativeMap @@ -18,10 +17,12 @@ import com.reactnativestripesdk.utils.toBundleObject import com.stripe.android.paymentsheet.PaymentSheet import com.stripe.android.paymentsheet.addresselement.AddressDetails import com.stripe.android.paymentsheet.addresselement.AddressLauncher -import com.stripe.android.paymentsheet.addresselement.AddressLauncherResult -class AddressSheetView(private val context: ThemedReactContext) : FrameLayout(context) { - private var eventDispatcher: EventDispatcher? = context.getNativeModule(UIManagerModule::class.java)?.eventDispatcher +class AddressSheetView( + private val context: ThemedReactContext, +) : FrameLayout(context) { + private var eventDispatcher: EventDispatcher? = + context.getNativeModule(UIManagerModule::class.java)?.eventDispatcher private var isVisible = false private var appearanceParams: ReadableMap? = null private var defaultAddress: AddressDetails? = null @@ -34,13 +35,13 @@ class AddressSheetView(private val context: ThemedReactContext) : FrameLayout(co private fun onSubmit(params: WritableMap) { eventDispatcher?.dispatchEvent( - AddressSheetEvent(id, AddressSheetEvent.EventType.OnSubmit, params) + AddressSheetEvent(id, AddressSheetEvent.EventType.OnSubmit, params), ) } private fun onError(params: WritableMap?) { eventDispatcher?.dispatchEvent( - AddressSheetEvent(id, AddressSheetEvent.EventType.OnError, params) + AddressSheetEvent(id, AddressSheetEvent.EventType.OnError, params), ) } @@ -48,18 +49,22 @@ class AddressSheetView(private val context: ThemedReactContext) : FrameLayout(co if (newVisibility && !isVisible) { launchAddressSheet() } else if (!newVisibility && isVisible) { - Log.w("StripeReactNative", "Programmatically dismissing the Address Sheet is not supported on Android.") + Log.w( + "StripeReactNative", + "Programmatically dismissing the Address Sheet is not supported on Android.", + ) } isVisible = newVisibility } private fun launchAddressSheet() { - val appearance = try { - buildPaymentSheetAppearance(toBundleObject(appearanceParams), context) - } catch (error: PaymentSheetAppearanceException) { - onError(createError(ErrorType.Failed.toString(), error)) - return - } + val appearance = + try { + buildPaymentSheetAppearance(toBundleObject(appearanceParams), context) + } catch (error: PaymentSheetAppearanceException) { + onError(createError(ErrorType.Failed.toString(), error)) + return + } AddressLauncherFragment().presentAddressSheet( context, appearance, @@ -69,7 +74,7 @@ class AddressSheetView(private val context: ThemedReactContext) : FrameLayout(co sheetTitle, googlePlacesApiKey, autocompleteCountries, - additionalFields + additionalFields, ) { error, address -> if (address != null) { onSubmit(buildResult(address)) @@ -113,18 +118,15 @@ class AddressSheetView(private val context: ThemedReactContext) : FrameLayout(co } companion object { - internal fun buildAddressDetails(bundle: Bundle): AddressDetails { - return AddressDetails( + internal fun buildAddressDetails(bundle: Bundle): AddressDetails = + AddressDetails( name = bundle.getString("name"), address = buildAddress(bundle.getBundle("address")), phoneNumber = bundle.getString("phone"), isCheckboxSelected = bundle.getBoolean("isCheckboxSelected"), ) - } - internal fun buildAddressDetails(map: ReadableMap): AddressDetails { - return buildAddressDetails(toBundleObject(map)) - } + internal fun buildAddressDetails(map: ReadableMap): AddressDetails = buildAddressDetails(toBundleObject(map)) internal fun buildAddress(bundle: Bundle?): PaymentSheet.Address? { if (bundle == null) { @@ -136,25 +138,24 @@ class AddressSheetView(private val context: ThemedReactContext) : FrameLayout(co line1 = bundle.getString("line1"), line2 = bundle.getString("line2"), state = bundle.getString("state"), - postalCode = bundle.getString("postalCode") + postalCode = bundle.getString("postalCode"), ) } - internal fun getFieldConfiguration(key: String?): AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration { - return when (key) { + internal fun getFieldConfiguration(key: String?): AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration = + when (key) { "hidden" -> AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.HIDDEN "optional" -> AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.OPTIONAL "required" -> AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.REQUIRED else -> AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.HIDDEN } - } internal fun buildAdditionalFieldsConfiguration(params: ReadableMap): AddressLauncher.AdditionalFieldsConfiguration { val phoneConfiguration = getFieldConfiguration(params.getString("phoneNumber")) return AddressLauncher.AdditionalFieldsConfiguration( phone = phoneConfiguration, - checkboxLabel = params.getString("checkboxLabel") + checkboxLabel = params.getString("checkboxLabel"), ) } diff --git a/android/src/main/java/com/reactnativestripesdk/addresssheet/AddressSheetViewManager.kt b/android/src/main/java/com/reactnativestripesdk/addresssheet/AddressSheetViewManager.kt index 23d0d9d4a..b13dc2b09 100644 --- a/android/src/main/java/com/reactnativestripesdk/addresssheet/AddressSheetViewManager.kt +++ b/android/src/main/java/com/reactnativestripesdk/addresssheet/AddressSheetViewManager.kt @@ -10,58 +10,85 @@ import com.facebook.react.uimanager.annotations.ReactProp class AddressSheetViewManager : SimpleViewManager() { override fun getName() = "AddressSheetView" - override fun getExportedCustomDirectEventTypeConstants(): MutableMap { - return MapBuilder.of( - AddressSheetEvent.ON_SUBMIT, MapBuilder.of("registrationName", "onSubmitAction"), - AddressSheetEvent.ON_ERROR, MapBuilder.of("registrationName", "onErrorAction")) - } + override fun getExportedCustomDirectEventTypeConstants(): MutableMap = + MapBuilder.of( + AddressSheetEvent.ON_SUBMIT, + MapBuilder.of("registrationName", "onSubmitAction"), + AddressSheetEvent.ON_ERROR, + MapBuilder.of("registrationName", "onErrorAction"), + ) @ReactProp(name = "visible") - fun setVisible(view: AddressSheetView, visibility: Boolean) { + fun setVisible( + view: AddressSheetView, + visibility: Boolean, + ) { view.setVisible(visibility) } @ReactProp(name = "appearance") - fun setAppearance(view: AddressSheetView, appearance: ReadableMap) { + fun setAppearance( + view: AddressSheetView, + appearance: ReadableMap, + ) { view.setAppearance(appearance) } @ReactProp(name = "defaultValues") - fun setDefaultValues(view: AddressSheetView, defaults: ReadableMap) { + fun setDefaultValues( + view: AddressSheetView, + defaults: ReadableMap, + ) { view.setDefaultValues(defaults) } @ReactProp(name = "additionalFields") - fun setAdditionalFields(view: AddressSheetView, fields: ReadableMap) { + fun setAdditionalFields( + view: AddressSheetView, + fields: ReadableMap, + ) { view.setAdditionalFields(fields) } @ReactProp(name = "allowedCountries") - fun setAllowedCountries(view: AddressSheetView, countries: ReadableArray) { + fun setAllowedCountries( + view: AddressSheetView, + countries: ReadableArray, + ) { view.setAllowedCountries(countries.toArrayList().filterIsInstance()) } @ReactProp(name = "autocompleteCountries") - fun setAutocompleteCountries(view: AddressSheetView, countries: ReadableArray) { + fun setAutocompleteCountries( + view: AddressSheetView, + countries: ReadableArray, + ) { view.setAutocompleteCountries(countries.toArrayList().filterIsInstance()) } @ReactProp(name = "primaryButtonTitle") - fun setPrimaryButtonTitle(view: AddressSheetView, title: String) { + fun setPrimaryButtonTitle( + view: AddressSheetView, + title: String, + ) { view.setPrimaryButtonTitle(title) } @ReactProp(name = "sheetTitle") - fun setSheetTitle(view: AddressSheetView, title: String) { + fun setSheetTitle( + view: AddressSheetView, + title: String, + ) { view.setSheetTitle(title) } @ReactProp(name = "googlePlacesApiKey") - fun setGooglePlacesApiKey(view: AddressSheetView, key: String) { + fun setGooglePlacesApiKey( + view: AddressSheetView, + key: String, + ) { view.setGooglePlacesApiKey(key) } - override fun createViewInstance(reactContext: ThemedReactContext): AddressSheetView { - return AddressSheetView(reactContext) - } + override fun createViewInstance(reactContext: ThemedReactContext): AddressSheetView = AddressSheetView(reactContext) } diff --git a/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetFragment.kt b/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetFragment.kt index 9fe2b4241..8d6fb4ddb 100644 --- a/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetFragment.kt +++ b/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetFragment.kt @@ -12,9 +12,18 @@ import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import androidx.fragment.app.Fragment -import com.facebook.react.bridge.* +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.WritableNativeMap import com.reactnativestripesdk.customersheet.ReactNativeCustomerAdapter -import com.reactnativestripesdk.utils.* +import com.reactnativestripesdk.utils.CreateTokenErrorType +import com.reactnativestripesdk.utils.ErrorType +import com.reactnativestripesdk.utils.PaymentSheetAppearanceException +import com.reactnativestripesdk.utils.createError +import com.reactnativestripesdk.utils.mapFromPaymentMethod +import com.reactnativestripesdk.utils.mapToPreferredNetworks import com.stripe.android.ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi import com.stripe.android.customersheet.CustomerAdapter import com.stripe.android.customersheet.CustomerEphemeralKey @@ -22,12 +31,11 @@ import com.stripe.android.customersheet.CustomerSheet import com.stripe.android.customersheet.CustomerSheetResult import com.stripe.android.customersheet.PaymentOptionSelection import com.stripe.android.model.PaymentMethod -import com.stripe.android.paymentsheet.* +import com.stripe.android.paymentsheet.PaymentSheet import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch - @OptIn(ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi::class) class CustomerSheetFragment : Fragment() { private var customerSheet: CustomerSheet? = null @@ -39,24 +47,33 @@ class CustomerSheetFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - return FrameLayout(requireActivity()).also { - it.visibility = View.GONE - } - } + savedInstanceState: Bundle?, + ): View = FrameLayout(requireActivity()).also { it.visibility = View.GONE } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) - val context = context ?: run { - Log.e("StripeReactNative", "No context found during CustomerSheet.initialize. Please file an issue: https://github.com/stripe/stripe-react-native/issues") - return - } - val initPromise = initPromise ?: run { - Log.e("StripeReactNative", "No promise found for CustomerSheet.initialize. Please file an issue: https://github.com/stripe/stripe-react-native/issues") - return - } + val context = + context + ?: run { + Log.e( + "StripeReactNative", + "No context found during CustomerSheet.initialize. Please file an issue: https://github.com/stripe/stripe-react-native/issues", + ) + return + } + val initPromise = + initPromise + ?: run { + Log.e( + "StripeReactNative", + "No promise found for CustomerSheet.initialize. Please file an issue: https://github.com/stripe/stripe-react-native/issues", + ) + return + } val headerTextForSelectionScreen = arguments?.getString("headerTextForSelectionScreen") val merchantDisplayName = arguments?.getString("merchantDisplayName") @@ -67,52 +84,68 @@ class CustomerSheetFragment : Fragment() { val customerId = arguments?.getString("customerId") val customerEphemeralKeySecret = arguments?.getString("customerEphemeralKeySecret") val customerAdapterOverrideParams = arguments?.getBundle("customerAdapter") - val allowsRemovalOfLastSavedPaymentMethod = arguments?.getBoolean("allowsRemovalOfLastSavedPaymentMethod", true) ?: true + val allowsRemovalOfLastSavedPaymentMethod = + arguments?.getBoolean("allowsRemovalOfLastSavedPaymentMethod", true) ?: true val paymentMethodOrder = arguments?.getStringArrayList("paymentMethodOrder") if (customerId == null) { - initPromise.resolve(createError(ErrorType.Failed.toString(), "You must provide a value for `customerId`")) + initPromise.resolve( + createError(ErrorType.Failed.toString(), "You must provide a value for `customerId`"), + ) return } if (customerEphemeralKeySecret == null) { - initPromise.resolve(createError(ErrorType.Failed.toString(), "You must provide a value for `customerEphemeralKeySecret`")) - return - } - - val appearance = try { - buildPaymentSheetAppearance(arguments?.getBundle("appearance"), context) - } catch (error: PaymentSheetAppearanceException) { - initPromise.resolve(createError(ErrorType.Failed.toString(), error)) + initPromise.resolve( + createError( + ErrorType.Failed.toString(), + "You must provide a value for `customerEphemeralKeySecret`", + ), + ) return } - val configuration = CustomerSheet.Configuration.builder(merchantDisplayName ?: "") - .appearance(appearance) - .googlePayEnabled(googlePayEnabled) - .headerTextForSelectionScreen(headerTextForSelectionScreen) - .preferredNetworks(mapToPreferredNetworks(arguments?.getIntegerArrayList("preferredNetworks"))) - .allowsRemovalOfLastSavedPaymentMethod(allowsRemovalOfLastSavedPaymentMethod) + val appearance = + try { + buildPaymentSheetAppearance(arguments?.getBundle("appearance"), context) + } catch (error: PaymentSheetAppearanceException) { + initPromise.resolve(createError(ErrorType.Failed.toString(), error)) + return + } - paymentMethodOrder?.let { - configuration.paymentMethodOrder(it) - } + val configuration = + CustomerSheet.Configuration + .builder(merchantDisplayName ?: "") + .appearance(appearance) + .googlePayEnabled(googlePayEnabled) + .headerTextForSelectionScreen(headerTextForSelectionScreen) + .preferredNetworks( + mapToPreferredNetworks(arguments?.getIntegerArrayList("preferredNetworks")), + ).allowsRemovalOfLastSavedPaymentMethod(allowsRemovalOfLastSavedPaymentMethod) + + paymentMethodOrder?.let { configuration.paymentMethodOrder(it) } billingDetailsBundle?.let { configuration.defaultBillingDetails(createDefaultBillingDetails(billingDetailsBundle)) } billingConfigParams?.let { - configuration.billingDetailsCollectionConfiguration(createBillingDetailsCollectionConfiguration(billingConfigParams)) - } - - val customerAdapter = createCustomerAdapter( - context, customerId, customerEphemeralKeySecret, setupIntentClientSecret, customerAdapterOverrideParams - ).also { - this.customerAdapter = it + configuration.billingDetailsCollectionConfiguration( + createBillingDetailsCollectionConfiguration(billingConfigParams), + ) } - customerSheet = CustomerSheet.create( - fragment = this, - customerAdapter = customerAdapter, - callback = ::handleResult - ) + val customerAdapter = + createCustomerAdapter( + context, + customerId, + customerEphemeralKeySecret, + setupIntentClientSecret, + customerAdapterOverrideParams, + ).also { this.customerAdapter = it } + + customerSheet = + CustomerSheet.create( + fragment = this, + customerAdapter = customerAdapter, + callback = ::handleResult, + ) customerSheet?.configure(configuration.build()) @@ -120,10 +153,12 @@ class CustomerSheetFragment : Fragment() { } private fun handleResult(result: CustomerSheetResult) { - val presentPromise = presentPromise ?: run { - Log.e("StripeReactNative", "No promise found for CustomerSheet.present") - return - } + val presentPromise = + presentPromise + ?: run { + Log.e("StripeReactNative", "No promise found for CustomerSheet.present") + return + } var promiseResult = Arguments.createMap() when (result) { @@ -135,70 +170,90 @@ class CustomerSheetFragment : Fragment() { } is CustomerSheetResult.Canceled -> { promiseResult = createPaymentOptionResult(result.selection) - promiseResult.putMap("error", Arguments.createMap().also { it.putString("code", ErrorType.Canceled.toString()) }) + promiseResult.putMap( + "error", + Arguments.createMap().also { it.putString("code", ErrorType.Canceled.toString()) }, + ) } } presentPromise.resolve(promiseResult) } - fun present(timeout: Long?, promise: Promise) { + fun present( + timeout: Long?, + promise: Promise, + ) { presentPromise = promise if (timeout != null) { presentWithTimeout(timeout, promise) } - customerSheet?.present() ?: run { - promise.resolve(createMissingInitError()) - } + customerSheet?.present() ?: run { promise.resolve(createMissingInitError()) } } - private fun presentWithTimeout(timeout: Long, promise: Promise) { + private fun presentWithTimeout( + timeout: Long, + promise: Promise, + ) { var customerSheetActivity: Activity? = null var activities: MutableList = mutableListOf() - val activityLifecycleCallbacks = object : Application.ActivityLifecycleCallbacks { - override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { - customerSheetActivity = activity - activities.add(activity) - } + val activityLifecycleCallbacks = + object : Application.ActivityLifecycleCallbacks { + override fun onActivityCreated( + activity: Activity, + savedInstanceState: Bundle?, + ) { + customerSheetActivity = activity + activities.add(activity) + } - override fun onActivityStarted(activity: Activity) {} + override fun onActivityStarted(activity: Activity) {} - override fun onActivityResumed(activity: Activity) {} + override fun onActivityResumed(activity: Activity) {} - override fun onActivityPaused(activity: Activity) {} + override fun onActivityPaused(activity: Activity) {} - override fun onActivityStopped(activity: Activity) {} + override fun onActivityStopped(activity: Activity) {} - override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} + override fun onActivitySaveInstanceState( + activity: Activity, + outState: Bundle, + ) {} - override fun onActivityDestroyed(activity: Activity) { - customerSheetActivity = null - activities = mutableListOf() - context?.currentActivity?.application?.unregisterActivityLifecycleCallbacks(this) - } - } - - Handler(Looper.getMainLooper()).postDelayed({ - //customerSheetActivity?.finish() - for (a in activities) { - a.finish() + override fun onActivityDestroyed(activity: Activity) { + customerSheetActivity = null + activities = mutableListOf() + context?.currentActivity?.application?.unregisterActivityLifecycleCallbacks(this) } - }, timeout) + } + Handler(Looper.getMainLooper()) + .postDelayed( + { + // customerSheetActivity?.finish() + for (a in activities) { + a.finish() + } + }, + timeout, + ) - context?.currentActivity?.application?.registerActivityLifecycleCallbacks(activityLifecycleCallbacks) + context + ?.currentActivity + ?.application + ?.registerActivityLifecycleCallbacks(activityLifecycleCallbacks) - customerSheet?.present() ?: run { - promise.resolve(createMissingInitError()) - } + customerSheet?.present() ?: run { promise.resolve(createMissingInitError()) } } internal fun retrievePaymentOptionSelection(promise: Promise) { CoroutineScope(Dispatchers.IO).launch { runCatching { - val result = customerSheet?.retrievePaymentOptionSelection() ?: run { - promise.resolve(createMissingInitError()) - return@launch - } + val result = + customerSheet?.retrievePaymentOptionSelection() + ?: run { + promise.resolve(createMissingInitError()) + return@launch + } var promiseResult = Arguments.createMap() when (result) { is CustomerSheetResult.Failed -> { @@ -209,7 +264,12 @@ class CustomerSheetFragment : Fragment() { } is CustomerSheetResult.Canceled -> { promiseResult = createPaymentOptionResult(result.selection) - promiseResult.putMap("error", Arguments.createMap().also { it.putString("code", ErrorType.Canceled.toString()) }) + promiseResult.putMap( + "error", + Arguments.createMap().also { + it.putString("code", ErrorType.Canceled.toString()) + }, + ) } } promise.resolve(promiseResult) @@ -222,35 +282,36 @@ class CustomerSheetFragment : Fragment() { companion object { internal const val TAG = "customer_sheet_launch_fragment" - internal fun createMissingInitError(): WritableMap { - return createError(ErrorType.Failed.toString(), "No customer sheet has been initialized yet.") - } + internal fun createMissingInitError(): WritableMap = + createError(ErrorType.Failed.toString(), "No customer sheet has been initialized yet.") internal fun createDefaultBillingDetails(bundle: Bundle): PaymentSheet.BillingDetails { val addressBundle = bundle.getBundle("address") - val address = PaymentSheet.Address( - addressBundle?.getString("city"), - addressBundle?.getString("country"), - addressBundle?.getString("line1"), - addressBundle?.getString("line2"), - addressBundle?.getString("postalCode"), - addressBundle?.getString("state")) + val address = + PaymentSheet.Address( + addressBundle?.getString("city"), + addressBundle?.getString("country"), + addressBundle?.getString("line1"), + addressBundle?.getString("line2"), + addressBundle?.getString("postalCode"), + addressBundle?.getString("state"), + ) return PaymentSheet.BillingDetails( address, bundle.getString("email"), bundle.getString("name"), - bundle.getString("phone")) + bundle.getString("phone"), + ) } - internal fun createBillingDetailsCollectionConfiguration(bundle: Bundle): PaymentSheet.BillingDetailsCollectionConfiguration { - return PaymentSheet.BillingDetailsCollectionConfiguration( + internal fun createBillingDetailsCollectionConfiguration(bundle: Bundle): PaymentSheet.BillingDetailsCollectionConfiguration = + PaymentSheet.BillingDetailsCollectionConfiguration( name = mapToCollectionMode(bundle.getString("name")), phone = mapToCollectionMode(bundle.getString("phone")), email = mapToCollectionMode(bundle.getString("email")), address = mapToAddressCollectionMode(bundle.getString("address")), - attachDefaultsToPaymentMethod = bundle.getBoolean("attachDefaultsToPaymentMethod") + attachDefaultsToPaymentMethod = bundle.getBoolean("attachDefaultsToPaymentMethod"), ) - } internal fun createCustomerAdapter( context: ReactApplicationContext, @@ -264,36 +325,44 @@ class CustomerSheetFragment : Fragment() { CustomerEphemeralKey.create( customerId = customerId, ephemeralKey = customerEphemeralKeySecret, - ) - ) - } - val customerAdapter = if (setupIntentClientSecret != null) { - CustomerAdapter.create( - context, - customerEphemeralKeyProvider = ephemeralKeyProvider, - setupIntentClientSecretProvider = { - CustomerAdapter.Result.success( - setupIntentClientSecret, - ) - } - ) - } else { - CustomerAdapter.create( - context, - customerEphemeralKeyProvider = ephemeralKeyProvider, - setupIntentClientSecretProvider = null + ), ) } + val customerAdapter = + if (setupIntentClientSecret != null) { + CustomerAdapter.create( + context, + customerEphemeralKeyProvider = ephemeralKeyProvider, + setupIntentClientSecretProvider = { + CustomerAdapter.Result.success( + setupIntentClientSecret, + ) + }, + ) + } else { + CustomerAdapter.create( + context, + customerEphemeralKeyProvider = ephemeralKeyProvider, + setupIntentClientSecretProvider = null, + ) + } return ReactNativeCustomerAdapter( context = context, adapter = customerAdapter, - overridesFetchPaymentMethods = customerAdapterOverrideParams?.getBoolean("fetchPaymentMethods") ?: false, - overridesAttachPaymentMethod = customerAdapterOverrideParams?.getBoolean("attachPaymentMethod") ?: false, - overridesDetachPaymentMethod = customerAdapterOverrideParams?.getBoolean("detachPaymentMethod") ?: false, - overridesSetSelectedPaymentOption = customerAdapterOverrideParams?.getBoolean("setSelectedPaymentOption") ?: false, - overridesFetchSelectedPaymentOption = customerAdapterOverrideParams?.getBoolean("fetchSelectedPaymentOption") ?: false, - overridesSetupIntentClientSecretForCustomerAttach = customerAdapterOverrideParams?.getBoolean("setupIntentClientSecretForCustomerAttach") ?: false + overridesFetchPaymentMethods = + customerAdapterOverrideParams?.getBoolean("fetchPaymentMethods") ?: false, + overridesAttachPaymentMethod = + customerAdapterOverrideParams?.getBoolean("attachPaymentMethod") ?: false, + overridesDetachPaymentMethod = + customerAdapterOverrideParams?.getBoolean("detachPaymentMethod") ?: false, + overridesSetSelectedPaymentOption = + customerAdapterOverrideParams?.getBoolean("setSelectedPaymentOption") ?: false, + overridesFetchSelectedPaymentOption = + customerAdapterOverrideParams?.getBoolean("fetchSelectedPaymentOption") ?: false, + overridesSetupIntentClientSecretForCustomerAttach = + customerAdapterOverrideParams?.getBoolean("setupIntentClientSecretForCustomerAttach") + ?: false, ) } @@ -302,16 +371,16 @@ class CustomerSheetFragment : Fragment() { when (selection) { is PaymentOptionSelection.GooglePay -> { - paymentOptionResult = buildResult( - selection.paymentOption.label, - selection.paymentOption.icon(), - null) + paymentOptionResult = + buildResult(selection.paymentOption.label, selection.paymentOption.icon(), null) } is PaymentOptionSelection.PaymentMethod -> { - paymentOptionResult = buildResult( - selection.paymentOption.label, - selection.paymentOption.icon(), - selection.paymentMethod) + paymentOptionResult = + buildResult( + selection.paymentOption.label, + selection.paymentOption.icon(), + selection.paymentMethod, + ) } null -> {} } @@ -319,12 +388,17 @@ class CustomerSheetFragment : Fragment() { return paymentOptionResult } - private fun buildResult(label: String, drawable: Drawable, paymentMethod: PaymentMethod?): WritableMap { + private fun buildResult( + label: String, + drawable: Drawable, + paymentMethod: PaymentMethod?, + ): WritableMap { val result = Arguments.createMap() - val paymentOption = Arguments.createMap().also { - it.putString("label", label) - it.putString("image", getBase64FromBitmap(getBitmapFromDrawable(drawable))) - } + val paymentOption = + Arguments.createMap().also { + it.putString("label", label) + it.putString("image", getBase64FromBitmap(getBitmapFromDrawable(drawable))) + } result.putMap("paymentOption", paymentOption) if (paymentMethod != null) { result.putMap("paymentMethod", mapFromPaymentMethod(paymentMethod)) diff --git a/android/src/main/java/com/reactnativestripesdk/customersheet/ReactNativeCustomerAdapter.kt b/android/src/main/java/com/reactnativestripesdk/customersheet/ReactNativeCustomerAdapter.kt index 0ae53d18b..ec13f0966 100644 --- a/android/src/main/java/com/reactnativestripesdk/customersheet/ReactNativeCustomerAdapter.kt +++ b/android/src/main/java/com/reactnativestripesdk/customersheet/ReactNativeCustomerAdapter.kt @@ -3,14 +3,13 @@ package com.reactnativestripesdk.customersheet import android.util.Log import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.WritableMap import com.reactnativestripesdk.StripeSdkModule import com.stripe.android.customersheet.CustomerAdapter import com.stripe.android.model.PaymentMethod import kotlinx.coroutines.CompletableDeferred -class ReactNativeCustomerAdapter ( +class ReactNativeCustomerAdapter( private val context: ReactApplicationContext, private val adapter: CustomerAdapter, private val overridesFetchPaymentMethods: Boolean, @@ -18,7 +17,7 @@ class ReactNativeCustomerAdapter ( private val overridesDetachPaymentMethod: Boolean, private val overridesSetSelectedPaymentOption: Boolean, private val overridesFetchSelectedPaymentOption: Boolean, - private val overridesSetupIntentClientSecretForCustomerAttach: Boolean + private val overridesSetupIntentClientSecretForCustomerAttach: Boolean, ) : CustomerAdapter by adapter { internal var fetchPaymentMethodsCallback: CompletableDeferred>? = null internal var attachPaymentMethodCallback: CompletableDeferred? = null @@ -44,9 +43,7 @@ class ReactNativeCustomerAdapter ( if (overridesAttachPaymentMethod) { CompletableDeferred().also { attachPaymentMethodCallback = it - val params = Arguments.createMap().also { - it.putString("paymentMethodId", paymentMethodId) - } + val params = Arguments.createMap().also { it.putString("paymentMethodId", paymentMethodId) } emitEvent("onCustomerAdapterAttachPaymentMethodCallback", params) val resultFromJavascript = it.await() return CustomerAdapter.Result.success(resultFromJavascript) @@ -60,9 +57,7 @@ class ReactNativeCustomerAdapter ( if (overridesDetachPaymentMethod) { CompletableDeferred().also { detachPaymentMethodCallback = it - val params = Arguments.createMap().also { - it.putString("paymentMethodId", paymentMethodId) - } + val params = Arguments.createMap().also { it.putString("paymentMethodId", paymentMethodId) } emitEvent("onCustomerAdapterDetachPaymentMethodCallback", params) val resultFromJavascript = it.await() return CustomerAdapter.Result.success(resultFromJavascript) @@ -76,9 +71,7 @@ class ReactNativeCustomerAdapter ( if (overridesSetSelectedPaymentOption) { CompletableDeferred().also { setSelectedPaymentOptionCallback = it - val params = Arguments.createMap().also { - it.putString("paymentOption", paymentOption?.id) - } + val params = Arguments.createMap().also { it.putString("paymentOption", paymentOption?.id) } emitEvent("onCustomerAdapterSetSelectedPaymentOptionCallback", params) val resultFromJavascript = it.await() return CustomerAdapter.Result.success(resultFromJavascript) @@ -99,7 +92,7 @@ class ReactNativeCustomerAdapter ( CustomerAdapter.PaymentOption.fromId(resultFromJavascript) } else { null - } + }, ) } } @@ -111,7 +104,10 @@ class ReactNativeCustomerAdapter ( if (overridesSetupIntentClientSecretForCustomerAttach) { CompletableDeferred().also { setupIntentClientSecretForCustomerAttachCallback = it - emitEvent("onCustomerAdapterSetupIntentClientSecretForCustomerAttachCallback", Arguments.createMap()) + emitEvent( + "onCustomerAdapterSetupIntentClientSecretForCustomerAttachCallback", + Arguments.createMap(), + ) val resultFromJavascript = it.await() return CustomerAdapter.Result.success(resultFromJavascript) } @@ -120,17 +116,18 @@ class ReactNativeCustomerAdapter ( return adapter.setupIntentClientSecretForCustomerAttach() } - private fun emitEvent(eventName: String, params: WritableMap) { + private fun emitEvent( + eventName: String, + params: WritableMap, + ) { val stripeSdkModule: StripeSdkModule? = context.getNativeModule(StripeSdkModule::class.java) if (stripeSdkModule == null || stripeSdkModule.eventListenerCount == 0) { Log.e( "StripeReactNative", - "Tried to call $eventName, but no callback was found. Please file an issue: https://github.com/stripe/stripe-react-native/issues" + "Tried to call $eventName, but no callback was found. Please file an issue: https://github.com/stripe/stripe-react-native/issues", ) } stripeSdkModule?.sendEvent(context, eventName, params) } } - - diff --git a/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonManager.kt b/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonManager.kt index 22df1d5ac..2166539f3 100644 --- a/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonManager.kt +++ b/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonManager.kt @@ -8,9 +8,11 @@ import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.annotations.ReactProp - -class AddToWalletButtonManager(applicationContext: Context) : SimpleViewManager() { +class AddToWalletButtonManager( + applicationContext: Context, +) : SimpleViewManager() { private val requestManager = Glide.with(applicationContext) + override fun getName() = "AddToWalletButton" override fun onDropViewInstance(view: AddToWalletButtonView) { @@ -23,33 +25,44 @@ class AddToWalletButtonManager(applicationContext: Context) : SimpleViewManager< view.onAfterUpdateTransaction() } - override fun createViewInstance(reactContext: ThemedReactContext): AddToWalletButtonView { - return AddToWalletButtonView(reactContext, requestManager) - } + override fun createViewInstance(reactContext: ThemedReactContext): AddToWalletButtonView = + AddToWalletButtonView(reactContext, requestManager) - override fun getExportedCustomDirectEventTypeConstants(): MutableMap { - return MapBuilder.of( - AddToWalletCompleteEvent.EVENT_NAME, MapBuilder.of("registrationName", "onCompleteAction") + override fun getExportedCustomDirectEventTypeConstants(): MutableMap = + MapBuilder.of( + AddToWalletCompleteEvent.EVENT_NAME, + MapBuilder.of("registrationName", "onCompleteAction"), ) - } @ReactProp(name = "androidAssetSource") - fun source(view: AddToWalletButtonView, source: ReadableMap) { + fun source( + view: AddToWalletButtonView, + source: ReadableMap, + ) { view.setSourceMap(source) } @ReactProp(name = "cardDetails") - fun cardDetails(view: AddToWalletButtonView, cardDetails: ReadableMap) { + fun cardDetails( + view: AddToWalletButtonView, + cardDetails: ReadableMap, + ) { view.setCardDetails(cardDetails) } @ReactProp(name = "ephemeralKey") - fun ephemeralKey(view: AddToWalletButtonView, ephemeralKey: ReadableMap) { + fun ephemeralKey( + view: AddToWalletButtonView, + ephemeralKey: ReadableMap, + ) { view.setEphemeralKey(ephemeralKey) } @ReactProp(name = "token") - fun token(view: AddToWalletButtonView, token: ReadableMap?) { + fun token( + view: AddToWalletButtonView, + token: ReadableMap?, + ) { view.setToken(token) } } diff --git a/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonView.kt b/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonView.kt index 653c29f3a..f33d98562 100644 --- a/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonView.kt +++ b/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonView.kt @@ -20,14 +20,17 @@ import com.facebook.react.uimanager.UIManagerModule import com.facebook.react.uimanager.events.EventDispatcher import com.reactnativestripesdk.utils.createError - -class AddToWalletButtonView(private val context: ThemedReactContext, private val requestManager: RequestManager) : AppCompatImageView(context) { +class AddToWalletButtonView( + private val context: ThemedReactContext, + private val requestManager: RequestManager, +) : AppCompatImageView(context) { private var cardDetails: ReadableMap? = null private var ephemeralKey: String? = null private var sourceMap: ReadableMap? = null private var token: ReadableMap? = null - private var eventDispatcher: EventDispatcher? = context.getNativeModule(UIManagerModule::class.java)?.eventDispatcher + private var eventDispatcher: EventDispatcher? = + context.getNativeModule(UIManagerModule::class.java)?.eventDispatcher private var loadedSource: Any? = null private var heightOverride: Int = 0 private var widthOverride: Int = 0 @@ -42,17 +45,26 @@ class AddToWalletButtonView(private val context: ThemedReactContext, private val this, cardDescription, ephemeralKey, - token) - } ?: run { - dispatchEvent( - createError("Failed", "Missing parameters. `ephemeralKey` must be supplied in the props to ") + token, ) } - } ?: run { - dispatchEvent( - createError("Failed", "Missing parameters. `cardDetails.cardDescription` must be supplied in the props to ") - ) + ?: run { + dispatchEvent( + createError( + "Failed", + "Missing parameters. `ephemeralKey` must be supplied in the props to ", + ), + ) + } } + ?: run { + dispatchEvent( + createError( + "Failed", + "Missing parameters. `cardDetails.cardDescription` must be supplied in the props to ", + ), + ) + } return true } @@ -78,23 +90,38 @@ class AddToWalletButtonView(private val context: ThemedReactContext, private val requestManager .load(sourceToLoad) - .addListener(object : RequestListener { - override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean { - dispatchEvent( - createError("Failed", "Failed to load the source from $sourceToLoad") - ) - return true - } - override fun onResourceReady(resource: Drawable?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { - setImageDrawable( - RippleDrawable( - ColorStateList.valueOf(Color.parseColor("#e0e0e0")), - resource, - null)) - return true - } - }) - .centerCrop() + .addListener( + object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean, + ): Boolean { + dispatchEvent( + createError("Failed", "Failed to load the source from $sourceToLoad"), + ) + return true + } + + override fun onResourceReady( + resource: Drawable?, + model: Any?, + target: Target?, + dataSource: DataSource?, + isFirstResource: Boolean, + ): Boolean { + setImageDrawable( + RippleDrawable( + ColorStateList.valueOf(Color.parseColor("#e0e0e0")), + resource, + null, + ), + ) + return true + } + }, + ).centerCrop() .override((widthOverride * scale).toInt(), (heightOverride * scale).toInt()) .into(this) } @@ -113,7 +140,12 @@ class AddToWalletButtonView(private val context: ThemedReactContext, private val return null } - override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + override fun onSizeChanged( + w: Int, + h: Int, + oldw: Int, + oldh: Int, + ) { super.onSizeChanged(w, h, oldw, oldh) if (w > 0 && h > 0) { heightOverride = h @@ -145,11 +177,6 @@ class AddToWalletButtonView(private val context: ThemedReactContext, private val } fun dispatchEvent(error: WritableMap?) { - eventDispatcher?.dispatchEvent( - AddToWalletCompleteEvent( - id, - error - ) - ) + eventDispatcher?.dispatchEvent(AddToWalletCompleteEvent(id, error)) } } diff --git a/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletCompleteEvent.kt b/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletCompleteEvent.kt index 4219dcde0..496b10d13 100644 --- a/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletCompleteEvent.kt +++ b/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletCompleteEvent.kt @@ -1,20 +1,20 @@ package com.reactnativestripesdk.pushprovisioning + import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.Event import com.facebook.react.uimanager.events.RCTEventEmitter -internal class AddToWalletCompleteEvent constructor(viewTag: Int, private val error: WritableMap?) : Event(viewTag) { - override fun getEventName(): String { - return EVENT_NAME - } +internal class AddToWalletCompleteEvent constructor( + viewTag: Int, + private val error: WritableMap?, +) : Event(viewTag) { + override fun getEventName(): String = EVENT_NAME override fun dispatch(rctEventEmitter: RCTEventEmitter) { rctEventEmitter.receiveEvent(viewTag, eventName, serializeEventData()) } - private fun serializeEventData(): WritableMap? { - return error - } + private fun serializeEventData(): WritableMap? = error companion object { const val EVENT_NAME = "onCompleteAction" diff --git a/android/src/main/java/com/reactnativestripesdk/pushprovisioning/EphemeralKeyProvider.kt b/android/src/main/java/com/reactnativestripesdk/pushprovisioning/EphemeralKeyProvider.kt index 604897e2c..06a017033 100644 --- a/android/src/main/java/com/reactnativestripesdk/pushprovisioning/EphemeralKeyProvider.kt +++ b/android/src/main/java/com/reactnativestripesdk/pushprovisioning/EphemeralKeyProvider.kt @@ -4,32 +4,30 @@ import android.os.Parcel import android.os.Parcelable import com.stripe.android.pushProvisioning.PushProvisioningEphemeralKeyProvider +class EphemeralKeyProvider( + private val ephemeralKey: String, +) : PushProvisioningEphemeralKeyProvider { + private constructor(parcel: Parcel) : this(ephemeralKey = parcel.readString() ?: "") -class EphemeralKeyProvider(private val ephemeralKey: String) : PushProvisioningEphemeralKeyProvider { + override fun describeContents(): Int = hashCode() - private constructor(parcel: Parcel) : this( - ephemeralKey = parcel.readString() ?: "" - ) - - override fun describeContents(): Int { - return hashCode() - } - - override fun writeToParcel(dest: Parcel, flags: Int) { + override fun writeToParcel( + dest: Parcel, + flags: Int, + ) { dest.writeString(ephemeralKey) } - override fun createEphemeralKey(apiVersion: String, keyUpdateListener: com.stripe.android.pushProvisioning.EphemeralKeyUpdateListener) { + override fun createEphemeralKey( + apiVersion: String, + keyUpdateListener: com.stripe.android.pushProvisioning.EphemeralKeyUpdateListener, + ) { keyUpdateListener.onKeyUpdate(ephemeralKey) } companion object CREATOR : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): EphemeralKeyProvider { - return EphemeralKeyProvider(parcel) - } + override fun createFromParcel(parcel: Parcel): EphemeralKeyProvider = EphemeralKeyProvider(parcel) - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } + override fun newArray(size: Int): Array = arrayOfNulls(size) } } diff --git a/android/src/main/java/com/reactnativestripesdk/pushprovisioning/PushProvisioningProxy.kt b/android/src/main/java/com/reactnativestripesdk/pushprovisioning/PushProvisioningProxy.kt index 4cf63ffe6..9f1651314 100644 --- a/android/src/main/java/com/reactnativestripesdk/pushprovisioning/PushProvisioningProxy.kt +++ b/android/src/main/java/com/reactnativestripesdk/pushprovisioning/PushProvisioningProxy.kt @@ -14,37 +14,34 @@ import com.reactnativestripesdk.utils.mapError import com.stripe.android.pushProvisioning.PushProvisioningActivity import com.stripe.android.pushProvisioning.PushProvisioningActivityStarter - object PushProvisioningProxy { private const val TAG = "StripePushProvisioning" private var description = "Added by Stripe" private var tokenRequiringTokenization: ReadableMap? = null - fun getApiVersion(): String { - return try { + fun getApiVersion(): String = + try { Class.forName("com.stripe.android.pushProvisioning.PushProvisioningActivity") PushProvisioningActivity.API_VERSION } catch (e: Exception) { Log.e(TAG, "PushProvisioning dependency not found") "" } - } - fun isNFCEnabled(context: ReactApplicationContext): Boolean { - return if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_NFC)) { + fun isNFCEnabled(context: ReactApplicationContext): Boolean = + if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_NFC)) { val adapter = NfcAdapter.getDefaultAdapter(context) adapter?.isEnabled ?: false } else { false } - } fun invoke( - context: ReactApplicationContext, - view: AddToWalletButtonView, - cardDescription: String, - ephemeralKey: String, - token: ReadableMap? + context: ReactApplicationContext, + view: AddToWalletButtonView, + cardDescription: String, + ephemeralKey: String, + token: ReadableMap?, ) { try { Class.forName("com.stripe.android.pushProvisioning.PushProvisioningActivityStarter") @@ -52,65 +49,84 @@ object PushProvisioningProxy { tokenRequiringTokenization = token createActivityEventListener(context, view) context.currentActivity?.let { - DefaultPushProvisioningProxy().beginPushProvisioning( - it, - description, - EphemeralKeyProvider(ephemeralKey) - ) - } ?: run { - view.dispatchEvent( - createError( - "Failed", - "Activity doesn't exist yet. You can safely retry.") - ) + DefaultPushProvisioningProxy() + .beginPushProvisioning(it, description, EphemeralKeyProvider(ephemeralKey)) } + ?: run { + view.dispatchEvent( + createError("Failed", "Activity doesn't exist yet. You can safely retry."), + ) + } } catch (e: Exception) { Log.e(TAG, "There was a problem using Stripe Android PushProvisioning: " + e.message) } } - fun isCardInWallet(activity: Activity, cardLastFour: String, callback: TokenCheckHandler) { + fun isCardInWallet( + activity: Activity, + cardLastFour: String, + callback: TokenCheckHandler, + ) { TapAndPayProxy.findExistingToken(activity, cardLastFour, callback) } - private fun createActivityEventListener(context: ReactApplicationContext, view: AddToWalletButtonView) { - val listener = object : BaseActivityEventListener() { - override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(activity, requestCode, resultCode, data) - if (requestCode == TapAndPayProxy.REQUEST_CODE_TOKENIZE) { - view.dispatchEvent( - if (resultCode == RESULT_OK) null else mapError("Failed", "Failed to verify identity.", null, null, null, null) - ) - } else if (requestCode == PushProvisioningActivityStarter.REQUEST_CODE) { - if (resultCode == PushProvisioningActivity.RESULT_OK) { - tokenRequiringTokenization?.let { tokenRequiringTokenization -> - val tokenReferenceId = tokenRequiringTokenization.getString("id") - if (tokenReferenceId.isNullOrBlank()) { - view.dispatchEvent( - mapError("Failed", "Token object passed to `` is missing the `id` field.", null, null, null, null) - ) + private fun createActivityEventListener( + context: ReactApplicationContext, + view: AddToWalletButtonView, + ) { + val listener = + object : BaseActivityEventListener() { + override fun onActivityResult( + activity: Activity, + requestCode: Int, + resultCode: Int, + data: Intent?, + ) { + super.onActivityResult(activity, requestCode, resultCode, data) + if (requestCode == TapAndPayProxy.REQUEST_CODE_TOKENIZE) { + view.dispatchEvent( + if (resultCode == RESULT_OK) { + null } else { - TapAndPayProxy.tokenize( - activity, - tokenReferenceId, - tokenRequiringTokenization, - description + mapError("Failed", "Failed to verify identity.", null, null, null, null) + }, + ) + } else if (requestCode == PushProvisioningActivityStarter.REQUEST_CODE) { + if (resultCode == PushProvisioningActivity.RESULT_OK) { + tokenRequiringTokenization?.let { tokenRequiringTokenization -> + val tokenReferenceId = tokenRequiringTokenization.getString("id") + if (tokenReferenceId.isNullOrBlank()) { + view.dispatchEvent( + mapError( + "Failed", + "Token object passed to `` is missing the `id` field.", + null, + null, + null, + null, + ), + ) + } else { + TapAndPayProxy.tokenize( + activity, + tokenReferenceId, + tokenRequiringTokenization, + description, + ) + } + } ?: run { view.dispatchEvent(null) } + } else if (resultCode == PushProvisioningActivity.RESULT_ERROR) { + data?.let { + val error: PushProvisioningActivityStarter.Error = + PushProvisioningActivityStarter.Error.fromIntent(data) + view.dispatchEvent( + mapError(error.code.toString(), error.message, null, null, null, null), ) } - } ?: run { - view.dispatchEvent(null) - } - } else if (resultCode == PushProvisioningActivity.RESULT_ERROR) { - data?.let { - val error: PushProvisioningActivityStarter.Error = PushProvisioningActivityStarter.Error.fromIntent(data) - view.dispatchEvent( - mapError(error.code.toString(), error.message, null, null, null, null) - ) } } } } - } context.addActivityEventListener(listener) } } @@ -119,11 +135,11 @@ class DefaultPushProvisioningProxy { fun beginPushProvisioning( activity: Activity, description: String, - provider: EphemeralKeyProvider + provider: EphemeralKeyProvider, ) { - PushProvisioningActivityStarter( - activity, - PushProvisioningActivityStarter.Args(description, provider, false) - ).startForResult() + PushProvisioningActivityStarter( + activity, + PushProvisioningActivityStarter.Args(description, provider, false), + ).startForResult() } } diff --git a/android/src/main/java/com/reactnativestripesdk/pushprovisioning/TapAndPayProxy.kt b/android/src/main/java/com/reactnativestripesdk/pushprovisioning/TapAndPayProxy.kt index 7ed5cd41a..22267271e 100644 --- a/android/src/main/java/com/reactnativestripesdk/pushprovisioning/TapAndPayProxy.kt +++ b/android/src/main/java/com/reactnativestripesdk/pushprovisioning/TapAndPayProxy.kt @@ -5,22 +5,21 @@ import android.util.Log import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.WritableMap import com.facebook.react.bridge.WritableNativeMap -import com.reactnativestripesdk.utils.createError import com.google.android.gms.tasks.Task +import com.reactnativestripesdk.utils.createError -typealias TokenCheckHandler = (isCardInWallet: Boolean, token: WritableMap?, error: WritableMap?) -> Unit +typealias TokenCheckHandler = + (isCardInWallet: Boolean, token: WritableMap?, error: WritableMap?) -> Unit object TapAndPayProxy { private const val TAG = "StripeTapAndPay" private var tapAndPayClient: Any? = null const val REQUEST_CODE_TOKENIZE = 90909 - private fun getTapandPayTokens(activity: Activity): Task>? { - return try { + private fun getTapandPayTokens(activity: Activity): Task>? = + try { val tapAndPayClass = Class.forName("com.google.android.gms.tapandpay.TapAndPay") - val getClientMethod = tapAndPayClass.getMethod( - "getClient", - Activity::class.java) + val getClientMethod = tapAndPayClass.getMethod("getClient", Activity::class.java) val client = getClientMethod.invoke(null, activity) val tapAndPayClientClass = Class.forName("com.google.android.gms.tapandpay.TapAndPayClient") @@ -31,21 +30,28 @@ object TapAndPayProxy { Log.e(TAG, "There was a problem listing tokens with Google TapAndPay: " + e.message) null } - } - internal fun isTokenInWallet(token: Any, newLastFour: String): Boolean { - return try { - val getFpanLastFourMethod = Class.forName("com.google.android.gms.tapandpay.issuer.TokenInfo").getMethod("getFpanLastFour") + internal fun isTokenInWallet( + token: Any, + newLastFour: String, + ): Boolean = + try { + val getFpanLastFourMethod = + Class + .forName("com.google.android.gms.tapandpay.issuer.TokenInfo") + .getMethod("getFpanLastFour") val existingFpanLastFour = getFpanLastFourMethod.invoke(token) as String existingFpanLastFour == newLastFour } catch (e: Exception) { Log.e(TAG, "There was a problem getting the FPAN with Google TapAndPay: " + e.message) false } - } - - fun findExistingToken(activity: Activity, newCardLastFour: String, callback: TokenCheckHandler) { + fun findExistingToken( + activity: Activity, + newCardLastFour: String, + callback: TokenCheckHandler, + ) { val tokens = getTapandPayTokens(activity) if (tokens == null) { callback(false, null, createError("Failed", "Google TapAndPay dependency not found.")) @@ -56,7 +62,7 @@ object TapAndPayProxy { if (task.isSuccessful) { for (token in task.result) { if (isTokenInWallet(token, newCardLastFour)) { - callback(true, mapFromTokenInfo(token), null) + callback(true, mapFromTokenInfo(token), null) return@addOnCompleteListener } } @@ -67,17 +73,35 @@ object TapAndPayProxy { } } - fun tokenize(activity: Activity, tokenReferenceId: String, token: ReadableMap, cardDescription: String) { + fun tokenize( + activity: Activity, + tokenReferenceId: String, + token: ReadableMap, + cardDescription: String, + ) { try { val tapAndPayClientClass = Class.forName("com.google.android.gms.tapandpay.TapAndPayClient") - val tokenizeMethod = tapAndPayClientClass::class.java.getMethod("tokenize", Activity::class.java, String::class.java, Int::class.java, String::class.java, Int::class.java, Int::class.java) - tokenizeMethod.invoke(tapAndPayClient, - activity, - tokenReferenceId, - token.getInt("serviceProvider"), - cardDescription, - token.getInt("network"), - REQUEST_CODE_TOKENIZE) + val tokenizeMethod = + tapAndPayClientClass::class + .java + .getMethod( + "tokenize", + Activity::class.java, + String::class.java, + Int::class.java, + String::class.java, + Int::class.java, + Int::class.java, + ) + tokenizeMethod.invoke( + tapAndPayClient, + activity, + tokenReferenceId, + token.getInt("serviceProvider"), + cardDescription, + token.getInt("network"), + REQUEST_CODE_TOKENIZE, + ) } catch (e: Exception) { Log.e(TAG, "There was a problem tokenizing with Google TapAndPay: " + e.message) } @@ -88,33 +112,29 @@ object TapAndPayProxy { token?.let { try { val tokenInfoClass = Class.forName("com.google.android.gms.tapandpay.issuer.TokenInfo") - result.putString( - "id", - tokenInfoClass.getMethod("getIssuerTokenId").invoke(it) as String) + result.putString("id", tokenInfoClass.getMethod("getIssuerTokenId").invoke(it) as String) val fpan = tokenInfoClass.getMethod("getFpanLastFour").invoke(it) as String - result.putString( - "cardLastFour", - fpan) - result.putString( - "fpanLastFour", - fpan) + result.putString("cardLastFour", fpan) + result.putString("fpanLastFour", fpan) result.putString( "dpanLastFour", - tokenInfoClass.getMethod("getDpanLastFour").invoke(it) as String) - result.putString( - "issuer", - tokenInfoClass.getMethod("getIssuerName").invoke(it) as String) + tokenInfoClass.getMethod("getDpanLastFour").invoke(it) as String, + ) + result.putString("issuer", tokenInfoClass.getMethod("getIssuerName").invoke(it) as String) result.putString( "status", - mapFromTokenState(tokenInfoClass.getMethod("getTokenState").invoke(it) as Int)) - result.putInt( - "network", - tokenInfoClass.getMethod("getNetwork").invoke(it) as Int) + mapFromTokenState(tokenInfoClass.getMethod("getTokenState").invoke(it) as Int), + ) + result.putInt("network", tokenInfoClass.getMethod("getNetwork").invoke(it) as Int) result.putInt( "serviceProvider", - tokenInfoClass.getMethod("getTokenServiceProvider").invoke(it) as Int) + tokenInfoClass.getMethod("getTokenServiceProvider").invoke(it) as Int, + ) } catch (e: Exception) { - Log.e(TAG, "There was a problem mapping the token information with Google TapAndPay: " + e.message) + Log.e( + TAG, + "There was a problem mapping the token information with Google TapAndPay: " + e.message, + ) } } return result @@ -124,12 +144,16 @@ object TapAndPayProxy { try { val tapAndPayClass = Class.forName("com.google.android.gms.tapandpay.TapAndPay") return when (status) { - tapAndPayClass.getField("TOKEN_STATE_NEEDS_IDENTITY_VERIFICATION").get(tapAndPayClass) -> "TOKEN_STATE_NEEDS_IDENTITY_VERIFICATION" + tapAndPayClass.getField("TOKEN_STATE_NEEDS_IDENTITY_VERIFICATION").get(tapAndPayClass) -> + "TOKEN_STATE_NEEDS_IDENTITY_VERIFICATION" tapAndPayClass.getField("TOKEN_STATE_PENDING").get(tapAndPayClass) -> "TOKEN_STATE_PENDING" - tapAndPayClass.getField("TOKEN_STATE_SUSPENDED").get(tapAndPayClass) -> "TOKEN_STATE_SUSPENDED" + tapAndPayClass.getField("TOKEN_STATE_SUSPENDED").get(tapAndPayClass) -> + "TOKEN_STATE_SUSPENDED" tapAndPayClass.getField("TOKEN_STATE_ACTIVE").get(tapAndPayClass) -> "TOKEN_STATE_ACTIVE" - tapAndPayClass.getField("TOKEN_STATE_FELICA_PENDING_PROVISIONING").get(tapAndPayClass) -> "TOKEN_STATE_FELICA_PENDING_PROVISIONING" - tapAndPayClass.getField("TOKEN_STATE_UNTOKENIZED").get(tapAndPayClass) -> "TOKEN_STATE_UNTOKENIZED" + tapAndPayClass.getField("TOKEN_STATE_FELICA_PENDING_PROVISIONING").get(tapAndPayClass) -> + "TOKEN_STATE_FELICA_PENDING_PROVISIONING" + tapAndPayClass.getField("TOKEN_STATE_UNTOKENIZED").get(tapAndPayClass) -> + "TOKEN_STATE_UNTOKENIZED" else -> "UNKNOWN" } } catch (e: Exception) { diff --git a/android/src/main/java/com/reactnativestripesdk/utils/Errors.kt b/android/src/main/java/com/reactnativestripesdk/utils/Errors.kt index db611efb2..40d48a689 100644 --- a/android/src/main/java/com/reactnativestripesdk/utils/Errors.kt +++ b/android/src/main/java/com/reactnativestripesdk/utils/Errors.kt @@ -10,42 +10,62 @@ import com.stripe.android.model.PaymentIntent import com.stripe.android.model.SetupIntent enum class ErrorType { - Failed, Canceled, Unknown + Failed, + Canceled, + Unknown, } enum class ConfirmPaymentErrorType { - Failed, Canceled, Unknown + Failed, + Canceled, + Unknown, } enum class CreateTokenErrorType { - Failed + Failed, } enum class ConfirmSetupIntentErrorType { - Failed, Canceled, Unknown + Failed, + Canceled, + Unknown, } enum class RetrievePaymentIntentErrorType { - Unknown + Unknown, } enum class RetrieveSetupIntentErrorType { - Unknown + Unknown, } enum class PaymentSheetErrorType { - Failed, Canceled, Timeout + Failed, + Canceled, + Timeout, } enum class GooglePayErrorType { - Failed, Canceled -} - -class PaymentSheetAppearanceException(message: String) : Exception(message) - -class PaymentSheetException(message: String) : Exception(message) - -internal fun mapError(code: String, message: String?, localizedMessage: String?, declineCode: String?, type: String?, stripeErrorCode: String?): WritableMap { + Failed, + Canceled, +} + +class PaymentSheetAppearanceException( + message: String, +) : Exception(message) + +class PaymentSheetException( + message: String, +) : Exception(message) + +internal fun mapError( + code: String, + message: String?, + localizedMessage: String?, + declineCode: String?, + type: String?, + stripeErrorCode: String?, +): WritableMap { val map: WritableMap = WritableNativeMap() val details: WritableMap = WritableNativeMap() details.putString("code", code) @@ -59,61 +79,107 @@ internal fun mapError(code: String, message: String?, localizedMessage: String?, return map } -internal fun createError(code: String, message: String?): WritableMap { - return mapError(code, message, message, null, null, null) -} +internal fun createError( + code: String, + message: String?, +): WritableMap = mapError(code, message, message, null, null, null) -internal fun createMissingActivityError(): WritableMap { - return mapError( +internal fun createMissingActivityError(): WritableMap = + mapError( "Failed", "Activity doesn't exist yet. You can safely retry this method.", null, null, null, - null) -} - -internal fun createError(code: String, error: PaymentIntent.Error?): WritableMap { - return mapError(code, error?.message, error?.message, error?.declineCode, error?.type?.code, error?.code) -} - -internal fun createError(code: String, error: SetupIntent.Error?): WritableMap { - return mapError(code, error?.message, error?.message, error?.declineCode, error?.type?.code, error?.code) -} + null, + ) -internal fun createError(code: String, error: Exception): WritableMap { - return when (error) { +internal fun createError( + code: String, + error: PaymentIntent.Error?, +): WritableMap = + mapError( + code, + error?.message, + error?.message, + error?.declineCode, + error?.type?.code, + error?.code, + ) + +internal fun createError( + code: String, + error: SetupIntent.Error?, +): WritableMap = + mapError( + code, + error?.message, + error?.message, + error?.declineCode, + error?.type?.code, + error?.code, + ) + +internal fun createError( + code: String, + error: Exception, +): WritableMap = + when (error) { is CardException -> { - mapError(code, error.message, error.localizedMessage, error.declineCode, error.stripeError?.type, error.stripeError?.code) + mapError( + code, + error.message, + error.localizedMessage, + error.declineCode, + error.stripeError?.type, + error.stripeError?.code, + ) } is InvalidRequestException -> { - mapError(code, error.message, error.localizedMessage, error.stripeError?.declineCode, error.stripeError?.type, error.stripeError?.code) + mapError( + code, + error.message, + error.localizedMessage, + error.stripeError?.declineCode, + error.stripeError?.type, + error.stripeError?.code, + ) } is AuthenticationException -> { - mapError(code, error.message, error.localizedMessage, error.stripeError?.declineCode, error.stripeError?.type, error.stripeError?.code) + mapError( + code, + error.message, + error.localizedMessage, + error.stripeError?.declineCode, + error.stripeError?.type, + error.stripeError?.code, + ) } is APIException -> { - mapError(code, error.message, error.localizedMessage, error.stripeError?.declineCode, error.stripeError?.type, error.stripeError?.code) + mapError( + code, + error.message, + error.localizedMessage, + error.stripeError?.declineCode, + error.stripeError?.type, + error.stripeError?.code, + ) } else -> mapError(code, error.message, error.localizedMessage.orEmpty(), null, null, null) } -} -internal fun createError(code: String, error: Throwable): WritableMap { +internal fun createError( + code: String, + error: Throwable, +): WritableMap { (error as? Exception)?.let { - return createError( - code, - it) + return createError(code, it) } - return mapError( - code, - error.message, - error.localizedMessage, - null, - null, - null) + return mapError(code, error.message, error.localizedMessage, null, null, null) } -internal fun createMissingInitError(): WritableMap { - return createError(ErrorType.Failed.toString(), "Stripe has not been initialized. Initialize Stripe in your app with the StripeProvider component or the initStripe method.") -} +internal fun createMissingInitError(): WritableMap = + createError( + ErrorType.Failed.toString(), + "Stripe has not been initialized. Initialize Stripe in your app with the StripeProvider component or the initStripe method.", + ) diff --git a/android/src/main/java/com/reactnativestripesdk/utils/Extensions.kt b/android/src/main/java/com/reactnativestripesdk/utils/Extensions.kt index e906849db..3d50aa3b9 100644 --- a/android/src/main/java/com/reactnativestripesdk/utils/Extensions.kt +++ b/android/src/main/java/com/reactnativestripesdk/utils/Extensions.kt @@ -32,6 +32,7 @@ fun Fragment.removeFragment(context: ReactApplicationContext) { } } -fun ReadableMap.getBooleanOr(key: String, default: Boolean): Boolean { - return if (this.hasKey(key)) this.getBoolean(key) else default -} +fun ReadableMap.getBooleanOr( + key: String, + default: Boolean, +): Boolean = if (this.hasKey(key)) this.getBoolean(key) else default diff --git a/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt b/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt index a9704ec22..c9ac1df2d 100644 --- a/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt +++ b/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt @@ -3,20 +3,45 @@ package com.reactnativestripesdk.utils import android.annotation.SuppressLint import android.os.Bundle import android.util.Log -import com.facebook.react.bridge.* -import com.stripe.android.financialconnections.analytics.FinancialConnectionsEvent +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.ReadableType +import com.facebook.react.bridge.WritableArray +import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.WritableNativeMap import com.stripe.android.PaymentAuthConfig -import com.stripe.android.model.* -import com.stripe.android.model.StripeIntent.NextActionType +import com.stripe.android.financialconnections.analytics.FinancialConnectionsEvent +import com.stripe.android.model.Address +import com.stripe.android.model.BankAccount +import com.stripe.android.model.BankAccountTokenParams +import com.stripe.android.model.Card +import com.stripe.android.model.CardBrand +import com.stripe.android.model.ConfirmPaymentIntentParams +import com.stripe.android.model.GooglePayResult +import com.stripe.android.model.MicrodepositType +import com.stripe.android.model.PaymentIntent +import com.stripe.android.model.PaymentMethod +import com.stripe.android.model.SetupIntent +import com.stripe.android.model.StripeIntent import com.stripe.android.model.StripeIntent.NextActionData +import com.stripe.android.model.StripeIntent.NextActionType +import com.stripe.android.model.Token -internal fun createResult(key: String, value: WritableMap): WritableMap { +internal fun createResult( + key: String, + value: WritableMap, +): WritableMap { val map = WritableNativeMap() map.putMap(key, value) return map } -internal fun createCanAddCardResult(canAddCard: Boolean, status: String? = null, token: WritableMap? = null): WritableNativeMap { +internal fun createCanAddCardResult( + canAddCard: Boolean, + status: String? = null, + token: WritableMap? = null, +): WritableNativeMap { val result = WritableNativeMap() val details = WritableNativeMap() result.putBoolean("canAddCard", canAddCard) @@ -30,8 +55,8 @@ internal fun createCanAddCardResult(canAddCard: Boolean, status: String? = null, return result } -internal fun mapIntentStatus(status: StripeIntent.Status?): String { - return when (status) { +internal fun mapIntentStatus(status: StripeIntent.Status?): String = + when (status) { StripeIntent.Status.Succeeded -> "Succeeded" StripeIntent.Status.RequiresPaymentMethod -> "RequiresPaymentMethod" StripeIntent.Status.RequiresConfirmation -> "RequiresConfirmation" @@ -41,24 +66,20 @@ internal fun mapIntentStatus(status: StripeIntent.Status?): String { StripeIntent.Status.RequiresCapture -> "RequiresCapture" else -> "Unknown" } -} - -internal fun mapCaptureMethod(captureMethod: PaymentIntent.CaptureMethod?): String { - return when (captureMethod) { +internal fun mapCaptureMethod(captureMethod: PaymentIntent.CaptureMethod?): String = + when (captureMethod) { PaymentIntent.CaptureMethod.Automatic -> "Automatic" PaymentIntent.CaptureMethod.Manual -> "Manual" else -> "Unknown" } -} -internal fun mapConfirmationMethod(captureMethod: PaymentIntent.ConfirmationMethod?): String { - return when (captureMethod) { +internal fun mapConfirmationMethod(captureMethod: PaymentIntent.ConfirmationMethod?): String = + when (captureMethod) { PaymentIntent.ConfirmationMethod.Automatic -> "Automatic" PaymentIntent.ConfirmationMethod.Manual -> "Manual" else -> "Unknown" } -} internal fun mapToReturnURL(urlScheme: String?): String? { if (urlScheme != null) { @@ -86,8 +107,8 @@ internal fun mapIntentShipping(shipping: PaymentIntent.Shipping): WritableMap { return map } -internal fun mapCardBrand(brand: CardBrand?): String { - return when (brand) { +internal fun mapCardBrand(brand: CardBrand?): String = + when (brand) { CardBrand.AmericanExpress -> "AmericanExpress" CardBrand.DinersClub -> "DinersClub" CardBrand.Discover -> "Discover" @@ -98,10 +119,9 @@ internal fun mapCardBrand(brand: CardBrand?): String { CardBrand.Unknown -> "Unknown" else -> "Unknown" } -} -internal fun mapPaymentMethodType(type: PaymentMethod.Type?): String { - return when (type) { +internal fun mapPaymentMethodType(type: PaymentMethod.Type?): String = + when (type) { PaymentMethod.Type.AfterpayClearpay -> "AfterpayClearpay" PaymentMethod.Type.Alipay -> "Alipay" PaymentMethod.Type.AuBecsDebit -> "AuBecsDebit" @@ -129,10 +149,9 @@ internal fun mapPaymentMethodType(type: PaymentMethod.Type?): String { PaymentMethod.Type.RevolutPay -> "RevolutPay" else -> "Unknown" } -} -internal fun mapToPaymentMethodType(type: String?): PaymentMethod.Type? { - return when (type) { +internal fun mapToPaymentMethodType(type: String?): PaymentMethod.Type? = + when (type) { "Card" -> PaymentMethod.Type.Card "Ideal" -> PaymentMethod.Type.Ideal "Alipay" -> PaymentMethod.Type.Alipay @@ -160,7 +179,6 @@ internal fun mapToPaymentMethodType(type: String?): PaymentMethod.Type? { "RevolutPay" -> PaymentMethod.Type.RevolutPay else -> null } -} internal fun mapFromBillingDetails(billingDatails: PaymentMethod.BillingDetails?): WritableMap { val details: WritableMap = WritableNativeMap() @@ -181,8 +199,8 @@ internal fun mapFromBillingDetails(billingDatails: PaymentMethod.BillingDetails? return details } -internal fun mapTokenType(type: Token.Type): String { - return when (type) { +internal fun mapTokenType(type: Token.Type): String = + when (type) { Token.Type.Account -> "Account" Token.Type.BankAccount -> "BankAccount" Token.Type.Card -> "Card" @@ -191,26 +209,23 @@ internal fun mapTokenType(type: Token.Type): String { Token.Type.Pii -> "Pii" else -> "Unknown" } -} -internal fun mapFromBankAccountType(type: BankAccount.Type?): String { - return when (type) { +internal fun mapFromBankAccountType(type: BankAccount.Type?): String = + when (type) { BankAccount.Type.Company -> "Company" BankAccount.Type.Individual -> "Individual" else -> "Unknown" } -} -internal fun mapToBankAccountType(type: String?): BankAccountTokenParams.Type { - return when (type) { +internal fun mapToBankAccountType(type: String?): BankAccountTokenParams.Type = + when (type) { "Company" -> BankAccountTokenParams.Type.Company "Individual" -> BankAccountTokenParams.Type.Individual else -> BankAccountTokenParams.Type.Individual } -} -internal fun mapFromBankAccountStatus(status: BankAccount.Status?): String { - return when (status) { +internal fun mapFromBankAccountStatus(status: BankAccount.Status?): String = + when (status) { BankAccount.Status.Errored -> "Errored" BankAccount.Status.New -> "New" BankAccount.Status.Validated -> "Validated" @@ -218,7 +233,6 @@ internal fun mapFromBankAccountStatus(status: BankAccount.Status?): String { BankAccount.Status.Verified -> "Verified" else -> "Unknown" } -} internal fun mapFromBankAccount(bankAccount: BankAccount?): WritableMap? { if (bankAccount == null) { @@ -229,7 +243,10 @@ internal fun mapFromBankAccount(bankAccount: BankAccount?): WritableMap? { bankAccountMap.putString("id", bankAccount.id) bankAccountMap.putString("bankName", bankAccount.bankName) bankAccountMap.putString("accountHolderName", bankAccount.accountHolderName) - bankAccountMap.putString("accountHolderType", mapFromBankAccountType(bankAccount.accountHolderType)) + bankAccountMap.putString( + "accountHolderType", + mapFromBankAccountType(bankAccount.accountHolderType), + ) bankAccountMap.putString("currency", bankAccount.currency) bankAccountMap.putString("country", bankAccount.countryCode) bankAccountMap.putString("routingNumber", bankAccount.routingNumber) @@ -240,37 +257,33 @@ internal fun mapFromBankAccount(bankAccount: BankAccount?): WritableMap? { return bankAccountMap } -internal fun mapToUSBankAccountHolderType(type: String?): PaymentMethod.USBankAccount.USBankAccountHolderType { - return when (type) { +internal fun mapToUSBankAccountHolderType(type: String?): PaymentMethod.USBankAccount.USBankAccountHolderType = + when (type) { "Company" -> PaymentMethod.USBankAccount.USBankAccountHolderType.COMPANY "Individual" -> PaymentMethod.USBankAccount.USBankAccountHolderType.INDIVIDUAL else -> PaymentMethod.USBankAccount.USBankAccountHolderType.INDIVIDUAL } -} -internal fun mapFromUSBankAccountHolderType(type: PaymentMethod.USBankAccount.USBankAccountHolderType?): String { - return when (type) { +internal fun mapFromUSBankAccountHolderType(type: PaymentMethod.USBankAccount.USBankAccountHolderType?): String = + when (type) { PaymentMethod.USBankAccount.USBankAccountHolderType.COMPANY -> "Company" PaymentMethod.USBankAccount.USBankAccountHolderType.INDIVIDUAL -> "Individual" else -> "Unknown" } -} -internal fun mapToUSBankAccountType(type: String?): PaymentMethod.USBankAccount.USBankAccountType { - return when (type) { +internal fun mapToUSBankAccountType(type: String?): PaymentMethod.USBankAccount.USBankAccountType = + when (type) { "Savings" -> PaymentMethod.USBankAccount.USBankAccountType.SAVINGS "Checking" -> PaymentMethod.USBankAccount.USBankAccountType.CHECKING else -> PaymentMethod.USBankAccount.USBankAccountType.CHECKING } -} -internal fun mapFromUSBankAccountType(type: PaymentMethod.USBankAccount.USBankAccountType?): String { - return when (type) { +internal fun mapFromUSBankAccountType(type: PaymentMethod.USBankAccount.USBankAccountType?): String = + when (type) { PaymentMethod.USBankAccount.USBankAccountType.CHECKING -> "Checking" PaymentMethod.USBankAccount.USBankAccountType.SAVINGS -> "Savings" else -> "Unknown" } -} internal fun mapFromCard(card: Card?): WritableMap? { val cardMap: WritableMap = WritableNativeMap() @@ -285,17 +298,9 @@ internal fun mapFromCard(card: Card?): WritableMap? { cardMap.putString("brand", mapCardBrand(card.brand)) cardMap.putString("currency", card.currency) - (card.expMonth)?.let { - cardMap.putInt("expMonth", it) - } ?: run { - cardMap.putNull("expMonth") - } + (card.expMonth)?.let { cardMap.putInt("expMonth", it) } ?: run { cardMap.putNull("expMonth") } - (card.expYear)?.let { - cardMap.putInt("expYear", it) - } ?: run { - cardMap.putNull("expYear") - } + (card.expYear)?.let { cardMap.putInt("expYear", it) } ?: run { cardMap.putNull("expYear") } cardMap.putString("id", card.id) cardMap.putString("last4", card.last4) @@ -314,7 +319,6 @@ internal fun mapFromCard(card: Card?): WritableMap? { return cardMap } - internal fun mapFromToken(token: Token): WritableMap { val tokenMap: WritableMap = WritableNativeMap() tokenMap.putString("id", token.id) @@ -336,66 +340,102 @@ internal fun mapFromPaymentMethod(paymentMethod: PaymentMethod): WritableMap { pm.putBoolean("livemode", paymentMethod.liveMode) pm.putString("customerId", paymentMethod.customerId) pm.putMap("billingDetails", mapFromBillingDetails(paymentMethod.billingDetails)) - pm.putMap("Card", WritableNativeMap().also { - it.putString("brand", mapCardBrand(paymentMethod.card?.brand)) - it.putString("country", paymentMethod.card?.country) - paymentMethod.card?.expiryYear?.let { year -> - it.putInt("expYear", year) - } - paymentMethod.card?.expiryMonth?.let { month -> - it.putInt("expMonth", month) - } - it.putString("funding", paymentMethod.card?.funding) - it.putString("last4", paymentMethod.card?.last4) - it.putString("fingerprint", paymentMethod.card?.fingerprint) - it.putString("preferredNetwork", paymentMethod.card?.networks?.preferred) - it.putArray("availableNetworks", paymentMethod.card?.networks?.available?.toList() as? ReadableArray) - it.putMap("threeDSecureUsage", WritableNativeMap().also { threeDSecureUsageMap -> - threeDSecureUsageMap.putBoolean("isSupported", paymentMethod.card?.threeDSecureUsage?.isSupported ?: false) - }) - }) - pm.putMap("SepaDebit", WritableNativeMap().also { - it.putString("bankCode", paymentMethod.sepaDebit?.bankCode) - it.putString("country", paymentMethod.sepaDebit?.country) - it.putString("fingerprint", paymentMethod.sepaDebit?.fingerprint) - it.putString("last4", paymentMethod.sepaDebit?.branchCode) - }) - pm.putMap("BacsDebit", WritableNativeMap().also { - it.putString("fingerprint", paymentMethod.bacsDebit?.fingerprint) - it.putString("last4", paymentMethod.bacsDebit?.last4) - it.putString("sortCode", paymentMethod.bacsDebit?.sortCode) - }) - pm.putMap("AuBecsDebit", + pm.putMap( + "Card", + WritableNativeMap().also { + it.putString("brand", mapCardBrand(paymentMethod.card?.brand)) + it.putString("country", paymentMethod.card?.country) + paymentMethod.card?.expiryYear?.let { year -> it.putInt("expYear", year) } + paymentMethod.card?.expiryMonth?.let { month -> it.putInt("expMonth", month) } + it.putString("funding", paymentMethod.card?.funding) + it.putString("last4", paymentMethod.card?.last4) + it.putString("fingerprint", paymentMethod.card?.fingerprint) + it.putString("preferredNetwork", paymentMethod.card?.networks?.preferred) + it.putArray( + "availableNetworks", + paymentMethod.card + ?.networks + ?.available + ?.toList() as? ReadableArray, + ) + it.putMap( + "threeDSecureUsage", + WritableNativeMap().also { threeDSecureUsageMap -> + threeDSecureUsageMap.putBoolean( + "isSupported", + paymentMethod.card?.threeDSecureUsage?.isSupported ?: false, + ) + }, + ) + }, + ) + pm.putMap( + "SepaDebit", + WritableNativeMap().also { + it.putString("bankCode", paymentMethod.sepaDebit?.bankCode) + it.putString("country", paymentMethod.sepaDebit?.country) + it.putString("fingerprint", paymentMethod.sepaDebit?.fingerprint) + it.putString("last4", paymentMethod.sepaDebit?.branchCode) + }, + ) + pm.putMap( + "BacsDebit", + WritableNativeMap().also { + it.putString("fingerprint", paymentMethod.bacsDebit?.fingerprint) + it.putString("last4", paymentMethod.bacsDebit?.last4) + it.putString("sortCode", paymentMethod.bacsDebit?.sortCode) + }, + ) + pm.putMap( + "AuBecsDebit", WritableNativeMap().also { it.putString("bsbNumber", paymentMethod.bacsDebit?.sortCode) it.putString("fingerprint", paymentMethod.bacsDebit?.fingerprint) it.putString("last4", paymentMethod.bacsDebit?.last4) - }) - pm.putMap("Sofort", WritableNativeMap().also { - it.putString("country", paymentMethod.sofort?.country) - }) - pm.putMap("Ideal", WritableNativeMap().also { - it.putString("bankName", paymentMethod.ideal?.bank) - it.putString("bankIdentifierCode", paymentMethod.ideal?.bankIdentifierCode) - }) - pm.putMap("Fpx", WritableNativeMap().also { - it.putString("accountHolderType", paymentMethod.fpx?.accountHolderType) - it.putString("bank", paymentMethod.fpx?.bank) - }) - pm.putMap("Upi", WritableNativeMap().also { - it.putString("vpa", paymentMethod.upi?.vpa) - }) - pm.putMap("USBankAccount", WritableNativeMap().also { - it.putString("routingNumber", paymentMethod.usBankAccount?.routingNumber) - it.putString("accountType", mapFromUSBankAccountType(paymentMethod.usBankAccount?.accountType)) - it.putString("accountHolderType", mapFromUSBankAccountHolderType(paymentMethod.usBankAccount?.accountHolderType)) - it.putString("last4", paymentMethod.usBankAccount?.last4) - it.putString("bankName", paymentMethod.usBankAccount?.bankName) - it.putString("linkedAccount", paymentMethod.usBankAccount?.linkedAccount) - it.putString("fingerprint", paymentMethod.usBankAccount?.fingerprint) - it.putString("preferredNetworks", paymentMethod.usBankAccount?.networks?.preferred) - it.putArray("supportedNetworks", paymentMethod.usBankAccount?.networks?.supported as? ReadableArray) - }) + }, + ) + pm.putMap( + "Sofort", + WritableNativeMap().also { it.putString("country", paymentMethod.sofort?.country) }, + ) + pm.putMap( + "Ideal", + WritableNativeMap().also { + it.putString("bankName", paymentMethod.ideal?.bank) + it.putString("bankIdentifierCode", paymentMethod.ideal?.bankIdentifierCode) + }, + ) + pm.putMap( + "Fpx", + WritableNativeMap().also { + it.putString("accountHolderType", paymentMethod.fpx?.accountHolderType) + it.putString("bank", paymentMethod.fpx?.bank) + }, + ) + pm.putMap("Upi", WritableNativeMap().also { it.putString("vpa", paymentMethod.upi?.vpa) }) + pm.putMap( + "USBankAccount", + WritableNativeMap().also { + it.putString("routingNumber", paymentMethod.usBankAccount?.routingNumber) + it.putString( + "accountType", + mapFromUSBankAccountType(paymentMethod.usBankAccount?.accountType), + ) + it.putString( + "accountHolderType", + mapFromUSBankAccountHolderType(paymentMethod.usBankAccount?.accountHolderType), + ) + it.putString("last4", paymentMethod.usBankAccount?.last4) + it.putString("bankName", paymentMethod.usBankAccount?.bankName) + it.putString("linkedAccount", paymentMethod.usBankAccount?.linkedAccount) + it.putString("fingerprint", paymentMethod.usBankAccount?.fingerprint) + it.putString("preferredNetworks", paymentMethod.usBankAccount?.networks?.preferred) + it.putArray( + "supportedNetworks", + paymentMethod.usBankAccount?.networks?.supported as? ReadableArray, + ) + }, + ) return pm } @@ -406,11 +446,10 @@ internal fun mapFromPaymentIntentResult(paymentIntent: PaymentIntent): WritableM map.putString("clientSecret", paymentIntent.clientSecret) map.putBoolean("livemode", paymentIntent.isLiveMode) map.putString("paymentMethodId", paymentIntent.paymentMethodId) - map.putMap("paymentMethod", paymentIntent.paymentMethod?.let { - mapFromPaymentMethod(it) - } ?: run { - null - }) + map.putMap( + "paymentMethod", + paymentIntent.paymentMethod?.let { mapFromPaymentMethod(it) } ?: run { null }, + ) map.putString("receiptEmail", paymentIntent.receiptEmail) map.putString("currency", paymentIntent.currency) map.putString("status", mapIntentStatus(paymentIntent.status)) @@ -419,7 +458,10 @@ internal fun mapFromPaymentIntentResult(paymentIntent: PaymentIntent): WritableM map.putString("created", convertToUnixTimestamp(paymentIntent.created)) map.putString("captureMethod", mapCaptureMethod(paymentIntent.captureMethod)) map.putString("confirmationMethod", mapConfirmationMethod(paymentIntent.confirmationMethod)) - map.putMap("nextAction", mapNextAction(paymentIntent.nextActionType, paymentIntent.nextActionData)) + map.putMap( + "nextAction", + mapNextAction(paymentIntent.nextActionType, paymentIntent.nextActionData), + ) map.putNull("lastPaymentError") map.putNull("shipping") map.putNull("amount") @@ -438,28 +480,26 @@ internal fun mapFromPaymentIntentResult(paymentIntent: PaymentIntent): WritableM map.putMap("lastPaymentError", paymentError) } - paymentIntent.shipping?.let { - map.putMap("shipping", mapIntentShipping(it)) - } + paymentIntent.shipping?.let { map.putMap("shipping", mapIntentShipping(it)) } - paymentIntent.amount?.let { - map.putDouble("amount", it.toDouble()) - } + paymentIntent.amount?.let { map.putDouble("amount", it.toDouble()) } map.putString("canceledAt", convertToUnixTimestamp(paymentIntent.canceledAt)) return map } @SuppressLint("RestrictedApi") -internal fun mapFromMicrodepositType(type: MicrodepositType): String { - return when (type) { +internal fun mapFromMicrodepositType(type: MicrodepositType): String = + when (type) { MicrodepositType.AMOUNTS -> "amounts" MicrodepositType.DESCRIPTOR_CODE -> "descriptorCode" else -> "unknown" } -} @SuppressLint("RestrictedApi") -internal fun mapNextAction(type: NextActionType?, data: NextActionData?): WritableNativeMap? { +internal fun mapNextAction( + type: NextActionType?, + data: NextActionData?, +): WritableNativeMap? { val nextActionMap = WritableNativeMap() when (type) { NextActionType.RedirectToUrl -> { @@ -493,7 +533,12 @@ internal fun mapNextAction(type: NextActionType?, data: NextActionData?): Writab NextActionType.AlipayRedirect -> { // TODO: Can't access, private return null } - NextActionType.CashAppRedirect, NextActionType.BlikAuthorize, NextActionType.UseStripeSdk, NextActionType.UpiAwaitNotification, null -> { + NextActionType.CashAppRedirect, + NextActionType.BlikAuthorize, + NextActionType.UseStripeSdk, + NextActionType.UpiAwaitNotification, + null, + -> { return null } NextActionType.DisplayBoletoDetails -> { @@ -524,8 +569,8 @@ internal fun mapNextAction(type: NextActionType?, data: NextActionData?): Writab return nextActionMap } -internal fun mapFromPaymentIntentLastErrorType(errorType: PaymentIntent.Error.Type?): String? { - return when (errorType) { +internal fun mapFromPaymentIntentLastErrorType(errorType: PaymentIntent.Error.Type?): String? = + when (errorType) { PaymentIntent.Error.Type.ApiConnectionError -> "api_connection_error" PaymentIntent.Error.Type.AuthenticationError -> "authentication_error" PaymentIntent.Error.Type.ApiError -> "api_error" @@ -535,10 +580,9 @@ internal fun mapFromPaymentIntentLastErrorType(errorType: PaymentIntent.Error.Ty PaymentIntent.Error.Type.RateLimitError -> "rate_limit_error" else -> null } -} -internal fun mapFromSetupIntentLastErrorType(errorType: SetupIntent.Error.Type?): String? { - return when (errorType) { +internal fun mapFromSetupIntentLastErrorType(errorType: SetupIntent.Error.Type?): String? = + when (errorType) { SetupIntent.Error.Type.ApiConnectionError -> "api_connection_error" SetupIntent.Error.Type.AuthenticationError -> "authentication_error" SetupIntent.Error.Type.ApiError -> "api_error" @@ -548,15 +592,20 @@ internal fun mapFromSetupIntentLastErrorType(errorType: SetupIntent.Error.Type?) SetupIntent.Error.Type.RateLimitError -> "rate_limit_error" else -> null } -} -fun getValOr(map: ReadableMap?, key: String, default: String? = ""): String? { - return map?.let { +fun getValOr( + map: ReadableMap?, + key: String, + default: String? = "", +): String? = + map?.let { if (it.hasKey(key)) it.getString(key) else default } ?: default -} -internal fun mapToAddress(addressMap: ReadableMap?, cardAddress: Address?): Address { +internal fun mapToAddress( + addressMap: ReadableMap?, + cardAddress: Address?, +): Address { val address = Address.Builder() addressMap?.let { @@ -581,12 +630,15 @@ internal fun mapToAddress(addressMap: ReadableMap?, cardAddress: Address?): Addr return address.build() } -internal fun mapToBillingDetails(billingDetails: ReadableMap?, cardAddress: Address?): PaymentMethod.BillingDetails? { +internal fun mapToBillingDetails( + billingDetails: ReadableMap?, + cardAddress: Address?, +): PaymentMethod.BillingDetails? { if (billingDetails == null && cardAddress == null) { return null } val address = mapToAddress(getMapOrNull(billingDetails, "address"), cardAddress) - val paymentMethodBillingDetailsBuilder = PaymentMethod.BillingDetails.Builder() + val paymentMethodBillingDetailsBuilder = PaymentMethod.BillingDetails.Builder() if (billingDetails != null) { paymentMethodBillingDetailsBuilder @@ -599,9 +651,7 @@ internal fun mapToBillingDetails(billingDetails: ReadableMap?, cardAddress: Addr return paymentMethodBillingDetailsBuilder.build() } -internal fun mapToMetadata(metadata: ReadableMap?): Map? { - return metadata?.toHashMap()?.mapValues { it.value.toString() } -} +internal fun mapToMetadata(metadata: ReadableMap?): Map? = metadata?.toHashMap()?.mapValues { it.value.toString() } internal fun mapToShippingDetails(shippingDetails: ReadableMap?): ConfirmPaymentIntentParams.Shipping? { if (shippingDetails == null) { @@ -612,29 +662,31 @@ internal fun mapToShippingDetails(shippingDetails: ReadableMap?): ConfirmPayment return ConfirmPaymentIntentParams.Shipping( name = getValOr(shippingDetails, "name") ?: "", - address = address + address = address, ) } -private fun getStringOrNull(map: ReadableMap?, key: String): String? { - return if (map?.hasKey(key) == true) map.getString(key) else null -} +private fun getStringOrNull( + map: ReadableMap?, + key: String, +): String? = if (map?.hasKey(key) == true) map.getString(key) else null -fun getIntOrNull(map: ReadableMap?, key: String): Int? { - return if (map?.hasKey(key) == true) map.getInt(key) else null -} +fun getIntOrNull( + map: ReadableMap?, + key: String, +): Int? = if (map?.hasKey(key) == true) map.getInt(key) else null -fun getMapOrNull(map: ReadableMap?, key: String): ReadableMap? { - return if (map?.hasKey(key) == true) map.getMap(key) else null -} +fun getMapOrNull( + map: ReadableMap?, + key: String, +): ReadableMap? = if (map?.hasKey(key) == true) map.getMap(key) else null -fun getBooleanOrFalse(map: ReadableMap?, key: String): Boolean { - return if (map?.hasKey(key) == true) map.getBoolean(key) else false -} +fun getBooleanOrFalse( + map: ReadableMap?, + key: String, +): Boolean = if (map?.hasKey(key) == true) map.getBoolean(key) else false -private fun convertToUnixTimestamp(timestamp: Long): String { - return (timestamp * 1000).toString() -} +private fun convertToUnixTimestamp(timestamp: Long): String = (timestamp * 1000).toString() fun mapToUICustomization(params: ReadableMap): PaymentAuthConfig.Stripe3ds2UiCustomization { val labelCustomization = getMapOrNull(params, "label") @@ -656,35 +708,35 @@ fun mapToUICustomization(params: ReadableMap): PaymentAuthConfig.Stripe3ds2UiCus val continueButtonCustomizationBuilder = PaymentAuthConfig.Stripe3ds2ButtonCustomization.Builder() val resendButtonCustomizationBuilder = PaymentAuthConfig.Stripe3ds2ButtonCustomization.Builder() - getStringOrNull(labelCustomization,"headingTextColor")?.let { + getStringOrNull(labelCustomization, "headingTextColor")?.let { labelCustomizationBuilder.setHeadingTextColor(it) } - getStringOrNull(labelCustomization,"textColor")?.let { + getStringOrNull(labelCustomization, "textColor")?.let { labelCustomizationBuilder.setTextColor(it) } - getIntOrNull(labelCustomization,"headingFontSize")?.let { + getIntOrNull(labelCustomization, "headingFontSize")?.let { labelCustomizationBuilder.setHeadingTextFontSize(it) } - getIntOrNull(labelCustomization,"textFontSize")?.let { + getIntOrNull(labelCustomization, "textFontSize")?.let { labelCustomizationBuilder.setTextFontSize(it) } - getStringOrNull(navigationBarCustomization,"headerText")?.let { + getStringOrNull(navigationBarCustomization, "headerText")?.let { toolbarCustomizationBuilder.setHeaderText(it) } - getStringOrNull(navigationBarCustomization,"buttonText")?.let { + getStringOrNull(navigationBarCustomization, "buttonText")?.let { toolbarCustomizationBuilder.setButtonText(it) } - getStringOrNull(navigationBarCustomization,"textColor")?.let { + getStringOrNull(navigationBarCustomization, "textColor")?.let { toolbarCustomizationBuilder.setTextColor(it) } - getStringOrNull(navigationBarCustomization,"statusBarColor")?.let { + getStringOrNull(navigationBarCustomization, "statusBarColor")?.let { toolbarCustomizationBuilder.setStatusBarColor(it) } - getStringOrNull(navigationBarCustomization,"backgroundColor")?.let { + getStringOrNull(navigationBarCustomization, "backgroundColor")?.let { toolbarCustomizationBuilder.setBackgroundColor(it) } - getIntOrNull(navigationBarCustomization,"textFontSize")?.let { + getIntOrNull(navigationBarCustomization, "textFontSize")?.let { toolbarCustomizationBuilder.setTextFontSize(it) } @@ -774,39 +826,29 @@ fun mapToUICustomization(params: ReadableMap): PaymentAuthConfig.Stripe3ds2UiCus resendButtonCustomizationBuilder.setTextFontSize(it) } - - - val uiCustomization = PaymentAuthConfig.Stripe3ds2UiCustomization.Builder() - .setLabelCustomization( - labelCustomizationBuilder.build() - ) - .setToolbarCustomization( - toolbarCustomizationBuilder.build() - ) - .setButtonCustomization( - submitButtonCustomizationBuilder.build(), - PaymentAuthConfig.Stripe3ds2UiCustomization.ButtonType.SUBMIT - ) - .setButtonCustomization( - continueButtonCustomizationBuilder.build(), - PaymentAuthConfig.Stripe3ds2UiCustomization.ButtonType.CONTINUE - ) - .setButtonCustomization( - nextButtonCustomizationBuilder.build(), - PaymentAuthConfig.Stripe3ds2UiCustomization.ButtonType.SELECT - ) - .setButtonCustomization( - cancelButtonCustomizationBuilder.build(), - PaymentAuthConfig.Stripe3ds2UiCustomization.ButtonType.CANCEL - ) - .setButtonCustomization( - resendButtonCustomizationBuilder.build(), - PaymentAuthConfig.Stripe3ds2UiCustomization.ButtonType.RESEND - ) - - getStringOrNull(params, "accentColor")?.let { - uiCustomization.setAccentColor(it) - } + val uiCustomization = + PaymentAuthConfig.Stripe3ds2UiCustomization + .Builder() + .setLabelCustomization(labelCustomizationBuilder.build()) + .setToolbarCustomization(toolbarCustomizationBuilder.build()) + .setButtonCustomization( + submitButtonCustomizationBuilder.build(), + PaymentAuthConfig.Stripe3ds2UiCustomization.ButtonType.SUBMIT, + ).setButtonCustomization( + continueButtonCustomizationBuilder.build(), + PaymentAuthConfig.Stripe3ds2UiCustomization.ButtonType.CONTINUE, + ).setButtonCustomization( + nextButtonCustomizationBuilder.build(), + PaymentAuthConfig.Stripe3ds2UiCustomization.ButtonType.SELECT, + ).setButtonCustomization( + cancelButtonCustomizationBuilder.build(), + PaymentAuthConfig.Stripe3ds2UiCustomization.ButtonType.CANCEL, + ).setButtonCustomization( + resendButtonCustomizationBuilder.build(), + PaymentAuthConfig.Stripe3ds2UiCustomization.ButtonType.RESEND, + ) + + getStringOrNull(params, "accentColor")?.let { uiCustomization.setAccentColor(it) } return uiCustomization.build() } @@ -820,11 +862,10 @@ internal fun mapFromSetupIntentResult(setupIntent: SetupIntent): WritableMap { map.putBoolean("livemode", setupIntent.isLiveMode) map.putString("clientSecret", setupIntent.clientSecret) map.putString("paymentMethodId", setupIntent.paymentMethodId) - map.putMap("paymentMethod", setupIntent.paymentMethod?.let { - mapFromPaymentMethod(it) - } ?: run { - null - }) + map.putMap( + "paymentMethod", + setupIntent.paymentMethod?.let { mapFromPaymentMethod(it) } ?: run { null }, + ) map.putString("usage", mapSetupIntentUsage(setupIntent.usage)) map.putString("created", convertToUnixTimestamp(setupIntent.created)) map.putMap("nextAction", mapNextAction(setupIntent.nextActionType, setupIntent.nextActionData)) @@ -852,22 +893,20 @@ internal fun mapFromSetupIntentResult(setupIntent: SetupIntent): WritableMap { return map } -internal fun mapSetupIntentUsage(type: StripeIntent.Usage?): String { - return when (type) { +internal fun mapSetupIntentUsage(type: StripeIntent.Usage?): String = + when (type) { StripeIntent.Usage.OffSession -> "OffSession" StripeIntent.Usage.OnSession -> "OnSession" StripeIntent.Usage.OneTime -> "OneTime" else -> "Unknown" } -} -fun mapToPaymentIntentFutureUsage(type: String?): ConfirmPaymentIntentParams.SetupFutureUsage? { - return when (type) { - "OffSession" -> ConfirmPaymentIntentParams.SetupFutureUsage.OffSession - "OnSession" -> ConfirmPaymentIntentParams.SetupFutureUsage.OnSession - else -> null +fun mapToPaymentIntentFutureUsage(type: String?): ConfirmPaymentIntentParams.SetupFutureUsage? = + when (type) { + "OffSession" -> ConfirmPaymentIntentParams.SetupFutureUsage.OffSession + "OnSession" -> ConfirmPaymentIntentParams.SetupFutureUsage.OnSession + else -> null } -} fun toBundleObject(readableMap: ReadableMap?): Bundle { val result = Bundle() @@ -880,17 +919,18 @@ fun toBundleObject(readableMap: ReadableMap?): Bundle { when (readableMap.getType(key)) { ReadableType.Null -> result.putString(key, null) ReadableType.Boolean -> result.putBoolean(key, readableMap.getBoolean(key)) - ReadableType.Number -> try { - val numAsInt = readableMap.getInt(key) - val numAsDouble = readableMap.getDouble(key) - if (numAsDouble - numAsInt != 0.0) { - result.putDouble(key, numAsDouble) - } else { - result.putInt(key, numAsInt) + ReadableType.Number -> + try { + val numAsInt = readableMap.getInt(key) + val numAsDouble = readableMap.getDouble(key) + if (numAsDouble - numAsInt != 0.0) { + result.putDouble(key, numAsDouble) + } else { + result.putInt(key, numAsInt) + } + } catch (e: Exception) { + Log.e("toBundleException", "Failed to add number to bundle. Failed on: $key.") } - } catch (e: Exception) { - Log.e("toBundleException", "Failed to add number to bundle. Failed on: $key.") - } ReadableType.String -> result.putString(key, readableMap.getString(key)) ReadableType.Map -> result.putBundle(key, toBundleObject(readableMap.getMap(key))) ReadableType.Array -> { @@ -903,7 +943,11 @@ fun toBundleObject(readableMap: ReadableMap?): Bundle { when (list.first()) { is String -> result.putStringArrayList(key, list as java.util.ArrayList) is Int -> result.putIntegerArrayList(key, list as java.util.ArrayList) - else -> Log.e("toBundleException", "Cannot put arrays of objects into bundles. Failed on: $key.") + else -> + Log.e( + "toBundleException", + "Cannot put arrays of objects into bundles. Failed on: $key.", + ) } } } @@ -920,11 +964,8 @@ internal fun mapFromShippingContact(googlePayResult: GooglePayResult): WritableM googlePayResult.name name.putString("givenName", googlePayResult.shippingInformation?.name) map.putMap("name", name) - googlePayResult.shippingInformation?.phone?.let { - map.putString("phoneNumber", it) - } ?: run { - map.putString("phoneNumber", googlePayResult.phoneNumber) - } + googlePayResult.shippingInformation?.phone?.let { map.putString("phoneNumber", it) } + ?: run { map.putString("phoneNumber", googlePayResult.phoneNumber) } val postalAddress = WritableNativeMap() postalAddress.putString("city", googlePayResult.shippingInformation?.address?.city) postalAddress.putString("country", googlePayResult.shippingInformation?.address?.country) @@ -932,9 +973,7 @@ internal fun mapFromShippingContact(googlePayResult: GooglePayResult): WritableM postalAddress.putString("state", googlePayResult.shippingInformation?.address?.state) val line1: String? = googlePayResult.shippingInformation?.address?.line1 val line2: String? = googlePayResult.shippingInformation?.address?.line2 - val street = - (if (line1 != null) "$line1" else "") + - (if (line2 != null) "\n$line2" else "") + val street = (if (line1 != null) "$line1" else "") + (if (line2 != null) "\n$line2" else "") postalAddress.putString("street", street) postalAddress.putString("isoCountryCode", googlePayResult.shippingInformation?.address?.country) map.putMap("postalAddress", postalAddress) @@ -946,29 +985,27 @@ internal fun mapToPreferredNetworks(networksAsInts: ArrayList?): List.toWritableArray(): WritableArray { val writableArray = Arguments.createArray() diff --git a/android/src/main/java/com/reactnativestripesdk/utils/PostalCodeUtilities.kt b/android/src/main/java/com/reactnativestripesdk/utils/PostalCodeUtilities.kt index 8b14d0df5..3333429a6 100644 --- a/android/src/main/java/com/reactnativestripesdk/utils/PostalCodeUtilities.kt +++ b/android/src/main/java/com/reactnativestripesdk/utils/PostalCodeUtilities.kt @@ -1,18 +1,9 @@ package com.reactnativestripesdk.utils class PostalCodeUtilities { - companion object { - internal fun isValidGlobalPostalCodeCharacter(c: Char): Boolean { - return Character.isLetterOrDigit(c) - || c.isWhitespace() - || c == '-' - } + internal fun isValidGlobalPostalCodeCharacter(c: Char): Boolean = Character.isLetterOrDigit(c) || c.isWhitespace() || c == '-' - internal fun isValidUsPostalCodeCharacter(c: Char): Boolean { - return Character.isDigit(c) - || c.isWhitespace() - || c == '-' - } + internal fun isValidUsPostalCodeCharacter(c: Char): Boolean = Character.isDigit(c) || c.isWhitespace() || c == '-' } }