├── file-tree.png ├── enable_embedded.png ├── 4_enablekeychain_2x.png ├── android ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── arttitude360 │ │ └── reactnative │ │ └── rnpaystack │ │ ├── RNPaystackPackage.java │ │ └── RNPaystackModule.java └── build.gradle ├── react-native-paystack.podspec ├── package.json ├── ios ├── RNPaystack.h ├── RNPaystack.xcodeproj │ └── project.pbxproj └── RNPaystack.m ├── .gitignore ├── index.js ├── v2-Docs.md ├── Old Docs.md ├── README-3-1.md └── README.md /file-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tolu360/react-native-paystack/HEAD/file-tree.png -------------------------------------------------------------------------------- /enable_embedded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tolu360/react-native-paystack/HEAD/enable_embedded.png -------------------------------------------------------------------------------- /4_enablekeychain_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tolu360/react-native-paystack/HEAD/4_enablekeychain_2x.png -------------------------------------------------------------------------------- /android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | RNPaystack 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /react-native-paystack.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, './package.json'))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = 'react-native-paystack' 7 | s.version = package['version'] 8 | s.summary = package['description'] 9 | s.description = package['description'] 10 | s.license = package['license'] 11 | s.author = package['author'] 12 | s.homepage = 'https://github.com/tolu360/react-native-paystack' 13 | s.source = { :git => 'https://github.com/tolu360/react-native-paystack.git', :tag => "v#{s.version}" } 14 | 15 | s.platform = :ios, '9.0' 16 | 17 | s.preserve_paths = 'README.md', 'package.json', 'index.js' 18 | s.source_files = 'ios/*.{h,m}' 19 | 20 | s.compiler_flags = '-fno-modules' 21 | 22 | s.dependency 'React' 23 | s.dependency 'Paystack' 24 | end -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-paystack", 3 | "description": "React Native Wrappers for Paystack Android & iOS Mobile SDKs", 4 | "main": "index.js", 5 | "author": "Tolu Olowu (Arttitude 360) ", 6 | "version": "3.4.0", 7 | "scripts": {}, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/tolu360/react-native-paystack" 11 | }, 12 | "license": "MIT", 13 | "bugs": { 14 | "url": "https://github.com/tolu360/react-native-paystack/issues" 15 | }, 16 | "homepage": "https://github.com/tolu360/react-native-paystack#readme", 17 | "keywords": [ 18 | "react", 19 | "react-native", 20 | "react-native module", 21 | "ios", 22 | "android", 23 | "paystack", 24 | "credit-card", 25 | "payments" 26 | ], 27 | "peerDependencies": { 28 | "react": ">=15.4.0 || >=16.x", 29 | "react-native": ">=0.40.0" 30 | }, 31 | "devDependencies": {} 32 | } -------------------------------------------------------------------------------- /ios/RNPaystack.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import 5 | 6 | @interface RNPaystackModule : NSObject 7 | 8 | @property (nonatomic) NSString *errorMsg; 9 | @property (nonatomic) NSString *errorCode; 10 | 11 | - (BOOL)isCardNumberValid:(NSString *)cardNumber validateCardBrand:(BOOL)validateCardBrand; 12 | - (BOOL)isExpMonthValid:(NSString *)expMonth; 13 | - (BOOL)isExpYearValid:(NSString *)expYear forMonth:(NSString *)expMonth; 14 | - (BOOL)isCvcValid:(NSString *)cvc withNumber:(NSString *)cardNumber; 15 | - (BOOL)isCardValid:(PSTCKCardParams *)card; 16 | - (BOOL)cardParamsAreValid:(NSString *)cardNumber withMonth:(NSString *)expMonth withYear:(NSString *)expYear andWithCvc:(NSString *)cvc; 17 | - (NSMutableDictionary*)setTokenMsg:(NSString *)token withCardLastDigits:(NSString *)last4; 18 | - (NSMutableDictionary*)setReferenceMsg:(NSString *)reference; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | npm-debug.log 29 | 30 | # OSX 31 | # 32 | .DS_Store 33 | 34 | examples/ 35 | 36 | # Xcode 37 | # 38 | build/ 39 | *.pbxuser 40 | !default.pbxuser 41 | *.mode1v3 42 | !default.mode1v3 43 | *.mode2v3 44 | !default.mode2v3 45 | *.perspectivev3 46 | !default.perspectivev3 47 | xcuserdata 48 | *.xccheckout 49 | *.moved-aside 50 | DerivedData 51 | *.hmap 52 | *.ipa 53 | *.xcuserstate 54 | project.xcworkspace 55 | 56 | # Android/IJ 57 | # 58 | react-native-dialogs.iml 59 | .idea 60 | .gradle 61 | local.properties 62 | -------------------------------------------------------------------------------- /android/src/main/java/com/arttitude360/reactnative/rnpaystack/RNPaystackPackage.java: -------------------------------------------------------------------------------- 1 | package com.arttitude360.reactnative.rnpaystack; 2 | 3 | 4 | import com.facebook.react.ReactPackage; 5 | import com.facebook.react.bridge.JavaScriptModule; 6 | import com.facebook.react.bridge.NativeModule; 7 | import com.facebook.react.bridge.ReactApplicationContext; 8 | import com.facebook.react.uimanager.ViewManager; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | public class RNPaystackPackage implements ReactPackage { 15 | 16 | // Deprecated RN 0.47 17 | public List> createJSModules() { 18 | return Collections.emptyList(); 19 | } 20 | 21 | @Override 22 | public List createViewManagers(ReactApplicationContext reactContext) { 23 | return Collections.emptyList(); 24 | } 25 | 26 | @Override 27 | public List createNativeModules(ReactApplicationContext reactContext) { 28 | List modules = new ArrayList<>(); 29 | modules.add(new RNPaystackModule(reactContext)); 30 | return modules; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react' 3 | 4 | import { NativeModules, Platform } from 'react-native' 5 | 6 | const { RNPaystackModule } = NativeModules; 7 | const checkInit = (instance) => { 8 | if (!instance.paystackInitialized) { 9 | throw new Error(`You should call init first, higher up your code like in your index file.\nRead more https://github.com/tolu360/react-native-paystack#3-usage`) 10 | } 11 | } 12 | 13 | class RNPaystack { 14 | paystackInitialized = false; 15 | 16 | init(options: { [x: string]: any }) { 17 | if (typeof options != 'object') { 18 | return Promise.reject(new Error("Method argument can only be a Javascript object")); 19 | } 20 | this.paystackInitialized = true; 21 | 22 | return RNPaystackModule.init(options); 23 | } 24 | 25 | chargeCard(chargeParams: { [x: string]: any }) { 26 | if (typeof chargeParams != 'object') { 27 | return Promise.reject(new Error("Method argument can only be a Javascript object")); 28 | } 29 | checkInit(this); 30 | 31 | return RNPaystackModule.chargeCard(chargeParams); 32 | } 33 | 34 | chargeCardWithAccessCode(chargeParams: { [x: string]: any }) { 35 | if (typeof chargeParams != 'object') { 36 | return Promise.reject(new Error("Method argument can only be a Javascript object")); 37 | } 38 | checkInit(this); 39 | 40 | return RNPaystackModule.chargeCardWithAccessCode(chargeParams); 41 | } 42 | } 43 | 44 | export default new RNPaystack() -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | apply plugin: 'com.android.library' 3 | 4 | def DEFAULT_COMPILE_SDK_VERSION = 28 5 | def DEFAULT_TARGET_SDK_VERSION = 28 6 | def DEFAULT_SUPPORT_LIB_VERSION = "28.0.0" 7 | def DEFAULT_MIN_SDK_VERSION = 16 8 | 9 | def safeExtGet(prop, fallback) { 10 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 11 | } 12 | 13 | android { 14 | compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION) 15 | 16 | defaultConfig { 17 | minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION) 18 | targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION) 19 | } 20 | 21 | packagingOptions { 22 | exclude 'META-INF/LICENSE' 23 | exclude 'META-INF/DEPENDENCIES.txt' 24 | exclude 'META-INF/LICENSE.txt' 25 | exclude 'META-INF/NOTICE.txt' 26 | exclude 'META-INF/NOTICE' 27 | exclude 'META-INF/DEPENDENCIES' 28 | exclude 'META-INF/notice.txt' 29 | exclude 'META-INF/license.txt' 30 | exclude 'META-INF/dependencies.txt' 31 | exclude 'META-INF/LGPL2.1' 32 | } 33 | 34 | lintOptions { 35 | disable 'InvalidPackage' 36 | } 37 | } 38 | 39 | dependencies { 40 | implementation fileTree(dir: "libs", include: ["*.jar"]) 41 | implementation "com.facebook.react:react-native:+" 42 | implementation 'com.squareup.retrofit2:retrofit:2.5.0' 43 | implementation 'com.squareup.retrofit2:converter-gson:2.5.0' 44 | implementation 'co.paystack.android.design.widget:pinpad:1.0.1' 45 | implementation 'co.paystack.android:paystack:3.0.12' 46 | } -------------------------------------------------------------------------------- /v2-Docs.md: -------------------------------------------------------------------------------- 1 | ## Use the API below for v2 of this package only. 2 | - Download a fresh copy of the `Paystack iOS framework` from [Dropbox](https://www.dropbox.com/s/ykt5h0xjjkfwmk6/Paystack.framework.zip?dl=0) to continue to work with v2.0.0. 3 | 4 | ### Getting a Token (iOS & Android) 5 | It's a cinch to obtain a single-use card token using the react-native-paystack module. Pls note, the SDK assumes you are responsible for building the card form/UI. 6 | 7 | ```javascript 8 | RNPaystack.getToken(cardNumber, expiryMonth, expiryYear, cvc); 9 | ``` 10 | To be more elaborate, `RNPaystack.getToken()` returns a Javascript `Promise` like: 11 | 12 | ```js 13 | import RNPaystack from 'react-native-paystack'; 14 | 15 | getToken() { 16 | 17 | RNPaystack.getToken('4123450131001381', '01', '17', '883') 18 | .then(response => { 19 | console.log(response); // do stuff with the token 20 | }) 21 | .catch(error => { 22 | console.log(error); // error is a javascript Error object 23 | console.log(error.message); 24 | console.log(error.code); 25 | }) 26 | 27 | } 28 | ``` 29 | 30 | #### Request Signature 31 | 32 | | Argument | Type | Description | 33 | | ------------- |:-------------:| :-----| 34 | | cardNumber | string | the card number as a String without any seperator e.g 5555555555554444 | 35 | | expiryMonth | string | the card expiry month as a double-digit ranging from 1-12 e.g 10 (October) | 36 | | expiryYear | string | the card expiry year as a double-digit e.g 15 | 37 | | cvc | string | the card 3/4 digit security code as a String e.g 123 | 38 | 39 | #### Response Object 40 | 41 | An object of the form is returned from a successful token request 42 | 43 | ```javascript 44 | { 45 | token: "PSTK_4aw6i0yizwvyzjx", 46 | last4: "1381" 47 | } 48 | ``` 49 | 50 | ### Charging a Card (Android Only) 51 | Using the react-native-paystack module, you can complete the transaction with the Paystack Android SDK. Note that as with getting a card token, the SDK assumes you are responsible for building the card form/UI. 52 | 53 | ```javascript 54 | RNPaystack.chargeCard(cardNumber, expiryMonth, expiryYear, cvc, email, amountInKobo); 55 | ``` 56 | To be more elaborate, `RNPaystack.chargeCard()` returns a Javascript `Promise` like: 57 | 58 | ```js 59 | import RNPaystack from 'react-native-paystack'; 60 | 61 | chargeCard() { 62 | 63 | RNPaystack.chargeCard('4123450131001381', '01', '17', '883', 'dev-master@rnpaystack.dev', '10000') 64 | .then(response => { 65 | console.log(response); // card charged successfully, get reference here 66 | }) 67 | .catch(error => { 68 | console.log(error); // error is a javascript Error object 69 | console.log(error.message); 70 | console.log(error.code); 71 | }) 72 | 73 | } 74 | ``` 75 | 76 | #### Request Signature 77 | 78 | | Argument | Type | Description | 79 | | ------------- |:-------------:| :-----| 80 | | cardNumber | string | the card number as a String without any seperator e.g 5555555555554444 | 81 | | expiryMonth | string | the card expiry month as a double-digit ranging from 1-12 e.g 10 (October) | 82 | | expiryYear | string | the card expiry year as a double-digit e.g 15 | 83 | | cvc | string | the card 3/4 digit security code as e.g 123 | 84 | | email | string | email of the user to be charged | 85 | | amountInKobo | integer | the transaction amount in kobo | 86 | 87 | #### Response Object 88 | 89 | An object of the form is returned from a successful charge 90 | 91 | ```javascript 92 | { 93 | reference: "trx_1k2o600w" 94 | } 95 | ``` -------------------------------------------------------------------------------- /Old Docs.md: -------------------------------------------------------------------------------- 1 | # React Native Wrapper for Paystack Mobile SDKs 2 | 3 | for Android & iOS by [Arttitude 360](http://www.arttitude360.com) 4 | 5 | ## Index 6 | 7 | 1. [Description](#1-description) 8 | 2. [Installation](#2-installation) 9 | 3. [Usage](#3-usage) 10 | 4. [Credits](#4-credits) 11 | 5. [Changelog](#5-changelog) 12 | 6. [License](#6-license) 13 | 14 | ## 1. Description 15 | 16 | This React Native module provides a wrapper to add Paystack Payments to your React Native application using the [Paystack Android Mobile SDK](https://github.com/PaystackHQ/paystack-android) and the [Paystack iOS Mobile SDK](https://github.com/PaystackHQ/paystack-ios) libraries. 17 | 18 | ## 2. Installation 19 | 20 | You can pull in react-native-paystack via npm: 21 | 22 | ```shell 23 | npm install react-native-paystack --save 24 | ``` 25 | 26 | ### Configuration 27 | 28 | #### IOS 29 | 30 | 1. In XCode's "Project navigator", right click on project name folder ➜ `Add Files to <...>` 31 | - Ensure `Copy items if needed` and `Create groups` are checked 32 | 2. Go to `node_modules` ➜ `react-native-paystack` ➜ add `paystack-ios` folder 33 | 3. To be sure you are all set, manually inspect your project target settings - Ensure: 34 | + path to `paystack-ios` folder is set in `Build Settings > Search Paths > Framework Search Paths` 35 | + `PaystackIOS.m` is listed in `Build Phases > Compile Sources` 36 | + `Paystack.framework` is listed in `Build Phases > Link Binary with Libraries`. 37 | 4. Edit your `AppDelegate.m` and import the Paystack framework: 38 | 39 | ```Objective-C 40 | #import 41 | ... 42 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 43 | { 44 | ... 45 | [Paystack setDefaultPublishableKey:@"INSERT-PUBLIC-KEY-HERE"]; 46 | ... 47 | 48 | } 49 | ``` 50 | *Compile and have some card tokens!* 51 | 52 | 53 | #### Android 54 | 55 | #### Step 1 - Update Gradle Settings 56 | 57 | ```gradle 58 | // file: android/settings.gradle 59 | ... 60 | 61 | include ':reactnativepaystack' 62 | project(':reactnativepaystack').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-paystack/paystack-android') 63 | ``` 64 | 65 | #### Step 2 - Update app Gradle Build 66 | 67 | ```gradle 68 | // file: android/app/build.gradle 69 | ... 70 | 71 | dependencies { 72 | ... 73 | compile project(':reactnativepaystack') 74 | } 75 | ``` 76 | 77 | #### Step 3 - Register React Package 78 | 79 | // file: android/app/src/main/java/.../MainActivity 80 | 81 | ##### react-native >= v0.18.0 82 | 83 | ```java 84 | import com.arttitude360.reactnativepaystack.ReactNativePaystackPackage; // <-- import 85 | ... 86 | /** 87 | * A list of packages used by the app. If the app uses additional views 88 | * or modules besides the default ones, add more packages here. 89 | */ 90 | @Override 91 | protected List getPackages() { 92 | return Arrays.asList( 93 | new MainReactPackage(), 94 | new ReactNativePaystackPackage()); // <-- Register package here 95 | } 96 | ``` 97 | 98 | 99 | Add the following tag in your `android/app/src/main/AndroidManifest.xml` file: 100 | 101 | ```xml 102 | 103 | ``` 104 | 105 | ## 3. Usage 106 | 107 | ### Getting a Token 108 | It's a cinch to obtain a single-use token with the PaystackSdk using the react-native-paystack module. 109 | 110 | ```js 111 | PaystackAndroid.getToken(cardNumber, expiryMonth, expiryYear, cvc, errorCallback, successCallback); 112 | PaystackIOS.getToken(cardNumber, expiryMonth, expiryYear, cvc, responseCallback); 113 | ``` 114 | To be more elaborate: 115 | 116 | #### On Android 117 | 118 | ```js 119 | import PaystackAndroid from 'react-native-paystack'; 120 | 121 | componentDidMount() { 122 | PaystackAndroid.getToken( 123 | '4123450131001384', 124 | '05', 125 | '16', 126 | '883', 127 | (resp) => { 128 | // didn't get a token - something went wrong 129 | this.setState({error: resp.error}); 130 | console.log(resp); 131 | }, 132 | (resp) => { 133 | // got a token - do your thang! 134 | this.setState({token: resp.token}); 135 | console.log(resp); 136 | } 137 | ); 138 | } 139 | ``` 140 | 141 | #### On iOS 142 | 143 | ```js 144 | import PaystackIOS from 'react-native-paystack'; 145 | 146 | componentDidMount() { 147 | PaystackIOS.getToken( 148 | '4123450131001384', 149 | '05', 150 | '16', 151 | '883', 152 | (error, resp) => { 153 | if (error) { 154 | // didn't get a token - something went wrong 155 | this.setState({errorMsg: error.error}); 156 | } 157 | if (resp) { 158 | // got a token - do your thang! 159 | this.setState({tokenStr: resp.token}); 160 | } 161 | } 162 | ); 163 | } 164 | ``` 165 | 166 | Explaining the arguments to `PaystackAndroid.getToken`: 167 | 168 | + {Function} responseCallback (only on iOS) - callback to be invoked with the result of calling `getToken` - takes in 2 arguments - (error, response). If any error is set, you get an error object with 2 keys: "error" is a string containing a description of the error, "code" is an arbitrary error code. 169 | If a token is returned, you get a response object with 2 keys: "token" is a string containing the returned token, while "last4" is a string containing the last 4 digits of the card the token belongs to. 170 | + {Function} successCallback (only on android) - callback to be invoked on successfully acquiring a token. 171 | * A single object argument will be passed which has 2 keys: "token" is a string containing the returned token, while "last4" is a string containing the last 4 digits of the card the token belongs to. 172 | + {Function} errorCallback (only on android) - callback to be invoked on failure to acquire a valid token. 173 | * A single object argument will be passed which has 2 keys: "error" is a string containing a description of the error, "code" is an arbitrary error code. 174 | + cardNumber: the card number as a String without any seperator e.g 5555555555554444 175 | + expiryMonth: the expiry month as an integer ranging from 1-12 e.g 10 (October) 176 | + expiryYear: the expiry year as an integer e.g 15 (2 digits - very !important for iOS) 177 | + cvc: the card security code as a String e.g 123 178 | + To make it easy for you, you can pass all string or all integer arguments - the module will handle the type castings for you. 179 | 180 | ### Charging the tokens. 181 | Send the token to your server and create a charge by calling the Paystack REST API. An authorization_code will be returned once the single-use token has been charged successfully. You can learn more about the Paystack API [here](https://developers.paystack.co/docs/getting-started). 182 | 183 | **Endpoint:** https://api.paystack.co/transaction/charge_token 184 | 185 | **Parameters:** 186 | 187 | - email - customer's email address (required) 188 | - reference - unique reference (required) 189 | - amount - Amount in Kobo (required) 190 | 191 | **Example** 192 | 193 | ```bash 194 | curl https://api.paystack.co/transaction/charge_token \ 195 | -H "Authorization: Bearer SECRET_KEY" \ 196 | -H "Content-Type: application/json" \ 197 | -d '{"token": "PSTK_r4ec2m75mrgsd8n9", "email": "customer@email.com", "amount": 10000, "reference": "amutaJHSYGWakinlade256"}' \ 198 | -X POST 199 | 200 | ``` 201 | 202 | 203 | ## 4. CREDITS 204 | 205 | Perhaps needless to say, this module leverages the [Paystack Android SDK](https://github.com/PaystackHQ/paystack-android) and the [Paystack IOS SDK](https://github.com/PaystackHQ/paystack-ios) for all the heavy liftings. 206 | 207 | ## 5. CHANGELOG 208 | 209 | + 1.0.12: initial version supporting Android. 210 | + 1.1.1: android library upgrade and initial iOS support. 211 | 212 | ## 6. License 213 | 214 | This should be [The MIT License (MIT)](http://www.opensource.org/licenses/mit-license.html). I would have to get back to you on that! 215 | 216 | -------------------------------------------------------------------------------- /ios/RNPaystack.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B3E7B58A1CC2AC0600A0062D /* RNPaystack.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNPaystack.m */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXCopyFilesBuildPhase section */ 14 | 58B511D91A9E6C8500147676 /* CopyFiles */ = { 15 | isa = PBXCopyFilesBuildPhase; 16 | buildActionMask = 2147483647; 17 | dstPath = "include/$(PRODUCT_NAME)"; 18 | dstSubfolderSpec = 16; 19 | files = ( 20 | ); 21 | runOnlyForDeploymentPostprocessing = 0; 22 | }; 23 | /* End PBXCopyFilesBuildPhase section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 134814201AA4EA6300B7C361 /* libRNPaystack.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNPaystack.a; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | B3E7B5881CC2AC0600A0062D /* RNPaystack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNPaystack.h; sourceTree = ""; }; 28 | B3E7B5891CC2AC0600A0062D /* RNPaystack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNPaystack.m; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 134814211AA4EA7D00B7C361 /* Products */ = { 43 | isa = PBXGroup; 44 | children = ( 45 | 134814201AA4EA6300B7C361 /* libRNPaystack.a */, 46 | ); 47 | name = Products; 48 | sourceTree = ""; 49 | }; 50 | 58B511D21A9E6C8500147676 = { 51 | isa = PBXGroup; 52 | children = ( 53 | B3E7B5881CC2AC0600A0062D /* RNPaystack.h */, 54 | B3E7B5891CC2AC0600A0062D /* RNPaystack.m */, 55 | 134814211AA4EA7D00B7C361 /* Products */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | /* End PBXGroup section */ 60 | 61 | /* Begin PBXNativeTarget section */ 62 | 58B511DA1A9E6C8500147676 /* RNPaystack */ = { 63 | isa = PBXNativeTarget; 64 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNPaystack" */; 65 | buildPhases = ( 66 | 58B511D71A9E6C8500147676 /* Sources */, 67 | 58B511D81A9E6C8500147676 /* Frameworks */, 68 | 58B511D91A9E6C8500147676 /* CopyFiles */, 69 | ); 70 | buildRules = ( 71 | ); 72 | dependencies = ( 73 | ); 74 | name = RNPaystack; 75 | productName = RCTDataManager; 76 | productReference = 134814201AA4EA6300B7C361 /* libRNPaystack.a */; 77 | productType = "com.apple.product-type.library.static"; 78 | }; 79 | /* End PBXNativeTarget section */ 80 | 81 | /* Begin PBXProject section */ 82 | 58B511D31A9E6C8500147676 /* Project object */ = { 83 | isa = PBXProject; 84 | attributes = { 85 | LastUpgradeCheck = 0610; 86 | ORGANIZATIONNAME = Facebook; 87 | TargetAttributes = { 88 | 58B511DA1A9E6C8500147676 = { 89 | CreatedOnToolsVersion = 6.1.1; 90 | }; 91 | }; 92 | }; 93 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNPaystack" */; 94 | compatibilityVersion = "Xcode 3.2"; 95 | developmentRegion = English; 96 | hasScannedForEncodings = 0; 97 | knownRegions = ( 98 | en, 99 | ); 100 | mainGroup = 58B511D21A9E6C8500147676; 101 | productRefGroup = 58B511D21A9E6C8500147676; 102 | projectDirPath = ""; 103 | projectRoot = ""; 104 | targets = ( 105 | 58B511DA1A9E6C8500147676 /* RNPaystack */, 106 | ); 107 | }; 108 | /* End PBXProject section */ 109 | 110 | /* Begin PBXSourcesBuildPhase section */ 111 | 58B511D71A9E6C8500147676 /* Sources */ = { 112 | isa = PBXSourcesBuildPhase; 113 | buildActionMask = 2147483647; 114 | files = ( 115 | B3E7B58A1CC2AC0600A0062D /* RNPaystack.m in Sources */, 116 | ); 117 | runOnlyForDeploymentPostprocessing = 0; 118 | }; 119 | /* End PBXSourcesBuildPhase section */ 120 | 121 | /* Begin XCBuildConfiguration section */ 122 | 58B511ED1A9E6C8500147676 /* Debug */ = { 123 | isa = XCBuildConfiguration; 124 | buildSettings = { 125 | ALWAYS_SEARCH_USER_PATHS = NO; 126 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 127 | CLANG_CXX_LIBRARY = "libc++"; 128 | CLANG_ENABLE_MODULES = YES; 129 | CLANG_ENABLE_OBJC_ARC = YES; 130 | CLANG_WARN_BOOL_CONVERSION = YES; 131 | CLANG_WARN_CONSTANT_CONVERSION = YES; 132 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 133 | CLANG_WARN_EMPTY_BODY = YES; 134 | CLANG_WARN_ENUM_CONVERSION = YES; 135 | CLANG_WARN_INT_CONVERSION = YES; 136 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 137 | CLANG_WARN_UNREACHABLE_CODE = YES; 138 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 139 | COPY_PHASE_STRIP = NO; 140 | ENABLE_STRICT_OBJC_MSGSEND = YES; 141 | GCC_C_LANGUAGE_STANDARD = gnu99; 142 | GCC_DYNAMIC_NO_PIC = NO; 143 | GCC_OPTIMIZATION_LEVEL = 0; 144 | GCC_PREPROCESSOR_DEFINITIONS = ( 145 | "DEBUG=1", 146 | "$(inherited)", 147 | ); 148 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 149 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 150 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 151 | GCC_WARN_UNDECLARED_SELECTOR = YES; 152 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 153 | GCC_WARN_UNUSED_FUNCTION = YES; 154 | GCC_WARN_UNUSED_VARIABLE = YES; 155 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 156 | MTL_ENABLE_DEBUG_INFO = YES; 157 | ONLY_ACTIVE_ARCH = YES; 158 | SDKROOT = iphoneos; 159 | }; 160 | name = Debug; 161 | }; 162 | 58B511EE1A9E6C8500147676 /* Release */ = { 163 | isa = XCBuildConfiguration; 164 | buildSettings = { 165 | ALWAYS_SEARCH_USER_PATHS = NO; 166 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 167 | CLANG_CXX_LIBRARY = "libc++"; 168 | CLANG_ENABLE_MODULES = YES; 169 | CLANG_ENABLE_OBJC_ARC = YES; 170 | CLANG_WARN_BOOL_CONVERSION = YES; 171 | CLANG_WARN_CONSTANT_CONVERSION = YES; 172 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 173 | CLANG_WARN_EMPTY_BODY = YES; 174 | CLANG_WARN_ENUM_CONVERSION = YES; 175 | CLANG_WARN_INT_CONVERSION = YES; 176 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 177 | CLANG_WARN_UNREACHABLE_CODE = YES; 178 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 179 | COPY_PHASE_STRIP = YES; 180 | ENABLE_NS_ASSERTIONS = NO; 181 | ENABLE_STRICT_OBJC_MSGSEND = YES; 182 | GCC_C_LANGUAGE_STANDARD = gnu99; 183 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 184 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 185 | GCC_WARN_UNDECLARED_SELECTOR = YES; 186 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 187 | GCC_WARN_UNUSED_FUNCTION = YES; 188 | GCC_WARN_UNUSED_VARIABLE = YES; 189 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 190 | MTL_ENABLE_DEBUG_INFO = NO; 191 | SDKROOT = iphoneos; 192 | VALIDATE_PRODUCT = YES; 193 | }; 194 | name = Release; 195 | }; 196 | 58B511F01A9E6C8500147676 /* Debug */ = { 197 | isa = XCBuildConfiguration; 198 | buildSettings = { 199 | FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../../../ios"; 200 | HEADER_SEARCH_PATHS = ( 201 | "$(inherited)", 202 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 203 | "$(SRCROOT)/../../../React/**", 204 | "$(SRCROOT)/../../react-native/React/**", 205 | "$(SRCROOT)/../react-native/React/**", 206 | "$(SRCROOT)/../../../ios/Paystack/**", 207 | "$(SRCROOT)/../../../ios/Pods/Headers/Public/**", 208 | ); 209 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 210 | OTHER_LDFLAGS = "-ObjC"; 211 | PRODUCT_NAME = RNPaystack; 212 | SKIP_INSTALL = YES; 213 | }; 214 | name = Debug; 215 | }; 216 | 58B511F11A9E6C8500147676 /* Release */ = { 217 | isa = XCBuildConfiguration; 218 | buildSettings = { 219 | FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../../../ios"; 220 | HEADER_SEARCH_PATHS = ( 221 | "$(inherited)", 222 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 223 | "$(SRCROOT)/../../../React/**", 224 | "$(SRCROOT)/../../react-native/React/**", 225 | "$(SRCROOT)/../react-native/React/**", 226 | "$(SRCROOT)/../../../ios/Paystack/**", 227 | "$(SRCROOT)/../../../ios/Pods/Headers/Public/**", 228 | ); 229 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 230 | OTHER_LDFLAGS = "-ObjC"; 231 | PRODUCT_NAME = RNPaystack; 232 | SKIP_INSTALL = YES; 233 | }; 234 | name = Release; 235 | }; 236 | /* End XCBuildConfiguration section */ 237 | 238 | /* Begin XCConfigurationList section */ 239 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNPaystack" */ = { 240 | isa = XCConfigurationList; 241 | buildConfigurations = ( 242 | 58B511ED1A9E6C8500147676 /* Debug */, 243 | 58B511EE1A9E6C8500147676 /* Release */, 244 | ); 245 | defaultConfigurationIsVisible = 0; 246 | defaultConfigurationName = Release; 247 | }; 248 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNPaystack" */ = { 249 | isa = XCConfigurationList; 250 | buildConfigurations = ( 251 | 58B511F01A9E6C8500147676 /* Debug */, 252 | 58B511F11A9E6C8500147676 /* Release */, 253 | ); 254 | defaultConfigurationIsVisible = 0; 255 | defaultConfigurationName = Release; 256 | }; 257 | /* End XCConfigurationList section */ 258 | }; 259 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 260 | } 261 | -------------------------------------------------------------------------------- /android/src/main/java/com/arttitude360/reactnative/rnpaystack/RNPaystackModule.java: -------------------------------------------------------------------------------- 1 | package com.arttitude360.reactnative.rnpaystack; 2 | 3 | import android.app.Activity; 4 | import android.util.Log; 5 | import android.net.Uri; 6 | import android.os.Bundle; 7 | import android.content.Intent; 8 | import android.util.Patterns; 9 | 10 | import co.paystack.android.Paystack; 11 | import co.paystack.android.PaystackSdk; 12 | import co.paystack.android.model.Card; 13 | import co.paystack.android.model.Charge; 14 | import co.paystack.android.Transaction; 15 | 16 | import org.json.JSONArray; 17 | import org.json.JSONException; 18 | import org.json.JSONObject; 19 | 20 | import com.facebook.react.bridge.Arguments; 21 | import com.facebook.react.bridge.Promise; 22 | import com.facebook.react.bridge.ReactApplicationContext; 23 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 24 | import com.facebook.react.bridge.ReactMethod; 25 | import com.facebook.react.bridge.WritableMap; 26 | import com.facebook.react.bridge.WritableNativeMap; 27 | import com.facebook.react.bridge.ReadableArray; 28 | import com.facebook.react.bridge.ReadableMap; 29 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 30 | 31 | public class RNPaystackModule extends ReactContextBaseJavaModule { 32 | 33 | protected Card card; 34 | private Charge charge; 35 | private Transaction transaction; 36 | 37 | private ReactApplicationContext reactContext; 38 | private Promise pendingPromise; 39 | private ReadableMap chargeOptions; 40 | private String mPublicKey; 41 | 42 | public static final String TAG = "RNPaystack"; 43 | 44 | public static String REACT_CLASS = "RNPaystackModule"; 45 | 46 | private static RNPaystackModule sInstance = null; 47 | 48 | public static RNPaystackModule getInstance() { 49 | return sInstance; 50 | } 51 | 52 | public RNPaystackModule(ReactApplicationContext reactContext) { 53 | super(reactContext); 54 | 55 | this.reactContext = reactContext; 56 | sInstance = this; 57 | 58 | // Initialize PaystackSdk 59 | PaystackSdk.initialize(this.reactContext); 60 | } 61 | 62 | @Override 63 | public String getName() { 64 | return REACT_CLASS; 65 | } 66 | 67 | @ReactMethod 68 | public void init(ReadableMap options) { 69 | String newPublicKey = options.getString("publicKey"); 70 | 71 | if (newPublicKey != null) { 72 | mPublicKey = newPublicKey; 73 | PaystackSdk.setPublicKey(newPublicKey); 74 | } 75 | } 76 | 77 | @ReactMethod 78 | public void chargeCard(ReadableMap cardData, final Promise promise) { 79 | 80 | this.chargeOptions = null; 81 | 82 | this.pendingPromise = promise; 83 | this.chargeOptions = cardData; 84 | 85 | validateFullTransaction(); 86 | 87 | if (card != null && card.isValid()) { 88 | try { 89 | createTransaction(); 90 | } catch (Exception error) { 91 | rejectPromise("E_CHARGE_ERROR", error.getMessage()); 92 | } 93 | 94 | } 95 | } 96 | 97 | @ReactMethod 98 | public void chargeCardWithAccessCode(ReadableMap cardData, final Promise promise) { 99 | 100 | this.chargeOptions = null; 101 | 102 | this.pendingPromise = promise; 103 | this.chargeOptions = cardData; 104 | 105 | validateAccessCodeTransaction(); 106 | 107 | if (card != null && card.isValid()) { 108 | try { 109 | createTransaction(); 110 | } catch (Exception error) { 111 | rejectPromise("E_CHARGE_ERROR", error.getMessage()); 112 | } 113 | 114 | } 115 | } 116 | 117 | private void validateCard(String cardNumber, String expiryMonth, String expiryYear, String cvc) { 118 | 119 | if (isEmpty(cardNumber)) { 120 | rejectPromise("E_INVALID_NUMBER", "Empty card number"); 121 | return; 122 | } 123 | 124 | // build card object with ONLY the number, update the other fields later 125 | card = new Card.Builder(cardNumber, 0, 0, "").build(); 126 | 127 | if (!card.validNumber()) { 128 | rejectPromise("E_INVALID_NUMBER", "Invalid card number"); 129 | return; 130 | } 131 | 132 | // validate cvc 133 | if (isEmpty(cvc)) { 134 | rejectPromise("E_INVALID_CVC", "Empty CVC"); 135 | return; 136 | } 137 | 138 | // update the cvc field of the card 139 | card.setCvc(cvc); 140 | 141 | // check that it's valid 142 | if (!card.validCVC()) { 143 | rejectPromise("E_INVALID_CVC", "Invalid CVC"); 144 | return; 145 | } 146 | 147 | int month = -1; 148 | try { 149 | month = Integer.parseInt(expiryMonth); 150 | } catch (Exception ignored) { 151 | } 152 | 153 | // validate expiry month 154 | if (month < 1) { 155 | rejectPromise("E_INVALID_MONTH", "Invalid expiration month"); 156 | return; 157 | } 158 | 159 | // update the expiryMonth field of the card 160 | card.setExpiryMonth(month); 161 | 162 | int year = -1; 163 | try { 164 | year = Integer.parseInt(expiryYear); 165 | } catch (Exception ignored) { 166 | } 167 | 168 | // validate expiry year 169 | if (year < 1) { 170 | rejectPromise("E_INVALID_YEAR", "Invalid expiration year"); 171 | return; 172 | } 173 | 174 | // update the expiryYear field of the card 175 | card.setExpiryYear(year); 176 | 177 | // validate expiry 178 | if (!card.validExpiryDate()) { 179 | rejectPromise("E_INVALID_DATE", "Invalid expiration date"); 180 | return; 181 | } 182 | } 183 | 184 | private void validateAccessCodeTransaction() { 185 | String cardNumber = chargeOptions.getString("cardNumber"); 186 | String expiryMonth = chargeOptions.getString("expiryMonth"); 187 | String expiryYear = chargeOptions.getString("expiryYear"); 188 | String cvc = chargeOptions.getString("cvc"); 189 | 190 | validateCard(cardNumber, expiryMonth, expiryYear, cvc); 191 | 192 | charge = new Charge(); 193 | charge.setCard(card); 194 | 195 | if (hasStringKey("accessCode")) { 196 | charge.setAccessCode(chargeOptions.getString("accessCode")); 197 | } 198 | } 199 | 200 | private void validateFullTransaction() { 201 | 202 | String cardNumber = chargeOptions.getString("cardNumber"); 203 | String expiryMonth = chargeOptions.getString("expiryMonth"); 204 | String expiryYear = chargeOptions.getString("expiryYear"); 205 | String cvc = chargeOptions.getString("cvc"); 206 | String email = chargeOptions.getString("email"); 207 | int amountInKobo = chargeOptions.getInt("amountInKobo"); 208 | 209 | validateCard(cardNumber, expiryMonth, expiryYear, cvc); 210 | 211 | charge = new Charge(); 212 | charge.setCard(card); 213 | 214 | if (isEmpty(email)) { 215 | rejectPromise("E_INVALID_EMAIL", "Email cannot be empty"); 216 | return; 217 | } 218 | 219 | if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) { 220 | rejectPromise("E_INVALID_EMAIL", "Invalid email"); 221 | return; 222 | } 223 | 224 | charge.setEmail(email); 225 | 226 | if (amountInKobo < 1) { 227 | rejectPromise("E_INVALID_AMOUNT", "Invalid amount"); 228 | return; 229 | } 230 | 231 | charge.setAmount(amountInKobo); 232 | 233 | if (hasStringKey("currency")) { 234 | charge.setCurrency(chargeOptions.getString("currency")); 235 | } 236 | 237 | if (hasStringKey("plan")) { 238 | charge.setPlan(chargeOptions.getString("plan")); 239 | } 240 | 241 | if (hasStringKey("subAccount")) { 242 | charge.setSubaccount(chargeOptions.getString("subAccount")); 243 | 244 | if (hasStringKey("bearer") && chargeOptions.getString("bearer") == "subaccount") { 245 | charge.setBearer(Charge.Bearer.subaccount); 246 | } 247 | 248 | if (hasStringKey("bearer") && chargeOptions.getString("bearer") == "account") { 249 | charge.setBearer(Charge.Bearer.account); 250 | } 251 | 252 | if (hasIntKey("transactionCharge")) { 253 | charge.setTransactionCharge(chargeOptions.getInt("transactionCharge")); 254 | } 255 | } 256 | 257 | if (hasStringKey("reference")) { 258 | charge.setReference(chargeOptions.getString("reference")); 259 | } 260 | 261 | } 262 | 263 | private void createTransaction() { 264 | 265 | transaction = null; 266 | Activity currentActivity = getCurrentActivity(); 267 | 268 | PaystackSdk.chargeCard(currentActivity, charge, new Paystack.TransactionCallback() { 269 | @Override 270 | public void onSuccess(Transaction transaction) { 271 | 272 | // This is called only after transaction is successful 273 | RNPaystackModule.this.transaction = transaction; 274 | 275 | WritableMap map = Arguments.createMap(); 276 | map.putString("reference", transaction.getReference()); 277 | 278 | resolvePromise(map); 279 | } 280 | 281 | @Override 282 | public void beforeValidate(Transaction transaction) { 283 | // This is called only before requesting OTP 284 | // Save reference so you may send to server if 285 | // error occurs with OTP 286 | RNPaystackModule.this.transaction = transaction; 287 | } 288 | 289 | @Override 290 | public void onError(Throwable error, Transaction transaction) { 291 | RNPaystackModule.this.transaction = transaction; 292 | 293 | if (transaction.getReference() == null) { 294 | rejectPromise("E_TRANSACTION_ERROR", error.getMessage()); 295 | } else { 296 | rejectPromise("E_TRANSACTION_ERROR", 297 | transaction.getReference() + " concluded with error: " + error.getMessage()); 298 | } 299 | } 300 | 301 | }); 302 | } 303 | 304 | private boolean isEmpty(String s) { 305 | return s == null || s.length() < 1; 306 | } 307 | 308 | private boolean hasStringKey(String key) { 309 | return chargeOptions.hasKey(key) && !chargeOptions.isNull(key) && !chargeOptions.getString(key).isEmpty(); 310 | } 311 | 312 | private boolean hasIntKey(String key) { 313 | return chargeOptions.hasKey(key) && !chargeOptions.isNull(key) && chargeOptions.getInt(key) > 0; 314 | } 315 | 316 | private void rejectPromise(String code, String message) { 317 | if (this.pendingPromise != null) { 318 | this.pendingPromise.reject(code, message); 319 | this.pendingPromise = null; 320 | } 321 | } 322 | 323 | private void resolvePromise(Object data) { 324 | if (this.pendingPromise != null) { 325 | this.pendingPromise.resolve(data); 326 | this.pendingPromise = null; 327 | } 328 | } 329 | 330 | } -------------------------------------------------------------------------------- /ios/RNPaystack.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "RNPaystack.h" 3 | #import 4 | #import 5 | 6 | @implementation RNPaystackModule 7 | { 8 | RCTPromiseResolveBlock _resolve; 9 | RCTPromiseRejectBlock _reject; 10 | NSString *publicKey; 11 | BOOL requestIsCompleted; 12 | } 13 | 14 | - (instancetype)init { 15 | if ((self = [super init])) { 16 | requestIsCompleted = YES; 17 | } 18 | return self; 19 | } 20 | 21 | - (dispatch_queue_t)methodQueue { 22 | return dispatch_get_main_queue(); 23 | } 24 | 25 | + (BOOL)requiresMainQueueSetup 26 | { 27 | return YES; 28 | } 29 | 30 | RCT_EXPORT_MODULE(); 31 | 32 | - (BOOL)isCardNumberValid:(NSString *)cardNumber validateCardBrand:(BOOL)validateCardBrand 33 | { 34 | BOOL isValid = ([PSTCKCardValidator validationStateForNumber:cardNumber validatingCardBrand:validateCardBrand] == PSTCKCardValidationStateValid); 35 | return isValid; 36 | } 37 | 38 | - (BOOL)isExpMonthValid:(NSString *)expMonth 39 | { 40 | BOOL isValid = ([PSTCKCardValidator validationStateForExpirationMonth:expMonth] == PSTCKCardValidationStateValid); 41 | return isValid; 42 | } 43 | 44 | - (BOOL)isExpYearValid:(NSString *)expYear forMonth:(NSString *)expMonth 45 | { 46 | BOOL isValid = ([PSTCKCardValidator validationStateForExpirationYear:expYear inMonth:expMonth] == PSTCKCardValidationStateValid); 47 | return isValid; 48 | } 49 | 50 | - (BOOL)isCvcValid:(NSString *)cvc withNumber:(NSString *)cardNumber 51 | { 52 | BOOL isValid = ([PSTCKCardValidator validationStateForCVC:cvc cardBrand:[PSTCKCardValidator brandForNumber:cardNumber]] == PSTCKCardValidationStateValid); 53 | return isValid; 54 | } 55 | 56 | - (BOOL)isCardValid:(PSTCKCardParams *)card 57 | { 58 | BOOL isValid = ([PSTCKCardValidator validationStateForCard:card] == PSTCKCardValidationStateValid); 59 | return isValid; 60 | } 61 | 62 | - (NSMutableDictionary*)setTokenMsg:(NSString *)token withCardLastDigits:(NSString *)last4 63 | { 64 | NSMutableDictionary *returnInfo; 65 | returnInfo = [NSMutableDictionary dictionaryWithCapacity:2]; 66 | 67 | [returnInfo setObject:token forKey:@"token"]; 68 | [returnInfo setObject:last4 forKey:@"last4"]; 69 | 70 | return returnInfo; 71 | } 72 | 73 | - (NSMutableDictionary*)setReferenceMsg:(NSString *)reference 74 | { 75 | NSMutableDictionary *returnInfo; 76 | returnInfo = [NSMutableDictionary dictionaryWithCapacity:1]; 77 | 78 | [returnInfo setObject:reference forKey:@"reference"]; 79 | 80 | return returnInfo; 81 | } 82 | 83 | - (BOOL)cardParamsAreValid:(NSString *)cardNumber withMonth:(NSString *)expMonth withYear:(NSString *)expYear andWithCvc:(NSString *)cvc 84 | { 85 | if (! [self isCardNumberValid:cardNumber validateCardBrand:YES]) { 86 | self.errorMsg = @"Invalid card number"; 87 | self.errorCode = @"E_INVALID_NUMBER"; 88 | return NO; 89 | } 90 | 91 | if (! [self isExpMonthValid:expMonth]) { 92 | self.errorMsg = @"Invalid expiration month"; 93 | self.errorCode = @"E_INVALID_MONTH"; 94 | return NO; 95 | } 96 | 97 | if (! [self isExpYearValid:expYear forMonth:expMonth]) { 98 | self.errorMsg = @"Invalid expiration year"; 99 | self.errorCode = @"E_INVALID_YEAR"; 100 | return NO; 101 | } 102 | 103 | if (! [self isCvcValid:cvc withNumber:cardNumber]) { 104 | self.errorMsg = @"Invalid CVC"; 105 | self.errorCode = @"E_INVALID_CVC"; 106 | return NO; 107 | } 108 | 109 | return YES; 110 | 111 | } 112 | 113 | RCT_EXPORT_METHOD(init:(NSDictionary *)options) { 114 | publicKey = options[@"publicKey"]; 115 | [Paystack setDefaultPublicKey:publicKey]; 116 | } 117 | 118 | RCT_EXPORT_METHOD(chargeCard:(NSDictionary *)params 119 | resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 120 | { 121 | _resolve = resolve; 122 | _reject = reject; 123 | UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController; 124 | 125 | if(!requestIsCompleted) { 126 | _reject(@"E_BUSY", @"Another request is still being processed, please wait", nil); 127 | return; 128 | } 129 | 130 | requestIsCompleted = NO; 131 | 132 | if (! [self cardParamsAreValid:params[@"cardNumber"] withMonth:params[@"expiryMonth"] withYear:params[@"expiryYear"] andWithCvc:params[@"cvc"]]) { 133 | 134 | // NSMutableDictionary *returnInfo = [self setErrorMsg:self.errorMsg withErrorCode:self.errorCode]; 135 | if (_reject) { 136 | _reject(self.errorCode, self.errorMsg, nil); 137 | } 138 | 139 | } else { 140 | 141 | PSTCKCardParams *cardParams = [[PSTCKCardParams alloc] init]; 142 | cardParams.number = params[@"cardNumber"]; 143 | cardParams.expMonth = [params[@"expiryMonth"] integerValue]; 144 | cardParams.expYear = [params[@"expiryYear"] integerValue]; 145 | cardParams.cvc = params[@"cvc"]; 146 | 147 | PSTCKTransactionParams *transactionParams = [[PSTCKTransactionParams alloc] init]; 148 | transactionParams.amount = [params[@"amountInKobo"] integerValue]; 149 | transactionParams.email = params[@"email"]; 150 | 151 | if (params[@"currency"] != nil) { 152 | transactionParams.currency = params[@"currency"]; 153 | } 154 | 155 | if (params[@"plan"] != nil) { 156 | transactionParams.plan = params[@"plan"]; 157 | } 158 | 159 | if (params[@"subAccount"] != nil) { 160 | transactionParams.subaccount = params[@"subAccount"]; 161 | 162 | if (params[@"bearer"] != nil) { 163 | transactionParams.bearer = params[@"bearer"]; 164 | } 165 | 166 | if (params[@"transactionCharge"] != nil) { 167 | transactionParams.transaction_charge = [params[@"transactionCharge"] integerValue]; 168 | } 169 | } 170 | 171 | if (params[@"reference"] != nil) { 172 | transactionParams.reference = params[@"reference"]; 173 | } 174 | 175 | if ([self isCardValid:cardParams]) { 176 | 177 | [[PSTCKAPIClient sharedClient] chargeCard:cardParams 178 | forTransaction:transactionParams 179 | onViewController:rootViewController 180 | didEndWithError:^(NSError *error, NSString *reference){ 181 | requestIsCompleted = YES; 182 | if (_reject) { 183 | _reject(@"E_CHARGE_ERROR", @"Error charging card", error); 184 | } 185 | } 186 | didRequestValidation: ^(NSString *reference){ 187 | // an OTP was requested, transaction has not yet succeeded 188 | NSLog(@"- RNPaystack: an OTP was requested, transaction has not yet succeeded"); 189 | } 190 | didTransactionSuccess: ^(NSString *reference){ 191 | requestIsCompleted = YES; 192 | // transaction may have succeeded, please verify on server 193 | NSLog(@"- RNPaystack: transaction may have succeeded, please verify on server"); 194 | NSMutableDictionary *returnInfo = [self setReferenceMsg:reference]; 195 | 196 | if (_resolve) { 197 | _resolve(returnInfo); 198 | } 199 | }]; 200 | 201 | } else { 202 | requestIsCompleted = YES; 203 | 204 | if (_reject) { 205 | _reject(@"E_INVALID_CARD", @"Card is invalid", nil); 206 | } 207 | } 208 | 209 | } 210 | } 211 | 212 | RCT_EXPORT_METHOD(chargeCardWithAccessCode:(NSDictionary *)params 213 | resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 214 | { 215 | _resolve = resolve; 216 | _reject = reject; 217 | UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController; 218 | 219 | if(!requestIsCompleted) { 220 | _reject(@"E_BUSY", @"Another request is still being processed, please wait", nil); 221 | return; 222 | } 223 | 224 | requestIsCompleted = NO; 225 | 226 | if (! [self cardParamsAreValid:params[@"cardNumber"] withMonth:params[@"expiryMonth"] withYear:params[@"expiryYear"] andWithCvc:params[@"cvc"]]) { 227 | 228 | // NSMutableDictionary *returnInfo = [self setErrorMsg:self.errorMsg withErrorCode:self.errorCode]; 229 | if (_reject) { 230 | _reject(self.errorCode, self.errorMsg, nil); 231 | } 232 | 233 | } else { 234 | 235 | PSTCKCardParams *cardParams = [[PSTCKCardParams alloc] init]; 236 | cardParams.number = params[@"cardNumber"]; 237 | cardParams.expMonth = [params[@"expiryMonth"] integerValue]; 238 | cardParams.expYear = [params[@"expiryYear"] integerValue]; 239 | cardParams.cvc = params[@"cvc"]; 240 | 241 | PSTCKTransactionParams *transactionParams = [[PSTCKTransactionParams alloc] init]; 242 | transactionParams.access_code = params[@"accessCode"]; 243 | 244 | if ([self isCardValid:cardParams]) { 245 | 246 | [[PSTCKAPIClient sharedClient] chargeCard:cardParams 247 | forTransaction:transactionParams 248 | onViewController:rootViewController 249 | didEndWithError:^(NSError *error, NSString *reference){ 250 | requestIsCompleted = YES; 251 | if (_reject) { 252 | _reject(@"E_CHARGE_ERROR", @"Error charging card", error); 253 | } 254 | } 255 | didRequestValidation: ^(NSString *reference){ 256 | // an OTP was requested, transaction has not yet succeeded 257 | NSLog(@"- RNPaystack: an OTP was requested, transaction has not yet succeeded"); 258 | } 259 | didTransactionSuccess: ^(NSString *reference){ 260 | requestIsCompleted = YES; 261 | // transaction may have succeeded, please verify on server 262 | NSLog(@"- RNPaystack: transaction may have succeeded, please verify on server"); 263 | NSMutableDictionary *returnInfo = [self setReferenceMsg:reference]; 264 | 265 | if (_resolve) { 266 | _resolve(returnInfo); 267 | } 268 | }]; 269 | 270 | } else { 271 | requestIsCompleted = YES; 272 | 273 | if (_reject) { 274 | _reject(@"E_INVALID_CARD", @"Card is invalid", nil); 275 | } 276 | } 277 | 278 | } 279 | 280 | } 281 | 282 | @end 283 | -------------------------------------------------------------------------------- /README-3-1.md: -------------------------------------------------------------------------------- 1 | # React Native Wrapper for Paystack Mobile SDKs 2 | 3 | for Android & iOS by [Arttitude 360](http://www.arttitude360.com) 4 | 5 | ## Index 6 | 7 | 1. [Description](#1-description) 8 | 2. [Installation](#2-installation) 9 | 3. [Usage](#3-usage) 10 | 4. [Credits](#4-credits) 11 | 5. [Changelog](#5-changelog) 12 | 6. [License](#6-license) 13 | 14 | ## 1. Description 15 | 16 | This React Native module provides a wrapper to add Paystack Payments to your React Native application using the [Paystack Android Mobile SDK](https://github.com/PaystackHQ/paystack-android) and the [Paystack iOS Mobile SDK](https://github.com/PaystackHQ/paystack-ios) libraries. If interested, there is a [Sample App](https://github.com/tolu360/vestarapp). 17 | 18 | ## 2. Installation 19 | 20 | You can pull in react-native-paystack via npm: 21 | 22 | ```shell 23 | npm install react-native-paystack --save 24 | ``` 25 | OR 26 | 27 | ```shell 28 | yarn add react-native-paystack 29 | ``` 30 | 31 | ### Versioning 32 | - For `RN <=0.39` use version 2+ e.g. react-native-paystack@2.2.0 (No longer updated) 33 | - For `RN >=0.40` use version 3+ e.g. react-native-paystack@3.1.4 34 | 35 | ### Configuration 36 | 37 | #### Automatic (iOS & Android) 38 | 39 | ```shell 40 | react-native link react-native-paystack 41 | ``` 42 | - (iOS only): The next steps are necessary for iOS at this time as publishing to NPM seems to break symlinks contained in the Paystack iOS framework shipped with this package, thus causing XCode build errors. 43 | - Download a fresh copy of the `Paystack iOS framework` from their [releases page on Github](https://github.com/PaystackHQ/paystack-ios/releases/). 44 | - Extract `Paystack.framework` from the downloaded zip. 45 | - In XCode's "Project navigator", right click on project name folder ➜ `Add Files to `. Ensure `Copy items if needed` and `Create groups` are checked and select your copy of `Paystack.framework`. 46 | - Your files tree in XCode should look similar to the screenshot below: 47 | 48 | 49 | 50 | - If you are working with XCode 8+, to allow encryptions work properly with the Paystack SDK, you may need to enable `Keychain Sharing` for your app. In the Capabilities pane, if Keychain Sharing isn’t enabled, toggle ON the switch in the Keychain Sharing section. 51 | 52 | 53 | 54 | #### Manual Config (iOS) 55 | 56 | - The following steps are optional, should be taken if you have not run `react-native link react-native-paystack` already. 57 | - In XCode's "Project navigator", right click on project name folder ➜ `Add Files to <...>`. Ensure `Copy items if needed` and `Create groups` are checked 58 | - Go to `node_modules` ➜ `react-native-paystack/ios` ➜ add `RNPaystack.xcodeproj`. 59 | 60 | #### Manual Config (Android) 61 | 62 | - The following steps are optional, should be taken if you have not run `react-native link react-native-paystack` already. 63 | - Add the following in your `android/settings.gradle` file: 64 | 65 | ```java 66 | include ':react-native-paystack' 67 | project(':react-native-paystack').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-paystack/android') 68 | ``` 69 | - Add the following in your `android/app/build.grade` file: 70 | 71 | ```java 72 | dependencies { 73 | ... 74 | compile project(':react-native-paystack') 75 | } 76 | ``` 77 | - Add the following in your `...MainApplication.java` file: 78 | 79 | ```java 80 | import com.arttitude360.reactnative.rnpaystack.RNPaystackPackage; 81 | 82 | @Override 83 | protected List getPackages() { 84 | return Arrays.asList( 85 | new MainReactPackage(), 86 | ... 87 | new RNPaystackPackage() //<-- Add line 88 | ); 89 | } 90 | ``` 91 | 92 | #### More Config (Android v3.1.4+) 93 | - Update Gradle plugin to v3.0.0 for your app, follow the following steps if you are not sure how: 94 | * Edit your `~ android/build.gradle` to look similar to [build.gradle](https://github.com/tolu360/vestarapp/blob/master/android/build.gradle) 95 | * Edit your `~ android/gradle/wrapper/gradle-wrapper.properties` to look similar to [gradle-wrapper.properties](https://github.com/tolu360/vestarapp/blob/master/android/gradle/wrapper/gradle-wrapper.properties) 96 | 97 | - Update your Android build tools and environment to v27+ after the Gradle plugin update 98 | * Edit your `~ android/app/build.gradle` to look similar to [build.gradle](https://github.com/tolu360/vestarapp/blob/master/android/app/build.gradle) 99 | 100 | 101 | ## 3. Usage 102 | 103 | ### Import Library 104 | - For ios, edit your `AppDelegate.m` file and import the Paystack framework: 105 | 106 | ```Objective-C 107 | #import 108 | ... 109 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 110 | { 111 | ... 112 | 113 | [Paystack setDefaultPublicKey:@"INSERT-PUBLIC-KEY-HERE"]; 114 | ... 115 | 116 | } 117 | ``` 118 | 119 | - For Android, add the following tag in your `android/app/src/main/AndroidManifest.xml` file within the `` tags: 120 | 121 | ```xml 122 | 123 | ``` 124 | 125 | ### Charging a Card with Access Code (iOS & Android) 126 | It's a cinch to charge a card token using the react-native-paystack module. This is the recommended or the most-preferred workflow favored by the folks at Paystack. Initiate a new transaction on your server side using the appropriate [Paystack endpoint](https://developers.paystack.co/reference#initialize-a-transaction) - obtain an `access_code` and complete the charge on your mobile application. Pls note, the SDK assumes you are responsible for building the card form/UI. 127 | 128 | ```javascript 129 | RNPaystack.chargeCardWithAccessCode(cardParams); 130 | ``` 131 | To be more elaborate, `cardParams` is a Javascript `Object` representing the card to be charged and `RNPaystack.chargeCardWithAccessCode()` returns a Javascript `Promise` like: 132 | 133 | ```js 134 | import RNPaystack from 'react-native-paystack'; 135 | 136 | chargeCard() { 137 | 138 | RNPaystack.chargeCardWithAccessCode({ 139 | cardNumber: '4123450131001381', 140 | expiryMonth: '10', 141 | expiryYear: '17', 142 | cvc: '883', 143 | accessCode: '2p3j42th639duy4' 144 | }) 145 | .then(response => { 146 | console.log(response); // do stuff with the token 147 | }) 148 | .catch(error => { 149 | console.log(error); // error is a javascript Error object 150 | console.log(error.message); 151 | console.log(error.code); 152 | }) 153 | 154 | } 155 | ``` 156 | 157 | #### Request Signature 158 | 159 | | Argument | Type | Description | 160 | | ------------- |:-------------:| :-----| 161 | | cardNumber | string | the card number as a String without any seperator e.g 5555555555554444 | 162 | | expiryMonth | string | the card expiry month as a double-digit ranging from 1-12 e.g 10 (October) | 163 | | expiryYear | string | the card expiry year as a double-digit e.g 15 | 164 | | cvc | string | the card 3/4 digit security code as a String e.g 123 | 165 | | accessCode | string | the access_code obtained for the charge | 166 | 167 | #### Response Object 168 | 169 | An object of the form is returned from a successful token request 170 | 171 | ```javascript 172 | { 173 | reference: "trx_1k2o600w" 174 | } 175 | ``` 176 | 177 | ### Charging a Card (iOS & Android) 178 | Using the react-native-paystack module, you can start and complete a transaction with the mobile Paystack Android and iOS SDKs. With this option, you pass both your charge and card properties to the SDK - with this worklow, you initiate and complete a transaction on your mobile app. Note that as with charging with an access_code, the SDK assumes you are responsible for building the card form/UI. 179 | 180 | ```javascript 181 | RNPaystack.chargeCard(chargeParams); 182 | ``` 183 | To be more elaborate, `chargeParams` is a Javascript `Object` representing the parameters of the charge to be initiated and `RNPaystack.chargeCard()` returns a Javascript `Promise` like: 184 | 185 | ```js 186 | import RNPaystack from 'react-native-paystack'; 187 | 188 | chargeCard() { 189 | 190 | RNPaystack.chargeCard({ 191 | cardNumber: '4123450131001381', 192 | expiryMonth: '10', 193 | expiryYear: '17', 194 | cvc: '883', 195 | email: 'chargeIOS@master.dev', 196 | amountInKobo: 150000, 197 | subAccount: 'ACCT_pz61jjjsslnx1d9', 198 | }) 199 | .then(response => { 200 | console.log(response); // card charged successfully, get reference here 201 | }) 202 | .catch(error => { 203 | console.log(error); // error is a javascript Error object 204 | console.log(error.message); 205 | console.log(error.code); 206 | }) 207 | 208 | } 209 | ``` 210 | 211 | #### Request Signature (chargeParams) 212 | 213 | | Argument | Type | Description | 214 | | ------------- |:-------------:| :-----| 215 | | cardNumber | string | the card number as a String without any seperator e.g 5555555555554444 | 216 | | expiryMonth | string | the card expiry month as a double-digit ranging from 1-12 e.g 10 (October) | 217 | | expiryYear | string | the card expiry year as a double-digit e.g 15 | 218 | | cvc | string | the card 3/4 digit security code as e.g 123 | 219 | | email | string | email of the user to be charged | 220 | | amountInKobo | integer | the transaction amount in kobo | 221 | | currency (optional) | string | sets the currency for the transaction e.g. USD | 222 | | plan (optional) | string | sets the plan ID if the transaction is to create a subscription e.g. PLN_n0p196bg73y4jcx | 223 | | subAccount (optional) | string | sets the subaccount ID for split-payment transactions e.g. ACCT_pz61jjjsslnx1d9 | 224 | | transactionCharge (optional) | integer | the amount to be charged on a split-payment, use only when `subAccount` is set | 225 | | bearer (optional) | string | sets which party bears paystack fees on a split-payment e.g. 'subaccount', use only when `subAccount` is set | 226 | | reference (optional) | string | sets the transaction reference which must be unique per transaction | 227 | 228 | #### Response Object 229 | 230 | An object of the form is returned from a successful charge 231 | 232 | ```javascript 233 | { 234 | reference: "trx_1k2o600w" 235 | } 236 | ``` 237 | 238 | ### Verifying a Charge 239 | Verify a charge by calling Paystack's [REST API](https://api.paystack.co/transaction/verify) with the `reference` obtained above. An `authorization_code` will be returned once the card has been charged successfully. Learn more about that [here](https://developers.paystack.co/docs/verify-transaction). 240 | 241 | **Parameter:** 242 | 243 | - reference - the transaction reference (required) 244 | 245 | **Example** 246 | 247 | ```bash 248 | $ curl https://api.paystack.co/transaction/verify/trx_1k2o600w \ 249 | -H "Authorization: Bearer SECRET_KEY" \ 250 | -H "Content-Type: application/json" \ 251 | -X GET 252 | ``` 253 | 254 | ## 4. CREDITS 255 | 256 | Perhaps needless to say, this module leverages the [Paystack Android SDK](https://github.com/PaystackHQ/paystack-android) and the [Paystack IOS SDK](https://github.com/PaystackHQ/paystack-ios) for all the heavy liftings. 257 | 258 | ## 5. CHANGELOG 259 | 260 | + 1.0.12: Initial version supporting Android. 261 | + 1.1.1: Android library upgrade and initial iOS support. 262 | + 2.0.0: A couple of breaking changes have been introduced, see [Old Docs](./Old Docs.md) for previous documentations. 263 | + 2.0.0: Upgraded to v2.0 of the Paystack Android SDK. 264 | + 2.0.0: Unified APIs across both platforms (iOS & Android). 265 | + 2.0.0: Methods now return Javascript Promises on both platforms. 266 | + 2.1.1: Upgraded to v2.1+ of both the Paystack iOS and Android SDKs. 267 | + 2.1.1: Added support for `chargeCard` on both platforms. 268 | + 2.1.1: Added support for `subscriptions` and `split-payments`. 269 | + 3.1.0: Retired support for `getToken` on both platforms. 270 | + 3.1.0: Added support for `chargeCardWithAccessCode` on both platforms. 271 | + 3.1.0: Upgraded to v3.*+ of both the Paystack iOS and Android SDKs. 272 | + 3.1.1: Fix for breaking change in RN v0.47+ 273 | * 3.1.4: Miscellaneous and dependencies update on Android 274 | 275 | ## 6. License 276 | 277 | This should be [The MIT License (MIT)](http://www.opensource.org/licenses/mit-license.html). I would have to get back to you on that! 278 | 279 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native Wrapper for Paystack Mobile SDKs 2 | 3 | for Android & iOS by [Arttitude 360](http://www.arttitude360.com) 4 | 5 | ## Index 6 | 7 | 1. [Description](#1-description) 8 | 2. [Installation](#2-installation) 9 | 3. [Usage](#3-usage) 10 | 4. [Credits](#4-credits) 11 | 5. [Changelog](#5-changelog) 12 | 6. [License](#6-license) 13 | 14 | ## 1. Description 15 | 16 | This React Native module provides a wrapper to add Paystack Payments to your React Native application using the [Paystack Android Mobile SDK](https://github.com/PaystackHQ/paystack-android) and the [Paystack iOS Mobile SDK](https://github.com/PaystackHQ/paystack-ios) libraries. 17 | 18 | ### PS: If you are using this library in production, please give the repo a star - I am not being vain and have no interest in the vanity metric, just trying to figure out if it is still worth the time or effort spent supporting the library. Cheers! 19 | 20 | ## 2. Installation 21 | 22 | You can pull in react-native-paystack via npm: 23 | 24 | ```shell 25 | npm install react-native-paystack --save 26 | ``` 27 | OR 28 | 29 | ```shell 30 | yarn add react-native-paystack 31 | ``` 32 | 33 | ### Versioning 34 | - For `RN >=0.40` only; 35 | - Breaking Change Alert for v3.2.0+. Looking for the docs for the 3.1.* version of this library? [Check here!](./README-3-1.md) 36 | 37 | ### Configuration 38 | 39 | #### Post-Install Steps (iOS) 40 | 41 | ##### 1) Auto Linking & Cocoapods Integration (React Native 0.59 and lower) 42 | - If you do not have CocoaPods already installed on your machine, run `gem install cocoapods` to set it up the first time. (Hint: Go grab a cup of coffee!) 43 | - If you are not using Cocoapods in your project already, run `cd ios && pod init` at the root directory of your project. This would create a `Podfile` in your `ios` directory. 44 | - Run `react-native link react-native-paystack` at the root directory of your project and ensure you edit your Podfile to look like the sample below (remove all the targets you are not building for, such as Tests and tvOS): 45 | 46 | ```ruby 47 | # platform :ios, '9.0' 48 | 49 | target '_YOUR_PROJECT_TARGET_' do 50 | 51 | # Pods for _YOUR_PROJECT_TARGET_ 52 | pod 'React', :path => '../node_modules/react-native', :subspecs => [ 53 | 'Core', 54 | 'CxxBridge', 55 | 'DevSupport', 56 | 'RCTText', 57 | 'RCTImage', 58 | 'RCTNetwork', 59 | 'RCTWebSocket', 60 | 'RCTSettings', 61 | 'RCTAnimation', 62 | 'RCTLinkingIOS', 63 | # Add any other subspecs you want to use in your project 64 | # Remove any subspecs you don't want to use in your project 65 | ] 66 | 67 | pod "yoga", :path => "../node_modules/react-native/ReactCommon/yoga" 68 | pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec' 69 | pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec' 70 | pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec' 71 | # This should already be auto-added for you, if not add the line below 72 | pod 'react-native-paystack', :path => '../node_modules/react-native-paystack' 73 | 74 | end 75 | ``` 76 | 77 | - Replace all references to _YOUR_PROJECT_TARGET_ with your project target (it's the same as project name by default). 78 | - By now, you should be all set to install the packages from your Podfile. Run `pod install` from your `ios` directory. 79 | - Close Xcode, and then open (double-click) your project's .xcworkspace file to launch Xcode. From this time onwards, you must use the `.xcworkspace` file to open the project. Or just use the `react-native run-ios` command as usual to run your app in the simulator. 80 | 81 | ##### 2) Auto Linking & Cocoapods Integration (React Native 0.60 and higher) 82 | Since React Native 0.60 and higher, [autolinking](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md) makes the installation process simpler. 83 | 84 | ```sh 85 | cd ios 86 | pod install 87 | ``` 88 | - Close Xcode, and then open (double-click) your project's .xcworkspace file to launch Xcode. From this time onwards, you must use the `.xcworkspace` file to open the project. Or just use the `react-native run-ios` command as usual to run your app in the simulator. 89 | 90 | #### Manual Config (iOS) 91 | 92 | - The following steps are optional, should be taken if you have not run `react-native link react-native-paystack` already. 93 | - In XCode's "Project navigator", right click on project name folder ➜ `Add Files to <...>`. Ensure `Copy items if needed` and `Create groups` are checked 94 | - Go to `node_modules` ➜ `react-native-paystack/ios` ➜ add `RNPaystack.xcodeproj`. 95 | - Click on your main project file (the one that represents the .xcodeproj for your project) select `Build Phases` and drag the static library, `libRNPaystack.a` from the `Products` folder inside `RNPaystack.xcodeproj` to `Link Binary With Libraries`. See the [react-native docs](https://facebook.github.io/react-native/docs/linking-libraries-ios.html) for more details. 96 | 97 | #### Autolinking on Android (React Native 0.59 and lower) 98 | - Run `react-native link react-native-paystack` at the root directory of your project. 99 | 100 | #### Autolinking on Android (React Native 0.60 and higher) 101 | Since React Native 0.60 and higher, [autolinking](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md) makes the installation process simpler. Nothing more to do here (Gradle has you all set to go) - just head over to usage! 102 | 103 | #### Manual Config (Android) 104 | 105 | - The following steps are optional, should be taken if you have not run `react-native link react-native-paystack` already. 106 | - Add the following in your `android/settings.gradle` file: 107 | 108 | ```java 109 | include ':react-native-paystack' 110 | project(':react-native-paystack').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-paystack/android') 111 | ``` 112 | - Add the following in your `android/app/build.grade` file: 113 | 114 | ```java 115 | dependencies { 116 | ... 117 | compile project(':react-native-paystack') 118 | } 119 | ``` 120 | - Add the following in your `...MainApplication.java` file: 121 | 122 | ```java 123 | import com.arttitude360.reactnative.rnpaystack.RNPaystackPackage; 124 | 125 | @Override 126 | protected List getPackages() { 127 | return Arrays.asList( 128 | new MainReactPackage(), 129 | ... 130 | new RNPaystackPackage() //<-- Add line 131 | ); 132 | } 133 | ``` 134 | 135 | #### More Config (Only applicable to Android using react-native-paystack v3.1.4+ & RN less than 0.57.0) 136 | - Update Gradle plugin to v3.0.0+ for your app, follow the following steps if you are not sure how: 137 | * Edit your `~ android/build.gradle` to look similar to [build.gradle](https://github.com/tolu360/vestarapp/blob/master/android/build.gradle) 138 | * Edit your `~ android/gradle/wrapper/gradle-wrapper.properties` to look similar to [gradle-wrapper.properties](https://github.com/tolu360/vestarapp/blob/master/android/gradle/wrapper/gradle-wrapper.properties) 139 | - To avoid build issues, enable `Aapt2` for your project by adding `android.enableAapt2=true` to your `android/gradle.properties` file. 140 | - If you are using RN with a version lower than 0.57.0, it is important you replace your `node-modules/react-native/react.gradle` file with [this version @ commit da6a5e0](https://github.com/facebook/react-native/blob/da6a5e0439c168147271ef66ad5ebbeebd6fce3b/react.gradle) to avoid further build issues when assembling a release version of your app. 141 | 142 | ## 3. Usage 143 | 144 | ### Initialize Library 145 | Somewhere high up in your project and way before calling any other method exposed by this library, your `index` file or equivalent is a good spot, ensure you initialize the library with your `public key` as follos: 146 | 147 | ```js 148 | import RNPaystack from 'react-native-paystack'; 149 | 150 | RNPaystack.init({ publicKey: 'YOUR_PUBLIC_KEY_HERE' }); 151 | ``` 152 | 153 | ### Charging a Card with Access Code (iOS & Android) 154 | It's a cinch to charge a card token using the react-native-paystack module. This is the recommended or the most-preferred workflow favored by the folks at Paystack. Initiate a new transaction on your server side using the appropriate [Paystack endpoint](https://developers.paystack.co/reference#initialize-a-transaction) - obtain an `access_code` and complete the charge on your mobile application. Pls note, the SDK assumes you are responsible for building the card form/UI. 155 | 156 | ```javascript 157 | RNPaystack.chargeCardWithAccessCode(cardParams); 158 | ``` 159 | To be more elaborate, `cardParams` is a Javascript `Object` representing the card to be charged and `RNPaystack.chargeCardWithAccessCode()` returns a Javascript `Promise` like: 160 | 161 | ```js 162 | import RNPaystack from 'react-native-paystack'; 163 | 164 | chargeCard() { 165 | 166 | RNPaystack.chargeCardWithAccessCode({ 167 | cardNumber: '4123450131001381', 168 | expiryMonth: '10', 169 | expiryYear: '17', 170 | cvc: '883', 171 | accessCode: '2p3j42th639duy4' 172 | }) 173 | .then(response => { 174 | console.log(response); // do stuff with the token 175 | }) 176 | .catch(error => { 177 | console.log(error); // error is a javascript Error object 178 | console.log(error.message); 179 | console.log(error.code); 180 | }) 181 | 182 | } 183 | ``` 184 | 185 | #### Request Signature 186 | 187 | | Argument | Type | Description | 188 | | ------------- |:-------------:| :-----| 189 | | cardNumber | string | the card number as a String without any seperator e.g 5555555555554444 | 190 | | expiryMonth | string | the card expiry month as a double-digit ranging from 1-12 e.g 10 (October) | 191 | | expiryYear | string | the card expiry year as a double-digit e.g 15 | 192 | | cvc | string | the card 3/4 digit security code as a String e.g 123 | 193 | | accessCode | string | the access_code obtained for the charge | 194 | 195 | #### Response Object 196 | 197 | An object of the form is returned from a successful token request 198 | 199 | ```javascript 200 | { 201 | reference: "trx_1k2o600w" 202 | } 203 | ``` 204 | 205 | ### Charging a Card (iOS & Android) 206 | Using the react-native-paystack module, you can start and complete a transaction with the mobile Paystack Android and iOS SDKs. With this option, you pass both your charge and card properties to the SDK - with this worklow, you initiate and complete a transaction on your mobile app. Note that as with charging with an access_code, the SDK assumes you are responsible for building the card form/UI. 207 | 208 | ```javascript 209 | RNPaystack.chargeCard(chargeParams); 210 | ``` 211 | To be more elaborate, `chargeParams` is a Javascript `Object` representing the parameters of the charge to be initiated and `RNPaystack.chargeCard()` returns a Javascript `Promise` like: 212 | 213 | ```js 214 | import RNPaystack from 'react-native-paystack'; 215 | 216 | chargeCard() { 217 | 218 | RNPaystack.chargeCard({ 219 | cardNumber: '4123450131001381', 220 | expiryMonth: '10', 221 | expiryYear: '17', 222 | cvc: '883', 223 | email: 'chargeIOS@master.dev', 224 | amountInKobo: 150000, 225 | subAccount: 'ACCT_pz61jjjsslnx1d9', 226 | }) 227 | .then(response => { 228 | console.log(response); // card charged successfully, get reference here 229 | }) 230 | .catch(error => { 231 | console.log(error); // error is a javascript Error object 232 | console.log(error.message); 233 | console.log(error.code); 234 | }) 235 | 236 | } 237 | ``` 238 | 239 | #### Request Signature (chargeParams) 240 | 241 | | Argument | Type | Description | 242 | | ------------- |:-------------:| :-----| 243 | | cardNumber | string | the card number as a String without any seperator e.g 5555555555554444 | 244 | | expiryMonth | string | the card expiry month as a double-digit ranging from 1-12 e.g 10 (October) | 245 | | expiryYear | string | the card expiry year as a double-digit e.g 15 | 246 | | cvc | string | the card 3/4 digit security code as e.g 123 | 247 | | email | string | email of the user to be charged | 248 | | amountInKobo | integer | the transaction amount in kobo | 249 | | currency (optional) | string | sets the currency for the transaction e.g. USD | 250 | | plan (optional) | string | sets the plan ID if the transaction is to create a subscription e.g. PLN_n0p196bg73y4jcx | 251 | | subAccount (optional) | string | sets the subaccount ID for split-payment transactions e.g. ACCT_pz61jjjsslnx1d9 | 252 | | transactionCharge (optional) | integer | the amount to be charged on a split-payment, use only when `subAccount` is set | 253 | | bearer (optional) | string | sets which party bears paystack fees on a split-payment e.g. 'subaccount', use only when `subAccount` is set | 254 | | reference (optional) | string | sets the transaction reference which must be unique per transaction | 255 | 256 | #### Response Object 257 | 258 | An object of the form is returned from a successful charge 259 | 260 | ```javascript 261 | { 262 | reference: "trx_1k2o600w" 263 | } 264 | ``` 265 | 266 | ### Verifying a Charge 267 | Verify a charge by calling Paystack's [REST API](https://api.paystack.co/transaction/verify) with the `reference` obtained above. An `authorization_code` will be returned once the card has been charged successfully. Learn more about that [here](https://developers.paystack.co/docs/verify-transaction). 268 | 269 | **Parameter:** 270 | 271 | - reference - the transaction reference (required) 272 | 273 | **Example** 274 | 275 | ```bash 276 | $ curl https://api.paystack.co/transaction/verify/trx_1k2o600w \ 277 | -H "Authorization: Bearer SECRET_KEY" \ 278 | -H "Content-Type: application/json" \ 279 | -X GET 280 | ``` 281 | 282 | ## 4. CREDITS 283 | 284 | Perhaps needless to say, this module leverages the [Paystack Android SDK](https://github.com/PaystackHQ/paystack-android) and the [Paystack IOS SDK](https://github.com/PaystackHQ/paystack-ios) for all the heavy liftings. 285 | 286 | ## 5. CHANGELOG 287 | 288 | + 1.0.12: Initial version supporting Android. 289 | + 1.1.1: Android library upgrade and initial iOS support. 290 | + 2.0.0: A couple of breaking changes have been introduced, see [Old Docs](./Old Docs.md) for previous documentations. 291 | + 2.0.0: Upgraded to v2.0 of the Paystack Android SDK. 292 | + 2.0.0: Unified APIs across both platforms (iOS & Android). 293 | + 2.0.0: Methods now return Javascript Promises on both platforms. 294 | + 2.1.1: Upgraded to v2.1+ of both the Paystack iOS and Android SDKs. 295 | + 2.1.1: Added support for `chargeCard` on both platforms. 296 | + 2.1.1: Added support for `subscriptions` and `split-payments`. 297 | + 3.1.0: Retired support for `getToken` on both platforms. 298 | + 3.1.0: Added support for `chargeCardWithAccessCode` on both platforms. 299 | + 3.1.0: Upgraded to v3.*+ of both the Paystack iOS and Android SDKs. 300 | + 3.1.1: Fix for breaking change in RN v0.47+ 301 | * 3.1.4: Miscellaneous and dependencies update on Android. 302 | * 3.2.0: A Breaking Change - Initialize library in JS, rather than in native code. 303 | * 3.3.0: Move to a CocoaPods Flow for iOS. 304 | 305 | ## 6. License 306 | 307 | This should be [The MIT License (MIT)](http://www.opensource.org/licenses/mit-license.html). I would have to get back to you on that! 308 | 309 | --------------------------------------------------------------------------------