├── .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 |
92 |
100 |
101 |
102 |
111 |
112 |
121 |
122 |
123 |
124 |
125 |
134 |
135 |
144 |
145 |
155 |
156 |
157 |
158 |
159 |
160 |
--------------------------------------------------------------------------------
/example/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PaystackHQ/paystack-android/25a23359c0f1a86c603763b90cbd6df2a1e7bb70/example/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PaystackHQ/paystack-android/25a23359c0f1a86c603763b90cbd6df2a1e7bb70/example/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PaystackHQ/paystack-android/25a23359c0f1a86c603763b90cbd6df2a1e7bb70/example/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PaystackHQ/paystack-android/25a23359c0f1a86c603763b90cbd6df2a1e7bb70/example/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/example/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/example/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Paystack Example
3 |
4 | Hello world!
5 | Settings
6 | Card number
7 | CVV
8 | MM
9 | YYYY
10 | Create token
11 | No card yet
12 | No token
13 | Token was null
14 | Unable to get token
15 | 4123450131001381
16 | 883
17 | 01
18 | 2020
19 | No transaction yet
20 | Amount in Kobo
21 |
22 |
--------------------------------------------------------------------------------
/example/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/src/main/resources/jacoco-agent.properties:
--------------------------------------------------------------------------------
1 | output=none
--------------------------------------------------------------------------------
/example/src/test/java/co/paystack/example/tests/CardTest.java:
--------------------------------------------------------------------------------
1 | package co.paystack.example.tests;
2 |
3 | import org.junit.Test;
4 |
5 | import java.util.Calendar;
6 |
7 | import co.paystack.android.model.Card;
8 |
9 | import static org.junit.Assert.assertSame;
10 | import static org.junit.Assert.assertTrue;
11 |
12 | /**
13 | * Test the card class
14 | */
15 |
16 | public class CardTest {
17 |
18 | private static final int YEAR = Calendar.getInstance().get(Calendar.YEAR);
19 | private static final int MONTH = Calendar.getInstance().get(Calendar.MONTH);
20 |
21 | private static final int YEAR_FUTURE = Calendar.getInstance().get(Calendar.YEAR) + 1;
22 | private static final int YEAR_PAST = Calendar.getInstance().get(Calendar.YEAR) - 2;
23 | private static final int MONTH_PAST = (MONTH > Calendar.getInstance().getMinimum(Calendar.MONTH)) ? MONTH - 1 : Calendar.getInstance().getMaximum(Calendar.MONTH);
24 |
25 |
26 | private static final String CVC_3 = "123";
27 | private static final String CVC_4 = "1234";
28 |
29 | private static final String MASTER_CARD_NUMBER = "5105105105105100";
30 |
31 | private static final String DISCOVER_CARD_NUMBER = "6500000000000002";
32 |
33 | private static final String VISA_CARD_NUMBER = "4111111111111111";
34 | private static final String VISA_CARD_NUMBER_2 = "4342-5611-1111-1118";
35 |
36 | private static final String AMEX_CARD_NUMBER = "341111111111111";
37 |
38 | @Test
39 | public void testExpiredCardMonth() throws Exception {
40 | Card card = new Card.Builder(MASTER_CARD_NUMBER, YEAR, MONTH_PAST, "123").build();
41 | assertSame(false, card.validExpiryDate());
42 | }
43 |
44 | @Test
45 | public void testExpiredCardYear() throws Exception {
46 | Card card = new Card.Builder(MASTER_CARD_NUMBER, YEAR_PAST, Calendar.DECEMBER + 1, "123").build();
47 | assertSame(false, card.validExpiryDate());
48 | }
49 |
50 | @Test
51 | public void canInitializeCardWithBuilder() throws Exception {
52 | Card card = new Card.Builder(MASTER_CARD_NUMBER, 3, YEAR_FUTURE, "123").build();
53 | assertTrue(card.isValid());
54 | }
55 |
56 | @Test
57 | public void testTypeDetectionMasterCard() throws Exception {
58 | Card card = new Card(MASTER_CARD_NUMBER, YEAR, MONTH, CVC_3);
59 | assertSame(Card.CardType.MASTERCARD, card.getType());
60 | }
61 |
62 | @Test
63 | public void testTypeDetectionAmericanExpress() throws Exception {
64 | Card card = new Card(AMEX_CARD_NUMBER, YEAR, MONTH, CVC_4);
65 | assertSame(Card.CardType.AMERICAN_EXPRESS, card.getType());
66 | }
67 |
68 | @Test
69 | public void testTypeDetectionVisaCard() throws Exception {
70 | Card card = new Card(VISA_CARD_NUMBER, YEAR, MONTH, CVC_3);
71 | assertSame(Card.CardType.VISA, card.getType());
72 | }
73 |
74 | //TODO: testTypeDetectionDiscoverCard This test was failing and we need to have look at it.
75 | }
76 |
--------------------------------------------------------------------------------
/example/src/test/java/co/paystack/example/tests/PaystackSdkTest.java:
--------------------------------------------------------------------------------
1 | package co.paystack.example.tests;
2 |
3 | import org.junit.Test;
4 |
5 | import co.paystack.android.PaystackSdk;
6 | import co.paystack.example.App;
7 |
8 | import static org.junit.Assert.assertTrue;
9 |
10 | /**
11 | * PaystackSdk Test Class
12 | *
13 | * Tests the paystack sdk
14 | */
15 |
16 | public class PaystackSdkTest {
17 |
18 | private static final String TEST_PUBLIC_KEY = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANIsL+RHqfkBiKGn/D1y1QnNrMkKzxWP\n" +
19 | "2wkeSokw2OJrCI+d6YGJPrHHx+nmb/Qn885/R01Gw6d7M824qofmCvkCAwEAAQ==";
20 |
21 | @Test(expected = NullPointerException.class)
22 | public void initPaystackSdkWithNullParamsShouldThrowException() {
23 | PaystackSdk.initialize(null);
24 | }
25 |
26 | //TODO: Look at this SDK Unit Test initPaystackSdkWithPaystackActivityShouldPass
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/fastlane/Appfile:
--------------------------------------------------------------------------------
1 | json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
2 | package_name("co.paystack.example") # e.g. com.krausefx.app
3 |
--------------------------------------------------------------------------------
/fastlane/Fastfile:
--------------------------------------------------------------------------------
1 | # This file contains the fastlane.tools configuration
2 | # You can find the documentation at https://docs.fastlane.tools
3 | #
4 | # For a list of all available actions, check out
5 | #
6 | # https://docs.fastlane.tools/actions
7 | #
8 | # For a list of all available plugins, check out
9 | #
10 | # https://docs.fastlane.tools/plugins/available-plugins
11 | #
12 |
13 | # Uncomment the line if you want fastlane to automatically update itself
14 | # update_fastlane
15 |
16 | default_platform(:android)
17 |
18 | platform :android do
19 | desc "Runs all the tests"
20 | lane :test do
21 | gradle(task: "test")
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | # This is neccessary because we use 'co.paystack.android.design.widget:pinpad:1.0.4'
21 | # Once pinpad has been updated (https://github.com/PaystackHQ/library-android-pinpad/pull/6)
22 | # Then android.enableJetifier=true can be deleted and full AndroidX migration is complete
23 | android.enableJetifier=true
24 | android.useAndroidX=true
25 | org.gradle.daemon=true
26 | org.gradle.jvmargs=-Xmx2560m
27 | GROUP=co.paystack.android
28 | VERSION_NAME=3.3.2
29 | POM_DESCRIPTION=Android SDK for Paystack
30 | POM_URL=https://github.com/PaystackHQ/paystack-android
31 | POM_SCM_URL=https://github.com/PaystackHQ/paystack-android
32 | POM_SCM_CONNECTION=scm:git:github.com/PaystackHQ/paystack-android.git
33 | POM_SCM_DEV_CONNECTION=scm:git:ssh://github.com/PaystackHQ/paystack-android.git
34 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
35 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
36 | POM_LICENCE_DIST=repo
37 | POM_DEVELOPER_ID=paystack
38 | POM_DEVELOPER_NAME=Paystack
39 | POM_DEVELOPER_EMAIL=developers@paystack.co
40 | org.gradle.unsafe.configuration-cache=false
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PaystackHQ/paystack-android/25a23359c0f1a86c603763b90cbd6df2a1e7bb70/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Jan 18 10:19:42 WAT 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
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 |
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz 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 |
--------------------------------------------------------------------------------
/paystack/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/paystack/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'jacoco'
4 | apply plugin: 'kotlin-kapt'
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 ktDebugTree = fileTree(dir: "$project.buildDir/tmp/kotlin-classes/debug", excludes: fileFilter)
25 | def mainSrc = "$project.projectDir/src/main/java"
26 |
27 | sourceDirectories.setFrom(files([mainSrc]))
28 | classDirectories.setFrom(files([debugTree, ktDebugTree]))
29 | executionData.setFrom(fileTree(dir: project.buildDir, includes: [
30 | '**/*.exec', '**/*.ec'
31 | ]))
32 | }
33 |
34 | android {
35 | compileSdkVersion rootProject.ext.compileSdkVersion
36 | buildToolsVersion rootProject.ext.buildToolsVersion
37 | lintOptions {
38 | abortOnError false
39 | }
40 | defaultConfig {
41 | minSdkVersion rootProject.ext.minSdkVersion
42 | targetSdkVersion rootProject.ext.targetSdkVersion
43 | versionName rootProject.ext.versionName
44 | consumerProguardFiles 'proguard-rules.pro'
45 |
46 | buildConfigField 'int', 'VERSION_CODE', "${rootProject.ext.versionCode}"
47 | buildConfigField 'String', 'VERSION_NAME', "\"${rootProject.ext.versionName}\""
48 | buildConfigField 'String', 'PUSHER_KEY', "\"${getEnvOrDefault("PUSHER_KEY", "")}\""
49 | }
50 | buildTypes {
51 | debug {
52 | testCoverageEnabled true
53 | }
54 | }
55 |
56 | compileOptions {
57 | sourceCompatibility JavaVersion.VERSION_1_8
58 | targetCompatibility JavaVersion.VERSION_1_8
59 | }
60 | testOptions {
61 | unitTests {
62 | includeAndroidResources = true
63 | }
64 | }
65 | }
66 |
67 | dependencies {
68 | implementation fileTree(dir: 'libs', include: ['*.jar'])
69 | implementation 'com.squareup.retrofit2:retrofit:2.9.0'
70 | implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
71 | implementation 'com.squareup.okhttp3:okhttp:3.14.9'
72 | implementation 'androidx.appcompat:appcompat:1.2.0'
73 | implementation 'co.paystack.android.design.widget:pinpad:1.0.4'
74 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin"
75 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$versions.coroutines"
76 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$versions.coroutines"
77 |
78 | implementation "com.squareup.moshi:moshi-kotlin:1.14.0"
79 | kapt "com.squareup.moshi:moshi-kotlin-codegen:1.14.0"
80 |
81 | implementation "com.pusher:pusher-java-client:$versions.pusher"
82 |
83 | testImplementation "org.robolectric:robolectric:$versions.robolectric"
84 | testImplementation "org.mockito:mockito-core:$versions.mockito"
85 | testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:$versions.mockito_kotlin"
86 | testImplementation "androidx.test.ext:junit-ktx:$versions.junit_ext"
87 | testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$versions.kotlin"
88 | }
89 |
90 | apply from: "https://raw.githubusercontent.com/PaystackHQ/publish-mavencentral/main/maven-publish.gradle"
91 |
--------------------------------------------------------------------------------
/paystack/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=paystack
2 | POM_NAME=paystack-android
3 | POM_PACKAGING=aar
--------------------------------------------------------------------------------
/paystack/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | -keepclassmembers class co.paystack.android.api.model.** { ; }
2 | -keepclassmembers class co.paystack.android.model.** { ; }
3 |
4 |
5 | # Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
6 | # EnclosingMethod is required to use InnerClasses.
7 | -keepattributes Signature, InnerClasses, EnclosingMethod
8 |
9 | # Retrofit does reflection on method and parameter annotations.
10 | -keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
11 |
12 | # Retain service method parameters when optimizing.
13 | -keepclassmembers,allowshrinking,allowobfuscation interface * {
14 | @retrofit2.http.* ;
15 | }
16 |
17 |
18 | ##MOSHI
19 | # JSR 305 annotations are for embedding nullability information.
20 | -dontwarn javax.annotation.**
21 |
22 | -keepclasseswithmembers class * {
23 | @com.squareup.moshi.* ;
24 | }
25 |
26 | -keep @com.squareup.moshi.JsonQualifier interface *
27 |
28 | # Enum field names are used by the integrated EnumJsonAdapter.
29 | # Annotate enums with @JsonClass(generateAdapter = false) to use them with Moshi.
30 | -keepclassmembers @com.squareup.moshi.JsonClass class * extends java.lang.Enum {
31 | ;
32 | }
33 |
34 | # The name of @JsonClass types is used to look up the generated adapter.
35 | -keepnames @com.squareup.moshi.JsonClass class *
36 |
37 | # Retain generated target class's synthetic defaults constructor and keep DefaultConstructorMarker's
38 | # name. We will look this up reflectively to invoke the type's constructor.
39 | #
40 | # We can't _just_ keep the defaults constructor because Proguard/R8's spec doesn't allow wildcard
41 | # matching preceding parameters.
42 | -keepnames class kotlin.jvm.internal.DefaultConstructorMarker
43 | -keepclassmembers @com.squareup.moshi.JsonClass @kotlin.Metadata class * {
44 | synthetic (...);
45 | }
46 |
47 | # Retain generated JsonAdapters if annotated type is retained.
48 | -if @com.squareup.moshi.JsonClass class *
49 | -keep class <1>JsonAdapter {
50 | (...);
51 | ;
52 | }
53 | -if @com.squareup.moshi.JsonClass class **$*
54 | -keep class <1>_<2>JsonAdapter {
55 | (...);
56 | ;
57 | }
58 | -if @com.squareup.moshi.JsonClass class **$*$*
59 | -keep class <1>_<2>_<3>JsonAdapter {
60 | (...);
61 | ;
62 | }
63 | -if @com.squareup.moshi.JsonClass class **$*$*$*
64 | -keep class <1>_<2>_<3>_<4>JsonAdapter {
65 | (...);
66 | ;
67 | }
68 | -if @com.squareup.moshi.JsonClass class **$*$*$*$*
69 | -keep class <1>_<2>_<3>_<4>_<5>JsonAdapter {
70 | (...);
71 | ;
72 | }
73 | -if @com.squareup.moshi.JsonClass class **$*$*$*$*$*
74 | -keep class <1>_<2>_<3>_<4>_<5>_<6>JsonAdapter {
75 | (...);
76 | ;
77 | }
78 |
79 | -keepclassmembers class kotlin.Metadata {
80 | public ;
81 | }
--------------------------------------------------------------------------------
/paystack/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
--------------------------------------------------------------------------------
/paystack/src/debug/java/co/paystack/android/TestActivity.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 |
6 | class TestActivity : AppCompatActivity() {
7 | override fun onCreate(savedInstanceState: Bundle?) {
8 | super.onCreate(savedInstanceState)
9 | PaystackSdk.initialize(this)
10 | }
11 | }
--------------------------------------------------------------------------------
/paystack/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
12 |
15 |
18 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/AuthType.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android
2 |
3 | object AuthType {
4 | const val PIN = "pin"
5 | const val OTP = "otp"
6 | const val THREE_DS = "3DS"
7 | const val PHONE = "phone"
8 | const val ADDRESS_VERIFICATION = "avs"
9 | }
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/Factory.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android
2 |
3 | /**
4 | * Definition of a Factory interface with a function to create objects of a type
5 | * */
6 | interface Factory {
7 | fun create(): T
8 | }
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/Paystack.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android;
2 |
3 | import android.app.Activity;
4 |
5 | import co.paystack.android.exceptions.AuthenticationException;
6 | import co.paystack.android.exceptions.PaystackSdkNotInitializedException;
7 | import co.paystack.android.model.Charge;
8 | import co.paystack.android.model.PaystackModel;
9 | import co.paystack.android.utils.Utils;
10 |
11 | /**
12 | * This is the Paystack model class.\n
13 | *
14 | * Try not to use this class directly.
15 | * Instead, access the functionalities of this class via the {@link PaystackSdk}
16 | *
17 | * @author {androidsupport@paystack.co} on 9/16/15.
18 | */
19 | public class Paystack extends PaystackModel {
20 |
21 | private String publicKey;
22 |
23 | /**
24 | * Constructor.
25 | */
26 | protected Paystack() throws PaystackSdkNotInitializedException {
27 | //validate sdk initialized
28 | Utils.Validate.validateSdkInitialized();
29 | }
30 |
31 | protected Paystack(String publicKey) throws AuthenticationException {
32 | setPublicKey(publicKey);
33 | }
34 |
35 | /**
36 | * Sets the public key
37 | *
38 | * @param publicKey - App Developer's public key
39 | */
40 | private void setPublicKey(String publicKey) throws AuthenticationException {
41 | //validate the public key
42 | validatePublicKey(publicKey);
43 | this.publicKey = publicKey;
44 | }
45 |
46 | private void validatePublicKey(String publicKey) throws AuthenticationException {
47 | //check for null value, and length and startswith pk_
48 | if (publicKey == null || publicKey.length() < 1 || !publicKey.startsWith("pk_")) {
49 | throw new AuthenticationException("Invalid public key. To create a token, " +
50 | "you must use a valid public key.\nEnsure that you have set a public key." +
51 | "\nCheck http://paystack.co for more");
52 | }
53 |
54 | }
55 |
56 | void chargeCard(Activity activity, Charge charge, TransactionCallback transactionCallback) {
57 | chargeCard(activity, charge, publicKey, transactionCallback);
58 | }
59 |
60 |
61 | private void chargeCard(Activity activity, Charge charge, String publicKey, TransactionCallback transactionCallback) {
62 | //check for the needed data, if absent, send an exception through the tokenCallback;
63 | try {
64 | //validate public key
65 | validatePublicKey(publicKey);
66 |
67 | TransactionManager transactionManager = PaystackSdkComponentKt.sdkComponent()
68 | .getTransactionManagerFactory()
69 | .create();
70 |
71 | transactionManager.chargeCard(activity, PaystackSdk.getPublicKey(), charge, transactionCallback);
72 |
73 | } catch (Exception ae) {
74 | assert transactionCallback != null;
75 | transactionCallback.onError(ae, null);
76 | }
77 | }
78 |
79 | private interface BaseCallback {
80 | }
81 |
82 | public interface TransactionCallback extends BaseCallback {
83 | void onSuccess(Transaction transaction);
84 | void beforeValidate(Transaction transaction);
85 | void showLoading(Boolean isProcessing);
86 | void onError(Throwable error, Transaction transaction);
87 | }
88 |
89 | }
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/PaystackSdk.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.pm.ApplicationInfo;
6 | import android.content.pm.PackageManager;
7 |
8 | import co.paystack.android.exceptions.PaystackSdkNotInitializedException;
9 | import co.paystack.android.model.Charge;
10 | import co.paystack.android.utils.Utils;
11 |
12 | /**
13 | * This is the overall paystack sdk manager class.
14 | * Must be used to initialize the Sdk.
15 | *
16 | * @author {androidsupport@paystack.co} on 9/20/15.
17 | */
18 | public final class PaystackSdk {
19 |
20 | /**
21 | * Value for the version code of this sdk
22 | */
23 | public static final int VERSION_CODE = BuildConfig.VERSION_CODE;
24 | /**
25 | * key for public key property in the AndroidManifest.xml
26 | */
27 | private static final String KEY_PUBLIC_KEY_PROP = "co.paystack.android.PublicKey";
28 | public static Context applicationContext;
29 | /**
30 | * Flag to know if sdk has been initialized
31 | */
32 | private static boolean sdkInitialized;
33 | /**
34 | * Reference to the public key
35 | */
36 | private static volatile String publicKey;
37 |
38 | /**
39 | * Initialize PaystackSdk with a callback when has been initilized successfully.
40 | *
41 | * @param applicationContext - Application Context
42 | * @param initializeCallback - callback to execute after initializing
43 | */
44 | private static synchronized void initialize(Context applicationContext, SdkInitializeCallback initializeCallback) {
45 | //do all the init work here
46 |
47 | //check if initialize callback is set and sdk is actually intialized
48 | if (initializeCallback != null && sdkInitialized) {
49 | initializeCallback.onInitialized();
50 | return;
51 | }
52 |
53 | //null check for applicationContext
54 | Utils.Validate.validateNotNull(applicationContext, "applicationContext");
55 |
56 | //check for internet permissions
57 | Utils.Validate.hasInternetPermission(applicationContext);
58 |
59 | //load data from PaystackSdk
60 | PaystackSdk.loadFromManifest(applicationContext);
61 |
62 | sdkInitialized = true;
63 | PaystackSdk.applicationContext = applicationContext;
64 |
65 | if (initializeCallback != null) {
66 | initializeCallback.onInitialized();
67 | }
68 | }
69 |
70 | /**
71 | * Initialize an sdk without a callback
72 | *
73 | * @param context - Application Context
74 | */
75 | public static synchronized void initialize(Context context) {
76 | initialize(context, null);
77 | }
78 |
79 |
80 | public static boolean isSdkInitialized() {
81 | return sdkInitialized;
82 | }
83 |
84 | /**
85 | * Return public key
86 | *
87 | * @return public key
88 | * @throws PaystackSdkNotInitializedException if the sdk hasn't been initialized
89 | */
90 | public static String getPublicKey() throws PaystackSdkNotInitializedException {
91 | //validate that the sdk has been initialized
92 | Utils.Validate.validateSdkInitialized();
93 |
94 | return publicKey;
95 | }
96 |
97 | /**
98 | * Sets the public key
99 | *
100 | * @param publicKey - App Developer's public key
101 | */
102 | public static void setPublicKey(String publicKey) {
103 | PaystackSdk.publicKey = publicKey;
104 | }
105 |
106 | private static void loadFromManifest(Context context) {
107 | if (context == null) {
108 | return;
109 | }
110 |
111 | ApplicationInfo applicationInfo;
112 | try {
113 | applicationInfo = context.getPackageManager().getApplicationInfo(
114 | context.getPackageName(), PackageManager.GET_META_DATA
115 | );
116 | } catch (PackageManager.NameNotFoundException e) {
117 | return;
118 | }
119 |
120 | //check if we can get any metadata, return if not
121 | if (applicationInfo == null || applicationInfo.metaData == null) {
122 | return;
123 | }
124 |
125 | //check public key
126 | if (publicKey == null) {
127 | publicKey = applicationInfo.metaData.getString(KEY_PUBLIC_KEY_PROP);
128 | }
129 |
130 | }
131 |
132 | private static void performChecks() {
133 | //validate that sdk has been initialized
134 | Utils.Validate.validateSdkInitialized();
135 |
136 | //validate public keys
137 | Utils.Validate.hasPublicKey();
138 | }
139 |
140 | public static void chargeCard(Activity activity, Charge charge, Paystack.TransactionCallback transactionCallback) {
141 | if (BuildConfig.DEBUG && (activity == null)) {
142 | throw new AssertionError("activity must not be null");
143 | }
144 |
145 | performChecks();
146 |
147 | //construct paystack object
148 | Paystack paystack = new Paystack(PaystackSdk.getPublicKey());
149 |
150 | //create token
151 | paystack.chargeCard(activity, charge, transactionCallback);
152 | }
153 |
154 | public interface SdkInitializeCallback {
155 | void onInitialized();
156 | }
157 |
158 | }
159 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/PaystackSdkComponent.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android
2 |
3 | import co.paystack.android.api.di.apiComponent
4 |
5 | internal fun sdkComponent(): PaystackSdkComponent = PaystackSdkModule
6 |
7 | internal interface PaystackSdkComponent {
8 | val transactionManagerFactory: Factory
9 | }
10 |
11 | internal object PaystackSdkModule : PaystackSdkComponent {
12 |
13 | override val transactionManagerFactory: Factory = object : Factory {
14 | override fun create(): TransactionManager {
15 | return TransactionManager(apiComponent().paystackRepository)
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/Transaction.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android;
2 |
3 | import co.paystack.android.api.model.TransactionApiResponse;
4 |
5 | public class Transaction {
6 | private String id;
7 | private String reference;
8 |
9 | public static Transaction EMPTY_TRANSACTION = new Transaction();
10 |
11 | void loadFromResponse(TransactionApiResponse t) {
12 | if (t.hasValidReferenceAndTrans()) {
13 | this.reference = t.reference;
14 | this.id = t.trans;
15 | }
16 | }
17 |
18 | String getId() {
19 | return id;
20 | }
21 |
22 | public String getReference() {
23 | return reference;
24 | }
25 |
26 | public void setReference(String reference) {
27 | this.reference = reference;
28 | }
29 |
30 | public void setId(String id) {
31 | this.id = id;
32 | }
33 |
34 | boolean hasStartedOnServer() {
35 | return (reference != null) && (id != null);
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/ApiCallback.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.api
2 |
3 | interface ApiCallback {
4 | fun onSuccess(data: T)
5 |
6 | fun onError(exception: Throwable)
7 | }
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/ChargeApiCallback.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.api
2 |
3 | import co.paystack.android.api.model.ChargeResponse
4 | import co.paystack.android.api.request.ChargeParams
5 |
6 | interface ChargeApiCallback {
7 | fun onSuccess(params: ChargeParams, response: ChargeResponse)
8 |
9 | fun onError(exception: Throwable, reference: String?)
10 | }
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/PaystackRepository.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.api
2 |
3 | import co.paystack.android.api.model.TransactionInitResponse
4 | import co.paystack.android.api.request.ChargeParams
5 | import co.paystack.android.model.Charge
6 | import co.paystack.android.ui.AddressHolder.Address
7 |
8 | interface PaystackRepository {
9 |
10 | fun initializeTransaction(publicKey: String, charge: Charge, deviceId: String, callback: ApiCallback)
11 |
12 | fun processCardCharge(chargeParams: ChargeParams, callback: ChargeApiCallback)
13 |
14 | fun validateTransaction(chargeParams: ChargeParams, token: String, callback: ChargeApiCallback)
15 |
16 | fun validateAddress(chargeParams: ChargeParams, address: Address, callback: ChargeApiCallback)
17 |
18 | fun requeryTransaction(chargeParams: ChargeParams, callback: ChargeApiCallback)
19 |
20 | fun getTransactionWithAccessCode(
21 | accessCode: String,
22 | callback: ApiCallback
23 | )
24 | }
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/PaystackRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.api
2 |
3 | import co.paystack.android.api.model.TransactionInitResponse
4 | import co.paystack.android.api.request.ChargeParams
5 | import co.paystack.android.api.request.TransactionInitRequestBody
6 | import co.paystack.android.api.request.ValidateTransactionParams
7 | import co.paystack.android.api.service.PaystackApiService
8 | import co.paystack.android.model.Charge
9 | import co.paystack.android.ui.AddressHolder
10 | import retrofit2.Call
11 | import retrofit2.Callback
12 | import retrofit2.HttpException
13 | import retrofit2.Response
14 |
15 | internal class PaystackRepositoryImpl(private val apiService: PaystackApiService) : PaystackRepository {
16 | override fun initializeTransaction(publicKey: String, charge: Charge, deviceId: String, callback: ApiCallback) {
17 | val requestBody = TransactionInitRequestBody(
18 | publicKey = publicKey,
19 | email = charge.email,
20 | amount = charge.amount,
21 | currency = charge.currency,
22 | metadata = charge.metadata,
23 | device = deviceId,
24 | reference = charge.reference,
25 | subAccount = charge.subaccount,
26 | transactionCharge = charge.transactionCharge,
27 | plan = charge.plan,
28 | bearer = charge.bearer,
29 | additionalParameters = charge.additionalParameters,
30 | ).toRequestMap()
31 |
32 | makeApiRequest(
33 | onSuccess = { data -> callback.onSuccess(data) },
34 | onError = { throwable -> callback.onError(throwable) },
35 | apiCall = { apiService.initializeTransaction(requestBody) }
36 | )
37 | }
38 |
39 | override fun processCardCharge(chargeParams: ChargeParams, callback: ChargeApiCallback) {
40 | makeApiRequest(
41 | onSuccess = { data -> callback.onSuccess(chargeParams, data) },
42 | onError = { throwable -> callback.onError(throwable, chargeParams.reference) },
43 | apiCall = { apiService.chargeCard(chargeParams.toRequestMap()) }
44 | )
45 | }
46 |
47 | override fun validateTransaction(chargeParams: ChargeParams, token: String, callback: ChargeApiCallback) {
48 | val requestBody = ValidateTransactionParams(
49 | transactionId = chargeParams.transactionId,
50 | token = token,
51 | deviceId = chargeParams.deviceId,
52 | ).toRequestMap()
53 |
54 | makeApiRequest(
55 | apiCall = { apiService.validateOtp(requestBody) },
56 | onSuccess = { data -> callback.onSuccess(chargeParams, data) },
57 | onError = { throwable -> callback.onError(throwable, chargeParams.reference) },
58 | )
59 | }
60 |
61 | override fun requeryTransaction(chargeParams: ChargeParams, callback: ChargeApiCallback) {
62 | makeApiRequest(
63 | apiCall = { apiService.requeryTransaction(chargeParams.transactionId) },
64 | onSuccess = { data -> callback.onSuccess(chargeParams, data) },
65 | onError = { throwable -> callback.onError(throwable, chargeParams.reference) },
66 | )
67 | }
68 |
69 | override fun validateAddress(chargeParams: ChargeParams, address: AddressHolder.Address, callback: ChargeApiCallback) {
70 | val requestBody = address.toHashMap()
71 | requestBody["trans"] = chargeParams.transactionId
72 |
73 | makeApiRequest(
74 | apiCall = { apiService.validateAddress(requestBody) },
75 | onSuccess = { data -> callback.onSuccess(chargeParams, data) },
76 | onError = { throwable -> callback.onError(throwable, chargeParams.reference) },
77 | )
78 | }
79 |
80 | override fun getTransactionWithAccessCode(accessCode: String, callback: ApiCallback) {
81 | makeApiRequest(
82 | onSuccess = { data -> callback.onSuccess(data) },
83 | onError = { throwable -> callback.onError(throwable) },
84 | apiCall = { apiService.getTransaction(accessCode) }
85 | )
86 | }
87 |
88 | private fun makeApiRequest(apiCall: () -> Call, onSuccess: (T) -> Unit, onError: (Throwable) -> Unit) {
89 | val retrofitCallback = object : Callback {
90 | override fun onResponse(call: Call, response: Response) {
91 | if (response.isSuccessful) {
92 | val responseBody = response.body()
93 | if (responseBody == null) {
94 | onError(RuntimeException("No response body available"))
95 | return
96 | }
97 |
98 | onSuccess(responseBody)
99 | } else {
100 | onError(HttpException(response))
101 | }
102 | }
103 |
104 | override fun onFailure(call: Call, t: Throwable) {
105 | onError(t)
106 | }
107 | }
108 |
109 | apiCall().enqueue(retrofitCallback)
110 | }
111 | }
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/di/ApiComponent.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.api.di
2 |
3 | import android.os.Build
4 | import co.paystack.android.BuildConfig
5 | import co.paystack.android.api.PaystackRepository
6 | import co.paystack.android.api.PaystackRepositoryImpl
7 | import co.paystack.android.api.service.ApiService
8 | import co.paystack.android.api.service.PaystackApiService
9 | import co.paystack.android.api.service.converter.WrappedResponseConverter
10 | import co.paystack.android.api.utils.TLSSocketFactory
11 | import com.squareup.moshi.Moshi
12 | import okhttp3.OkHttpClient
13 | import retrofit2.Retrofit
14 | import retrofit2.converter.moshi.MoshiConverterFactory
15 | import java.util.concurrent.TimeUnit
16 |
17 | internal fun apiComponent(): ApiComponent = ApiModule
18 |
19 | internal interface ApiComponent {
20 | val tlsV1point2factory: TLSSocketFactory
21 | val okHttpClient: OkHttpClient
22 | val paystackApiService: PaystackApiService
23 | val paystackRepository: PaystackRepository
24 | }
25 |
26 | internal object ApiModule : ApiComponent {
27 | const val LEGACY_API_URL = "https://standard.paystack.co/"
28 | private const val PAYSTACK_API_URL = "https://api.paystack.co/"
29 |
30 | override val tlsV1point2factory = TLSSocketFactory()
31 |
32 | override val okHttpClient: OkHttpClient = OkHttpClient.Builder()
33 | .addInterceptor { chain ->
34 | val original = chain.request()
35 | // Add headers so we get Android version and Paystack Library version
36 | val builder = original.newBuilder()
37 | .header(
38 | "User-Agent",
39 | "Android_" + Build.VERSION.SDK_INT + "_Paystack_" + BuildConfig.VERSION_NAME
40 | )
41 | .header("X-Paystack-Build", BuildConfig.VERSION_CODE.toString())
42 | .header("Accept", "application/json")
43 | .method(original.method(), original.body())
44 |
45 | chain.proceed(builder.build())
46 | }
47 | .sslSocketFactory(tlsV1point2factory, tlsV1point2factory.getX509TrustManager())
48 | .connectTimeout(5, TimeUnit.MINUTES)
49 | .readTimeout(5, TimeUnit.MINUTES)
50 | .writeTimeout(5, TimeUnit.MINUTES)
51 | .build()
52 |
53 | private val moshi = Moshi.Builder().build()
54 |
55 | override val paystackApiService: PaystackApiService = Retrofit.Builder()
56 | .baseUrl(PAYSTACK_API_URL)
57 | .client(okHttpClient)
58 | .addConverterFactory(WrappedResponseConverter.Factory())
59 | .addConverterFactory(MoshiConverterFactory.create(moshi).asLenient())
60 | .build()
61 | .create(PaystackApiService::class.java)
62 |
63 | override val paystackRepository: PaystackRepository = PaystackRepositoryImpl(paystackApiService)
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/model/ChargeResponse.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.api.model
2 |
3 |
4 | import androidx.annotation.Keep
5 | import com.squareup.moshi.Json
6 | import com.squareup.moshi.JsonClass
7 | import com.squareup.moshi.Moshi
8 |
9 | @Keep
10 | @JsonClass(generateAdapter = true)
11 | data class ChargeResponse(
12 |
13 | val status: String?,
14 |
15 | @Json(name = "trans")
16 | val transactionId: String?,
17 |
18 | val reference: String?,
19 |
20 | val message: String?,
21 |
22 | @Json(name = "otpmessage")
23 | val otpMessage: String? = null,
24 |
25 | val auth: String? = null,
26 |
27 | @Json(name = "countryCode")
28 | val countryCode: String? = null,
29 | ) {
30 |
31 | companion object {
32 | fun fromJsonString(jsonString: String?): ChargeResponse {
33 | return try {
34 | Moshi.Builder().build()
35 | .adapter(ChargeResponse::class.java)
36 | .fromJson(jsonString) ?: error("Failed to parse charge response")
37 | } catch (e: Exception) {
38 | ChargeResponse(
39 | status = "0",
40 | transactionId = "",
41 | reference = "",
42 | message = e.message ?: "An error occurred while reading Auth data"
43 | )
44 | }
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/model/TransactionApiResponse.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.api.model
2 |
3 | import android.webkit.URLUtil
4 | import com.squareup.moshi.Json
5 | import com.squareup.moshi.JsonClass
6 |
7 | @JsonClass(generateAdapter = true)
8 | data class TransactionApiResponse(
9 | @Json(name = "status")
10 | val status: String? = null,
11 |
12 | @Json(name = "message")
13 | val message: String? = null,
14 |
15 | @JvmField
16 | @Json(name = "reference")
17 | val reference: String? = null,
18 |
19 | @JvmField
20 | @Json(name = "trans")
21 | val trans: String? = null,
22 |
23 | @Json(name = "auth")
24 | val auth: String? = null,
25 |
26 | @Json(name = "otpmessage")
27 | val otpmessage: String? = null,
28 |
29 | @Json(name = "countryCode")
30 | val avsCountryCode: String? = null,
31 | ) {
32 | fun hasValidReferenceAndTrans(): Boolean {
33 | return reference != null && trans != null
34 | }
35 |
36 | fun hasValidUrl(): Boolean {
37 | return otpmessage != null && URLUtil.isValidUrl(otpmessage)
38 | }
39 |
40 | fun hasValidOtpMessage(): Boolean {
41 | return otpmessage != null
42 | }
43 |
44 | fun hasValidAuth(): Boolean {
45 | return auth != null
46 | }
47 | }
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/model/TransactionInitResponse.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.api.model
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | data class TransactionInitResponse(
8 | @Json(name = "id")
9 | val transactionId: String,
10 |
11 | @Json(name = "reference")
12 | val reference: String?
13 | )
14 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/request/ChargeParams.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.api.request
2 |
3 | import co.paystack.android.Transaction
4 | import co.paystack.android.api.utils.pruneNullValues
5 | import com.squareup.moshi.JsonClass
6 |
7 | @JsonClass(generateAdapter = true)
8 | data class ChargeParams(
9 | val clientData: String,
10 | val transactionId: String,
11 | val last4: String,
12 | val deviceId: String,
13 | val reference: String?,
14 | val handle: String? = null
15 | ) {
16 | fun toRequestMap() = mapOf(
17 | FIELD_CLIENT_DATA to clientData,
18 | FIELD_HANDLE to handle,
19 | FIELD_TRANS to transactionId,
20 | FIELD_LAST4 to last4,
21 | FIELD_DEVICE to deviceId,
22 | FIELD_SOURCE to "android"
23 | ).pruneNullValues()
24 |
25 | fun addPin(pin: String) = copy(handle = pin)
26 |
27 | fun getTransaction(): Transaction {
28 | val transaction = Transaction()
29 | transaction.setId(transactionId)
30 | transaction.reference = reference
31 | return transaction
32 | }
33 |
34 | companion object {
35 | const val FIELD_CLIENT_DATA = "clientdata"
36 | const val FIELD_HANDLE = "handle"
37 | const val FIELD_TRANS = "trans"
38 | const val FIELD_LAST4 = "last4"
39 | const val FIELD_SOURCE = "source"
40 | const val FIELD_DEVICE = "device"
41 | }
42 | }
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/request/TransactionInitRequestBody.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.api.request
2 |
3 | import co.paystack.android.api.utils.pruneNullValues
4 | import co.paystack.android.model.Charge.Bearer
5 | import com.squareup.moshi.JsonClass
6 |
7 | @JsonClass(generateAdapter = true)
8 | data class TransactionInitRequestBody(
9 | val publicKey: String,
10 | val email: String,
11 | val amount: Int,
12 | val currency: String?,
13 | val metadata: String?,
14 | val device: String,
15 | val reference: String?,
16 | val subAccount: String?,
17 | val transactionCharge: Int?,
18 | val plan: String?,
19 | val bearer: Bearer?,
20 | val additionalParameters: Map,
21 | ) {
22 | fun toRequestMap() = additionalParameters + mapOf(
23 | FIELD_KEY to publicKey,
24 | FIELD_EMAIL to email,
25 | FIELD_AMOUNT to amount,
26 | FIELD_CURRENCY to currency,
27 | FIELD_METADATA to metadata,
28 | FIELD_DEVICE to device,
29 | FIELD_REFERENCE to reference,
30 | FIELD_SUBACCOUNT to subAccount,
31 | FIELD_TRANSACTION_CHARGE to transactionCharge,
32 | FIELD_BEARER to bearer?.name,
33 | FIELD_PLAN to plan,
34 | ).pruneNullValues()
35 |
36 | companion object {
37 | const val FIELD_KEY = "key"
38 | const val FIELD_EMAIL = "email"
39 | const val FIELD_AMOUNT = "amount"
40 | const val FIELD_CURRENCY = "currency"
41 | const val FIELD_METADATA = "metadata"
42 | const val FIELD_DEVICE = "device"
43 |
44 | const val FIELD_REFERENCE = "reference";
45 | const val FIELD_SUBACCOUNT = "subaccount";
46 | const val FIELD_TRANSACTION_CHARGE = "transaction_charge";
47 | const val FIELD_BEARER = "bearer";
48 | const val FIELD_PLAN = "plan";
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/request/ValidateTransactionParams.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.api.request
2 |
3 | import co.paystack.android.api.utils.pruneNullValues
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | data class ValidateTransactionParams(
8 | val transactionId: String,
9 | val token: String? = null,
10 | val deviceId: String? = null
11 | ) {
12 | fun toRequestMap() = mapOf(
13 | FIELD_TOKEN to token,
14 | FIELD_DEVICE to deviceId,
15 | FIELD_TRANS to transactionId,
16 | ).pruneNullValues()
17 |
18 | companion object {
19 | const val FIELD_TOKEN = "token"
20 | const val FIELD_DEVICE = "device"
21 | const val FIELD_TRANS = "trans"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/service/ApiService.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.api.service;
2 |
3 | import java.util.HashMap;
4 |
5 | import co.paystack.android.api.model.TransactionApiResponse;
6 | import retrofit2.Call;
7 | import retrofit2.http.FieldMap;
8 | import retrofit2.http.FormUrlEncoded;
9 | import retrofit2.http.GET;
10 | import retrofit2.http.POST;
11 | import retrofit2.http.Path;
12 |
13 | /**
14 | * ApiService
15 | */
16 | public interface ApiService {
17 |
18 | @FormUrlEncoded
19 | @POST("/charge/mobile_charge")
20 | Call charge(@FieldMap HashMap fields);
21 |
22 | @FormUrlEncoded
23 | @POST("/charge/validate")
24 | Call validateCharge(@FieldMap HashMap fields);
25 |
26 | @GET("/requery/{trans}")
27 | Call requeryTransaction(@Path("trans") String trans);
28 |
29 | @FormUrlEncoded
30 | @POST("/charge/avs")
31 | Call submitCardAddress(@FieldMap HashMap fields);
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/service/PaystackApiFactory.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.mobilemoney.data.api
2 |
3 | import android.os.Build
4 | import co.paystack.android.BuildConfig
5 | import co.paystack.android.api.service.PaystackApiService
6 | import co.paystack.android.api.service.converter.WrappedResponseConverter
7 | import co.paystack.android.api.utils.TLSSocketFactory
8 | import com.squareup.moshi.Moshi
9 | import okhttp3.OkHttpClient
10 | import retrofit2.Retrofit
11 | import retrofit2.converter.moshi.MoshiConverterFactory
12 | import java.security.KeyManagementException
13 | import java.security.KeyStoreException
14 | import java.security.NoSuchAlgorithmException
15 | import java.util.concurrent.TimeUnit
16 |
17 | /*
18 | * Generates an API client for new paystack API (https://api.paystack.co)
19 | * */
20 | internal object PaystackApiFactory {
21 | private const val BASE_URL = "https://api.paystack.co/"
22 |
23 | @Throws(
24 | NoSuchAlgorithmException::class,
25 | KeyManagementException::class,
26 | KeyStoreException::class
27 | )
28 | fun createRetrofitService(): PaystackApiService {
29 |
30 | val tlsV1point2factory = TLSSocketFactory()
31 | val okHttpClient = OkHttpClient.Builder()
32 | .addInterceptor { chain ->
33 | val original = chain.request()
34 | // Add headers so we get Android version and Paystack Library version
35 | val builder = original.newBuilder()
36 | .header(
37 | "User-Agent",
38 | "Android_" + Build.VERSION.SDK_INT + "_Paystack_" + BuildConfig.VERSION_NAME
39 | )
40 | .header("X-Paystack-Build", BuildConfig.VERSION_CODE.toString())
41 | .header("Accept", "application/json")
42 | .method(original.method(), original.body())
43 | val request = builder.build()
44 | chain.proceed(request)
45 | }
46 | .sslSocketFactory(tlsV1point2factory, tlsV1point2factory.x509TrustManager)
47 | .connectTimeout(1, TimeUnit.MINUTES)
48 | .readTimeout(1, TimeUnit.MINUTES)
49 | .writeTimeout(1, TimeUnit.MINUTES)
50 | .build()
51 |
52 | val retrofit = Retrofit.Builder()
53 | .baseUrl(BASE_URL)
54 | .client(okHttpClient)
55 | .addConverterFactory(WrappedResponseConverter.Factory())
56 | .addConverterFactory(MoshiConverterFactory.create(Moshi.Builder().build()))
57 | .build()
58 |
59 | return retrofit.create(PaystackApiService::class.java)
60 | }
61 |
62 | }
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/service/PaystackApiService.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.api.service
2 |
3 | import co.paystack.android.api.model.ChargeResponse
4 | import co.paystack.android.api.model.TransactionInitResponse
5 | import co.paystack.android.api.service.converter.NoWrap
6 | import co.paystack.android.model.AvsState
7 | import retrofit2.Call
8 | import retrofit2.http.*
9 |
10 | internal interface PaystackApiService {
11 | @GET("/address_verification/states")
12 | suspend fun getAddressVerificationStates(@Query("country") countryCode: String): List
13 |
14 | @GET("/checkout/request_inline")
15 | fun initializeTransaction(@QueryMap params: Map): Call
16 |
17 | @GET("/transaction/verify_access_code/{accessCode}")
18 | fun getTransaction(@Path("accessCode") accessCode: String): Call
19 |
20 | @FormUrlEncoded
21 | @POST("/checkout/card/charge")
22 | @NoWrap
23 | fun chargeCard(@FieldMap params: Map): Call
24 |
25 | @NoWrap
26 | @FormUrlEncoded
27 | @POST("/checkout/card/validate")
28 | fun validateOtp(@FieldMap params: Map): Call
29 |
30 | @NoWrap
31 | @FormUrlEncoded
32 | @POST("/checkout/card/avs")
33 | fun validateAddress(@FieldMap fields: Map): Call
34 |
35 | @NoWrap
36 | @GET("/checkout/requery/{transactionId}")
37 | fun requeryTransaction(@Path("transactionId") transactionId: String): Call
38 |
39 | }
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/service/converter/NoWrap.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.api.service.converter
2 |
3 | @Retention(AnnotationRetention.RUNTIME)
4 | @Target(AnnotationTarget.FUNCTION)
5 | annotation class NoWrap
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/service/converter/WrappedResponseConverter.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.api.service.converter
2 |
3 | import com.squareup.moshi.JsonClass
4 | import okhttp3.ResponseBody
5 | import retrofit2.Converter
6 | import retrofit2.Retrofit
7 | import java.lang.reflect.ParameterizedType
8 | import java.lang.reflect.Type
9 |
10 | class WrappedResponseConverter(
11 | private val delegate: Converter>
12 | ) : Converter {
13 | override fun convert(value: ResponseBody): T? {
14 | val response = delegate.convert(value)
15 | return response?.data
16 | }
17 |
18 |
19 | class Factory : Converter.Factory() {
20 | override fun responseBodyConverter(
21 | type: Type,
22 | annotations: Array,
23 | retrofit: Retrofit
24 | ): Converter? {
25 |
26 | // Should not use this converter if function is annotated with [NoWrap]
27 | if (annotations.any { it is NoWrap }) {
28 | return null
29 | }
30 |
31 | val wrappedType: Type = object : ParameterizedType {
32 | override fun getRawType(): Type {
33 | return WrappedResponse::class.java
34 | }
35 |
36 | override fun getOwnerType(): Type? {
37 | return null
38 | }
39 |
40 | override fun getActualTypeArguments(): Array {
41 | return arrayOf(type)
42 | }
43 | }
44 |
45 | val delegate = retrofit.nextResponseBodyConverter>(this, wrappedType, annotations)
46 | return WrappedResponseConverter(delegate)
47 |
48 | }
49 | }
50 | }
51 |
52 | @JsonClass(generateAdapter = true)
53 | open class WrappedResponse(
54 | val `data`: T,
55 | val message: String,
56 | val status: Boolean
57 | )
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/utils/MapExt.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.api.utils
2 |
3 | @Suppress("UNCHECKED_CAST")
4 | fun Map.pruneNullValues(): Map {
5 | return filter { it.value != null } as Map
6 | }
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/api/utils/TLSSocketFactory.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.api.utils;
2 |
3 | /**
4 | * Created by ibrahimlawal on Mar/14/2016.
5 | *
6 | * @author fkrauthan
7 | * @see http://blog.dev-area.net/2015/08/13/android-4-1-enable-tls-1-1-and-tls-1-2/
8 | * @since 1.2.0
9 | *
10 | * Modified to work with okHttp3.1.2
11 | * And so it only uses TLSv1.2
12 | */
13 |
14 | import java.io.IOException;
15 | import java.net.InetAddress;
16 | import java.net.Socket;
17 | import java.security.KeyManagementException;
18 | import java.security.KeyStore;
19 | import java.security.KeyStoreException;
20 | import java.security.NoSuchAlgorithmException;
21 |
22 | import javax.net.ssl.SSLContext;
23 | import javax.net.ssl.SSLSocket;
24 | import javax.net.ssl.SSLSocketFactory;
25 | import javax.net.ssl.TrustManager;
26 | import javax.net.ssl.TrustManagerFactory;
27 | import javax.net.ssl.X509TrustManager;
28 |
29 | public class TLSSocketFactory extends SSLSocketFactory {
30 |
31 | public X509TrustManager getX509TrustManager() {
32 | return x509TrustManager;
33 | }
34 |
35 | // Field named delegate so okHttp 3.1.2 will be
36 | // able to get our trust manager as suggested here:
37 | // https://github.com/square/okhttp/issues/2323#issuecomment-185055040
38 | private SSLSocketFactory delegate;
39 | private X509TrustManager x509TrustManager;
40 |
41 | public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
42 | SSLContext context = SSLContext.getInstance("TLS");
43 |
44 | // Get, so we can use default Trust managers for our Factory
45 | TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
46 | trustManagerFactory.init((KeyStore) null);
47 | TrustManager[] tm = trustManagerFactory.getTrustManagers();
48 |
49 | context.init(null, tm, null);
50 | for (int i = 0; i < tm.length; i++) {
51 | if (tm[i] instanceof X509TrustManager) {
52 | x509TrustManager = (X509TrustManager) tm[i];
53 | }
54 | }
55 |
56 | delegate = context.getSocketFactory();
57 | }
58 |
59 | @Override
60 | public String[] getDefaultCipherSuites() {
61 | return delegate.getDefaultCipherSuites();
62 | }
63 |
64 | @Override
65 | public String[] getSupportedCipherSuites() {
66 | return delegate.getSupportedCipherSuites();
67 | }
68 |
69 | @Override
70 | public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
71 | return enableTLSOnSocket(delegate.createSocket(s, host, port, autoClose));
72 | }
73 |
74 | @Override
75 | public Socket createSocket(String host, int port) throws IOException {
76 | return enableTLSOnSocket(delegate.createSocket(host, port));
77 | }
78 |
79 | @Override
80 | public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
81 | return enableTLSOnSocket(delegate.createSocket(host, port, localHost, localPort));
82 | }
83 |
84 | @Override
85 | public Socket createSocket(InetAddress host, int port) throws IOException {
86 | return enableTLSOnSocket(delegate.createSocket(host, port));
87 | }
88 |
89 | @Override
90 | public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
91 | return enableTLSOnSocket(delegate.createSocket(address, port, localAddress, localPort));
92 | }
93 |
94 | private Socket enableTLSOnSocket(Socket socket) {
95 | if (socket != null && (socket instanceof SSLSocket)) {
96 | // Only use TLSv1.2
97 | ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.2"});
98 | }
99 | return socket;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/exceptions/AuthenticationException.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.exceptions;
2 |
3 | /**
4 | * @author {androidsupport@paystack.co} on 9/16/15.
5 | */
6 | public class AuthenticationException extends PaystackException {
7 | public AuthenticationException(String message) {
8 | super(message);
9 | }
10 |
11 | public AuthenticationException(String message, Throwable e) {
12 | super(message, e);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/exceptions/CardException.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.exceptions;
2 |
3 | /**
4 | * @author {androidsupport@paystack.co} on 9/13/15.
5 | */
6 | public class CardException extends PaystackException {
7 |
8 | public CardException(String message) {
9 | super(message);
10 | }
11 |
12 | public CardException(String message, Throwable e) {
13 | super(message, e);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/exceptions/ChargeException.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.exceptions;
2 |
3 | /**
4 | * @author {androidsupport@paystack.co} on 9/25/15.
5 | */
6 | public class ChargeException extends PaystackException {
7 | public ChargeException(String message) {
8 | super(message);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/exceptions/ExpiredAccessCodeException.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.exceptions;
2 |
3 | /**
4 | * @author {androidsupport@paystack.co} on 9/25/15.
5 | */
6 | public class ExpiredAccessCodeException extends PaystackException {
7 | public ExpiredAccessCodeException(String message) {
8 | super(message);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/exceptions/InvalidAmountException.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.exceptions;
2 |
3 | /**
4 | * Created by i on 24/08/2016.
5 | */
6 | public class InvalidAmountException extends PaystackException {
7 |
8 | private int amount;
9 |
10 | public InvalidAmountException(int amount) {
11 | super(amount + " is not a valid amount. only positive non-zero values are allowed.");
12 | this.setAmount(amount);
13 | }
14 |
15 | public int getAmount() {
16 | return amount;
17 | }
18 |
19 | public InvalidAmountException setAmount(int amount) {
20 | this.amount = amount;
21 | return this;
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/exceptions/InvalidEmailException.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.exceptions;
2 |
3 | /**
4 | * Created by i on 24/08/2016.
5 | */
6 | public class InvalidEmailException extends PaystackException {
7 |
8 | private String email;
9 |
10 | public InvalidEmailException(String email) {
11 | super(email + " is not a valid email");
12 | this.setEmail(email);
13 | }
14 |
15 | public String getEmail() {
16 | return email;
17 | }
18 |
19 | public InvalidEmailException setEmail(String email) {
20 | this.email = email;
21 | return this;
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/exceptions/PaystackActivityNotFoundException.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.exceptions;
2 |
3 | /**
4 | * @author {androidsupport@paystack.co} on 9/22/15.
5 | */
6 | public class PaystackActivityNotFoundException extends PaystackException {
7 | public PaystackActivityNotFoundException(String message) {
8 | super(message);
9 | }
10 |
11 | public PaystackActivityNotFoundException(String message, Throwable e) {
12 | super(message, e);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/exceptions/PaystackException.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.exceptions;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * Base class for exceptions
7 | *
8 | * @author {androidsupport@paystack.co} on 9/13/15.
9 | */
10 | public class PaystackException extends RuntimeException implements Serializable {
11 |
12 | public PaystackException(String message) {
13 | super(message, null);
14 | }
15 |
16 | public PaystackException(String message, Throwable e) {
17 | super(message, e);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/exceptions/PaystackSdkNotInitializedException.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.exceptions;
2 |
3 | /**
4 | * @author {androidsupport@paystack.co} on 9/22/15.
5 | */
6 | public class PaystackSdkNotInitializedException extends PaystackException {
7 | public PaystackSdkNotInitializedException(String message) {
8 | super(message);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/exceptions/ProcessingException.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.exceptions;
2 |
3 | /**
4 | * @author {androidsupport@paystack.co} on 9/25/15.
5 | */
6 | public class ProcessingException extends ChargeException {
7 | public ProcessingException() {
8 | super("A transaction is currently processing, please wait till it concludes before attempting a new charge.");
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/exceptions/TokenException.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.exceptions;
2 |
3 | /**
4 | * @author {androidsupport@paystack.co} on 9/20/15.
5 | */
6 | public class TokenException extends PaystackException {
7 | public TokenException(String message) {
8 | super(message);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/exceptions/ValidateException.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.exceptions;
2 |
3 | /**
4 | * Created by i on 24/08/2016.
5 | */
6 | public class ValidateException extends PaystackException {
7 | public ValidateException(String message) {
8 | super(message);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/model/AvsState.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.model
2 |
3 | import com.squareup.moshi.JsonClass
4 |
5 | @JsonClass(generateAdapter = true)
6 | data class AvsState(
7 | val name: String,
8 | val slug: String,
9 | val abbreviation: String
10 | )
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/model/Charge.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.model;
2 |
3 | import android.util.Log;
4 | import android.util.Patterns;
5 |
6 | import org.json.JSONArray;
7 | import org.json.JSONException;
8 | import org.json.JSONObject;
9 |
10 | import java.util.HashMap;
11 | import java.util.Locale;
12 |
13 | import co.paystack.android.BuildConfig;
14 | import co.paystack.android.Paystack;
15 | import co.paystack.android.exceptions.ChargeException;
16 | import co.paystack.android.exceptions.InvalidAmountException;
17 | import co.paystack.android.exceptions.InvalidEmailException;
18 |
19 | public class Charge extends PaystackModel {
20 | private final String TAG = Charge.class.getSimpleName();
21 | private Card card;
22 | private String email;
23 | private String access_code;
24 | private int amount;
25 | private JSONObject metadata;
26 | private JSONArray custom_fields;
27 | private boolean hasMeta = false;
28 | private HashMap additionalParameters;
29 | private int transactionCharge;
30 | private String subaccount;
31 | private String reference;
32 | private Bearer bearer;
33 | private String currency;
34 | private String plan;
35 |
36 | private boolean localStarted = false;
37 | private boolean remoteStarted = false;
38 |
39 | private void beforeLocalSet(String fieldName){
40 | if(remoteStarted && BuildConfig.DEBUG){
41 | throw new ChargeException("You can not set " + fieldName + " after specifying an access code");
42 | }
43 | localStarted = true;
44 | }
45 | private void beforeRemoteSet(){
46 | if(localStarted && BuildConfig.DEBUG){
47 | throw new ChargeException("You can not set access code when providing transaction parameters in app");
48 | }
49 | remoteStarted = true;
50 | }
51 | public Charge() {
52 | this.metadata = new JSONObject();
53 | this.amount = -1;
54 | this.additionalParameters = new HashMap<>();
55 | this.custom_fields = new JSONArray();
56 | try {
57 | this.metadata.put("custom_fields", this.custom_fields);
58 | } catch (JSONException e) {
59 | Log.d(TAG, e.toString());
60 | }
61 | }
62 |
63 | public void addParameter(String key, String value) {
64 | beforeLocalSet(key);
65 | this.additionalParameters.put(key, value);
66 | }
67 |
68 | public HashMap getAdditionalParameters() {
69 | return additionalParameters;
70 | }
71 |
72 | public String getAccessCode() {
73 | return access_code;
74 | }
75 |
76 | public Charge setAccessCode(String access_code) {
77 | beforeRemoteSet();
78 | this.access_code = access_code;
79 | return this;
80 | }
81 |
82 | public String getCurrency() {
83 | return currency;
84 | }
85 |
86 | public Charge setCurrency(String currency) {
87 | beforeLocalSet("currency");
88 | this.currency = currency;
89 | return this;
90 | }
91 |
92 | public String getPlan() {
93 | return plan;
94 | }
95 |
96 | public Charge setPlan(String plan) {
97 | beforeLocalSet("plan");
98 | this.plan = plan;
99 | return this;
100 | }
101 |
102 | public int getTransactionCharge() {
103 | return transactionCharge;
104 | }
105 |
106 | public Charge setTransactionCharge(int transactionCharge) {
107 | beforeLocalSet("transaction charge");
108 | this.transactionCharge = transactionCharge;
109 | return this;
110 | }
111 |
112 | public String getSubaccount() {
113 | return subaccount;
114 | }
115 |
116 | public Charge setSubaccount(String subaccount) {
117 | beforeLocalSet("subaccount");
118 | this.subaccount = subaccount;
119 | return this;
120 | }
121 |
122 | public String getReference() {
123 | return reference;
124 | }
125 |
126 | public Charge setReference(String reference) {
127 | beforeLocalSet("reference");
128 | this.reference = reference;
129 | return this;
130 | }
131 |
132 | public Bearer getBearer() {
133 | return bearer;
134 | }
135 |
136 | public Charge setBearer(Bearer bearer) {
137 | beforeLocalSet("bearer");
138 | this.bearer = bearer;
139 | return this;
140 | }
141 |
142 | public Card getCard() {
143 | return card;
144 | }
145 |
146 | public Charge setCard(Card card) {
147 | this.card = card;
148 | return this;
149 | }
150 |
151 | public Charge putMetadata(String name, String value) throws JSONException{
152 | beforeLocalSet("metadata");
153 | this.metadata.put(name, value);
154 | this.hasMeta = true;
155 | return this;
156 | }
157 |
158 | public Charge putMetadata(String name, JSONObject value) throws JSONException{
159 | beforeLocalSet("metadata");
160 | this.metadata.put(name, value);
161 | this.hasMeta = true;
162 | return this;
163 | }
164 |
165 | public Charge putCustomField(String displayName, String value) throws JSONException{
166 | beforeLocalSet("custom field");
167 | JSONObject customObj = new JSONObject();
168 | customObj.put("value", value);
169 | customObj.put("display_name", displayName);
170 | customObj.put("variable_name", displayName.toLowerCase(Locale.getDefault()).replaceAll("[^a-z0-9]","_"));
171 | this.custom_fields.put(customObj);
172 | this.hasMeta = true;
173 | return this;
174 | }
175 |
176 | public String getMetadata(){
177 | if(!hasMeta){
178 | return null;
179 | }
180 | return this.metadata.toString();
181 | }
182 |
183 | public String getEmail() {
184 | return email;
185 | }
186 |
187 | public Charge setEmail(String email) {
188 | beforeLocalSet("email");
189 | if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
190 | throw new InvalidEmailException(email);
191 | }
192 | this.email = email;
193 | return this;
194 | }
195 |
196 | public int getAmount() {
197 | return amount;
198 | }
199 |
200 | public Charge setAmount(int amount) throws InvalidAmountException {
201 | beforeLocalSet("amount");
202 | if (amount <= 0)
203 | throw new InvalidAmountException(amount);
204 | this.amount = amount;
205 | return this;
206 | }
207 |
208 | public enum Bearer {
209 | account, subaccount
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/model/PaystackModel.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.model;
2 |
3 | /**
4 | * Base class for Paystack Model classes
5 | *
6 | * @author {androidsupport@paystack.co} on 9/13/15.
7 | */
8 | public abstract class PaystackModel {
9 | }
10 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/model/Token.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.model;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * The class for Token model.
7 | *
8 | * @author {androidsupport@paystack.co} on 8/10/15.
9 | */
10 | public class Token extends PaystackModel implements Serializable {
11 |
12 | public String token;
13 |
14 | public String last4;
15 |
16 |
17 | // private Token(Parcel in){
18 | // String[] data = new String[2];
19 | //
20 | // in.readStringArray(data);
21 | // token = data[0];
22 | // last4 = data[1];
23 | // }
24 | //
25 | // @Override
26 | // public int describeContents() {
27 | // return 0;
28 | // }
29 | //
30 | // @Override
31 | // public void writeToParcel(Parcel parcel, int i) {
32 | //
33 | // }
34 | }
35 |
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/ui/AddressHolder.java:
--------------------------------------------------------------------------------
1 | package co.paystack.android.ui;
2 |
3 | import java.util.HashMap;
4 |
5 | public class AddressHolder {
6 | private static AddressHolder instance = new AddressHolder();
7 | private static Object lock = new Object();
8 | private Address address = null;
9 |
10 | private AddressHolder() {
11 | }
12 |
13 | public static AddressHolder getInstance() {
14 | return instance;
15 | }
16 |
17 | public static Object getLock() {
18 | return lock;
19 | }
20 |
21 | public Address getAddress() {
22 | return address;
23 | }
24 |
25 | public void setAddress(Address address) {
26 | this.address = address;
27 | }
28 |
29 | public static class Address {
30 | private String state = "";
31 | private String zipCode = "";
32 | private String city = "";
33 | private String street = "";
34 |
35 | public static String FIELD_ADDRESS = "address";
36 | public static String FIELD_CITY = "city";
37 | public static String FIELD_ZIP_CODE = "zip_code";
38 | public static String FIELD_STATE = "state";
39 |
40 | public String getState() {
41 | return state;
42 | }
43 |
44 | public void setState(String state) {
45 | this.state = state;
46 | }
47 |
48 | public String getZipCode() {
49 | return zipCode;
50 | }
51 |
52 | public void setZipCode(String zipCode) {
53 | this.zipCode = zipCode;
54 | }
55 |
56 | public String getCity() {
57 | return city;
58 | }
59 |
60 | public void setCity(String city) {
61 | this.city = city;
62 | }
63 |
64 | public String getStreet() {
65 | return street;
66 | }
67 |
68 | public void setStreet(String street) {
69 | this.street = street;
70 | }
71 |
72 | @Override
73 | public String toString() {
74 | return "Address{" +
75 | "state='" + state + '\'' +
76 | ", zipCode='" + zipCode + '\'' +
77 | ", city='" + city + '\'' +
78 | ", street='" + street + '\'' +
79 | '}';
80 | }
81 |
82 | public HashMap toHashMap() {
83 | HashMap params = new HashMap<>();
84 | params.put(FIELD_STATE, this.state);
85 | params.put(FIELD_ZIP_CODE, this.zipCode);
86 | params.put(FIELD_CITY, this.city);
87 | params.put(FIELD_ADDRESS, this.street);
88 | return params;
89 | }
90 | }
91 |
92 | }
--------------------------------------------------------------------------------
/paystack/src/main/java/co/paystack/android/ui/AddressVerificationActivity.kt:
--------------------------------------------------------------------------------
1 | package co.paystack.android.ui
2 |
3 | import android.os.Bundle
4 | import android.text.Editable
5 | import android.text.TextWatcher
6 | import android.util.Log
7 | import android.view.View
8 | import android.view.WindowManager
9 | import android.widget.*
10 | import androidx.appcompat.app.AppCompatActivity
11 | import co.paystack.android.R
12 | import co.paystack.android.mobilemoney.data.api.PaystackApiFactory
13 | import co.paystack.android.model.AvsState
14 | import kotlinx.coroutines.CoroutineScope
15 | import kotlinx.coroutines.Dispatchers
16 | import kotlinx.coroutines.Job
17 | import kotlinx.coroutines.launch
18 | import kotlin.coroutines.CoroutineContext
19 | import kotlin.properties.Delegates
20 |
21 | class AddressVerificationActivity : AppCompatActivity(), CoroutineScope {
22 | private lateinit var job: Job
23 | override val coroutineContext: CoroutineContext
24 | get() = job + Dispatchers.Main
25 |
26 | private val addressHolder = AddressHolder.getInstance()
27 | private val lock = AddressHolder.getLock()
28 |
29 |
30 | private val paystackApiService by lazy {
31 | PaystackApiFactory.createRetrofitService()
32 | }
33 |
34 | private val etState by lazy { findViewById(R.id.etState) }
35 | private val etStreet by lazy { findViewById(R.id.etStreet) }
36 | private val etCity by lazy { findViewById(R.id.etCity) }
37 | private val etZipCode by lazy { findViewById(R.id.etZipCode) }
38 | private val tvError by lazy { findViewById(R.id.tvError) }
39 | private val btnRetry by lazy { findViewById