Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

WIP: Google pay android implementation #441

Merged
merged 42 commits into from
Jul 30, 2021
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
769cb42
chore: google pay new approach implementation
Jul 20, 2021
9efbc93
chore: clean up
Jul 20, 2021
e530d28
chore: google pay customization
Jul 21, 2021
df560a6
chore: add documentation, fix linter
Jul 21, 2021
1c674c9
fix: appium tests
Jul 21, 2021
a70d8fb
chore: increate JVM memory size
Jul 21, 2021
a337cf4
chore: improvements, refactor
Jul 21, 2021
46972c6
chore: bump android-emulator api level to 30
Jul 22, 2021
5ccb71f
chore: bump api level
Jul 22, 2021
855f699
Update e2e-tests.yml
arekkubaczkowski Jul 22, 2021
54e10b5
fix: tests
Jul 22, 2021
b505971
chore: merge branch
Jul 22, 2021
825eaf4
chore: bump node version
Jul 22, 2021
86e1e57
chore: bump node version
Jul 22, 2021
20ec84e
chore: bump node version
Jul 22, 2021
4e0ab4e
chore: bump chrome driver
Jul 22, 2021
97bcf9f
chore: appium conf
Jul 22, 2021
d69a21a
chore: appium conf
Jul 22, 2021
7c3c468
chore: appium conf
Jul 22, 2021
55054fc
chore: merge master
Jul 26, 2021
915c064
chore: downgrade api-level
Jul 26, 2021
03f5dd9
chore: use google apis
Jul 26, 2021
c766d7c
chore: change arch
Jul 26, 2021
e3ab58a
chore: update chrome driver version
Jul 26, 2021
7650919
chore: e2e config attempt
Jul 26, 2021
355702e
chore: e2e config attempt
Jul 26, 2021
b42f81b
chore: e2e conf attempt
Jul 27, 2021
e000fa5
chore: accept terms and conditions
Jul 27, 2021
c61276b
chore: accept terms and conditions
Jul 27, 2021
5399565
chore: accept terms and conditions
Jul 27, 2021
4a5be7d
chore: accept terms and conditions
Jul 27, 2021
b322b68
chore: merge master
Jul 27, 2021
778a168
fix: init on mount only
Jul 27, 2021
f949321
fix: init issue
Jul 27, 2021
4479be5
chore: handle exception
Jul 27, 2021
7b5ef7b
chore: add google pay button
Jul 28, 2021
7a696c3
chore: handle dark mode
Jul 28, 2021
1ceb0b6
chore: clean up
Jul 28, 2021
15689ab
chore: tests clean up
Jul 29, 2021
244eee7
chore: clean up
Jul 30, 2021
a88e5fd
chore: merge master
Jul 30, 2021
530e74c
chore: clean up
Jul 30, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: Setup Node.js environment
uses: actions/setup-node@v2.1.5
with:
node-version: 14.4.0
node-version: 14.15.0

- name: Install React Native CLI
run: npm install react-native-cli
Expand All @@ -41,7 +41,9 @@ jobs:
- name: Run Android Emulator and app
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
api-level: 28
target: google_apis
arch: x86
script: |
yarn run-example-android
sleep 15
Expand Down Expand Up @@ -88,7 +90,7 @@ jobs:
- name: Setup Node.js environment
uses: actions/setup-node@v2.1.5
with:
node-version: 14.4.0
node-version: 14.15.0

- name: Install React Native CLI
run: |
Expand Down
2 changes: 1 addition & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
chromedriver_version=74.0.3729.6
chromedriver_version=2.44
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ dependencies {
// noinspection GradleDynamicVersion
api 'com.facebook.react:react-native:+'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'com.stripe:stripe-android:16.10.2'
implementation 'com.stripe:stripe-android:17.0.0'
arekkubaczkowski marked this conversation as resolved.
Show resolved Hide resolved
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
Expand Down
5 changes: 5 additions & 0 deletions android/src/main/java/com/reactnativestripesdk/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ var ON_PAYMENT_OPTION_ACTION = "com.reactnativestripesdk.PAYMENT_OPTION_ACTION"
var ON_CONFIGURE_FLOW_CONTROLLER = "com.reactnativestripesdk.CONFIGURE_FLOW_CONTROLLER_ACTION"
var ON_INIT_PAYMENT_SHEET = "com.reactnativestripesdk.INIT_PAYMENT_SHEET"
var ON_FRAGMENT_CREATED = "com.reactnativestripesdk.FRAGMENT_CREATED_ACTION"

var ON_GOOGLE_PAY_FRAGMENT_CREATED = "com.reactnativestripesdk.ON_GOOGLE_PAY_FRAGMENT_CREATED"
var ON_INIT_GOOGLE_PAY = "com.reactnativestripesdk.ON_INIT_GOOGLE_PAY"
var ON_GOOGLE_PAY_RESULT = "com.reactnativestripesdk.ON_GOOGLE_PAY_RESULT"
var ON_GOOGLE_PAYMENT_METHOD_RESULT = "com.reactnativestripesdk.ON_GOOGLE_PAYMENT_METHOD_RESULT"
4 changes: 4 additions & 0 deletions android/src/main/java/com/reactnativestripesdk/Errors.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ enum class PaymentSheetErrorType {
Failed, Canceled
}

enum class GooglePayErrorType {
Failed, Canceled, Unknown
}

internal fun mapError(code: String, message: String?, localizedMessage: String?, declineCode: String?, type: String?, stripeErrorCode: String?): WritableMap {
val map: WritableMap = WritableNativeMap()
val details: WritableMap = WritableNativeMap()
Expand Down
173 changes: 173 additions & 0 deletions android/src/main/java/com/reactnativestripesdk/GooglePayFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package com.reactnativestripesdk

import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.fragment.app.Fragment
import com.facebook.react.bridge.WritableNativeMap
import com.stripe.android.googlepaylauncher.GooglePayEnvironment
import com.stripe.android.googlepaylauncher.GooglePayLauncher
import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncher
import com.stripe.android.model.StripeIntent

class GooglePayFragment : Fragment() {
private var googlePayLauncher: GooglePayLauncher? = null
private var googlePayMethodLauncher: GooglePayPaymentMethodLauncher? = null

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return FrameLayout(requireActivity()).also {
it.visibility = View.GONE
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val testEnv = arguments?.getBoolean("testEnv")
val merchantName = arguments?.getString("merchantName").orEmpty()
val countryCode = arguments?.getString("countryCode").orEmpty()
val isEmailRequired = arguments?.getBoolean("isEmailRequired") ?: false
val existingPaymentMethodRequired = arguments?.getBoolean("existingPaymentMethodRequired") ?: false

val billingAddressConfigBundle = arguments?.getBundle("billingAddressConfig") ?: Bundle()
val isRequired = billingAddressConfigBundle.getBoolean("isRequired")
val formatString = billingAddressConfigBundle.getString("format").orEmpty()
val isPhoneNumberRequired = billingAddressConfigBundle.getBoolean("isPhoneNumberRequired")

val billingAddressConfig = mapToGooglePayPaymentMethodLauncherBillingAddressConfig(formatString, isRequired, isPhoneNumberRequired)

googlePayMethodLauncher = GooglePayPaymentMethodLauncher(
fragment = this,
config = GooglePayPaymentMethodLauncher.Config(
environment = if (testEnv == true) GooglePayEnvironment.Test else GooglePayEnvironment.Production,
merchantCountryCode = countryCode,
merchantName = merchantName,
billingAddressConfig = billingAddressConfig,
isEmailRequired = isEmailRequired,
existingPaymentMethodRequired = existingPaymentMethodRequired
),
readyCallback = ::onGooglePayReady,
resultCallback = ::onGooglePayResult
)

val paymentMethodBillingAddressConfig = mapToGooglePayLauncherBillingAddressConfig(formatString, isRequired, isPhoneNumberRequired)
googlePayLauncher = GooglePayLauncher(
fragment = this,
config = GooglePayLauncher.Config(
environment = if (testEnv == true) GooglePayEnvironment.Test else GooglePayEnvironment.Production,
merchantCountryCode = countryCode,
merchantName = merchantName,
billingAddressConfig = paymentMethodBillingAddressConfig,
isEmailRequired = isEmailRequired,
existingPaymentMethodRequired = existingPaymentMethodRequired
),
readyCallback = ::onGooglePayReady,
resultCallback = ::onGooglePayResult
)

val intent = Intent(ON_GOOGLE_PAY_FRAGMENT_CREATED)
activity?.sendBroadcast(intent)
}

fun presentForPaymentIntent(clientSecret: String) {
val launcher = googlePayLauncher ?: run {
val intent = Intent(ON_GOOGLE_PAY_RESULT)
intent.putExtra("error", "GooglePayLauncher is not initialized.")
activity?.sendBroadcast(intent)
return
}
runCatching {
launcher.presentForPaymentIntent(clientSecret)
}.onFailure {
val intent = Intent(ON_GOOGLE_PAY_RESULT)
intent.putExtra("error", it.localizedMessage)
activity?.sendBroadcast(intent)
}
}

fun presentForSetupIntent(clientSecret: String, currencyCode: String) {
val launcher = googlePayLauncher ?: run {
val intent = Intent(ON_GOOGLE_PAY_RESULT)
intent.putExtra("error", "GooglePayLauncher is not initialized.")
activity?.sendBroadcast(intent)
return
}
runCatching {
launcher.presentForSetupIntent(clientSecret, currencyCode)
}.onFailure {
val intent = Intent(ON_GOOGLE_PAY_RESULT)
intent.putExtra("error", it.localizedMessage)
activity?.sendBroadcast(intent)
}
}

fun createPaymentMethod(currencyCode: String, amount: Int) {
val launcher = googlePayMethodLauncher ?: run {
val intent = Intent(ON_GOOGLE_PAYMENT_METHOD_RESULT)
intent.putExtra("error", "GooglePayPaymentMethodLauncher is not initialized.")
activity?.sendBroadcast(intent)
return
}

runCatching {
launcher.present(
currencyCode = currencyCode,
amount = amount
)
}.onFailure {
val intent = Intent(ON_GOOGLE_PAYMENT_METHOD_RESULT)
intent.putExtra("error", it.localizedMessage)
activity?.sendBroadcast(intent)
}
}

private fun onGooglePayReady(isReady: Boolean) {
val intent = Intent(ON_INIT_GOOGLE_PAY)
intent.putExtra("isReady", isReady)
activity?.sendBroadcast(intent)
}

private fun onGooglePayResult(result: GooglePayLauncher.Result) {
val intent = Intent(ON_GOOGLE_PAY_RESULT)
intent.putExtra("paymentResult", result)
activity?.sendBroadcast(intent)
}

private fun onGooglePayResult(result: GooglePayPaymentMethodLauncher.Result) {
val intent = Intent(ON_GOOGLE_PAYMENT_METHOD_RESULT)
intent.putExtra("paymentResult", result)
activity?.sendBroadcast(intent)
}

private fun mapToGooglePayLauncherBillingAddressConfig(formatString: String, isRequired: Boolean, isPhoneNumberRequired: Boolean): GooglePayLauncher.BillingAddressConfig {
val format = when (formatString) {
"FULL" -> GooglePayLauncher.BillingAddressConfig.Format.Full
"MIN" -> GooglePayLauncher.BillingAddressConfig.Format.Min
else -> GooglePayLauncher.BillingAddressConfig.Format.Min
}
return GooglePayLauncher.BillingAddressConfig(
isRequired = isRequired,
format = format,
isPhoneNumberRequired = isPhoneNumberRequired
)
}

private fun mapToGooglePayPaymentMethodLauncherBillingAddressConfig(formatString: String, isRequired: Boolean, isPhoneNumberRequired: Boolean): GooglePayPaymentMethodLauncher.BillingAddressConfig {
val format = when (formatString) {
"FULL" -> GooglePayPaymentMethodLauncher.BillingAddressConfig.Format.Full
"MIN" -> GooglePayPaymentMethodLauncher.BillingAddressConfig.Format.Min
else -> GooglePayPaymentMethodLauncher.BillingAddressConfig.Format.Min
}
return GooglePayPaymentMethodLauncher.BillingAddressConfig(
isRequired = isRequired,
format = format,
isPhoneNumberRequired = isPhoneNumberRequired
)
}
}
27 changes: 27 additions & 0 deletions android/src/main/java/com/reactnativestripesdk/Mappers.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.reactnativestripesdk

import android.os.Bundle
import android.util.Log
import com.facebook.react.bridge.*
import com.stripe.android.PaymentAuthConfig
import com.stripe.android.model.*
Expand Down Expand Up @@ -700,3 +702,28 @@ fun mapToPaymentIntentFutureUsage(type: String?): ConfirmPaymentIntentParams.Set
else -> null
}
}

fun toBundleObject(readableMap: ReadableMap?): Bundle? {
val result = Bundle()
if (readableMap == null) {
return result
}
val iterator = readableMap.keySetIterator()
while (iterator.hasNextKey()) {
val key = iterator.nextKey()
when (readableMap.getType(key)) {
ReadableType.Null -> result.putString(key, null)
ReadableType.Boolean -> result.putBoolean(key, readableMap.getBoolean(key))
ReadableType.Number -> try {
result.putInt(key, readableMap.getInt(key))
} catch (e: Exception) {
result.putDouble(key, readableMap.getDouble(key))
}
ReadableType.String -> result.putString(key, readableMap.getString(key))
ReadableType.Map -> result.putBundle(key, toBundleObject(readableMap.getMap(key)))
ReadableType.Array -> Log.e("toBundleException", "Cannot put arrays of objects into bundles. Failed on: $key.")
else -> Log.e("toBundleException", "Could not convert object with key: $key.")
}
}
return result
}
Loading