Skip to content

Commit

Permalink
feat: add canAddCardToWallet method for Push Provisioning (#986)
Browse files Browse the repository at this point in the history
  • Loading branch information
charliecruzan-stripe authored Jun 28, 2022
1 parent c34ec21 commit 94f83e1
Show file tree
Hide file tree
Showing 34 changed files with 667 additions and 94 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:

- name: Install Dependencies
run: yarn bootstrap-no-pods

- name: Setup Java environment
uses: actions/setup-java@v3
with:
Expand Down Expand Up @@ -67,7 +67,7 @@ jobs:
sleep 15
adb shell 'echo "chrome --disable-fre --no-default-browser-check --no-first-run" > /data/local/tmp/chrome-command-line'
adb shell am set-debug-app --persistent com.android.chrome
yarn test:android
yarn test:e2e:android
- name: Upload Screenshoots
uses: actions/upload-artifact@v1
Expand Down Expand Up @@ -128,7 +128,7 @@ jobs:
- name: Run Tests
run: |
yarn test:ios
yarn test:e2e:ios
- name: Upload Screenshoots
uses: actions/upload-artifact@v1
Expand Down
106 changes: 106 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Unit Tests

on:
push:
branches: [master]
pull_request:
branches: ['**']

jobs:
test-ios:
name: unit-test-ios
runs-on: macos-latest
steps:
- name: checkout
uses: actions/checkout@v2

- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"

- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- uses: actions/cache@v2
with:
path: example/ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-
- name: Setup Node.js environment
uses: actions/[email protected]
with:
node-version: 14.15.0

- name: Install React Native CLI
run: npm install react-native-cli

- name: Install Dependencies
run: yarn bootstrap

- name: Run tests
run: yarn test:unit:ios

test-android:
name: unit-test-android
runs-on: macos-latest
steps:
- name: checkout
uses: actions/checkout@v2

- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"

- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Setup Node.js environment
uses: actions/[email protected]
with:
node-version: 14.15.0

- name: Install React Native CLI
run: npm install react-native-cli

- name: Install Dependencies
run: yarn bootstrap-no-pods

- name: Setup Java environment
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: '11'

- name: Gradle cache
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/buildSrc/**/*.kt') }}

- name: Run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
arch: x86_64
profile: Galaxy Nexus
target: google_apis
force-avd-creation: false
disable-animations: true
cores: 3
ram-size: 8192M
script: |
yarn test:unit:android
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ project.xcworkspace
local.properties
android.iml
*.hprof
android/build/reports
example/android/app/build/reports

# Cocoapods
#
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ In order to do that, edit .npmrc accordinly to https://raw.githubusercontent.com
e.g. when you have `71.0.3578` chrome version installed you must specify `2.46` version of chrome-driver.

1. run `yarn run-example-ios` / `yarn run-example-android` to build and open example app.
2. run `yarn test:ios` / `yarn test:android` to run e2e tests.
2. run `yarn test:e2e:ios` / `yarn test:e2e:android` to run e2e tests.

### Scripts

Expand Down
11 changes: 11 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ android {
versionCode 1
versionName "1.0"
vectorDrawables.useSupportLibrary = true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
Expand Down Expand Up @@ -146,4 +147,14 @@ dependencies {

// Users need to declare this dependency on their own, otherwise all methods are a no-op
compileOnly 'com.stripe:stripe-android-issuing-push-provisioning:1.1.0'

androidTestImplementation "junit:junit:4.13"
androidTestImplementation "androidx.test:core:1.4.0"
androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation "org.mockito:mockito-core:3.+"
}

configurations {
// We want to include all of our compileOnly libraries in our tests
androidTestImplementation.extendsFrom compileOnly
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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.Test

class PushProvisioningProxyTest {
private val reactApplicationContext = ReactApplicationContext(
ApplicationProvider.getApplicationContext<Context>()
)

@Test
fun getApiVersion() {
// This value very rarely changes, so the test is hard coded
assertEquals(
"2019-09-09",
PushProvisioningProxy.getApiVersion()
)
}

@Test
fun isNFCEnabled() {
assertEquals(
false,
PushProvisioningProxy.isNFCEnabled(reactApplicationContext)
)
}

@Test
fun isTokenInWallet() {
assertEquals(TapAndPayProxy.isTokenInWallet({}, "4242"), false)
}
}
16 changes: 16 additions & 0 deletions android/src/main/java/com/reactnativestripesdk/Mappers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ internal fun createResult(key: String, value: WritableMap): WritableMap {
return map
}

internal fun createCanAddCardResult(canAddCard: Boolean, status: String? = null, token: WritableMap? = null): WritableNativeMap {
val result = WritableNativeMap()
val details = WritableNativeMap()
if (status != null) {
result.putBoolean("canAddCard", false)
details.putString("status", status)
} else {
result.putBoolean("canAddCard", canAddCard)
if (token != null) {
details.putMap("token", token)
}
}
result.putMap("details", details)
return result
}

internal fun mapIntentStatus(status: StripeIntent.Status?): String {
return when (status) {
StripeIntent.Status.Succeeded -> "Succeeded"
Expand Down
39 changes: 37 additions & 2 deletions android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@ import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.facebook.react.bridge.*
import com.facebook.react.module.annotations.ReactModule
import com.reactnativestripesdk.pushprovisioning.PushProvisioningProxy
import com.stripe.android.*
import com.stripe.android.core.AppInfo
import com.stripe.android.core.ApiVersion
import com.stripe.android.core.AppInfo
import com.stripe.android.model.*
import com.stripe.android.payments.bankaccount.CollectBankAccountConfiguration
import com.stripe.android.view.AddPaymentMethodActivityStarter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch


@ReactModule(name = StripeSdkModule.NAME)
class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
override fun getName(): String {
Expand Down Expand Up @@ -525,14 +527,47 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
googlePayFragment?.createPaymentMethod(currencyCode, amount, promise)
}

@ReactMethod
fun canAddCardToWallet(params: ReadableMap, promise: Promise) {
val last4 = getValOr(params, "cardLastFour", null) ?: run {
promise.resolve(createError("Failed", "You must provide cardLastFour"))
return
}

if (!PushProvisioningProxy.isNFCEnabled(reactApplicationContext)) {
promise.resolve(createCanAddCardResult(false, "UNSUPPORTED_DEVICE"))
return
}

getCurrentActivityOrResolveWithError(promise)?.let {
PushProvisioningProxy.isCardInWallet(it, last4) { isCardInWallet, token, error ->
if (error != null) {
promise.resolve(createCanAddCardResult(false, "MISSING_CONFIGURATION", null))
} else {
val status = if (isCardInWallet) "CARD_ALREADY_EXISTS" else null
promise.resolve(createCanAddCardResult(!isCardInWallet, status, token))
}
}
}
}

@ReactMethod
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, promise)
PushProvisioningProxy.isCardInWallet(it, last4) { isCardInWallet, token, error ->
if (error != null) {
promise.resolve(error)
} else {
val result = WritableNativeMap()
result.putBoolean("isInWallet", isCardInWallet)
result.putMap("token", token)
promise.resolve(result)
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerModule
import com.facebook.react.uimanager.events.EventDispatcher
import com.reactnativestripesdk.PushProvisioningProxy
import com.reactnativestripesdk.createError


Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package com.reactnativestripesdk
package com.reactnativestripesdk.pushprovisioning

import android.app.Activity
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.content.pm.PackageManager
import android.nfc.NfcAdapter
import android.util.Log
import com.facebook.react.bridge.*
import com.reactnativestripesdk.pushprovisioning.AddToWalletButtonView
import com.reactnativestripesdk.pushprovisioning.EphemeralKeyProvider
import com.reactnativestripesdk.pushprovisioning.TapAndPayProxy
import com.facebook.react.bridge.BaseActivityEventListener
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableMap
import com.reactnativestripesdk.createError
import com.reactnativestripesdk.mapError
import com.stripe.android.pushProvisioning.PushProvisioningActivity
import com.stripe.android.pushProvisioning.PushProvisioningActivityStarter

Expand All @@ -27,6 +30,15 @@ object PushProvisioningProxy {
}
}

fun isNFCEnabled(context: ReactApplicationContext): Boolean {
return if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_NFC)) {
val adapter = NfcAdapter.getDefaultAdapter(context)
adapter.isEnabled
} else {
false
}
}

fun invoke(
context: ReactApplicationContext,
view: AddToWalletButtonView,
Expand Down Expand Up @@ -57,8 +69,8 @@ object PushProvisioningProxy {
}
}

fun isCardInWallet(activity: Activity, cardLastFour: String, promise: Promise) {
TapAndPayProxy.invoke(activity, cardLastFour, promise)
fun isCardInWallet(activity: Activity, cardLastFour: String, callback: TokenCheckHandler) {
TapAndPayProxy.findExistingToken(activity, cardLastFour, callback)
}

private fun createActivityEventListener(context: ReactApplicationContext, view: AddToWalletButtonView) {
Expand Down
Loading

0 comments on commit 94f83e1

Please sign in to comment.