├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SampleApp ├── .gitignore ├── README.md ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ ├── androidTest │ ├── assets │ │ └── test_card_images │ │ │ ├── README.md │ │ │ ├── amex.png │ │ │ └── amex_large.png │ └── java │ │ └── io │ │ └── card │ │ ├── payment │ │ └── CardScannerTester.java │ │ └── test │ │ ├── CardIOActivityTest.java │ │ ├── CustomActivityTestRule.java │ │ └── SampleActivityTest.java │ ├── debug │ └── java │ │ └── io │ │ └── card │ │ └── development │ │ └── DebugTools.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── io │ │ │ └── card │ │ │ ├── development │ │ │ ├── SampleActivity.java │ │ │ └── SampleApplication.java │ │ │ └── test │ │ │ └── CardIOTestActivity.java │ └── res │ │ ├── layout │ │ └── sample_activity.xml │ │ ├── values-v11 │ │ └── styles.xml │ │ ├── values-v21 │ │ └── styles.xml │ │ └── values │ │ └── styles.xml │ └── release │ └── java │ └── io │ └── card │ └── development │ └── DebugTools.java ├── aars └── card.io-5.5.1.aar └── acknowledgements.md /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### General information 2 | 3 | * SDK/Library version: 4 | * Android Version and Device: 5 | 6 | ### Issue description 7 | 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Misc 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: oraclejdk8 3 | android: 4 | components: 5 | - tools 6 | - tools 7 | - android-25 8 | - build-tools-25.0.2 9 | - platform-tools 10 | before_install: 11 | - export ANDROID_HOME=/usr/local/android-sdk 12 | - export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools 13 | - echo "sdk.dir=$ANDROID_HOME" > local.properties 14 | script: 15 | - cd SampleApp/ 16 | - ./gradlew clean assembleDebug 17 | notifications: 18 | webhooks: 19 | urls: 20 | - secure: ZbnbVXnG+HrsBmYz0kyXk3XogdZ7IU/FhUMKi8ZKa7r+9xKDIpnxwZH2yO+dRffjIGQGzuPChgMtRexwOASp7YwqEkHI8n5donKCzKEJraLA5C9O1bgpam3U8g8wZl8bUY3u+SnYxB2aN1ZIgBxwcDJAlgpC/YM7kDEBDhEQaTY= 21 | on_success: change 22 | on_failure: always 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | card.io Android SDK change log and release notes 2 | ================================================ 3 | 4 | 5.5.1 5 | ----- 6 | * Remove sample app signing (fixes [#125](https://github.com/card-io/card.io-Android-source/issues/125)). 7 | * Fix manual entry Activity started twice when rejecting camera permission (fixes [#128](https://github.com/card-io/card.io-Android-source/pull/128), [#150](https://github.com/card-io/card.io-Android-SDK/issues/150)). 8 | * Remove unnecessary logging (fixes [#184](https://github.com/card-io/card.io-Android-SDK/issues/184)). 9 | * Compile distributed package with NDK r14. 10 | 11 | 5.5.0 12 | ----- 13 | * Update Gradle build plugin to 2.2.3. 14 | * Update compile and target SDK versions to 25. 15 | * Update NDK to r13b. 16 | * Increase `minSdkVersion` to 16. 17 | * Upgrade OpenCV to 2.4.13. 18 | 19 | 5.4.2 20 | ----- 21 | * Add Mastercard 2-series support. 22 | * Bump compile SDK to 24 for reals. 23 | * Compile distributed package with NDK to r12b (previous was r11c). 24 | * Update Android Gradle plugin to 2.2.0. 25 | 26 | 5.4.1 27 | ----- 28 | * Add ability to specify an alternative search path for the native libraries via `CardIONativeLibsConfig` [card.io-Android-source#86](https://github.com/card-io/card.io-Android-source/pull/86). Thank you Thorben Primke! 29 | * Updated gradle plugin and wrapper versions. 30 | * Bump compile SDK to 24. 31 | 32 | 5.4.0 33 | ----- 34 | * Add ability to blur all digits in the scanned card image, minus any number of digits to remain unblurred, enabled via `CardIOActivity.EXTRA_UNBLUR_DIGITS`. Thank you Michael Schmoock! 35 | * Fix issue where Maestro cards were not correctly recognized [#154](https://github.com/card-io/card.io-Android-SDK/issues/154). 36 | * Fix issue on Android 23 and above where `CardIOActivity#canReadCardWithCamera()` would return the incorrect value if permissions had not been granted [#136](https://github.com/card-io/card.io-Android-SDK/issues/136). Now defaults to `true` in such cases. 37 | * Add missing locales to javadocs [card.io-Android-source#75](https://github.com/card-io/card.io-Android-source/issues/75). 38 | * Upgrade gradle to 2.13. 39 | * Upgrade Android Gradle plugin to 2.1.0. 40 | 41 | 5.3.4 42 | ----- 43 | * Fix crash on Android 23 and above where `onRequestPermissionsResult()` returns an empty array [card.io-Android-source#70](https://github.com/card-io/card.io-Android-source/issues/70). 44 | 45 | 5.3.3 46 | ----- 47 | * Fix newline issue in ES locale [#142](https://github.com/card-io/card.io-Android-SDK/issues/142). 48 | * Fix build issue with ndk 11 [card.io-Android-source#60](https://github.com/card-io/card.io-Android-source/issues/60). 49 | * Upgrade gradle to 2.12. 50 | * Upgrade Android Gradle plugin to 2.0.0. 51 | 52 | 5.3.2 53 | ----- 54 | * Fix issue where Android 23 and above devices would crash when the library's `.so` files were removed [PayPal-Android-SDK#279](https://github.com/paypal/PayPal-Android-SDK/issues/279). 55 | 56 | 5.3.1 57 | ----- 58 | * Fix issue where the camera was flipped when the app was backgrounded with card.io open [#118](https://github.com/card-io/card.io-Android-SDK/issues/118). 59 | * Add proguard config to `aar` file [#112](https://github.com/card-io/card.io-Android-SDK/issues/112), [#117](https://github.com/card-io/card.io-Android-SDK/issues/117). 60 | 61 | 5.3.0 62 | ----- 63 | * Add option for only numeric input for postal code, `EXTRA_RESTRICT_POSTAL_CODE_TO_NUMERIC_ONLY` [#100](https://github.com/card-io/card.io-Android-SDK/issues/100). 64 | 65 | 5.2.0 66 | ----- 67 | * Add Cardholder Name to list of available manual entry fields, enabled via `CardIOActivity.EXTRA_REQUIRE_CARDHOLDER_NAME`. Thank you Dan Nizri and Zach Sweigart! 68 | * Fix issue where certain devices would show the camera preview upside down [#91](https://github.com/card-io/card.io-Android-SDK/issues/91). 69 | * Fix issue where null could be set in the return bundle value for `CardIOActivity.EXTRA_SCAN_RESULT`. 70 | * Upgrade build tools. 71 | 72 | 5.1.2 73 | ----- 74 | * Fix bug where denying the camera permission on Android 23 results in invalid data [#37-source](https://github.com/card-io/card.io-Android-source/issues/37). 75 | 76 | 5.1.1 77 | ----- 78 | * Fix bug where ActionBar was not properly hiding immediately after accepting the Camera permission. 79 | 80 | 5.1.0 81 | ----- 82 | * Add arm64-v8a processor support [#33-source](https://github.com/card-io/card.io-Android-source/issues/33), [#51](https://github.com/card-io/card.io-Android-SDK/issues/51). 83 | * Add x86 processor support [#26-source](https://github.com/card-io/card.io-Android-source/issues/26). 84 | * Add x86_64 processor support. 85 | * Add support for Android 23 new permission model for the Camera permission [#78](https://github.com/card-io/card.io-Android-SDK/issues/78). When permission is granted, the SDK performs as in previous versions. When permission is or has already been denied, the SDK falls back to manual entry. Note: this SDK does not call the `shouldShowRequestPermissionRationale()` method and does not show a rationale. It is up to the implementor whether or not to show the Camera permission rationale before opening the SDK. 86 | * Populate CardIOActivity.EXTRA_CAPTURED_CARD_IMAGE when confirmation is shown [#10-source](https://github.com/card-io/card.io-Android-source/issues/10). 87 | * Fix issue where setting `EXTRA_KEEP_APPLICATION_THEME` would not create buttons that matched the theme [#24-source](https://github.com/card-io/card.io-Android-source/issues/24). 88 | * Add a default edit text hint color [#22-source](https://github.com/card-io/card.io-Android-source/issues/22). 89 | * Fix leaking IntentReceiver [#76](https://github.com/card-io/card.io-Android-SDK/issues/76). 90 | 91 | 5.0.1 92 | ----- 93 | * Prevent screenshots when the app is backgrounded via [FLAG_SECURE](http://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_SECURE). 94 | * Fix issue where arm64-v8a devices were not allowing the scanning of devices [#62](https://github.com/card-io/card.io-Android-SDK/issues/62)). 95 | 96 | 5.0.0 97 | ----- 98 | * Add automatic expiry-scanning. 99 | You can disable this feature via the new `EXTRA_SCAN_EXPIRY` extra of `CardIOActivity`. 100 | Note: Expiry scans will not infrequently fail to obtain the correct expiry date. 101 | We are continuing to work to improve expiry-scanning accuracy. 102 | * Fix crash when the `DataEntryActivity` is missing extras [#19](https://github.com/card-io/card.io-Android-SDK/issues/19)). 103 | 104 | 4.0.2 105 | ----- 106 | * Fix crash caused when an application's theme specifies no action bar [#44](https://github.com/card-io/card.io-Android-SDK/issues/44)). 107 | * Use the application theme to define most of the UI's colors. 108 | 109 | 4.0.1 110 | ----- 111 | * Minor bug fixes. 112 | 113 | 4.0.0 114 | ----- 115 | * Distribute .aar file instead of .jar and .so files. 116 | * New extras on `CardIOActivity`: `EXTRA_SCAN_INSTRUCTIONS`, `EXTRA_HIDE_CARDIO_LOGO`, `EXTRA_SCAN_OVERLAY_LAYOUT_ID`, `EXTRA_SUPPRESS_SCAN`, `EXTRA_RETURN_CARD_IMAGE`, `EXTRA_KEEP_APPLICATION_THEME`, and `EXTRA_USE_PAYPAL_ACTIONBAR_ICON`. 117 | * Remove deprecated extras and methods in `CardIOActivity`: `canReadCardWithCamera(Context)` and `EXTRA_REQUIRE_ZIP`. 118 | * New extra `EXTRA_CAPTURED_CARD_IMAGE` returned to calling Activity. 119 | * New class `BuildConfig`. 120 | 121 | 122 | 3.2.0 123 | ----- 124 | * Eliminate App Token. Developers no longer need to sign up on the card.io site before using card.io. 125 | * Add Icelandic (is) to our supported localizations. Thank you, Martin Kaplan! 126 | 127 | 3.1.6 128 | ----- 129 | * Add support for Diners Club and China UnionPay cards. 130 | * Rename zh-Hant_HK -> zh-Hant so that HK is chosen by default for other regions. 131 | 132 | 3.1.5 May 1, 2014 133 | ----------------- 134 | * Add Thai (th) to our supported localizations. 135 | * Update PayPal logo. 136 | 137 | 3.1.4 January 3, 2014 138 | ------------------------ 139 | * Remove any transparency from guide color. 140 | * Add language support for Arabic and Malay. 141 | 142 | 3.1.3 October 9, 2013 143 | ------------------------ 144 | * Add guide color customization through `CardIOActivity.EXTRA_GUIDE_COLOR`. 145 | * Add ability to skip confirmation activity through `CardIOActivity.EXTRA_SUPPRESS_CONFIRMATION`. 146 | * Fix camera not recognized issue for some devices. 147 | 148 | 3.1.2 - September 16, 2013 149 | ------------------------ 150 | * Restrict postal code maximum length. 151 | * Add convenience method to localize CardType. 152 | * Fix Hebrew phone settings detection bug. 153 | * Add SampleApp. 154 | * Reverse release_notes order. 155 | 156 | 3.1.1 - August 30, 2013 157 | ------------------------ 158 | * Update globalization strings for 25 languages/locales. 159 | 160 | 3.1.0 - August 13, 2013 161 | ------------------------ 162 | * Add translations of all strings into ~20 languages, in addition to American English. 163 | * Translation choice is controlled by `CardIOActivity.EXTRA_LANGUAGE_OR_LOCALE`. 164 | * The translations that a few developers had previously created for their own apps will no longer be used by the SDK. 165 | * NOTE: Default language, if not set by your app, will now be based upon the device's current language setting. 166 | 167 | 3.0.8 - July 3, 2013 168 | --------------------- 169 | * Fix pre Android 4.0 hanging issue. 170 | 171 | 3.0.7 - July 3, 2013 172 | --------------------- 173 | * Fix rotation issue when starting in landscape mode. 174 | * Fix card image disappearing when rotating device in card details screen. 175 | 176 | 3.0.6 - June 21, 2013 177 | --------------------- 178 | * Add missing string to internationalization list. 179 | * Add log note when processor is not supported. 180 | 181 | 3.0.5 - May 23, 2013 182 | -------------------- 183 | * UI updates. 184 | * Don't require portrait for DataEntryActivity. 185 | * Fix scanner crash in landscape. 186 | 187 | 3.0.4 - May 14, 2013 188 | -------------------- 189 | * Fix orientation bugs to support some tablets. 190 | * Use PayPal logo instead of card.io by default. See javadoc to switch back. 191 | 192 | 3.0.3 - Nov 27, 2012 193 | -------------------- 194 | * Fix a couple of NPEs associated with the back button. 195 | 196 | 3.0.2 - Oct 26, 2012 197 | -------------------- 198 | * Fix NPE if values/strings.xml is missing from the project. 199 | * Fix native crash if a second scanner is launched from `onActivityResult()` of completion of the first. 200 | 201 | 3.0.1 - Oct 8, 2012 202 | -------------------- 203 | * Client side scanning! 204 | 205 | 3.0.0 206 | -------------------- 207 | * Skipped. 208 | 209 | 2.4.3 - Aug 31, 2012 210 | -------------------- 211 | * Correct layouts for high resolution cameras & xhdpi displays. 212 | * Fix crash on HTC ICS. 213 | 214 | 2.4.2 - Aug 23, 2012 215 | -------------------- 216 | * Additional null checking. 217 | * Improve private label support. 218 | 219 | 2.4.1 - Aug 22, 2012 220 | -------------------- 221 | * Work around a crash in some Qualcomm camera drivers, e.g. Samsung Galaxy II S running Android 4.0.4. 222 | * Improve efficiency of internal API. 223 | 224 | 2.4.0 - June 25, 2012 225 | -------------------- 226 | * Charges are no longer supported. 227 | 228 | 2.3.3 - June 6, 2012 229 | -------------------- 230 | * Fix a bug that caused some mod builds (e.g. CyanogenMod) to hang at the charge screen. 231 | * Fix a problem that could have caused GPS to stay active for longer than necessary. 232 | 233 | 2.3.2 - June 4, 2012 234 | ------------------ 235 | * Support scanning in non-NEON ARMv7 devices, e.g. nVidia Tegra2. 236 | * Support manual entry in all other devices. Including those based on MIPS or x86. 237 | * Fix exception in charge flow caused by users pressing the home button while the charge is processing. 238 | 239 | ************************* 240 | IMPORTANT: You should delete libs/*/libcardio*.so from your project directory before unzipping the new SDK. This library is obsolete and will only make your .apk bigger if it is included. 241 | 242 | The bundled libraries are: 243 | 244 | libs/card.io.jar 245 | libs/armeabi/libcardioDecider.so 246 | libs/armeabi-v7a/libcardioDecider.so 247 | libs/armeabi-v7a/libcardioRecognizer.so 248 | libs/armeabi-v7a/libcardioRecognizer_tegra2.so 249 | libs/armeabi-v7a/libopencv_core.so 250 | libs/armeabi-v7a/libopencv_imgproc.so 251 | libs/mips/libcardioDecider.so 252 | libs/x86/libcardioDecider.so 253 | 254 | Note that if your app is not targeting x86 or MIPS, you can safely leave out these libraries. However, doing so will cause Google Play to filter out your app for users of those devices. 255 | 256 | ************************* 257 | 258 | 2.3.1 259 | -------------------- 260 | * Skipped. 261 | 262 | 2.3.0 263 | -------------------- 264 | * Skipped. 265 | 266 | 2.2.1 - April 20, 2012 267 | -------------------- 268 | * Make CardType.getLogoBitmap() a public method. 269 | * Repackage library jar to avoid proguard parsing problems in client apps. 270 | * Fix string display bug in the charge screens. 271 | 272 | 2.2.0 - April 11, 2012 273 | -------------------- 274 | 275 | ************************* 276 | IMPORTANT: You should delete libs/armeabi*/libcardio.so from your project directory before unzipping the new SDK. This library is obsolete and will only make your .apk bigger if it is included. 277 | 278 | ************************* 279 | 280 | * (2.1.2)Fix native library loading bug in Android 4.0.1-4.0.3 that could cause manual entry to be used instead of camera. 281 | * Add capability to suppress manual number entry in scan-only mode. 282 | * Add support for developer provided localization strings in scan-only mode. 283 | * Handle case where device returns locations with a future timestamp that could cause charges to fail. 284 | 285 | 2.1.1 - April 9, 2012 286 | -------------------- 287 | * Fix NullPointerException on manual entry press in scan only mode. 288 | 289 | 2.1.0 - April 4, 2012 290 | -------------------- 291 | * Made the charge screens more beautiful. 292 | * Add support for JCB cards. 293 | * Removed first digit card detection from CardType. Use CardType.fromCardNumber(String) instead. 294 | * Remove deprecated CardIOCreditCardResult. Use CreditCard instead. 295 | * Gather additional information for fraud detection. 296 | * Add additional sanity checking for crash prevention. 297 | 298 | 2.0.5 - February 22, 2012 299 | -------------------- 300 | * Performance improvements in SSL pinning. 301 | * Fix SSL pinning bug in Android 4.0. 302 | 303 | 2.0.4 - February 13, 2012 304 | -------------------- 305 | * Enhance security with SSL pinning. 306 | * Properly check for the absence of the NEON instruction set in some ARMv7 devices. Notably those based on nVidia Tegra 2. 307 | * Fix bugs relating to cancel/back. 308 | 309 | 2.0.3 - January 31, 2012 310 | -------------------- 311 | * Ensure the library passes detectAll() checks imposed by StrictMode.setThreadPolicy(...) and StrictMode.setVmPolicy(...) as of Android-14. 312 | * Fix a crash caused by using a recycled bitmap when the user goes back after scanning a card. 313 | 314 | 2.0.2 - January 27, 2012 315 | -------------------- 316 | * Additional error checking and catching. 317 | * Be more explicit about device problems when we fall back to manual entry. 318 | * Expose more logging to developers. 319 | 320 | 2.0.1 - January 20, 2012 321 | -------------------- 322 | * Fix crash caused by certain malformed server responses. 323 | * Add connection status messages to logs. 324 | * Correctly report network type. Fixes problems associated with networks other than WiFi or mobile. 325 | 326 | 2.0.0 - January 18, 2012 327 | ------------------- 328 | * (new!) Support processing charges. 329 | * Rename package io.card.scan to io.card.payments. 330 | * Refactor scan interface. See https://card.io/resources/javadoc/index.html to see a complete list of deprecated methods & constants. 331 | * Updated HTTPS library for better performance while scanning. 332 | * Fixed crashes in Android 4.0/Ice Cream Sandwich. 333 | 334 | 1.0.0 - Wednesday, 8/24/2011 335 | ------------------- 336 | * First release. 337 | 338 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute to the card.io Android SDK 2 | 3 | We love your contributions. If you're looking to submit changes to the sample app or documentation available within this repo, feel free to submit a PR. 4 | 5 | If you are looking to make a change to the card.io source, please take a look at [the open source repo](https://github.com/card-io/card.io-Android-source). 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | All files are released under the MIT License: 2 | 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2013-2016 PayPal Holdings, Inc. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/card-io/card.io-Android-SDK.svg)](https://travis-ci.org/card-io/card.io-Android-SDK) 2 | 3 | card.io SDK for Android 4 | ======================== 5 | 6 | [card.io](https://www.card.io/) provides fast, easy credit card scanning in mobile apps. 7 | 8 | Stay up to date 9 | --------------- 10 | 11 | Please be sure to keep your app up to date with the latest version of the SDK. 12 | All releases follow [semantic versioning](http://semver.org/). 13 | 14 | The latest version is available via `mavenCentral()`. Just add the following dependency: 15 | 16 | ``` 17 | compile 'io.card:android-sdk:5.5.1' 18 | ``` 19 | 20 | You can receive updates about new versions via a few different channels: 21 | 22 | * Follow [@cardio](https://twitter.com/cardio) (also great to send us feedback) 23 | * Subscribe to our [card-io-sdk-announce](https://groups.google.com/forum/#!forum/card-io-sdk-announce) list. 24 | * "Watch" this GitHub repository 25 | 26 | Also be sure to check and post to the [Stack Overflow card.io tag](http://stackoverflow.com/questions/tagged/card.io). 27 | 28 | Integration instructions 29 | ------------------------ 30 | 31 | The information in this guide is enough to get started. For additional details, see our **[javadoc](http://card-io.github.io/card.io-Android-SDK/)**. 32 | 33 | *(Note: in the past, developers needed to sign up at the [card.io site](https://www.card.io) and obtain an* `app token`. *This is no longer required.)* 34 | 35 | ### Requirements for card scanning 36 | 37 | * Rear-facing camera. 38 | * Android SDK version 16 (Android 4.1) or later. 39 | * armeabi-v7a, arm64-v8, x86, or x86_64 processor. 40 | 41 | A manual entry fallback mode is provided for devices that do not meet these requirements. 42 | 43 | ### Setup 44 | 45 | Add the dependency in your `build.gradle`: 46 | 47 | ``` 48 | compile 'io.card:android-sdk:5.5.0' 49 | ``` 50 | 51 | ### Sample code (See the SampleApp for an example) 52 | 53 | First, we'll assume that you're going to launch the scanner from a button, 54 | and that you've set the button's `onClick` handler in the layout XML via `android:onClick="onScanPress"`. 55 | Then, add the method as: 56 | 57 | ```java 58 | public void onScanPress(View v) { 59 | Intent scanIntent = new Intent(this, CardIOActivity.class); 60 | 61 | // customize these values to suit your needs. 62 | scanIntent.putExtra(CardIOActivity.EXTRA_REQUIRE_EXPIRY, true); // default: false 63 | scanIntent.putExtra(CardIOActivity.EXTRA_REQUIRE_CVV, false); // default: false 64 | scanIntent.putExtra(CardIOActivity.EXTRA_REQUIRE_POSTAL_CODE, false); // default: false 65 | 66 | // MY_SCAN_REQUEST_CODE is arbitrary and is only used within this activity. 67 | startActivityForResult(scanIntent, MY_SCAN_REQUEST_CODE); 68 | } 69 | ``` 70 | 71 | Next, we'll override `onActivityResult()` to get the scan result. 72 | 73 | ```java 74 | @Override 75 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 76 | super.onActivityResult(requestCode, resultCode, data); 77 | 78 | if (requestCode == MY_SCAN_REQUEST_CODE) { 79 | String resultDisplayStr; 80 | if (data != null && data.hasExtra(CardIOActivity.EXTRA_SCAN_RESULT)) { 81 | CreditCard scanResult = data.getParcelableExtra(CardIOActivity.EXTRA_SCAN_RESULT); 82 | 83 | // Never log a raw card number. Avoid displaying it, but if necessary use getFormattedCardNumber() 84 | resultDisplayStr = "Card Number: " + scanResult.getRedactedCardNumber() + "\n"; 85 | 86 | // Do something with the raw number, e.g.: 87 | // myService.setCardNumber( scanResult.cardNumber ); 88 | 89 | if (scanResult.isExpiryValid()) { 90 | resultDisplayStr += "Expiration Date: " + scanResult.expiryMonth + "/" + scanResult.expiryYear + "\n"; 91 | } 92 | 93 | if (scanResult.cvv != null) { 94 | // Never log or display a CVV 95 | resultDisplayStr += "CVV has " + scanResult.cvv.length() + " digits.\n"; 96 | } 97 | 98 | if (scanResult.postalCode != null) { 99 | resultDisplayStr += "Postal Code: " + scanResult.postalCode + "\n"; 100 | } 101 | } 102 | else { 103 | resultDisplayStr = "Scan was canceled."; 104 | } 105 | // do something with resultDisplayStr, maybe display it in a textView 106 | // resultTextView.setText(resultDisplayStr); 107 | } 108 | // else handle other activity results 109 | } 110 | ``` 111 | 112 | ### Hints & Tips 113 | 114 | * [Javadocs](http://card-io.github.io/card.io-Android-SDK/) are provided in this repo for a complete reference. 115 | * Note: the correct proguard file is automatically imported into your gradle project from the `aar` package. Anyone not using gradle will need to extract the proguard file and add it to their proguard config. 116 | * card.io errors and warnings will be logged to the "card.io" tag. 117 | * If upgrading the card.io SDK, first remove all card.io libraries so that you don't accidentally ship obsolete or unnecessary libraries. The bundled libraries may change. 118 | * Processing images can be memory intensive. 119 | * [Memory Analysis for Android Applications](http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html) provides some useful information about how to track and reduce your app's memory useage. 120 | * card.io recommends the use of [SSL pinning](http://blog.thoughtcrime.org/authenticity-is-broken-in-ssl-but-your-app-ha) when transmitting sensitive information to protect against man-in-the-middle attacks. 121 | 122 | Contributing 123 | ------------ 124 | 125 | Please read our [contributing guidelines](CONTRIBUTING.md) prior to submitting a Pull Request. 126 | 127 | License 128 | ------- 129 | 130 | Please refer to this repo's [license file](LICENSE). 131 | -------------------------------------------------------------------------------- /SampleApp/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Eclipse settings files 23 | .settings/ 24 | 25 | # IntelliJ project files 26 | *.iml 27 | *.ipr 28 | *.iws 29 | .idea/ 30 | 31 | # Misc 32 | .DS_Store 33 | 34 | example.keystore 35 | -------------------------------------------------------------------------------- /SampleApp/README.md: -------------------------------------------------------------------------------- 1 | card.io for Android example 2 | =========================== 3 | 4 | This sample shows how to integrate card.io in Android. 5 | 6 | To give it a try: 7 | 8 | ## With Gradle (modern) 9 | 10 | 1. Clone this repo. 11 | 2. `$ cd SampleApp` 12 | 3. Plug in your device 13 | 4. `$ ./gradlew installDebug` 14 | 5. Run the demo app! 15 | 16 | 17 | That's it!! 18 | 19 | Questions? Comments? Contact us via https://card.io/support. 20 | -------------------------------------------------------------------------------- /SampleApp/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | dependencies { 6 | classpath 'com.android.tools.build:gradle:2.3.0' 7 | } 8 | } 9 | 10 | repositories { 11 | jcenter() 12 | maven { 13 | url "https://jitpack.io" 14 | } 15 | } 16 | 17 | apply plugin: 'com.android.application' 18 | 19 | android { 20 | compileSdkVersion 25 21 | buildToolsVersion "25.0.2" 22 | 23 | defaultConfig { 24 | applicationId "io.card.development" 25 | minSdkVersion 18 26 | targetSdkVersion 25 27 | versionCode 1 28 | versionName "1.0.0" 29 | testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner' 30 | } 31 | 32 | buildTypes { 33 | release { 34 | minifyEnabled true 35 | proguardFile getDefaultProguardFile('proguard-android.txt') 36 | } 37 | } 38 | 39 | android.applicationVariants.all { variant -> 40 | variant.outputs.each { output -> 41 | def outputFile = output.outputFile 42 | if (outputFile != null && outputFile.name.endsWith('.apk')) { 43 | output.outputFile = new File(outputFile.parent, "card.io-sample-app-${variant.name}.apk") 44 | } 45 | } 46 | } 47 | } 48 | 49 | dependencies { 50 | if (parent != null) { 51 | compile project(':card.io') 52 | } else { 53 | compile 'io.card:android-sdk:5.5.1' 54 | } 55 | 56 | debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4' 57 | 58 | androidTestCompile "com.android.support.test.espresso:espresso-core:2.2.2" 59 | androidTestCompile "com.github.lkorth:device-automator:01d85912ec" 60 | } 61 | -------------------------------------------------------------------------------- /SampleApp/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/card-io/card.io-Android-SDK/c3fcd5e6b563aca8b87cbc7f4ff008a8bb4b46ef/SampleApp/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /SampleApp/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Mar 14 15:49:42 EDT 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /SampleApp/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /SampleApp/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /SampleApp/src/androidTest/assets/test_card_images/README.md: -------------------------------------------------------------------------------- 1 | # Test Card Images 2 | 3 | Card images in this folder are included for automated testing. Images should be 640px x 480px for 4 | tests. 5 | -------------------------------------------------------------------------------- /SampleApp/src/androidTest/assets/test_card_images/amex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/card-io/card.io-Android-SDK/c3fcd5e6b563aca8b87cbc7f4ff008a8bb4b46ef/SampleApp/src/androidTest/assets/test_card_images/amex.png -------------------------------------------------------------------------------- /SampleApp/src/androidTest/assets/test_card_images/amex_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/card-io/card.io-Android-SDK/c3fcd5e6b563aca8b87cbc7f4ff008a8bb4b46ef/SampleApp/src/androidTest/assets/test_card_images/amex_large.png -------------------------------------------------------------------------------- /SampleApp/src/androidTest/java/io/card/payment/CardScannerTester.java: -------------------------------------------------------------------------------- 1 | package io.card.payment; 2 | 3 | /* CardScannerTester.java 4 | * See the file "LICENSE.md" for the full license governing this code. 5 | */ 6 | 7 | import android.graphics.Bitmap; 8 | import android.graphics.BitmapFactory; 9 | import android.os.Handler; 10 | import android.view.SurfaceHolder; 11 | 12 | import java.io.IOException; 13 | 14 | import static android.support.test.InstrumentationRegistry.getInstrumentation; 15 | 16 | public class CardScannerTester extends CardScanner { 17 | 18 | private static final long FRAME_INTERVAL = (long) (1000.0 / 30); 19 | 20 | private static String sCardAssetName; 21 | 22 | private boolean mScanAllowed; 23 | private Handler mHandler; 24 | private byte[] mFrame; 25 | 26 | public static void setCardAsset(String cardAssetName) { 27 | sCardAssetName = cardAssetName; 28 | } 29 | 30 | public CardScannerTester(CardIOActivity scanActivity, int currentFrameOrientation) { 31 | super(scanActivity, currentFrameOrientation); 32 | useCamera = false; 33 | mScanAllowed = false; 34 | mHandler = new Handler(); 35 | 36 | try { 37 | Bitmap bitmap = BitmapFactory.decodeStream(getInstrumentation().getContext().getAssets() 38 | .open("test_card_images/" + sCardAssetName)); 39 | mFrame = getNV21FormattedImage(bitmap.getWidth(), bitmap.getHeight(), bitmap); 40 | } catch (IOException e) { 41 | throw new RuntimeException(e); 42 | } 43 | } 44 | 45 | private Runnable mFrameRunnable = new Runnable() { 46 | @Override 47 | public void run() { 48 | if (!mScanAllowed) { 49 | return; 50 | } 51 | 52 | onPreviewFrame(mFrame, null); 53 | mHandler.postDelayed(this, FRAME_INTERVAL); 54 | } 55 | }; 56 | 57 | @Override 58 | boolean resumeScanning(SurfaceHolder holder) { 59 | boolean result = super.resumeScanning(holder); 60 | mScanAllowed = true; 61 | mHandler.postDelayed(mFrameRunnable, FRAME_INTERVAL); 62 | return result; 63 | } 64 | 65 | @Override 66 | public void pauseScanning() { 67 | mScanAllowed = false; 68 | super.pauseScanning(); 69 | } 70 | 71 | private byte[] getNV21FormattedImage(int width, int height, Bitmap bitmap) { 72 | int [] argb = new int[width * height]; 73 | byte [] yuv = new byte[width * height * 3 / 2]; 74 | 75 | bitmap.getPixels(argb, 0, width, 0, 0, width, height); 76 | encodeYUV420SP(yuv, argb, width, height); 77 | bitmap.recycle(); 78 | 79 | return yuv; 80 | } 81 | 82 | private void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, int height) { 83 | int frameSize = width * height; 84 | int yIndex = 0; 85 | int uvIndex = frameSize; 86 | 87 | int R, G, B, Y, U, V; 88 | int index = 0; 89 | for (int j = 0; j < height; j++) { 90 | for (int i = 0; i < width; i++) { 91 | R = (argb[index] & 0xff0000) >> 16; 92 | G = (argb[index] & 0xff00) >> 8; 93 | B = (argb[index] & 0xff); 94 | 95 | // well known RGB to YUV algorithm 96 | Y = ( ( 66 * R + 129 * G + 25 * B + 128) >> 8) + 16; 97 | U = ( ( -38 * R - 74 * G + 112 * B + 128) >> 8) + 128; 98 | V = ( ( 112 * R - 94 * G - 18 * B + 128) >> 8) + 128; 99 | 100 | // NV21 has a plane of Y and interleaved planes of VU each sampled by a factor of 2 101 | // meaning for every 4 Y pixels there are 1 V and 1 U. Note the sampling is every 102 | // other pixel AND every other scanline. 103 | yuv420sp[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y)); 104 | if (j % 2 == 0 && index % 2 == 0) { 105 | yuv420sp[uvIndex++] = (byte)((V<0) ? 0 : ((V > 255) ? 255 : V)); 106 | yuv420sp[uvIndex++] = (byte)((U<0) ? 0 : ((U > 255) ? 255 : U)); 107 | } 108 | 109 | index ++; 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /SampleApp/src/androidTest/java/io/card/test/CardIOActivityTest.java: -------------------------------------------------------------------------------- 1 | package io.card.test; 2 | 3 | import android.Manifest; 4 | import android.app.Activity; 5 | import android.content.Intent; 6 | 7 | import org.junit.Rule; 8 | import org.junit.Test; 9 | 10 | import java.lang.reflect.Field; 11 | 12 | import io.card.payment.CardIOActivity; 13 | import io.card.payment.CardScannerTester; 14 | import io.card.payment.CreditCard; 15 | 16 | import static com.lukekorth.deviceautomator.DeviceAutomator.onDevice; 17 | import static junit.framework.Assert.assertEquals; 18 | import static org.hamcrest.core.Is.is; 19 | import static org.junit.Assert.assertThat; 20 | 21 | public class CardIOActivityTest { 22 | 23 | private CardIOTestActivity mActivity; 24 | 25 | @Rule 26 | public final CustomActivityTestRule mActivityTestRule = 27 | new CustomActivityTestRule<>(CardIOTestActivity.class, false, false); 28 | 29 | @Test(timeout = 30000) 30 | public void scansAmexCards() { 31 | CardScannerTester.setCardAsset("amex.png"); 32 | 33 | startScan(); 34 | 35 | waitForActivityToFinish(); 36 | CreditCard result = getActivityResultIntent().getParcelableExtra(CardIOActivity.EXTRA_SCAN_RESULT); 37 | assertEquals("3743 260055 74998", result.getFormattedCardNumber()); 38 | } 39 | 40 | private void startScan() { 41 | mActivityTestRule.launchActivity(null); 42 | mActivity = mActivityTestRule.getActivity(); 43 | 44 | onDevice().acceptRuntimePermission(Manifest.permission.CAMERA); 45 | } 46 | 47 | private void waitForActivityToFinish() { 48 | long endTime = System.currentTimeMillis() + 10000; 49 | 50 | do { 51 | try { 52 | if (mActivity.isFinishing()) { 53 | return; 54 | } 55 | } catch (Exception ignored) {} 56 | } while (System.currentTimeMillis() < endTime); 57 | 58 | throw new RuntimeException("Maximum wait elapsed (10s) while waiting for activity to finish"); 59 | } 60 | 61 | private Intent getActivityResultIntent() { 62 | assertThat("Activity did not finish", mActivity.isFinishing(), is(true)); 63 | 64 | try { 65 | Field resultDataField = Activity.class.getDeclaredField("mResultData"); 66 | resultDataField.setAccessible(true); 67 | return (Intent) resultDataField.get(mActivity); 68 | } catch (Exception e) { 69 | throw new RuntimeException(e); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /SampleApp/src/androidTest/java/io/card/test/CustomActivityTestRule.java: -------------------------------------------------------------------------------- 1 | package io.card.test; 2 | 3 | 4 | import android.app.Activity; 5 | import android.app.KeyguardManager; 6 | import android.content.Context; 7 | 8 | import static android.support.test.InstrumentationRegistry.getTargetContext; 9 | 10 | public class CustomActivityTestRule extends android.support.test.rule.ActivityTestRule { 11 | 12 | private KeyguardManager.KeyguardLock mKeyguardLock; 13 | 14 | public CustomActivityTestRule(Class activityClass) { 15 | super(activityClass); 16 | init(); 17 | } 18 | 19 | public CustomActivityTestRule(Class activityClass, boolean initialTouchMode) { 20 | super(activityClass, initialTouchMode); 21 | init(); 22 | } 23 | 24 | public CustomActivityTestRule(Class activityClass, boolean initialTouchMode, boolean launchActivity) { 25 | super(activityClass, initialTouchMode, launchActivity); 26 | init(); 27 | } 28 | 29 | private void init() { 30 | mKeyguardLock = ((KeyguardManager) getTargetContext().getSystemService(Context.KEYGUARD_SERVICE)) 31 | .newKeyguardLock("CardIOTest"); 32 | mKeyguardLock.disableKeyguard(); 33 | } 34 | 35 | @Override 36 | protected void afterActivityFinished() { 37 | super.afterActivityFinished(); 38 | 39 | mKeyguardLock.reenableKeyguard(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /SampleApp/src/androidTest/java/io/card/test/SampleActivityTest.java: -------------------------------------------------------------------------------- 1 | package io.card.test; 2 | 3 | import android.Manifest; 4 | 5 | import org.junit.Before; 6 | import org.junit.Rule; 7 | import org.junit.Test; 8 | 9 | import io.card.development.R; 10 | import io.card.development.SampleActivity; 11 | import io.card.payment.i18n.LocalizedStrings; 12 | import io.card.payment.i18n.StringKey; 13 | 14 | import static android.support.test.espresso.Espresso.onView; 15 | import static android.support.test.espresso.action.ViewActions.click; 16 | import static android.support.test.espresso.action.ViewActions.typeText; 17 | import static android.support.test.espresso.assertion.ViewAssertions.matches; 18 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; 19 | import static android.support.test.espresso.matcher.ViewMatchers.withId; 20 | import static android.support.test.espresso.matcher.ViewMatchers.withText; 21 | import static com.lukekorth.deviceautomator.DeviceAutomator.onDevice; 22 | import static org.hamcrest.Matchers.containsString; 23 | 24 | public class SampleActivityTest { 25 | 26 | @Rule 27 | public final CustomActivityTestRule mActivityTestRule = 28 | new CustomActivityTestRule<>(SampleActivity.class); 29 | 30 | @Before 31 | public void setup() { 32 | mActivityTestRule.getActivity(); 33 | } 34 | 35 | @Test 36 | public void cancelInManualEntryExistsActivity() { 37 | onView(withText("Force keyboard entry (bypass scan)")).perform(click()); 38 | onView(withText("Scan Credit Card using Card.io")).perform(click()); 39 | onView(withText("Card Number")).check(matches(isDisplayed())); 40 | 41 | onView(withText(LocalizedStrings.getString(StringKey.CANCEL))).perform(click()); 42 | 43 | onView(withText("Force keyboard entry (bypass scan)")).check(matches(isDisplayed())); 44 | } 45 | 46 | @Test 47 | public void manualEntryReturnsCardData() { 48 | onView(withText("Expiry")).perform(click()); 49 | onView(withText("CVV")).perform(click()); 50 | onView(withText("Postal Code")).perform(click()); 51 | onView(withText("Cardholder Name")).perform(click()); 52 | onView(withText("Force keyboard entry (bypass scan)")).perform(click()); 53 | onView(withText("Scan Credit Card using Card.io")).perform(click()); 54 | 55 | fillInCardForm(); 56 | onView(withText(LocalizedStrings.getString(StringKey.DONE))).perform(click()); 57 | 58 | onView(withId(R.id.result)).check(matches(withText(containsString("1111")))); 59 | onView(withId(R.id.result)).check(matches(withText(containsString("Expiry: 12/2022")))); 60 | onView(withId(R.id.result)).check(matches(withText(containsString("CVV: 123")))); 61 | onView(withId(R.id.result)).check(matches(withText(containsString("Postal Code: 95131")))); 62 | onView(withId(R.id.result)).check(matches(withText(containsString("Cardholder Name: John Doe")))); 63 | } 64 | 65 | @Test 66 | public void canEnterManualEntryFromScanActivity() { 67 | onView(withText("Expiry")).perform(click()); 68 | onView(withText("CVV")).perform(click()); 69 | onView(withText("Postal Code")).perform(click()); 70 | onView(withText("Cardholder Name")).perform(click()); 71 | onView(withText("Scan Credit Card using Card.io")).perform(click()); 72 | 73 | onDevice().acceptRuntimePermission(Manifest.permission.CAMERA); 74 | 75 | onView(withText(LocalizedStrings.getString(StringKey.KEYBOARD))).check(matches(isDisplayed())); 76 | onView(withText(LocalizedStrings.getString(StringKey.KEYBOARD))).perform(click()); 77 | 78 | fillInCardForm(); 79 | onView(withText(LocalizedStrings.getString(StringKey.DONE))).perform(click()); 80 | } 81 | 82 | private void fillInCardForm() { 83 | onView(withId(100)).perform(click(), typeText("4111111111111111")); 84 | onView(withId(101)).perform(click(), typeText("1222")); 85 | onView(withId(102)).perform(click(), typeText("123")); 86 | onView(withId(103)).perform(click(), typeText("95131")); 87 | onView(withId(104)).perform(click(), typeText("John Doe")); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /SampleApp/src/debug/java/io/card/development/DebugTools.java: -------------------------------------------------------------------------------- 1 | package io.card.development; 2 | 3 | import android.app.Application; 4 | import android.os.StrictMode; 5 | 6 | import com.squareup.leakcanary.LeakCanary; 7 | 8 | public class DebugTools { 9 | 10 | public static void setup(Application application) { 11 | StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 12 | .detectAll() 13 | .penaltyLog() 14 | .penaltyDeath() 15 | .build()); 16 | StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() 17 | .detectFileUriExposure() 18 | .detectLeakedClosableObjects() 19 | .detectLeakedRegistrationObjects() 20 | .detectLeakedSqlLiteObjects() 21 | .penaltyLog() 22 | .penaltyDeath() 23 | .build()); 24 | 25 | LeakCanary.install(application); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SampleApp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /SampleApp/src/main/java/io/card/development/SampleActivity.java: -------------------------------------------------------------------------------- 1 | package io.card.development; 2 | 3 | /* SampleActivity.java 4 | * See the file "LICENSE.md" for the full license governing this code. 5 | */ 6 | 7 | import android.app.Activity; 8 | import android.content.Intent; 9 | import android.graphics.Bitmap; 10 | import android.graphics.Color; 11 | import android.os.Bundle; 12 | import android.os.Handler; 13 | import android.util.Log; 14 | import android.view.View; 15 | import android.widget.ArrayAdapter; 16 | import android.widget.CheckBox; 17 | import android.widget.EditText; 18 | import android.widget.ImageView; 19 | import android.widget.Spinner; 20 | import android.widget.TextView; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | import io.card.payment.CardIOActivity; 26 | import io.card.payment.CardType; 27 | import io.card.payment.CreditCard; 28 | import io.card.payment.i18n.StringKey; 29 | import io.card.payment.i18n.SupportedLocale; 30 | import io.card.payment.i18n.locales.LocalizedStringsList; 31 | 32 | public class SampleActivity extends Activity { 33 | 34 | protected static final String TAG = SampleActivity.class.getSimpleName(); 35 | 36 | private static final int REQUEST_SCAN = 100; 37 | private static final int REQUEST_AUTOTEST = 200; 38 | 39 | private CheckBox mManualToggle; 40 | private CheckBox mEnableExpiryToggle; 41 | private CheckBox mScanExpiryToggle; 42 | private CheckBox mCvvToggle; 43 | private CheckBox mPostalCodeToggle; 44 | private CheckBox mPostalCodeNumericOnlyToggle; 45 | private CheckBox mCardholderNameToggle; 46 | private CheckBox mSuppressManualToggle; 47 | private CheckBox mSuppressConfirmationToggle; 48 | private CheckBox mSuppressScanToggle; 49 | 50 | private TextView mResultLabel; 51 | private ImageView mResultImage; 52 | private ImageView mResultCardTypeImage; 53 | 54 | private boolean autotestMode; 55 | private int numAutotestsPassed; 56 | private CheckBox mUseCardIOLogoToggle; 57 | private CheckBox mShowPayPalActionBarIconToggle; 58 | private CheckBox mKeepApplicationThemeToggle; 59 | private Spinner mLanguageSpinner; 60 | private EditText mUnblurEdit; 61 | 62 | @Override 63 | public void onCreate(Bundle savedInstanceState) { 64 | super.onCreate(savedInstanceState); 65 | setContentView(R.layout.sample_activity); 66 | 67 | mManualToggle = (CheckBox) findViewById(R.id.force_manual); 68 | mEnableExpiryToggle = (CheckBox) findViewById(R.id.gather_expiry); 69 | mScanExpiryToggle = (CheckBox) findViewById(R.id.scan_expiry); 70 | mCvvToggle = (CheckBox) findViewById(R.id.gather_cvv); 71 | mPostalCodeToggle = (CheckBox) findViewById(R.id.gather_postal_code); 72 | mPostalCodeNumericOnlyToggle = (CheckBox) findViewById(R.id.postal_code_numeric_only); 73 | mCardholderNameToggle = (CheckBox) findViewById(R.id.gather_cardholder_name); 74 | mSuppressManualToggle = (CheckBox) findViewById(R.id.suppress_manual); 75 | mSuppressConfirmationToggle = (CheckBox) findViewById(R.id.suppress_confirmation); 76 | mSuppressScanToggle = (CheckBox) findViewById(R.id.detect_only); 77 | 78 | mUseCardIOLogoToggle = (CheckBox) findViewById(R.id.use_card_io_logo); 79 | mShowPayPalActionBarIconToggle = (CheckBox) findViewById(R.id.show_paypal_action_bar_icon); 80 | mKeepApplicationThemeToggle = (CheckBox) findViewById(R.id.keep_application_theme); 81 | 82 | mLanguageSpinner = (Spinner) findViewById(R.id.language); 83 | mUnblurEdit = (EditText) findViewById(R.id.unblur); 84 | 85 | mResultLabel = (TextView) findViewById(R.id.result); 86 | mResultImage = (ImageView) findViewById(R.id.result_image); 87 | mResultCardTypeImage = (ImageView) findViewById(R.id.result_card_type_image); 88 | 89 | TextView version = (TextView) findViewById(R.id.version); 90 | version.setText("card.io library: " + CardIOActivity.sdkVersion() + "\n" + 91 | "Build date: " + CardIOActivity.sdkBuildDate()); 92 | 93 | setScanExpiryEnabled(); 94 | setupLanguageList(); 95 | } 96 | 97 | private void setScanExpiryEnabled() { 98 | mScanExpiryToggle.setEnabled(mEnableExpiryToggle.isChecked()); 99 | } 100 | 101 | public void onExpiryToggle(View v) { 102 | setScanExpiryEnabled(); 103 | } 104 | 105 | public void onScan(View pressed) { 106 | Intent intent = new Intent(this, CardIOActivity.class) 107 | .putExtra(CardIOActivity.EXTRA_NO_CAMERA, mManualToggle.isChecked()) 108 | .putExtra(CardIOActivity.EXTRA_REQUIRE_EXPIRY, mEnableExpiryToggle.isChecked()) 109 | .putExtra(CardIOActivity.EXTRA_SCAN_EXPIRY, mScanExpiryToggle.isChecked()) 110 | .putExtra(CardIOActivity.EXTRA_REQUIRE_CVV, mCvvToggle.isChecked()) 111 | .putExtra(CardIOActivity.EXTRA_REQUIRE_POSTAL_CODE, mPostalCodeToggle.isChecked()) 112 | .putExtra(CardIOActivity.EXTRA_RESTRICT_POSTAL_CODE_TO_NUMERIC_ONLY, mPostalCodeNumericOnlyToggle.isChecked()) 113 | .putExtra(CardIOActivity.EXTRA_REQUIRE_CARDHOLDER_NAME, mCardholderNameToggle.isChecked()) 114 | .putExtra(CardIOActivity.EXTRA_SUPPRESS_MANUAL_ENTRY, mSuppressManualToggle.isChecked()) 115 | .putExtra(CardIOActivity.EXTRA_USE_CARDIO_LOGO, mUseCardIOLogoToggle.isChecked()) 116 | .putExtra(CardIOActivity.EXTRA_LANGUAGE_OR_LOCALE, (String) mLanguageSpinner.getSelectedItem()) 117 | .putExtra(CardIOActivity.EXTRA_USE_PAYPAL_ACTIONBAR_ICON, mShowPayPalActionBarIconToggle.isChecked()) 118 | .putExtra(CardIOActivity.EXTRA_KEEP_APPLICATION_THEME, mKeepApplicationThemeToggle.isChecked()) 119 | .putExtra(CardIOActivity.EXTRA_GUIDE_COLOR, Color.GREEN) 120 | .putExtra(CardIOActivity.EXTRA_SUPPRESS_CONFIRMATION, mSuppressConfirmationToggle.isChecked()) 121 | .putExtra(CardIOActivity.EXTRA_SUPPRESS_SCAN, mSuppressScanToggle.isChecked()) 122 | .putExtra(CardIOActivity.EXTRA_RETURN_CARD_IMAGE, true); 123 | 124 | try { 125 | int unblurDigits = Integer.parseInt(mUnblurEdit.getText().toString()); 126 | intent.putExtra(CardIOActivity.EXTRA_UNBLUR_DIGITS, unblurDigits); 127 | } catch(NumberFormatException ignored) {} 128 | 129 | startActivityForResult(intent, REQUEST_SCAN); 130 | } 131 | 132 | public void onAutotest(View v) { 133 | Log.i(TAG, "\n\n\n ============================== \n" + "successfully completed " 134 | + numAutotestsPassed + " tests\n" + "beginning new test run\n"); 135 | 136 | Intent intent = new Intent(this, CardIOActivity.class) 137 | .putExtra(CardIOActivity.EXTRA_REQUIRE_EXPIRY, false) 138 | .putExtra(CardIOActivity.EXTRA_REQUIRE_CVV, false) 139 | .putExtra(CardIOActivity.EXTRA_REQUIRE_POSTAL_CODE, false) 140 | .putExtra(CardIOActivity.EXTRA_REQUIRE_CARDHOLDER_NAME, false) 141 | .putExtra("debug_autoAcceptResult", true); 142 | 143 | startActivityForResult(intent, REQUEST_AUTOTEST); 144 | 145 | autotestMode = true; 146 | } 147 | 148 | @Override 149 | public void onStop() { 150 | super.onStop(); 151 | 152 | mResultLabel.setText(""); 153 | } 154 | 155 | @Override 156 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 157 | super.onActivityResult(requestCode, resultCode, data); 158 | Log.v(TAG, "onActivityResult(" + requestCode + ", " + resultCode + ", " + data + ")"); 159 | 160 | String outStr = new String(); 161 | Bitmap cardTypeImage = null; 162 | 163 | if ((requestCode == REQUEST_SCAN || requestCode == REQUEST_AUTOTEST) && data != null 164 | && data.hasExtra(CardIOActivity.EXTRA_SCAN_RESULT)) { 165 | CreditCard result = data.getParcelableExtra(CardIOActivity.EXTRA_SCAN_RESULT); 166 | if (result != null) { 167 | outStr += "Card number: " + result.getRedactedCardNumber() + "\n"; 168 | 169 | CardType cardType = result.getCardType(); 170 | cardTypeImage = cardType.imageBitmap(this); 171 | outStr += "Card type: " + cardType.name() + " cardType.getDisplayName(null)=" 172 | + cardType.getDisplayName(null) + "\n"; 173 | 174 | if (mEnableExpiryToggle.isChecked()) { 175 | outStr += "Expiry: " + result.expiryMonth + "/" + result.expiryYear + "\n"; 176 | } 177 | 178 | if (mCvvToggle.isChecked()) { 179 | outStr += "CVV: " + result.cvv + "\n"; 180 | } 181 | 182 | if (mPostalCodeToggle.isChecked()) { 183 | outStr += "Postal Code: " + result.postalCode + "\n"; 184 | } 185 | 186 | if (mCardholderNameToggle.isChecked()) { 187 | outStr += "Cardholder Name: " + result.cardholderName + "\n"; 188 | } 189 | } 190 | 191 | if (autotestMode) { 192 | numAutotestsPassed++; 193 | new Handler().postDelayed(new Runnable() { 194 | @Override 195 | public void run() { 196 | onAutotest(null); 197 | } 198 | }, 500); 199 | } 200 | } else if (resultCode == Activity.RESULT_CANCELED) { 201 | autotestMode = false; 202 | } 203 | 204 | Bitmap card = CardIOActivity.getCapturedCardImage(data); 205 | mResultImage.setImageBitmap(card); 206 | mResultCardTypeImage.setImageBitmap(cardTypeImage); 207 | 208 | Log.i(TAG, "Set result: " + outStr); 209 | 210 | mResultLabel.setText(outStr); 211 | } 212 | 213 | private void setupLanguageList() { 214 | List languages = new ArrayList<>(); 215 | for (SupportedLocale locale : LocalizedStringsList.ALL_LOCALES) { 216 | languages.add(locale.getName()); 217 | } 218 | 219 | ArrayAdapter adapter = new ArrayAdapter<>(this, 220 | android.R.layout.simple_dropdown_item_1line, languages); 221 | mLanguageSpinner.setAdapter(adapter); 222 | mLanguageSpinner.setSelection(adapter.getPosition("en")); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /SampleApp/src/main/java/io/card/development/SampleApplication.java: -------------------------------------------------------------------------------- 1 | package io.card.development; 2 | 3 | import android.app.Application; 4 | 5 | public class SampleApplication extends Application { 6 | 7 | @Override 8 | public void onCreate() { 9 | super.onCreate(); 10 | DebugTools.setup(this); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /SampleApp/src/main/java/io/card/test/CardIOTestActivity.java: -------------------------------------------------------------------------------- 1 | package io.card.test; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | 7 | import io.card.payment.CardIOActivity; 8 | 9 | public class CardIOTestActivity extends Activity { 10 | 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | 15 | Intent intent = new Intent(this, CardIOActivity.class) 16 | .putExtra(CardIOActivity.EXTRA_SUPPRESS_CONFIRMATION, true) 17 | .putExtra("io.card.payment.cameraBypassTestMode", true); 18 | 19 | startActivityForResult(intent, 1); 20 | } 21 | 22 | @Override 23 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 24 | super.onActivityResult(requestCode, resultCode, data); 25 | 26 | setResult(resultCode, data); 27 | finish(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SampleApp/src/main/res/layout/sample_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 19 | 20 | 24 | 25 | 33 | 34 | 41 | 42 | 49 | 50 | 51 | 52 | 57 | 58 | 63 | 64 | 70 | 71 | 76 | 77 | 82 | 83 | 88 | 89 | 94 | 95 | 100 | 101 | 106 | 107 | 112 | 113 | 118 | 119 | 124 | 125 | 130 | 131 | 137 | 138 | 145 | 146 | 147 | 148 | 152 | 153 |