├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── Enforce_Labels.yml │ └── SonarCloud.yml ├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── HISTORY.md ├── LICENSE.txt ├── README.md ├── build.gradle ├── example ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── co │ │ │ └── paystack │ │ │ └── example │ │ │ ├── App.java │ │ │ └── MainActivity.java │ ├── res │ │ ├── layout │ │ │ └── activity_main.xml │ │ ├── menu │ │ │ └── menu_main.xml │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ └── values │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ └── resources │ │ └── jacoco-agent.properties │ └── test │ └── java │ └── co │ └── paystack │ └── example │ └── tests │ ├── CardTest.java │ └── PaystackSdkTest.java ├── fastlane ├── Appfile └── Fastfile ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── paystack ├── .gitignore ├── build.gradle ├── gradle.properties ├── proguard-rules.pro └── src │ ├── debug │ ├── AndroidManifest.xml │ └── java │ │ └── co │ │ └── paystack │ │ └── android │ │ └── TestActivity.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── co │ │ │ └── paystack │ │ │ └── android │ │ │ ├── AuthType.kt │ │ │ ├── Factory.kt │ │ │ ├── Paystack.java │ │ │ ├── PaystackSdk.java │ │ │ ├── PaystackSdkComponent.kt │ │ │ ├── Transaction.java │ │ │ ├── TransactionManager.java │ │ │ ├── api │ │ │ ├── ApiCallback.kt │ │ │ ├── ChargeApiCallback.kt │ │ │ ├── PaystackRepository.kt │ │ │ ├── PaystackRepositoryImpl.kt │ │ │ ├── di │ │ │ │ └── ApiComponent.kt │ │ │ ├── model │ │ │ │ ├── ChargeResponse.kt │ │ │ │ ├── TransactionApiResponse.kt │ │ │ │ └── TransactionInitResponse.kt │ │ │ ├── request │ │ │ │ ├── ChargeParams.kt │ │ │ │ ├── TransactionInitRequestBody.kt │ │ │ │ └── ValidateTransactionParams.kt │ │ │ ├── service │ │ │ │ ├── ApiService.java │ │ │ │ ├── PaystackApiFactory.kt │ │ │ │ ├── PaystackApiService.kt │ │ │ │ └── converter │ │ │ │ │ ├── NoWrap.kt │ │ │ │ │ └── WrappedResponseConverter.kt │ │ │ └── utils │ │ │ │ ├── MapExt.kt │ │ │ │ └── TLSSocketFactory.java │ │ │ ├── exceptions │ │ │ ├── AuthenticationException.java │ │ │ ├── CardException.java │ │ │ ├── ChargeException.java │ │ │ ├── ExpiredAccessCodeException.java │ │ │ ├── InvalidAmountException.java │ │ │ ├── InvalidEmailException.java │ │ │ ├── PaystackActivityNotFoundException.java │ │ │ ├── PaystackException.java │ │ │ ├── PaystackSdkNotInitializedException.java │ │ │ ├── ProcessingException.java │ │ │ ├── TokenException.java │ │ │ └── ValidateException.java │ │ │ ├── model │ │ │ ├── AvsState.kt │ │ │ ├── Card.java │ │ │ ├── Charge.java │ │ │ ├── PaystackModel.java │ │ │ └── Token.java │ │ │ ├── ui │ │ │ ├── AddressHolder.java │ │ │ ├── AddressVerificationActivity.kt │ │ │ ├── AuthActivity.java │ │ │ ├── AuthSingleton.java │ │ │ ├── CardActivity.java │ │ │ ├── CardSingleton.java │ │ │ ├── OtpActivity.java │ │ │ ├── OtpSingleton.java │ │ │ ├── PinActivity.java │ │ │ └── PinSingleton.java │ │ │ └── utils │ │ │ ├── CardUtils.java │ │ │ ├── Crypto.java │ │ │ ├── Logger.java │ │ │ ├── StringUtils.java │ │ │ └── Utils.java │ └── res │ │ ├── drawable │ │ └── ic_close_24.xml │ │ ├── layout │ │ ├── co_paystack_android____activity_auth.xml │ │ ├── co_paystack_android____activity_avs.xml │ │ ├── co_paystack_android____activity_card.xml │ │ ├── co_paystack_android____activity_otp.xml │ │ └── co_paystack_android____activity_pin.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── co │ └── paystack │ └── android │ ├── CardTest.java │ ├── PaystackSdkTest.java │ ├── TransactionManagerTest.kt │ └── api │ ├── FakeCall.kt │ └── PaystackRepositoryImplTest.kt └── settings.gradle /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests. 6 | 7 | 8 | ## Pull Requests 9 | 10 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 11 | 12 | - **Create feature branches** - Don't ask us to pull from your master branch. 13 | 14 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 15 | 16 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 17 | 18 | 19 | ## Running Tests 20 | Make sure to run tests before submitting your patch. And if you added new classes or functions, please add tests for them too. 21 | 22 | **Happy coding**! 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Problem/Motivation 2 | (Why the issue was filed, steps to reproduce the problem, etc.) 3 | 4 | ## Proposed resolution 5 | (Description of the proposed solution, the rationale behind it, and workarounds for people who cannot use the patch.) 6 | 7 | ## Repeatable 8 | Always|Sometimes|Specific conditions|Specific times 9 | 10 | (If it is a bug, you are reporting lease specify:) 11 | 12 | ## Steps to repeat: (Describe how the issue can be repeated by someone who is to work on it) 13 | 1. Step 1 14 | 2. Step 2 15 | 3. ... 16 | 17 | ## Expected Results: 18 | (What you expected steps 1, 2 and 3 to give) 19 | 20 | ## Actual Results: 21 | (What is gave including any error messages, memory dump etc that can help) 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes #0 (Enter the number for the issue this fixes. If you have not yet created an issue, please do so now or delete this line if you are only submitting a patch) 2 | 3 | ## Changes made by this pull request 4 | - 5 | - 6 | - 7 | 8 | -------------------------------------------------------------------------------- /.github/workflows/Enforce_Labels.yml: -------------------------------------------------------------------------------- 1 | name: Enforce PR labels 2 | 3 | on: 4 | pull_request: 5 | types: [labeled, unlabeled, opened, edited, synchronize] 6 | jobs: 7 | enforce-label: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: yogevbd/enforce-label-action@2.1.0 11 | with: 12 | REQUIRED_LABELS_ANY: "bug,enhancement,skip-changelog" 13 | REQUIRED_LABELS_ANY_DESCRIPTION: "Select at least one label ['bug','enhancement','skip-changelog']" 14 | BANNED_LABELS: "banned" 15 | -------------------------------------------------------------------------------- /.github/workflows/SonarCloud.yml: -------------------------------------------------------------------------------- 1 | name: Sonar Cloud 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | pull_request: 6 | types: [opened, synchronize, reopened] 7 | workflow_dispatch: 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | - name: Set up JDK 11 17 | uses: actions/setup-java@v1 18 | with: 19 | java-version: 11 20 | - name: Cache SonarCloud packages 21 | uses: actions/cache@v1 22 | with: 23 | path: ~/.sonar/cache 24 | key: ${{ runner.os }}-sonar 25 | restore-keys: ${{ runner.os }}-sonar 26 | - name: Cache Gradle packages 27 | uses: actions/cache@v1 28 | with: 29 | path: ~/.gradle/caches 30 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} 31 | restore-keys: ${{ runner.os }}-gradle 32 | - name: Build and analyze 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 36 | run: ./gradlew JacocoTestReport sonarqube --scan 37 | 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .DS_Store 3 | .idea 4 | *.iml 5 | /local.properties 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | projectFilesBackup/ 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | Last updated May 05, 2018 3 | (from http://keepachangelog.com/) 4 | 5 | All notable changes to this project will be documented in this file. 6 | 7 | ## Released v3.0.12 on May 08, 2019 8 | - Update dependencies 9 | 10 | ## Released v3.0.11 on May 05, 2018 11 | - fix - Handle auth=pin 12 | 13 | ## Released v3.0.10 on Apr 10, 2018 14 | - Allow adding JSONObject to metadata 15 | 16 | ## Released v3.0.8 on Nov 4, 2017 17 | - Resolved issue that caused build to fail when using AAPT2 18 | 19 | ## Released v3.0.7 on Oct 27, 2017 20 | - Invalid card details will force us to show a popup that allows the user correct their card entry 21 | - Updated all dependencies 22 | - In debug mode, an assertion has been added to force the developer to choose either remote or local 23 | transaction initialization exclusively. 24 | - Added utility method to strip card of all non-numeric characters 25 | 26 | ## Released v3.0.5 on Jun 20, 2017 27 | - Fixed a bug that made some verve cards get bounced by the validator 28 | - Other minor bug fixes 29 | 30 | ## Released v3.0.1 on Apr 7, 2017 31 | - Add access_code support (`chargeCard` will resume transaction initialized on server) 32 | - Transaction reference can be fetched even if an error occured 33 | - removed support for token flow 34 | - Added support for Bank hosted (3DSecure) authorization 35 | - SDK is able to conclude card enrollment 36 | 37 | ## Released v2.1.0 on Nov 13, 2016 38 | - Add metadata support. 39 | - HOTFIX Verve card support fully functional 40 | 41 | ## Released v2.0.2 on Sep 5, 2016 42 | - Add split payment awareness. 43 | 44 | ## Released v2.0.1 on Sep 5, 2016 45 | - Increase all connection timeout to 5 minutes. 46 | 47 | ## Released v2.0 - 2016-08-25 48 | - Added Perform Transaction 49 | - Added Verve Card Support 50 | - Added PIN and OTP 51 | 52 | ## Released v1.2.1 - 2016-07-30 53 | - Update retrofit and okHttp 54 | - okHttp no more uses reflection so we get it the default trust manager 55 | 56 | ## Released v1.2.0 - 2016-03-14 57 | - ApiClient will throw new exceptions in case `TLSv1.2` is not found 58 | - Make `retrofit` use custom `okHttpClient` that uses our socket factory to create requests 59 | 60 | **Fixed** 61 | - Rework Android Library to work with only TLS v1.2 62 | 63 | ## NEXT - YYYY-MM-DD 64 | 65 | ## Added - 2016-03-02 66 | - Markdown templates - @ibrahimlawal 67 | 68 | ## Released v1.1.1 - 2016-03-01 69 | - Updates Gradle plugin to 1.5.0 > @ibrahimlawal 70 | - Updated Retrofit to Retrofit 2 > @ibrahimlawal 71 | 72 | **Removed** 73 | - Retrofit 74 | 75 | **Added** 76 | - Retrofit 2 77 | 78 | **Fixed** 79 | - Compatibility with apps using Retrofit 2 80 | 81 | ## Released v1.1.0 - 2016-01-26 82 | - Updated readme to include instructions for charging tokens, charging returning customers - @shollsman 83 | - RSA KEY now a constant fixed in library. - @ibrahimlawal 84 | 85 | ## Released v1.1.0 - 2015-10-07 86 | - Initial release published - @segunfamisa 87 | 88 | ## Commits on Oct 6, 2015 89 | - Added files > @segunfamisa - 3472b97 90 | - Initial commit > @shollsman 91 | 92 | 93 | ### Security 94 | - Nothing 95 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.3) 5 | addressable (2.8.0) 6 | public_suffix (>= 2.0.2, < 5.0) 7 | artifactory (3.0.15) 8 | atomos (0.1.3) 9 | aws-eventstream (1.1.1) 10 | aws-partitions (1.448.0) 11 | aws-sdk-core (3.114.0) 12 | aws-eventstream (~> 1, >= 1.0.2) 13 | aws-partitions (~> 1, >= 1.239.0) 14 | aws-sigv4 (~> 1.1) 15 | jmespath (~> 1.0) 16 | aws-sdk-kms (1.43.0) 17 | aws-sdk-core (~> 3, >= 3.112.0) 18 | aws-sigv4 (~> 1.1) 19 | aws-sdk-s3 (1.94.0) 20 | aws-sdk-core (~> 3, >= 3.112.0) 21 | aws-sdk-kms (~> 1) 22 | aws-sigv4 (~> 1.1) 23 | aws-sigv4 (1.2.3) 24 | aws-eventstream (~> 1, >= 1.0.2) 25 | babosa (1.0.4) 26 | claide (1.0.3) 27 | colored (1.2) 28 | colored2 (3.1.2) 29 | commander-fastlane (4.4.6) 30 | highline (~> 1.7.2) 31 | declarative (0.0.20) 32 | digest-crc (0.6.3) 33 | rake (>= 12.0.0, < 14.0.0) 34 | domain_name (0.5.20190701) 35 | unf (>= 0.0.5, < 1.0.0) 36 | dotenv (2.7.6) 37 | emoji_regex (3.2.2) 38 | excon (0.80.1) 39 | faraday (1.4.1) 40 | faraday-excon (~> 1.1) 41 | faraday-net_http (~> 1.0) 42 | faraday-net_http_persistent (~> 1.1) 43 | multipart-post (>= 1.2, < 3) 44 | ruby2_keywords (>= 0.0.4) 45 | faraday-cookie_jar (0.0.7) 46 | faraday (>= 0.8.0) 47 | http-cookie (~> 1.0.0) 48 | faraday-excon (1.1.0) 49 | faraday-net_http (1.0.1) 50 | faraday-net_http_persistent (1.1.0) 51 | faraday_middleware (1.0.0) 52 | faraday (~> 1.0) 53 | fastimage (2.2.3) 54 | fastlane (2.181.0) 55 | CFPropertyList (>= 2.3, < 4.0.0) 56 | addressable (>= 2.3, < 3.0.0) 57 | artifactory (~> 3.0) 58 | aws-sdk-s3 (~> 1.0) 59 | babosa (>= 1.0.3, < 2.0.0) 60 | bundler (>= 1.12.0, < 3.0.0) 61 | colored 62 | commander-fastlane (>= 4.4.6, < 5.0.0) 63 | dotenv (>= 2.1.1, < 3.0.0) 64 | emoji_regex (>= 0.1, < 4.0) 65 | excon (>= 0.71.0, < 1.0.0) 66 | faraday (~> 1.0) 67 | faraday-cookie_jar (~> 0.0.6) 68 | faraday_middleware (~> 1.0) 69 | fastimage (>= 2.1.0, < 3.0.0) 70 | gh_inspector (>= 1.1.2, < 2.0.0) 71 | google-api-client (>= 0.37.0, < 0.39.0) 72 | google-cloud-storage (>= 1.15.0, < 2.0.0) 73 | highline (>= 1.7.2, < 2.0.0) 74 | json (< 3.0.0) 75 | jwt (>= 2.1.0, < 3) 76 | mini_magick (>= 4.9.4, < 5.0.0) 77 | multipart-post (~> 2.0.0) 78 | naturally (~> 2.2) 79 | plist (>= 3.1.0, < 4.0.0) 80 | rubyzip (>= 2.0.0, < 3.0.0) 81 | security (= 0.1.3) 82 | simctl (~> 1.6.3) 83 | slack-notifier (>= 2.0.0, < 3.0.0) 84 | terminal-notifier (>= 2.0.0, < 3.0.0) 85 | terminal-table (>= 1.4.5, < 2.0.0) 86 | tty-screen (>= 0.6.3, < 1.0.0) 87 | tty-spinner (>= 0.8.0, < 1.0.0) 88 | word_wrap (~> 1.0.0) 89 | xcodeproj (>= 1.13.0, < 2.0.0) 90 | xcpretty (~> 0.3.0) 91 | xcpretty-travis-formatter (>= 0.0.3) 92 | gh_inspector (1.1.3) 93 | google-api-client (0.38.0) 94 | addressable (~> 2.5, >= 2.5.1) 95 | googleauth (~> 0.9) 96 | httpclient (>= 2.8.1, < 3.0) 97 | mini_mime (~> 1.0) 98 | representable (~> 3.0) 99 | retriable (>= 2.0, < 4.0) 100 | signet (~> 0.12) 101 | google-apis-core (0.3.0) 102 | addressable (~> 2.5, >= 2.5.1) 103 | googleauth (~> 0.14) 104 | httpclient (>= 2.8.1, < 3.0) 105 | mini_mime (~> 1.0) 106 | representable (~> 3.0) 107 | retriable (>= 2.0, < 4.0) 108 | rexml 109 | signet (~> 0.14) 110 | webrick 111 | google-apis-iamcredentials_v1 (0.3.0) 112 | google-apis-core (~> 0.1) 113 | google-apis-storage_v1 (0.3.0) 114 | google-apis-core (~> 0.1) 115 | google-cloud-core (1.6.0) 116 | google-cloud-env (~> 1.0) 117 | google-cloud-errors (~> 1.0) 118 | google-cloud-env (1.5.0) 119 | faraday (>= 0.17.3, < 2.0) 120 | google-cloud-errors (1.1.0) 121 | google-cloud-storage (1.31.0) 122 | addressable (~> 2.5) 123 | digest-crc (~> 0.4) 124 | google-apis-iamcredentials_v1 (~> 0.1) 125 | google-apis-storage_v1 (~> 0.1) 126 | google-cloud-core (~> 1.2) 127 | googleauth (~> 0.9) 128 | mini_mime (~> 1.0) 129 | googleauth (0.16.1) 130 | faraday (>= 0.17.3, < 2.0) 131 | jwt (>= 1.4, < 3.0) 132 | memoist (~> 0.16) 133 | multi_json (~> 1.11) 134 | os (>= 0.9, < 2.0) 135 | signet (~> 0.14) 136 | highline (1.7.10) 137 | http-cookie (1.0.3) 138 | domain_name (~> 0.5) 139 | httpclient (2.8.3) 140 | jmespath (1.4.0) 141 | json (2.5.1) 142 | jwt (2.2.3) 143 | memoist (0.16.2) 144 | mini_magick (4.11.0) 145 | mini_mime (1.1.0) 146 | multi_json (1.15.0) 147 | multipart-post (2.0.0) 148 | nanaimo (0.3.0) 149 | naturally (2.2.1) 150 | os (1.1.1) 151 | plist (3.6.0) 152 | public_suffix (4.0.6) 153 | rake (13.0.3) 154 | representable (3.1.1) 155 | declarative (< 0.1.0) 156 | trailblazer-option (>= 0.1.1, < 0.2.0) 157 | uber (< 0.2.0) 158 | retriable (3.1.2) 159 | rexml (3.2.5) 160 | rouge (2.0.7) 161 | ruby2_keywords (0.0.4) 162 | rubyzip (2.3.0) 163 | security (0.1.3) 164 | signet (0.15.0) 165 | addressable (~> 2.3) 166 | faraday (>= 0.17.3, < 2.0) 167 | jwt (>= 1.5, < 3.0) 168 | multi_json (~> 1.10) 169 | simctl (1.6.8) 170 | CFPropertyList 171 | naturally 172 | slack-notifier (2.3.2) 173 | terminal-notifier (2.0.0) 174 | terminal-table (1.8.0) 175 | unicode-display_width (~> 1.1, >= 1.1.1) 176 | trailblazer-option (0.1.1) 177 | tty-cursor (0.7.1) 178 | tty-screen (0.8.1) 179 | tty-spinner (0.9.3) 180 | tty-cursor (~> 0.7) 181 | uber (0.1.0) 182 | unf (0.1.4) 183 | unf_ext 184 | unf_ext (0.0.7.7) 185 | unicode-display_width (1.7.0) 186 | webrick (1.7.0) 187 | word_wrap (1.0.0) 188 | xcodeproj (1.19.0) 189 | CFPropertyList (>= 2.3.3, < 4.0) 190 | atomos (~> 0.1.3) 191 | claide (>= 1.0.2, < 2.0) 192 | colored2 (~> 3.1) 193 | nanaimo (~> 0.3.0) 194 | xcpretty (0.3.0) 195 | rouge (~> 2.0.7) 196 | xcpretty-travis-formatter (1.0.1) 197 | xcpretty (~> 0.2, >= 0.0.7) 198 | 199 | PLATFORMS 200 | x86_64-darwin-19 201 | x86_64-linux 202 | 203 | DEPENDENCIES 204 | fastlane 205 | 206 | BUNDLED WITH 207 | 2.2.15 208 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # History 2 | Last updated Nov 4, 2017 3 | 4 | ## Commits on Nov 4, 2017 5 | - Resolve issue that caused build to fail when using AAPT2 6 | 7 | ## Commits on Oct 27, 2017 8 | - Invalid card details will force us to show a popup that allows the user correct their card entry 9 | - Updated all dependencies 10 | - In debug mode, an assertion has been added to force the developer to choose either remote or local 11 | transaction initialization exclusively. 12 | - Added utility method to strip card of all non-numeric characters 13 | 14 | ## Commits on Jun 20, 2017 15 | - Fixed a bug that made some verve cards get bounced by the validator 16 | - Other minor bug fixes 17 | 18 | ## Commits on Apr 7, 2017 19 | - `chargeCard` now resumes a transaction initialized by server 20 | - Remove deprecated methods 21 | - Add support for SecureCode 22 | - Bug fixes 23 | 24 | ## Commits on Sep 5, 2016 25 | - Increase all connection timeout to 5 minutes. 26 | 27 | ## Commits on Aug 24, 2016 28 | - Add chargeCard 29 | - Deprecate PaystackSdk.createToken 30 | - Deprecate createToken 31 | 32 | ## Commits on Aug 24, 2016 33 | - Add chargeCard 34 | - Deprecate PaystackSdk.createToken 35 | - Deprecate createToken 36 | 37 | ## Commits on Mar 14, 2016 38 | - Bump `PaystackSdk` version to `1.2.0` 39 | - Example App will use `v1.2.0` 40 | - Added default sample card to example activity so token request is one-click 41 | - Added `TLSSocketFactory` class that pegs all socket calls to use `TLSv1.2` 42 | - ApiClient will throw new exceptions in case `TLSv1.2` is not found 43 | - Make `retrofit` use custom `okHttpClient` that uses `TLSSocketFactory` to create requests 44 | - Took out stray `statuscode` integer 45 | 46 | ## Commits on Mar 2, 2016 47 | - Added Markdown templates > @ibrahimlawal - bb030a6 48 | - Example app should use published library > ibrahimlawal - 657dc7c 49 | 50 | ## Commits on Mar 1, 2016 51 | - Fix javadoc errors and warnngs > @ibrahimlawal - b852006 52 | - Update README.md for release of v1.1.1 > @ibrahimlawal - ba7ab2a 53 | - Updates Gradle plugin to 1.5.0 > @ibrahimlawal - 415101b 54 | - Updated Retrofit to Retrofit 2 > @ibrahimlawal - c4bfadb 55 | 56 | ## Commits on Feb 12, 2016 57 | Added sample code using PHP library/class > @ibrahimlawal - a343243 58 | 59 | ## Commits on Jan 28, 2016 60 | Version 1.1.0 published to bintray > @ibrahimlawal - a4c2e55 61 | 62 | ## Commits on Jan 26, 2016 63 | - added instructions for charging returning customers > @shollsman - 3324b1b 64 | - charge token endpoint updated > @shollsman - c08c755 65 | - RSA KEY now a constant fixed in library. > @ibrahimlawal - 0df1adf 66 | - Updated readme to include instructions for charging tokens > @shollsman - aa0345a 67 | 68 | ## Commits on Oct 9, 2015 69 | - Updated README.md > segunfamisa - 6a3bcf4 70 | 71 | ## Commits on Oct 7, 2015 72 | - Updated README.md > @segunfamisa - 268ea4a 73 | - Merge branch 'master' > @segunfamisa - e8d3caa 74 | - Fixed gradle for upload to maven > @segunfamisa - 95f3aeb 75 | 76 | ## Commits on Oct 6, 2015 77 | - Added files > @segunfamisa - 3472b97 78 | - Initial commit > @shollsman 79 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Paystack !nc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.jacocoVersion = '0.8.7' 5 | ext.versions = [ 6 | 'kotlin' : '1.7.22', 7 | 'coroutines' : '1.4.2', 8 | 'pusher' : '2.2.1', 9 | 'robolectric' : '4.9.2', 10 | 'mockito' : '3.8.0', 11 | 'mockito_kotlin': '2.2.0', 12 | 'junit_ext' : '1.1.2', 13 | ] 14 | 15 | repositories { 16 | mavenCentral() 17 | google() 18 | } 19 | dependencies { 20 | classpath 'com.android.tools.build:gradle:7.4.0' 21 | classpath 'org.robolectric:robolectric-gradle-plugin:1.1.0' 22 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin" 23 | // NOTE: Do not place your application dependencies here; they belong 24 | // in the individual module build.gradle files 25 | } 26 | } 27 | plugins { 28 | id "org.sonarqube" version "3.5.0.2730" 29 | } 30 | 31 | sonarqube { 32 | properties { 33 | property "sonar.projectKey", "PaystackHQ_paystack-android" 34 | property "sonar.organization", "paystackhq" 35 | property "sonar.host.url", "https://sonarcloud.io" 36 | property 'sonar.core.codeCoveragePlugin', 'jacoco' 37 | property "sonar.coverage.jacoco.xmlReportPaths", "${project.projectDir}/paystack/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml" 38 | property("sonar.sources", "src/main") 39 | property("sonar.tests", "src/test") 40 | } 41 | } 42 | 43 | 44 | allprojects { 45 | repositories { 46 | mavenCentral() 47 | google() 48 | jcenter() 49 | } 50 | 51 | configurations.all { 52 | resolutionStrategy { 53 | force 'org.objenesis:objenesis:2.6' 54 | eachDependency { details -> 55 | if (details.requested.group == 'org.jacoco') { 56 | details.useVersion jacocoVersion 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | ext { 64 | compileSdkVersion = 29 65 | minSdkVersion = 16 66 | targetSdkVersion = 29 67 | versionCode = 23 68 | 69 | buildToolsVersion = "29.0.2" 70 | versionName = "3.3.2" 71 | } 72 | 73 | Object getEnvOrDefault(String propertyName, Object defaultValue) { 74 | // Check 'local.properties' first 75 | String propertyValue 76 | 77 | def propFile = file('local.properties') 78 | if (propFile.exists()) { 79 | Properties localProps = new Properties() 80 | localProps.load(propFile.newDataInputStream()) 81 | propertyValue = localProps.getProperty(propertyName) 82 | if (propertyValue != null) { 83 | return propertyValue 84 | } 85 | } 86 | 87 | propertyValue = project.properties[propertyName] 88 | 89 | if (propertyValue == null) { 90 | logger.error("Build property named {} not defined. Falling back to default value.", propertyName) 91 | return defaultValue 92 | } 93 | 94 | return propertyValue 95 | } 96 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /example/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | //apply plugin: 'org.robolectric' 3 | 4 | apply plugin: 'jacoco' 5 | 6 | jacoco { 7 | toolVersion = "$jacocoVersion" 8 | } 9 | 10 | tasks.withType(Test) { 11 | jacoco.includeNoLocationClasses = true 12 | jacoco.excludes = ['jdk.internal.*'] 13 | } 14 | 15 | task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) { 16 | 17 | reports { 18 | xml.enabled = true 19 | html.enabled = true 20 | } 21 | 22 | def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*'] 23 | def debugTree = fileTree(dir: "$project.buildDir/intermediates/javac/debug/classes", excludes: fileFilter) 24 | def mainSrc = "$project.projectDir/src/main/java" 25 | 26 | sourceDirectories.setFrom(files([mainSrc])) 27 | classDirectories.setFrom(files([debugTree])) 28 | executionData.setFrom(fileTree(dir: project.buildDir, includes: [ 29 | '**/*.exec', '**/*.ec' 30 | ])) 31 | } 32 | 33 | android { 34 | compileSdkVersion rootProject.ext.compileSdkVersion 35 | 36 | defaultConfig { 37 | applicationId "co.paystack.example" 38 | minSdkVersion rootProject.ext.minSdkVersion 39 | targetSdkVersion rootProject.ext.targetSdkVersion 40 | versionCode rootProject.ext.versionCode 41 | versionName rootProject.ext.versionName 42 | } 43 | buildTypes { 44 | debug { 45 | testCoverageEnabled false 46 | } 47 | release { 48 | minifyEnabled false 49 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 50 | } 51 | } 52 | compileOptions { 53 | sourceCompatibility = 1.8 54 | targetCompatibility = 1.8 55 | } 56 | testOptions { 57 | unitTests.all { 58 | jacoco { 59 | includeNoLocationClasses = true 60 | } 61 | } 62 | } 63 | } 64 | 65 | dependencies { 66 | implementation 'androidx.appcompat:appcompat:1.2.0' 67 | implementation fileTree(include: ['*.jar'], dir: 'libs') 68 | testImplementation 'junit:junit:4.13.2' 69 | testImplementation 'org.assertj:assertj-core:3.12.2' 70 | testImplementation 'org.robolectric:robolectric:4.5.1' 71 | testImplementation 'org.mockito:mockito-core:3.8.0' 72 | implementation project(':paystack') 73 | // implementation "co.paystack.android:paystack:$rootProject.ext.versionName" 74 | } 75 | -------------------------------------------------------------------------------- /example/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/segunfamisa/Documents/Softwares/adt-bundle-mac-x86_64-20130729/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /example/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /example/src/main/java/co/paystack/example/App.java: -------------------------------------------------------------------------------- 1 | package co.paystack.example; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | /** 7 | * The Example App 8 | *

9 | * This class allows us capture the application context and capture the app instance 10 | */ 11 | public class App extends Application { 12 | 13 | private static Context sContext; 14 | 15 | private static App sInstance; 16 | 17 | public static Context getAppContext() { 18 | // if(sContext == null){ 19 | // return getApplica; 20 | // } 21 | 22 | return sContext; 23 | } 24 | 25 | private void setAppContext(Context context) { 26 | sContext = context; 27 | } 28 | 29 | @Override 30 | public void onCreate() { 31 | super.onCreate(); 32 | 33 | sInstance = this; 34 | sContext = getApplicationContext(); 35 | 36 | setAppContext(sContext); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example/src/main/java/co/paystack/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package co.paystack.example; 2 | 3 | import android.app.ProgressDialog; 4 | import android.os.AsyncTask; 5 | import android.os.Bundle; 6 | import android.util.Log; 7 | import android.view.Menu; 8 | import android.view.View; 9 | import android.widget.Button; 10 | import android.widget.EditText; 11 | import android.widget.TextView; 12 | import android.widget.Toast; 13 | 14 | import androidx.appcompat.app.AppCompatActivity; 15 | 16 | import org.json.JSONException; 17 | 18 | import java.io.BufferedReader; 19 | import java.io.InputStreamReader; 20 | import java.net.URL; 21 | import java.util.Calendar; 22 | 23 | import co.paystack.android.Paystack; 24 | import co.paystack.android.PaystackSdk; 25 | import co.paystack.android.Transaction; 26 | import co.paystack.android.exceptions.ExpiredAccessCodeException; 27 | import co.paystack.android.model.Card; 28 | import co.paystack.android.model.Charge; 29 | 30 | public class MainActivity extends AppCompatActivity { 31 | 32 | // To get started quickly, change this to your heroku deployment of 33 | // https://github.com/PaystackHQ/sample-charge-card-backend 34 | // Step 1. Visit https://github.com/PaystackHQ/sample-charge-card-backend 35 | // Step 2. Click "Deploy to heroku" 36 | // Step 3. Login with your heroku credentials or create a free heroku account 37 | // Step 4. Provide your secret key and an email with which to start all test transactions 38 | // Step 5. Copy the url generated by heroku (format https://some-url.herokuapp.com) into the space below 39 | String backend_url = "https://charge-sample-service.onrender.com"; 40 | // Set this to a public key that matches the secret key you supplied while creating the heroku instance 41 | String paystack_public_key = "pk_test_9eb0263ed776c4c892e0281348aee4136cd0dd52"; 42 | 43 | EditText mEditCardNum; 44 | EditText mEditCVC; 45 | EditText mEditExpiryMonth; 46 | EditText mEditExpiryYear; 47 | 48 | TextView mTextError; 49 | TextView mTextBackendMessage; 50 | 51 | ProgressDialog dialog; 52 | private TextView mTextReference; 53 | private Charge charge; 54 | private Transaction transaction; 55 | private static final String TAG = "MainActivity"; 56 | 57 | @Override 58 | protected void onCreate(Bundle savedInstanceState) { 59 | super.onCreate(savedInstanceState); 60 | setContentView(R.layout.activity_main); 61 | 62 | if (BuildConfig.DEBUG && (backend_url.equals(""))) { 63 | throw new AssertionError("Please set a backend url before running the sample"); 64 | } 65 | if (BuildConfig.DEBUG && (paystack_public_key.equals(""))) { 66 | throw new AssertionError("Please set a public key before running the sample"); 67 | } 68 | 69 | PaystackSdk.setPublicKey(paystack_public_key); 70 | 71 | mEditCardNum = findViewById(R.id.edit_card_number); 72 | mEditCVC = findViewById(R.id.edit_cvc); 73 | mEditExpiryMonth = findViewById(R.id.edit_expiry_month); 74 | mEditExpiryYear = findViewById(R.id.edit_expiry_year); 75 | 76 | Button mButtonPerformTransaction = findViewById(R.id.button_perform_transaction); 77 | Button mButtonPerformLocalTransaction = findViewById(R.id.button_perform_local_transaction); 78 | 79 | mTextError = findViewById(R.id.textview_error); 80 | mTextBackendMessage = findViewById(R.id.textview_backend_message); 81 | mTextReference = findViewById(R.id.textview_reference); 82 | 83 | //initialize sdk 84 | PaystackSdk.initialize(getApplicationContext()); 85 | 86 | //set click listener 87 | mButtonPerformTransaction.setOnClickListener(new View.OnClickListener() { 88 | @Override 89 | public void onClick(View view) { 90 | 91 | try { 92 | startAFreshCharge(false); 93 | } catch (Exception e) { 94 | MainActivity.this.mTextError.setText(String.format("An error occurred while charging card: %s %s", e.getClass().getSimpleName(), e.getMessage())); 95 | 96 | } 97 | } 98 | }); 99 | mButtonPerformLocalTransaction.setOnClickListener(new View.OnClickListener() { 100 | @Override 101 | public void onClick(View view) { 102 | 103 | try { 104 | startAFreshCharge(true); 105 | } catch (Exception e) { 106 | MainActivity.this.mTextError.setText(String.format("An error occurred while charging card: %s %s", e.getClass().getSimpleName(), e.getMessage())); 107 | 108 | } 109 | } 110 | }); 111 | } 112 | 113 | private void startAFreshCharge(boolean local) { 114 | // initialize the charge 115 | charge = new Charge(); 116 | charge.setCard(loadCardFromForm()); 117 | 118 | dialog = new ProgressDialog(MainActivity.this); 119 | dialog.setMessage("Performing transaction... please wait"); 120 | dialog.show(); 121 | 122 | if (local) { 123 | // Set transaction params directly in app (note that these params 124 | // are only used if an access_code is not set. In debug mode, 125 | // setting them after setting an access code would throw an exception 126 | 127 | charge.setAmount(2000); 128 | charge.setEmail("customer@email.com"); 129 | charge.setReference("ChargedFromAndroid_" + Calendar.getInstance().getTimeInMillis()); 130 | try { 131 | charge.putCustomField("Charged From", "Android SDK"); 132 | } catch (JSONException e) { 133 | e.printStackTrace(); 134 | } 135 | chargeCard(); 136 | } else { 137 | // Perform transaction/initialize on our server to get an access code 138 | // documentation: https://developers.paystack.co/reference#initialize-a-transaction 139 | new fetchAccessCodeFromServer().execute(backend_url + "/new-access-code"); 140 | } 141 | } 142 | 143 | /** 144 | * Method to validate the form, and set errors on the edittexts. 145 | */ 146 | private Card loadCardFromForm() { 147 | //validate fields 148 | Card card; 149 | 150 | String cardNum = mEditCardNum.getText().toString().trim(); 151 | 152 | //build card object with ONLY the number, update the other fields later 153 | card = new Card.Builder(cardNum, 0, 0, "").build(); 154 | String cvc = mEditCVC.getText().toString().trim(); 155 | //update the cvc field of the card 156 | card.setCvc(cvc); 157 | 158 | //validate expiry month; 159 | String sMonth = mEditExpiryMonth.getText().toString().trim(); 160 | int month = 0; 161 | try { 162 | month = Integer.parseInt(sMonth); 163 | } catch (Exception ignored) { 164 | } 165 | 166 | card.setExpiryMonth(month); 167 | 168 | String sYear = mEditExpiryYear.getText().toString().trim(); 169 | int year = 0; 170 | try { 171 | year = Integer.parseInt(sYear); 172 | } catch (Exception ignored) { 173 | } 174 | card.setExpiryYear(year); 175 | 176 | return card; 177 | } 178 | 179 | @Override 180 | public void onPause() { 181 | super.onPause(); 182 | 183 | if ((dialog != null) && dialog.isShowing()) { 184 | dialog.dismiss(); 185 | } 186 | dialog = null; 187 | } 188 | 189 | private void chargeCard() { 190 | transaction = null; 191 | PaystackSdk.chargeCard(MainActivity.this, charge, new Paystack.TransactionCallback() { 192 | // This is called only after transaction is successful 193 | @Override 194 | public void onSuccess(Transaction transaction) { 195 | dismissDialog(); 196 | 197 | MainActivity.this.transaction = transaction; 198 | mTextError.setText(" "); 199 | Toast.makeText(MainActivity.this, transaction.getReference(), Toast.LENGTH_LONG).show(); 200 | updateTextViews(); 201 | new verifyOnServer().execute(transaction.getReference()); 202 | } 203 | 204 | // This is called only before requesting OTP 205 | // Save reference so you may send to server if 206 | // error occurs with OTP 207 | // No need to dismiss dialog 208 | @Override 209 | public void beforeValidate(Transaction transaction) { 210 | MainActivity.this.transaction = transaction; 211 | Toast.makeText(MainActivity.this, transaction.getReference(), Toast.LENGTH_LONG).show(); 212 | updateTextViews(); 213 | } 214 | 215 | @Override 216 | public void showLoading(Boolean isProcessing) { 217 | Log.i(TAG, "Paystack SDK loading: " + isProcessing); 218 | if (isProcessing) { 219 | Toast.makeText(MainActivity.this, "Processing...", Toast.LENGTH_LONG).show(); 220 | } 221 | } 222 | 223 | @Override 224 | public void onError(Throwable error, Transaction transaction) { 225 | // If an access code has expired, simply ask your server for a new one 226 | // and restart the charge instead of displaying error 227 | MainActivity.this.transaction = transaction; 228 | if (error instanceof ExpiredAccessCodeException) { 229 | MainActivity.this.startAFreshCharge(false); 230 | MainActivity.this.chargeCard(); 231 | return; 232 | } 233 | 234 | dismissDialog(); 235 | 236 | if (transaction.getReference() != null) { 237 | Toast.makeText(MainActivity.this, transaction.getReference() + " concluded with error: " + error.getMessage(), Toast.LENGTH_LONG).show(); 238 | mTextError.setText(String.format("%s concluded with error: %s %s", transaction.getReference(), error.getClass().getSimpleName(), error.getMessage())); 239 | new verifyOnServer().execute(transaction.getReference()); 240 | } else { 241 | Toast.makeText(MainActivity.this, error.getMessage(), Toast.LENGTH_LONG).show(); 242 | mTextError.setText(String.format("Error: %s %s", error.getClass().getSimpleName(), error.getMessage())); 243 | } 244 | updateTextViews(); 245 | } 246 | 247 | }); 248 | } 249 | 250 | private void dismissDialog() { 251 | if ((dialog != null) && dialog.isShowing()) { 252 | dialog.dismiss(); 253 | } 254 | } 255 | 256 | private void updateTextViews() { 257 | if (transaction.getReference() != null) { 258 | mTextReference.setText(String.format("Reference: %s", transaction.getReference())); 259 | } else { 260 | mTextReference.setText("No transaction"); 261 | } 262 | } 263 | 264 | @Override 265 | public boolean onCreateOptionsMenu(Menu menu) { 266 | // Inflate the menu; this adds items to the action bar if it is present. 267 | getMenuInflater().inflate(R.menu.menu_main, menu); 268 | return true; 269 | } 270 | 271 | private boolean isEmpty(String s) { 272 | return s == null || s.length() < 1; 273 | } 274 | 275 | private class fetchAccessCodeFromServer extends AsyncTask { 276 | private String error; 277 | 278 | @Override 279 | protected void onPostExecute(String result) { 280 | super.onPostExecute(result); 281 | if (result != null) { 282 | charge.setAccessCode(result); 283 | chargeCard(); 284 | } else { 285 | MainActivity.this.mTextBackendMessage.setText(String.format("There was a problem getting a new access code form the backend: %s", error)); 286 | dismissDialog(); 287 | } 288 | } 289 | 290 | @Override 291 | protected String doInBackground(String... ac_url) { 292 | try { 293 | URL url = new URL(ac_url[0]); 294 | BufferedReader in = new BufferedReader( 295 | new InputStreamReader( 296 | url.openStream())); 297 | 298 | String inputLine; 299 | inputLine = in.readLine(); 300 | in.close(); 301 | return inputLine; 302 | } catch (Exception e) { 303 | error = e.getClass().getSimpleName() + ": " + e.getMessage(); 304 | } 305 | return null; 306 | } 307 | } 308 | 309 | private class verifyOnServer extends AsyncTask { 310 | private String reference; 311 | private String error; 312 | 313 | @Override 314 | protected void onPostExecute(String result) { 315 | super.onPostExecute(result); 316 | if (result != null) { 317 | MainActivity.this.mTextBackendMessage.setText(String.format("Gateway response: %s", result)); 318 | 319 | } else { 320 | MainActivity.this.mTextBackendMessage.setText(String.format("There was a problem verifying %s on the backend: %s ", this.reference, error)); 321 | dismissDialog(); 322 | } 323 | } 324 | 325 | @Override 326 | protected String doInBackground(String... reference) { 327 | try { 328 | this.reference = reference[0]; 329 | URL url = new URL(backend_url + "/verify/" + this.reference); 330 | BufferedReader in = new BufferedReader( 331 | new InputStreamReader( 332 | url.openStream())); 333 | 334 | String inputLine; 335 | inputLine = in.readLine(); 336 | in.close(); 337 | return inputLine; 338 | } catch (Exception e) { 339 | error = e.getClass().getSimpleName() + ": " + e.getMessage(); 340 | } 341 | return null; 342 | } 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /example/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 18 | 19 | 26 | 27 | 37 | 38 | 48 | 49 | 50 | 55 | 56 | 69 | 70 | 83 | 84 |