├── .gitignore ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── flutterwave.gemspec ├── lib ├── flutterwave.rb └── flutterwave │ ├── account.rb │ ├── ach.rb │ ├── bank.rb │ ├── bin.rb │ ├── bvn.rb │ ├── card.rb │ ├── client.rb │ ├── ip.rb │ ├── pay.rb │ ├── response.rb │ ├── utils │ ├── constants.rb │ ├── encryption_manager.rb │ ├── helpers.rb │ ├── missing_key_error.rb │ └── network_manager.rb │ └── version.rb └── test ├── account_test.rb ├── ach_test.rb ├── bank_test.rb ├── bin_test.rb ├── bvn_test.rb ├── card_test.rb ├── client_test.rb ├── flutterwave_test.rb ├── ip_test.rb ├── pay_test.rb ├── response_test.rb ├── test_helper.rb └── utils ├── encryption_manager_test.rb └── network_manager_test.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 2.2.3 5 | before_install: gem install bundler -v 1.12.5 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in flutterwave.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Tobi Oduah 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutterwave 2 | 3 | Ruby SDK for convenient access to the Flutterwave API from Ruby applications. Full API documentation avaialable at https://www.flutterwave.com/documentation 4 | 5 | ## Installation 6 | 7 | gem install flutterwave 8 | 9 | By default, the base API URL used is the test/staging URL - `http://staging1flutterwave.co:8080` 10 | 11 | To change to production, set the value of the `FLUTTERWAVE_BASE_URL` environment variable to `https://prod1flutterwave.co:8181` 12 | 13 | 14 | Here's a guide on cross-platform ways to set environment variables - https://www.twilio.com/blog/2015/02/managing-development-environment-variables-across-multiple-ruby-applications.html 15 | 16 | 17 | ## Usage 18 | 19 | The library needs to be configured with your merchant key and API key. These are accessible from the Settings panel at https://www.flutterwavedev.com/ 20 | 21 | To initialize the Ruby client: 22 | 23 | ```ruby 24 | require 'flutterwave' 25 | 26 | merchant_key = 'tk_aT0BPO14Rs' 27 | api_key = 'tk_ybh0luGWQbM04Is15lXh' 28 | client = Flutterwave::Client.new(merchant_key, api_key) 29 | ``` 30 | 31 | ## API operations 32 | All API operations are performed through the client instance. API responses are used to initialize a response class that allows direct access to JSON keys as methods. Arguments to operation methods adapt the same signature as sample requests to the API endpoint. 33 | 34 | For instance, an API call that returns: 35 | 36 | ```json 37 | { 38 | "data": { 39 | "responsecode": "02", 40 | "responsemessage": "Successful, pending OTP validation", 41 | "transactionreference": "ABC1234444" 42 | }, 43 | "status": "success" 44 | } 45 | ``` 46 | Gets initialized as a `Flutterwave::Response` object with method keys - `responsecode`, `responsemessage`, `transactionreference`, `successful?`, `failed?` 47 | 48 | ## Examples 49 | An example using the wrapper for accessing https://www.flutterwave.com/documentation/alternative-payments/ 50 | 51 | The resend-otp operation could be accessed through this sample: 52 | 53 | ```ruby 54 | client = Flutterwave::Client.new('sample_merchant_key', 'sample_api_key') 55 | response = client.account.resend({ 56 | validateoption: 'SMS', 57 | transactionreference: 'FLW02391188' 58 | }) 59 | 60 | print response.responsemessage if response.successful? 61 | ``` 62 | 63 | The method arguments to the resend method match the same hash-signature as the request sample at https://www.flutterwave.com/documentation/alternative-payments/#resend-otp. 64 | 65 | Response from the API is used to construct an instance of `Flutterwave::Response` which makes keys in the `data` hash accessible as methods. 66 | 67 | ## Banks Listing 68 | The only operation that does not follow the description above is when obtaining listing of banks, alongside their codes and names. To ease operation with the listing, a `Flutterwave::Bank` object is created for each bank object returned by the API. The `Flutterwave::BankAPI` also comes with some helper methods that help find a bank by code, or by a regex matching the bank's name. 69 | 70 | An example usage is described below: 71 | 72 | ```ruby 73 | client = Flutterwave::Client.new('sample_merchant_key', 'sample_api_key') 74 | all_banks = client.bank.list 75 | names_of_all_banks = all_banks.inject([]) { |list, bank| list << bank.name } 76 | 77 | p names_of_all_banks # => ["Fidelity Bank", "Heritage"..."Unity Bank"] 78 | 79 | # sample for finding a bank by name 80 | access_bank_code = client.bank.find_by_name('access').code # => '044' 81 | 82 | # sample for finding a bank by code 83 | bank_name = client.bank.find_by_code('058').name # => GTBank Plc 84 | ``` 85 | 86 | ## Mappings (API to Method) 87 | Each method has the corresponding API link as a comment above the method signature. To access details about the arguments to the method, please see the tests for that class, or visit the API link. 88 | 89 | ## Development 90 | 91 | Run all tests: 92 | 93 | bundle exec rake 94 | 95 | Run a single test suite: 96 | 97 | bundle exec rake TEST=test/pay_test.rb 98 | 99 | ## Contributing 100 | 101 | 1. Fork it by visiting - https://github.com/0duaht/flutterwave-ruby/fork 102 | 103 | 2. Create your feature branch 104 | 105 | $ git checkout -b new_feature 106 | 107 | 3. Contribute to code 108 | 109 | 4. Commit changes made 110 | 111 | $ git commit -a -m 'descriptive_message_about_change' 112 | 113 | 5. Push to branch created 114 | 115 | $ git push origin new_feature 116 | 117 | 6. Then, create a new Pull Request -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test" 6 | t.libs << "lib" 7 | t.test_files = FileList['test/**/*_test.rb'] 8 | end 9 | 10 | task :default => :test 11 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "flutterwave" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /flutterwave.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'flutterwave/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'flutterwave' 8 | spec.version = Flutterwave::VERSION 9 | spec.authors = ['Tobi Oduah'] 10 | spec.email = ['tobi.oduah@andela.com'] 11 | 12 | spec.summary = 'Ruby client for interacting with Flutterwave APIs' 13 | spec.homepage = 'https://github.com/0duaht/flutterwave-ruby' 14 | spec.license = 'MIT' 15 | 16 | if spec.respond_to?(:metadata) 17 | spec.metadata['allowed_push_host'] = 'https://rubygems.org' 18 | else 19 | raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.' 20 | end 21 | 22 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 23 | spec.bindir = 'exe' 24 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 25 | spec.require_paths = ['lib'] 26 | 27 | spec.add_development_dependency 'bundler', '~> 1.12' 28 | spec.add_development_dependency 'rake', '~> 10.0' 29 | spec.add_development_dependency 'minitest', '~> 5.0' 30 | spec.add_development_dependency 'faker' 31 | spec.add_development_dependency 'webmock' 32 | end 33 | -------------------------------------------------------------------------------- /lib/flutterwave.rb: -------------------------------------------------------------------------------- 1 | require 'flutterwave/version' 2 | require 'flutterwave/client' 3 | -------------------------------------------------------------------------------- /lib/flutterwave/account.rb: -------------------------------------------------------------------------------- 1 | require 'flutterwave/response' 2 | 3 | module Flutterwave 4 | class Account 5 | include Flutterwave::Helpers 6 | attr_accessor :client, :options 7 | 8 | def initialize(client) 9 | @client = client 10 | end 11 | 12 | # https://www.flutterwave.com/documentation/alternative-payments-recurrent/#initiate 13 | def initiate_recurrent(options = {}) 14 | @options = options 15 | 16 | request_params = { 17 | accountNumber: encrypt(:accountNumber), 18 | merchantid: client.merchant_key 19 | } 20 | 21 | response = post( 22 | Flutterwave::Utils::Constants::ACCOUNT[:initiate_recurrent_url], 23 | request_params 24 | ) 25 | 26 | Flutterwave::Response.new(response) 27 | end 28 | 29 | # https://www.flutterwave.com/documentation/alternative-payments-recurrent/#validate 30 | def validate_recurrent(options = {}) 31 | @options = options 32 | 33 | request_params = { 34 | accountNumber: encrypt(:accountNumber), 35 | otp: encrypt(:otp), 36 | reference: encrypt(:reference), 37 | billingamount: encrypt(:billingamount), 38 | debitnarration: encrypt(:debitnarration), 39 | merchantid: client.merchant_key 40 | } 41 | 42 | response = post( 43 | Flutterwave::Utils::Constants::ACCOUNT[:validate_recurrent_url], 44 | request_params 45 | ) 46 | 47 | Flutterwave::Response.new(response) 48 | end 49 | 50 | # https://www.flutterwave.com/documentation/alternative-payments-recurrent/#initiate 51 | def charge_recurrent(options = {}) 52 | @options = options 53 | 54 | request_params = { 55 | accountToken: encrypt(:accountToken), 56 | billingamount: encrypt(:billingamount), 57 | debitnarration: encrypt(:debitnarration), 58 | merchantid: client.merchant_key 59 | } 60 | 61 | response = post( 62 | Flutterwave::Utils::Constants::ACCOUNT[:charge_recurrent_url], 63 | request_params 64 | ) 65 | 66 | Flutterwave::Response.new(response) 67 | end 68 | 69 | # https://www.flutterwave.com/documentation/alternative-payments/#charge-ii 70 | def charge(options = {}) 71 | @options = options 72 | options[:country] ||= 'NG' 73 | 74 | request_params = { 75 | validateoption: encrypt(:validateoption), 76 | accountnumber: encrypt(:accountnumber), 77 | bankcode: encrypt(:bankcode), 78 | amount: encrypt(:amount), 79 | currency: encrypt(:currency), 80 | firstname: encrypt(:firstname), 81 | lastname: encrypt(:lastname), 82 | email: encrypt(:email), 83 | narration: encrypt(:narration), 84 | transactionreference: encrypt(:transactionreference), 85 | merchantid: client.merchant_key 86 | } 87 | 88 | request_params[:passcode] = encrypt(:passcode) if options[:passcode] 89 | 90 | response = post( 91 | Flutterwave::Utils::Constants::ACCOUNT[:charge_url], 92 | request_params 93 | ) 94 | 95 | Flutterwave::Response.new(response) 96 | end 97 | 98 | # https://www.flutterwave.com/documentation/alternative-payments/#resend-otp 99 | def resend(options = {}) 100 | @options = options 101 | 102 | request_params = { 103 | validateoption: encrypt(:validateoption), 104 | transactionreference: encrypt(:transactionreference), 105 | merchantid: client.merchant_key 106 | } 107 | 108 | response = post( 109 | Flutterwave::Utils::Constants::ACCOUNT[:resend_url], 110 | request_params 111 | ) 112 | 113 | Flutterwave::Response.new(response) 114 | end 115 | 116 | # https://www.flutterwave.com/documentation/alternative-payments/#validate-ii 117 | def validate(options = {}) 118 | @options = options 119 | 120 | request_params = { 121 | otp: encrypt(:otp), 122 | transactionreference: encrypt(:transactionreference), 123 | merchantid: client.merchant_key 124 | } 125 | 126 | response = post( 127 | Flutterwave::Utils::Constants::ACCOUNT[:validate_url], 128 | request_params 129 | ) 130 | 131 | Flutterwave::Response.new(response) 132 | end 133 | 134 | # https://www.flutterwave.com/documentation/alternative-payments/#validate-ii-alt 135 | def alt_validate(options = {}) 136 | @options = options 137 | 138 | request_params = { 139 | otp: encrypt(:otp), 140 | phonenumber: encrypt(:phonenumber), 141 | merchantid: client.merchant_key 142 | } 143 | 144 | response = post( 145 | Flutterwave::Utils::Constants::ACCOUNT[:alt_validate_url], 146 | request_params 147 | ) 148 | 149 | Flutterwave::Response.new(response) 150 | end 151 | 152 | def encrypt(key) 153 | plain_text = options[key].to_s 154 | raise Flutterwave::Utils::MissingKeyError.new( 155 | "#{key.capitalize} key required!" 156 | ) if plain_text.empty? 157 | 158 | encrypt_data(plain_text, client.api_key) 159 | end 160 | end 161 | end 162 | -------------------------------------------------------------------------------- /lib/flutterwave/ach.rb: -------------------------------------------------------------------------------- 1 | module Flutterwave 2 | class ACH 3 | include Flutterwave::Helpers 4 | attr_accessor :client, :options 5 | 6 | def initialize(client) 7 | @client = client 8 | end 9 | 10 | # https://www.flutterwave.com/documentation/ach-payments/#get-institutions 11 | def list 12 | response = post( 13 | Flutterwave::Utils::Constants::ACH[:list_url], 14 | merchantid: client.merchant_key 15 | ) 16 | 17 | Flutterwave::Response.new(response) 18 | end 19 | 20 | # https://www.flutterwave.com/documentation/ach-payments/#get-institution-by-id 21 | def find_by_id(options = {}) 22 | @options = options 23 | 24 | response = post( 25 | Flutterwave::Utils::Constants::ACH[:id_url], 26 | institutionid: encrypt(:id), 27 | merchantid: client.merchant_key 28 | ) 29 | 30 | Flutterwave::Response.new(response) 31 | end 32 | 33 | # https://www.flutterwave.com/documentation/ach-payments/#add-user 34 | def add_user(options = {}) 35 | @options = options 36 | 37 | request_params = { 38 | username: encrypt(:username), 39 | password: encrypt(:password), 40 | email: encrypt(:email), 41 | institution: encrypt(:institution), 42 | merchantid: client.merchant_key 43 | } 44 | 45 | request_params[:pin] = encrypt(:pin) if options[:pin] 46 | 47 | response = post( 48 | Flutterwave::Utils::Constants::ACH[:add_user_url], 49 | request_params 50 | ) 51 | 52 | Flutterwave::Response.new(response) 53 | end 54 | 55 | # https://www.flutterwave.com/documentation/ach-payments/#charge 56 | def charge(options = {}) 57 | @options = options 58 | 59 | response = post( 60 | Flutterwave::Utils::Constants::ACH[:charge_url], 61 | publictoken: encrypt(:publictoken), 62 | accountid: encrypt(:accountid), 63 | custid: encrypt(:custid), 64 | narration: encrypt(:narration), 65 | trxreference: encrypt(:trxreference), 66 | amount: encrypt(:amount), 67 | currency: encrypt(:currency), 68 | merchantid: client.merchant_key 69 | ) 70 | 71 | Flutterwave::Response.new(response) 72 | end 73 | 74 | def encrypt(key) 75 | plain_text = options[key].to_s 76 | raise Flutterwave::Utils::MissingKeyError.new( 77 | "#{key.capitalize} key required!" 78 | ) if plain_text.empty? 79 | 80 | encrypt_data(plain_text, client.api_key) 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/flutterwave/bank.rb: -------------------------------------------------------------------------------- 1 | module Flutterwave 2 | class BankAPI 3 | attr_accessor :client 4 | 5 | def initialize(client) 6 | @client = client 7 | end 8 | 9 | # https://www.flutterwave.com/documentation/banks-enquiry/ 10 | def list 11 | response = post( 12 | Flutterwave::Utils::Constants::BANK[:list_url] 13 | ) 14 | 15 | all_banks = response['data'] 16 | all_banks.keys.inject([]) do |list, code| 17 | list << Bank.new(code, all_banks[code]) 18 | end 19 | end 20 | 21 | def find_by_code(code) 22 | list.detect do |bank| 23 | bank.code == code 24 | end 25 | end 26 | 27 | def find_by_name(name_match) 28 | list.detect do |bank| 29 | !(bank.name.downcase =~ /#{name_match.downcase}/).nil? 30 | end 31 | end 32 | 33 | def post(url, data = {}) 34 | Flutterwave::Utils::NetworkManager.post(url, data) 35 | end 36 | end 37 | 38 | class Bank 39 | attr_accessor :code, :name 40 | 41 | def initialize(code, name) 42 | @code = code 43 | @name = name 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/flutterwave/bin.rb: -------------------------------------------------------------------------------- 1 | module Flutterwave 2 | class BIN 3 | include Flutterwave::Helpers 4 | attr_accessor :client, :options 5 | 6 | def initialize(client) 7 | @client = client 8 | end 9 | 10 | # https://www.flutterwave.com/documentation/compliance/ - Verify Card BIN API 11 | def check(options = {}) 12 | @options = options 13 | 14 | request_params = { 15 | card6: encrypt(:card6) 16 | } 17 | 18 | response = post( 19 | Flutterwave::Utils::Constants::BIN[:check_url], 20 | request_params 21 | ) 22 | 23 | Flutterwave::Response.new(response) 24 | end 25 | 26 | def encrypt(key) 27 | plain_text = options[key].to_s 28 | raise Flutterwave::Utils::MissingKeyError.new( 29 | "#{key.capitalize} key required!" 30 | ) if plain_text.empty? 31 | 32 | encrypt_data(plain_text, client.api_key) 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/flutterwave/bvn.rb: -------------------------------------------------------------------------------- 1 | module Flutterwave 2 | class BVN 3 | include Flutterwave::Helpers 4 | attr_accessor :client, :options 5 | 6 | def initialize(client) 7 | @client = client 8 | end 9 | 10 | # https://www.flutterwave.com/documentation/compliance/ - Verify (request a user enter their BVN for verification) 11 | def verify(options = {}) 12 | @options = options 13 | 14 | request_params = { 15 | otpoption: encrypt(:otpoption), 16 | bvn: encrypt(:bvn), 17 | merchantid: client.merchant_key 18 | } 19 | 20 | response = post( 21 | Flutterwave::Utils::Constants::BVN[:verify_url], 22 | request_params 23 | ) 24 | 25 | Flutterwave::Response.new(response) 26 | end 27 | 28 | # https://www.flutterwave.com/documentation/compliance/ - Resend OTP 29 | def resend(options = {}) 30 | @options = options 31 | 32 | request_params = { 33 | validateoption: encrypt(:validateoption), 34 | transactionreference: encrypt(:transactionreference), 35 | merchantid: client.merchant_key 36 | } 37 | 38 | response = post( 39 | Flutterwave::Utils::Constants::BVN[:resend_url], 40 | request_params 41 | ) 42 | 43 | Flutterwave::Response.new(response) 44 | end 45 | 46 | # https://www.flutterwave.com/documentation/compliance/ - Validate (This is used to validate the OTP entered by the user) 47 | def validate(options = {}) 48 | @options = options 49 | 50 | request_params = { 51 | otp: encrypt(:otp), 52 | transactionreference: encrypt(:transactionreference), 53 | bvn: encrypt(:bvn), 54 | merchantid: client.merchant_key 55 | } 56 | 57 | response = post( 58 | Flutterwave::Utils::Constants::BVN[:validate_url], 59 | request_params 60 | ) 61 | 62 | Flutterwave::Response.new(response) 63 | end 64 | 65 | def encrypt(key) 66 | plain_text = options[key].to_s 67 | raise Flutterwave::Utils::MissingKeyError.new( 68 | "#{key.capitalize} key required!" 69 | ) if plain_text.empty? 70 | 71 | encrypt_data(plain_text, client.api_key) 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/flutterwave/card.rb: -------------------------------------------------------------------------------- 1 | require 'flutterwave/response' 2 | 3 | module Flutterwave 4 | class Card 5 | include Flutterwave::Helpers 6 | attr_accessor :client, :options 7 | 8 | def initialize(client) 9 | @client = client 10 | end 11 | 12 | # https://www.flutterwave.com/documentation/card-payments-with-preauth/#tokenize 13 | def tokenize(options = {}) 14 | @options = options 15 | options[:country] ||= 'NG' 16 | 17 | request_params = { 18 | validateoption: encrypt(:validateoption), 19 | authmodel: encrypt(:authmodel), 20 | cardno: encrypt(:cardno), 21 | cvv: encrypt(:cvv), 22 | country: encrypt(:country), 23 | expirymonth: encrypt(:expirymonth), 24 | expiryyear: encrypt(:expiryyear), 25 | merchantid: client.merchant_key 26 | } 27 | 28 | request_params[:bvn] = encrypt(:bvn) if options[:authmodel] == 'BVN' 29 | 30 | response = post( 31 | Flutterwave::Utils::Constants::CARD[:tokenize_url], 32 | request_params 33 | ) 34 | 35 | Flutterwave::Response.new(response) 36 | end 37 | 38 | # https://www.flutterwave.com/documentation/card-payments-with-preauth/#preauthorize 39 | def preauthorize(options = {}) 40 | @options = options 41 | options[:country] ||= 'NG' 42 | 43 | request_params = { 44 | amount: encrypt(:amount), 45 | currency: encrypt(:currency), 46 | chargetoken: encrypt(:chargetoken), 47 | country: encrypt(:country), 48 | merchantid: client.merchant_key 49 | } 50 | 51 | response = post( 52 | Flutterwave::Utils::Constants::CARD[:preauthorize_url], 53 | request_params 54 | ) 55 | 56 | Flutterwave::Response.new(response) 57 | end 58 | 59 | # https://www.flutterwave.com/documentation/card-payments-with-preauth/#capture 60 | def capture(options = {}) 61 | @options = options 62 | options[:country] ||= 'NG' 63 | 64 | request_params = { 65 | amount: encrypt(:amount), 66 | currency: encrypt(:currency), 67 | country: encrypt(:country), 68 | trxreference: encrypt(:trxreference), 69 | trxauthorizeid: encrypt(:trxauthorizeid), 70 | chargetoken: encrypt(:chargetoken), 71 | merchantid: client.merchant_key 72 | } 73 | 74 | response = post( 75 | Flutterwave::Utils::Constants::CARD[:capture_url], 76 | request_params 77 | ) 78 | 79 | Flutterwave::Response.new(response) 80 | end 81 | 82 | # https://www.flutterwave.com/documentation/card-payments-with-preauth/#refund 83 | def refund(options = {}) 84 | @options = options 85 | options[:country] ||= 'NG' 86 | 87 | request_params = { 88 | amount: encrypt(:amount), 89 | currency: encrypt(:currency), 90 | country: encrypt(:country), 91 | trxreference: encrypt(:trxreference), 92 | trxauthorizeid: encrypt(:trxauthorizeid), 93 | merchantid: client.merchant_key 94 | } 95 | 96 | response = post( 97 | Flutterwave::Utils::Constants::CARD[:refund_url], 98 | request_params 99 | ) 100 | 101 | Flutterwave::Response.new(response) 102 | end 103 | 104 | # https://www.flutterwave.com/documentation/card-payments-with-preauth/#void 105 | def void(options = {}) 106 | @options = options 107 | options[:country] ||= 'NG' 108 | 109 | request_params = { 110 | amount: encrypt(:amount), 111 | currency: encrypt(:currency), 112 | country: encrypt(:country), 113 | trxreference: encrypt(:trxreference), 114 | trxauthorizeid: encrypt(:trxauthorizeid), 115 | merchantid: client.merchant_key 116 | } 117 | 118 | response = post( 119 | Flutterwave::Utils::Constants::CARD[:void_url], 120 | request_params 121 | ) 122 | 123 | Flutterwave::Response.new(response) 124 | end 125 | 126 | # https://www.flutterwave.com/documentation/card-enquiry/ - Card Enquiry 127 | def enquiry(options = {}) 128 | @options = options 129 | 130 | request_params = { 131 | cardno: encrypt(:cardno), 132 | cvv: encrypt(:cvv), 133 | expirymonth: encrypt(:expirymonth), 134 | expiryyear: encrypt(:expiryyear), 135 | pin: encrypt(:pin), 136 | trxreference: encrypt(:trxreference), 137 | merchantid: client.merchant_key 138 | } 139 | 140 | response = post( 141 | Flutterwave::Utils::Constants::CARD[:enquiry_url], 142 | request_params 143 | ) 144 | 145 | Flutterwave::Response.new(response) 146 | end 147 | 148 | # https://www.flutterwave.com/documentation/card-enquiry/ - Validate 149 | def validate_enquiry(options = {}) 150 | @options = options 151 | 152 | request_params = { 153 | otp: encrypt(:otp), 154 | otptransactionidentifier: encrypt(:otptransactionidentifier), 155 | trxreference: encrypt(:trxreference), 156 | merchantid: client.merchant_key 157 | } 158 | 159 | response = post( 160 | Flutterwave::Utils::Constants::CARD[:validate_enquiry_url], 161 | request_params 162 | ) 163 | 164 | Flutterwave::Response.new(response) 165 | end 166 | 167 | # https://www.flutterwave.com/documentation/card-payments/#tokenize-and-charge 168 | def charge(options = {}) 169 | @options = options 170 | options[:country] ||= 'NG' 171 | 172 | request_params = { 173 | amount: encrypt(:amount), 174 | authmodel: encrypt(:authmodel), 175 | cardno: encrypt(:cardno), 176 | currency: encrypt(:currency), 177 | custid: encrypt(:custid), 178 | country: encrypt(:country), 179 | cvv: encrypt(:cvv), 180 | expirymonth: encrypt(:expirymonth), 181 | expiryyear: encrypt(:expiryyear), 182 | narration: encrypt(:narration), 183 | merchantid: client.merchant_key 184 | } 185 | 186 | request_params[:pin] = encrypt(:pin) if options[:authmodel] == 'PIN' 187 | 188 | request_params[:bvn] = encrypt(:bvn) if options[:authmodel] == 'BVN' 189 | 190 | request_params[:responseurl] = encrypt(:responseurl) if 191 | options[:authmodel] == 'VBVSECURECODE' 192 | 193 | request_params[:cardtype] = encrypt(:cardtype) if options[:cardtype] 194 | 195 | response = post( 196 | Flutterwave::Utils::Constants::CARD[:charge_url], 197 | request_params 198 | ) 199 | 200 | Flutterwave::Response.new(response) 201 | end 202 | 203 | # https://www.flutterwave.com/documentation/card-payments/#validate 204 | def validate_charge(options = {}) 205 | @options = options 206 | 207 | request_params = { 208 | otp: encrypt(:otp), 209 | otptransactionidentifier: encrypt(:otptransactionidentifier), 210 | merchantid: client.merchant_key 211 | } 212 | 213 | response = post( 214 | Flutterwave::Utils::Constants::CARD[:validate_charge_url], 215 | request_params 216 | ) 217 | 218 | Flutterwave::Response.new(response) 219 | end 220 | 221 | # https://www.flutterwave.com/documentation/card-payments/#recurrent-charge 222 | def recurrent_charge(options = {}) 223 | @options = options 224 | options[:country] ||= 'NG' 225 | 226 | request_params = { 227 | amount: encrypt(:amount), 228 | currency: encrypt(:currency), 229 | custid: encrypt(:custid), 230 | country: encrypt(:country), 231 | narration: encrypt(:narration), 232 | chargetoken: encrypt(:chargetoken), 233 | merchantid: client.merchant_key 234 | } 235 | 236 | request_params[:cardtype] = encrypt(:cardtype) if options[:cardtype] 237 | 238 | response = post( 239 | Flutterwave::Utils::Constants::CARD[:charge_url], 240 | request_params 241 | ) 242 | 243 | Flutterwave::Response.new(response) 244 | end 245 | 246 | # https://www.flutterwave.com/documentation/check-transaction-status/ 247 | def verify(options = {}) 248 | @options = options 249 | 250 | request_params = { 251 | trxreference: encrypt(:trxreference), 252 | merchantid: client.merchant_key 253 | } 254 | 255 | response = post( 256 | Flutterwave::Utils::Constants::CARD[:verify_url], 257 | request_params 258 | ) 259 | 260 | Flutterwave::Response.new(response) 261 | end 262 | 263 | def encrypt(key) 264 | plain_text = options[key].to_s 265 | raise Flutterwave::Utils::MissingKeyError.new( 266 | "#{key.capitalize} key required!" 267 | ) if plain_text.empty? 268 | 269 | encrypt_data(plain_text, client.api_key) 270 | end 271 | end 272 | end 273 | -------------------------------------------------------------------------------- /lib/flutterwave/client.rb: -------------------------------------------------------------------------------- 1 | require 'flutterwave/utils/helpers' 2 | require 'flutterwave/utils/missing_key_error' 3 | require 'flutterwave/bvn' 4 | require 'flutterwave/bin' 5 | require 'flutterwave/ip' 6 | require 'flutterwave/bank' 7 | require 'flutterwave/card' 8 | require 'flutterwave/account' 9 | require 'flutterwave/ach' 10 | require 'flutterwave/pay' 11 | 12 | module Flutterwave 13 | class Client 14 | attr_accessor :merchant_key, :api_key, :bvn, :bin, :ip, :bank, 15 | :card, :account, :ach, :pay 16 | 17 | def initialize(merchant_key, api_key) 18 | @merchant_key = merchant_key 19 | @api_key = api_key 20 | 21 | @bvn = Flutterwave::BVN.new(self) 22 | @bin = Flutterwave::BIN.new(self) 23 | @ip = Flutterwave::IP.new(self) 24 | @bank = Flutterwave::BankAPI.new(self) 25 | @card = Flutterwave::Card.new(self) 26 | @account = Flutterwave::Account.new(self) 27 | @ach = Flutterwave::ACH.new(self) 28 | @pay = Flutterwave::Pay.new(self) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/flutterwave/ip.rb: -------------------------------------------------------------------------------- 1 | module Flutterwave 2 | class IP 3 | attr_accessor :client 4 | 5 | def initialize(client) 6 | @client = client 7 | end 8 | 9 | def check(options = {}) 10 | raise Flutterwave::Utils::MissingKeyError.new( 11 | 'IP key required!' 12 | ) unless options[:ip] 13 | 14 | response = post( 15 | Flutterwave::Utils::Constants::IP[:check_url], 16 | ip: options[:ip] 17 | ) 18 | 19 | Flutterwave::Response.new(response) 20 | end 21 | 22 | def post(url, data) 23 | Flutterwave::Utils::NetworkManager.post(url, data) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/flutterwave/pay.rb: -------------------------------------------------------------------------------- 1 | require 'flutterwave/response' 2 | require 'time' 3 | 4 | module Flutterwave 5 | class Pay 6 | include Flutterwave::Helpers 7 | attr_accessor :client, :options 8 | 9 | def initialize(client) 10 | @client = client 11 | end 12 | 13 | # https://www.flutterwave.com/documentation/pay/ - Link Account 14 | def link(options = {}) 15 | @options = options 16 | options[:country] ||= 'NG' 17 | 18 | response = post( 19 | Flutterwave::Utils::Constants::PAY[:link_url], 20 | merchantid: client.merchant_key, 21 | accountnumber: encrypt(:accountnumber), 22 | country: encrypt(:country) 23 | ) 24 | 25 | Flutterwave::Response.new(response) 26 | end 27 | 28 | # https://www.flutterwave.com/documentation/pay/ - Validate Step 1 29 | def validate(options = {}) 30 | @options = options 31 | options[:country] ||= 'NG' 32 | 33 | response = post( 34 | Flutterwave::Utils::Constants::PAY[:validate_url], 35 | merchantid: client.merchant_key, 36 | otp: encrypt(:otp), 37 | relatedreference: encrypt(:relatedreference), 38 | otptype: encrypt(:otptype), 39 | country: encrypt(:country) 40 | ) 41 | 42 | Flutterwave::Response.new(response) 43 | end 44 | 45 | # https://www.flutterwave.com/documentation/pay/ - Send 46 | def send(options = {}) 47 | @options = options 48 | options[:country] ||= 'NG' 49 | 50 | response = post( 51 | Flutterwave::Utils::Constants::PAY[:send_url], 52 | merchantid: client.merchant_key, 53 | accounttoken: encrypt(:accounttoken), 54 | destbankcode: encrypt(:destbankcode), 55 | currency: encrypt(:currency), 56 | country: encrypt(:country), 57 | transferamount: encrypt(:transferamount), 58 | uniquereference: encrypt(:uniquereference), 59 | narration: encrypt(:narration), 60 | recipientname: encrypt(:recipientname), 61 | sendername: encrypt(:sendername), 62 | recipientaccount: encrypt(:recipientaccount) 63 | ) 64 | 65 | Flutterwave::Response.new(response) 66 | end 67 | 68 | # https://www.flutterwave.com/documentation/pay/ - Get Linked Accounts 69 | def linked_accounts(options = {}) 70 | @options = options 71 | options[:country] ||= 'NG' 72 | 73 | response = post( 74 | Flutterwave::Utils::Constants::PAY[:linked_accounts_url], 75 | merchantid: client.merchant_key, 76 | country: encrypt(:country) 77 | ) 78 | 79 | Flutterwave::Response.new(response) 80 | end 81 | 82 | # https://www.flutterwave.com/documentation/pay/ - Unlink Account 83 | def unlink(options = {}) 84 | @options = options 85 | options[:country] ||= 'NG' 86 | 87 | response = post( 88 | Flutterwave::Utils::Constants::PAY[:unlink_url], 89 | merchantid: client.merchant_key, 90 | accountnumber: encrypt(:accountnumber), 91 | country: encrypt(:country) 92 | ) 93 | 94 | Flutterwave::Response.new(response) 95 | end 96 | 97 | # https://www.flutterwave.com/documentation/pay/ - Get Transaction Status 98 | def status(options = {}) 99 | @options = options 100 | options[:country] ||= 'NG' 101 | 102 | response = post( 103 | Flutterwave::Utils::Constants::PAY[:status_url], 104 | merchantid: client.merchant_key, 105 | uniquereference: encrypt(:uniquereference), 106 | country: encrypt(:country) 107 | ) 108 | 109 | Flutterwave::Response.new(response) 110 | end 111 | 112 | def encrypt(key) 113 | plain_text = options[key].to_s 114 | raise Flutterwave::Utils::MissingKeyError.new( 115 | "#{key.capitalize} key required!" 116 | ) if plain_text.empty? 117 | 118 | encrypt_data(plain_text, client.api_key) 119 | end 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /lib/flutterwave/response.rb: -------------------------------------------------------------------------------- 1 | module Flutterwave 2 | class Response 3 | attr_accessor :response 4 | 5 | def initialize(response) 6 | @response = response ? OpenStruct.new(response['data']) : OpenStruct.new({}) 7 | end 8 | 9 | def successful? 10 | (try('responseCode') || try('responsecode')) == '00' 11 | end 12 | 13 | def failed? 14 | !successful? 15 | end 16 | 17 | def method_missing(method_name, *args) 18 | return response.send(method_name, *args) if response.respond_to?(method_name) 19 | super 20 | end 21 | 22 | def try(method_name) 23 | instance_eval method_name 24 | rescue NameError 25 | nil 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/flutterwave/utils/constants.rb: -------------------------------------------------------------------------------- 1 | module Flutterwave 2 | module Utils 3 | module Constants 4 | BASE_URL = (ENV['FLUTTERWAVE_BASE_URL'] || 5 | 'http://staging1flutterwave.co:8080').freeze 6 | KEY = 'tk_0f86a4ef436f76faab1d3'.freeze 7 | 8 | BANK = { 9 | list_url: '/pwc/rest/fw/banks' 10 | }.freeze 11 | 12 | ACH = { 13 | list_url: '/pwc/rest/card/mvva/institutions', 14 | id_url: '/pwc/rest/card/mvva/institutions/id', 15 | add_user_url: '/pwc/rest/card/mvva/adduser', 16 | charge_url: '/pwc/rest/card/mvva/chargeach' 17 | }.freeze 18 | 19 | PAY = { 20 | link_url: '/pwc/rest/pay/linkaccount', 21 | validate_url: '/pwc/rest/pay/linkaccount/validate', 22 | linked_accounts_url: '/pwc/rest/pay/getlinkedaccounts', 23 | send_url: '/pwc/rest/pay/send', 24 | unlink_url: '/pwc/rest/pay/unlinkaccount', 25 | status_url: '/pwc/rest/pay/status' 26 | }.freeze 27 | 28 | BVN = { 29 | verify_url: '/pwc/rest/bvn/verify/', 30 | resend_url: '/pwc/rest/bvn/resendotp/', 31 | validate_url: '/pwc/rest/bvn/validate/' 32 | }.freeze 33 | 34 | BIN = { 35 | check_url: '/pwc/rest/fw/check/' 36 | }.freeze 37 | 38 | IP = { 39 | check_url: '/pwc/rest/fw/ipcheck' 40 | }.freeze 41 | 42 | CARD = { 43 | tokenize_url: '/pwc/rest/card/mvva/tokenize', 44 | preauthorize_url: '/pwc/rest/card/mvva/preauthorize', 45 | capture_url: '/pwc/rest/card/mvva/capture', 46 | refund_url: '/pwc/rest/card/mvva/refund', 47 | void_url: '/pwc/rest/card/mvva/void', 48 | enquiry_url: '/pwc/rest/card/mvva/cardenquiry', 49 | validate_enquiry_url: '/pwc/rest/card/mvva/cardenquiry/validate', 50 | charge_url: '/pwc/rest/card/mvva/pay', 51 | verify_url: '/pwc/rest/card/mvva/status' 52 | }.freeze 53 | 54 | ACCOUNT = { 55 | initiate_recurrent_url: '/pwc/rest/recurrent/account', 56 | charge_url: '/pwc/rest/account/pay', 57 | charge_recurrent_url: '/pwc/rest/recurrent/account/charge', 58 | validate_url: '/pwc/rest/account/pay/validate', 59 | validate_recurrent_url: '/pwc/rest/recurrent/account/validate', 60 | alt_validate_url: '/pwc/rest/accessbank/ussd/validate', 61 | resend_url: '/pwc/rest/account/pay/resendotp' 62 | }.freeze 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/flutterwave/utils/encryption_manager.rb: -------------------------------------------------------------------------------- 1 | require 'openssl' 2 | require 'base64' 3 | 4 | module Flutterwave 5 | module Utils 6 | module EncryptionManager 7 | KEY = Flutterwave::Utils::Constants::KEY 8 | 9 | def self.encrypt(text, key = KEY) 10 | key = digest(key) 11 | cipher = OpenSSL::Cipher::Cipher.new('des-ede3') 12 | cipher.encrypt 13 | cipher.key = key 14 | cipher_text = cipher.update(text.to_s) 15 | cipher_text << cipher.final 16 | 17 | Base64.encode64(cipher_text).gsub(/\n/, '') 18 | end 19 | 20 | def self.decrypt(text, key = KEY) 21 | key = digest(key) 22 | cipher = OpenSSL::Cipher::Cipher.new('des-ede3') 23 | cipher.decrypt 24 | cipher.key = key 25 | plain_text = cipher.update(Base64.decode64(text.to_s)) 26 | 27 | plain_text << cipher.final 28 | end 29 | 30 | def self.digest(key) 31 | digest = Digest::MD5.digest(key) 32 | 33 | digest + digest[0, 8] 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/flutterwave/utils/helpers.rb: -------------------------------------------------------------------------------- 1 | require 'flutterwave/utils/network_manager' 2 | require 'flutterwave/utils/encryption_manager' 3 | 4 | module Flutterwave 5 | module Helpers 6 | def post(url, data) 7 | Flutterwave::Utils::NetworkManager.post(url, data) 8 | end 9 | 10 | def encrypt_data(plain_text, api_key) 11 | Flutterwave::Utils::EncryptionManager.encrypt(plain_text, api_key) 12 | end 13 | end 14 | end -------------------------------------------------------------------------------- /lib/flutterwave/utils/missing_key_error.rb: -------------------------------------------------------------------------------- 1 | module Flutterwave 2 | module Utils 3 | class MissingKeyError < StandardError 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/flutterwave/utils/network_manager.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'uri' 3 | require 'json' 4 | require 'flutterwave/utils/constants' 5 | 6 | module Flutterwave 7 | module Utils 8 | module NetworkManager 9 | BASE_URL = Flutterwave::Utils::Constants::BASE_URL 10 | 11 | def self.post(url, body) 12 | uri = URI.parse("#{BASE_URL}#{url}") 13 | request = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json') 14 | request.body = body.to_json 15 | response = Net::HTTP.start(uri.hostname, uri.port) do |http| 16 | http.request(request) 17 | end 18 | 19 | JSON.parse(response.body) 20 | rescue SocketError, TypeError, EOFError, JSON::ParserError 21 | return nil 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/flutterwave/version.rb: -------------------------------------------------------------------------------- 1 | module Flutterwave 2 | VERSION = '0.1.3'.freeze 3 | end 4 | -------------------------------------------------------------------------------- /test/account_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AccountTest < Minitest::Test 4 | include FlutterWaveTestHelper 5 | 6 | attr_reader :client, :url, :response_data 7 | 8 | def setup 9 | merchant_key = "tk_#{Faker::Crypto.md5[0, 10]}" 10 | api_key = "tk_#{Faker::Crypto.md5[0, 20]}" 11 | @client = Flutterwave::Client.new(merchant_key, api_key) 12 | end 13 | 14 | def sample_initiate_recurrent_response 15 | { 16 | 'data' => { 17 | 'responsecode' => '00', 18 | 'responsemessage' => Faker::Lorem.sentence, 19 | 'transactionreference' => "FLW#{Faker::Number.number(8)}" 20 | }, 21 | 'status' => 'success' 22 | } 23 | end 24 | 25 | def sample_initiate_recurrent_body 26 | { 27 | accountNumber: Faker::Number.number(10) 28 | } 29 | end 30 | 31 | def sample_validate_response 32 | { 33 | 'data' => { 34 | 'responsecode' => '00', 35 | 'responsemessage' => Faker::Lorem.sentence, 36 | 'transactionreference' => Faker::Crypto.md5[0, 5] 37 | }, 38 | 'status' => 'success' 39 | } 40 | end 41 | 42 | def sample_validate_body 43 | { 44 | otp: Faker::Number.number(6), 45 | transactionreference: Faker::Crypto.md5[0, 5] 46 | } 47 | end 48 | 49 | def sample_validate_recurrent_response 50 | { 51 | 'data' => { 52 | 'accountToken' => Faker::Crypto.md5[0, 22], 53 | 'responseCode' => '00', 54 | 'responseDescription' => Faker::Lorem.sentence, 55 | 'transactionreference' => Faker::Crypto.md5[0, 5] 56 | }, 57 | 'status' => 'success' 58 | } 59 | end 60 | 61 | def sample_validate_recurrent_body 62 | { 63 | accountNumber: Faker::Number.number(11), 64 | otp: Faker::Number.number(6), 65 | reference: Faker::Crypto.md5[0, 5], 66 | billingamount: Faker::Number.number(3), 67 | debitnarration: Faker::Lorem.sentence 68 | } 69 | end 70 | 71 | def sample_charge_recurrent_response 72 | { 73 | 'data' => { 74 | 'responseCode' => '00', 75 | 'responseDescription' => Faker::Lorem.sentence, 76 | 'transactionreference' => Faker::Crypto.md5[0, 5] 77 | }, 78 | 'status' => 'success' 79 | } 80 | end 81 | 82 | def sample_charge_recurrent_body 83 | { 84 | accountToken: Faker::Crypto.md5[0, 22], 85 | billingamount: Faker::Number.number(3), 86 | debitnarration: Faker::Lorem.sentence 87 | } 88 | end 89 | 90 | def sample_alt_validate_response 91 | { 92 | 'data' => { 93 | 'valid' => true, 94 | 'responsecode' => '00', 95 | 'responsemessage' => Faker::Lorem.sentence, 96 | 'transactionreference' => Faker::Crypto.md5[0, 5] 97 | }, 98 | 'status' => 'success' 99 | } 100 | end 101 | 102 | def sample_alt_validate_body 103 | { 104 | otp: Faker::Number.number(6), 105 | phonenumber: Faker::Number.number(11) 106 | } 107 | end 108 | 109 | def sample_resend_response 110 | { 111 | 'data' => { 112 | 'responsecode' => '00', 113 | 'responsemessage' => Faker::Lorem.sentence, 114 | 'transactionreference' => Faker::Crypto.md5[0, 5] 115 | }, 116 | 'status' => 'success' 117 | } 118 | end 119 | 120 | def sample_resend_body 121 | { 122 | validateoption: 'SMS', 123 | transactionreference: Faker::Crypto.md5[0, 5] 124 | } 125 | end 126 | 127 | def sample_charge_response 128 | { 129 | 'data' => { 130 | 'responsecode' => '02', 131 | 'responsemessage' => Faker::Lorem.sentence, 132 | 'transactionreference' => Faker::Crypto.md5[0, 5] 133 | }, 134 | 'status' => 'success' 135 | } 136 | end 137 | 138 | def sample_charge_body 139 | { 140 | validateoption: 'SMS', 141 | accountnumber: Faker::Number.number(10), 142 | bankcode: Faker::Number.number(3), 143 | amount: 1, 144 | currency: 'NGN', 145 | firstname: Faker::Name.first_name, 146 | lastname: Faker::Name.last_name, 147 | email: Faker::Internet.email, 148 | transactionreference: Faker::Crypto.md5[0, 5], 149 | narration: Faker::Lorem.sentence 150 | } 151 | end 152 | 153 | def test_charge 154 | @response_data = sample_charge_response 155 | @url = Flutterwave::Utils::Constants::ACCOUNT[:charge_url] 156 | 157 | stub_flutterwave 158 | 159 | response = client.account.charge(sample_charge_body) 160 | assert response.responsecode.eql? '02' 161 | end 162 | 163 | def test_resend 164 | @response_data = sample_resend_response 165 | @url = Flutterwave::Utils::Constants::ACCOUNT[:resend_url] 166 | 167 | stub_flutterwave 168 | 169 | response = client.account.resend(sample_resend_body) 170 | assert response.successful? 171 | end 172 | 173 | def test_validate 174 | @response_data = sample_validate_response 175 | @url = Flutterwave::Utils::Constants::ACCOUNT[:validate_url] 176 | 177 | stub_flutterwave 178 | 179 | response = client.account.validate(sample_validate_body) 180 | assert response.successful? 181 | end 182 | 183 | def test_alt_validate 184 | @response_data = sample_alt_validate_response 185 | @url = Flutterwave::Utils::Constants::ACCOUNT[:alt_validate_url] 186 | 187 | stub_flutterwave 188 | 189 | response = client.account.alt_validate(sample_alt_validate_body) 190 | assert response.successful? 191 | end 192 | 193 | def test_initiate_recurrent 194 | @response_data = sample_initiate_recurrent_response 195 | @url = Flutterwave::Utils::Constants::ACCOUNT[:initiate_recurrent_url] 196 | 197 | stub_flutterwave 198 | 199 | response = client.account.initiate_recurrent(sample_initiate_recurrent_body) 200 | assert response.successful? 201 | end 202 | 203 | def test_validate_recurrent 204 | @response_data = sample_validate_recurrent_response 205 | @url = Flutterwave::Utils::Constants::ACCOUNT[:validate_recurrent_url] 206 | 207 | stub_flutterwave 208 | 209 | response = client.account.validate_recurrent( 210 | sample_validate_recurrent_body 211 | ) 212 | assert response.successful? 213 | end 214 | 215 | def test_charge_recurrent 216 | @response_data = sample_charge_recurrent_response 217 | @url = Flutterwave::Utils::Constants::ACCOUNT[:charge_recurrent_url] 218 | 219 | stub_flutterwave 220 | 221 | response = client.account.charge_recurrent( 222 | sample_charge_recurrent_body 223 | ) 224 | assert response.successful? 225 | end 226 | end 227 | -------------------------------------------------------------------------------- /test/ach_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ACHTest < Minitest::Test 4 | include FlutterWaveTestHelper 5 | 6 | attr_reader :client, :sample_institution, :url, :response_data 7 | 8 | def setup 9 | merchant_key = "tk_#{Faker::Crypto.md5[0, 10]}" 10 | api_key = "tk_#{Faker::Crypto.md5[0, 20]}" 11 | @client = Flutterwave::Client.new(merchant_key, api_key) 12 | @sample_institution = { 13 | 'credentials' => { 14 | 'password' => 'Password', 15 | 'pin' => nil, 16 | 'username' => 'Online ID' 17 | }, 18 | 'name' => Faker::Company.name, 19 | 'hasmfa' => true, 20 | 'id' => Faker::Crypto.md5[0, 24], 21 | 'type' => 'bofa', 22 | 'mfatypes' => ['questions(3)'] 23 | } 24 | end 25 | 26 | def response_container(institution_list) 27 | { 28 | 'data' => { 29 | 'institutions' => institution_list, 30 | 'responsecode' => '00', 31 | 'responsemessage' => '00', 32 | 'transactionreference' => "FLW#{Faker::Number.number(8)}" 33 | }, 34 | 'status' => 'success' 35 | } 36 | end 37 | 38 | def sample_list_response 39 | institutions = [] 40 | 4.times { institutions << sample_institution } 41 | 42 | response_container(institutions) 43 | end 44 | 45 | def sample_find_by_id_response 46 | response_container([sample_institution]) 47 | end 48 | 49 | def sample_charge_body 50 | { 51 | publictoken: Faker::Crypto.md5[0, 19].to_s, 52 | accountid: Faker::Number.number(2), 53 | custid: Faker::Number.number(2), 54 | narration: Faker::Lorem.sentence, 55 | amount: 1, 56 | currency: 'NGN', 57 | trxreference: "FLW#{Faker::Number.number(8)}" 58 | } 59 | end 60 | 61 | def sample_charge_response 62 | { 63 | 'data' => { 64 | 'responsetoken' => nil, 65 | 'responsecode' => '00', 66 | 'responsemessage' => 'Successful', 67 | 'transactionreference' => "TST#{Faker::Number.number(9)}", 68 | 'otptransactionidentifier' => nil, 69 | 'responsehtml' => nil 70 | }, 71 | 'status' => 'success' 72 | } 73 | end 74 | 75 | def sample_add_user_body 76 | { 77 | username: Faker::Internet.user_name, 78 | password: Faker::Internet.password, 79 | pin: Faker::Number.number(4), 80 | email: Faker::Internet.email, 81 | institution: sample_institution['type'], 82 | country: Faker::Address.country_code 83 | } 84 | end 85 | 86 | def sample_add_user_response 87 | @sample_account = { 88 | 'balance' => { 89 | 'available' => Faker::Number.number(5), 90 | 'current' => Faker::Number.number(4) 91 | }, 92 | 'meta' => { 93 | 'limit' => nil, 94 | 'name' => Faker::Company.name, 95 | 'number' => Faker::Number.number(4) 96 | }, 97 | 'numbers' => nil, 98 | 'type' => 'depository', 99 | 'subtype' => 'savings', 100 | 'id' => nil, 101 | 'item' => nil, 102 | 'user' => nil, 103 | 'institutionType' => nil 104 | } 105 | 106 | { 107 | 'data' => { 108 | 'responsecode' => '00', 109 | 'responsemessage' => Faker::Lorem.sentence, 110 | 'transactionreference' => "FLWT#{Faker::Number.number(8)}", 111 | 'accounts' => [ 112 | @sample_account, @sample_account, @sample_account, @sample_account 113 | ] 114 | }, 115 | 'status' => 'success' 116 | } 117 | end 118 | 119 | def test_list 120 | @response_data = sample_list_response 121 | @url = Flutterwave::Utils::Constants::ACH[:list_url] 122 | 123 | stub_flutterwave 124 | 125 | response = client.ach.list 126 | 127 | assert response.successful? 128 | assert response.institutions.is_a? Array 129 | assert response.institutions.length == 4 130 | end 131 | 132 | def test_find_by_id 133 | @response_data = sample_find_by_id_response 134 | @url = Flutterwave::Utils::Constants::ACH[:id_url] 135 | 136 | stub_flutterwave 137 | response = client.ach.find_by_id(id: sample_institution['id']) 138 | 139 | assert_equal sample_institution['name'], 140 | response.institutions.first['name'] 141 | end 142 | 143 | def test_add_user 144 | @response_data = sample_add_user_response 145 | @url = Flutterwave::Utils::Constants::ACH[:add_user_url] 146 | 147 | stub_flutterwave 148 | response = client.ach.add_user(sample_add_user_body) 149 | 150 | assert response.successful? 151 | assert response.accounts.is_a? Array 152 | assert response.accounts.length == 4 153 | end 154 | 155 | def test_charge 156 | @response_data = sample_charge_response 157 | @url = Flutterwave::Utils::Constants::ACH[:charge_url] 158 | 159 | stub_flutterwave 160 | response = client.ach.charge(sample_charge_body) 161 | 162 | assert response.successful? 163 | end 164 | end 165 | -------------------------------------------------------------------------------- /test/bank_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class BankTest < Minitest::Test 4 | include FlutterWaveTestHelper 5 | 6 | attr_reader :client, :gtb_name, :response_data, :url 7 | 8 | def setup 9 | merchant_key = "tk_#{Faker::Crypto.md5[0, 10]}" 10 | api_key = "tk_#{Faker::Crypto.md5[0, 20]}" 11 | @client = Flutterwave::Client.new(merchant_key, api_key) 12 | @gtb_code = '058' 13 | @gtb_name = 'GTBank Plc' 14 | end 15 | 16 | def sample_data 17 | { 18 | 'data' => { 19 | @gtb_code => gtb_name, 20 | Faker::Number.number(3) => Faker::Company.name, 21 | Faker::Number.number(3) => Faker::Company.name, 22 | Faker::Number.number(3) => Faker::Company.name 23 | }, 24 | 'status' => 'success' 25 | } 26 | end 27 | 28 | def test_list 29 | stub_values 30 | response = client.bank.list 31 | 32 | assert response.is_a? Array 33 | assert response.length.eql? 4 34 | assert response.all? { |item| item.is_a? Flutterwave::Bank } 35 | end 36 | 37 | def test_find_by_code 38 | stub_values 39 | 40 | gtb_instance = Flutterwave::Bank.new(@gtb_code, gtb_name) 41 | response = client.bank.find_by_code(@gtb_code) 42 | 43 | assert_equal gtb_instance.code, response.code 44 | assert_equal gtb_instance.name, response.name 45 | end 46 | 47 | def test_find_by_name 48 | stub_values 49 | gtb_instance = Flutterwave::Bank.new(@gtb_code, gtb_name) 50 | response = client.bank.find_by_name(gtb_name) 51 | 52 | assert_equal gtb_instance.code, response.code 53 | assert_equal gtb_instance.name, response.name 54 | end 55 | 56 | def stub_values 57 | @response_data = sample_data 58 | @url = Flutterwave::Utils::Constants::BANK[:list_url] 59 | 60 | stub_flutterwave 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /test/bin_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class BINTest < Minitest::Test 4 | include FlutterWaveTestHelper 5 | 6 | attr_reader :client, :card6, :url, :response_data 7 | 8 | def setup 9 | merchant_key = "tk_#{Faker::Crypto.md5[0, 10]}" 10 | api_key = "tk_#{Faker::Crypto.md5[0, 20]}" 11 | @client = Flutterwave::Client.new(merchant_key, api_key) 12 | @card6 = Faker::Number.number(6) 13 | end 14 | 15 | def sample_check_body 16 | { 17 | card6: card6 18 | } 19 | end 20 | 21 | def sample_check_response 22 | { 23 | 'data' => { 24 | 'country' => Faker::Address.country, 25 | 'cardBin' => card6, 26 | 'cardName' => Faker::Lorem.sentence, 27 | 'nigeriancard' => Faker::Boolean.boolean, 28 | 'transactionReference' => Faker::Crypto.md5[0, 7].upcase, 29 | 'responseCode' => '00' 30 | }, 31 | 'status' => 'success' 32 | } 33 | end 34 | 35 | def test_check_fails_without_card6_argument 36 | assert_raises Flutterwave::Utils::MissingKeyError do 37 | client.bin.check({}) 38 | end 39 | end 40 | 41 | def test_check 42 | @response_data = sample_check_response 43 | @url = Flutterwave::Utils::Constants::BIN[:check_url] 44 | 45 | stub_flutterwave 46 | 47 | response = client.bin.check(sample_check_body) 48 | assert response.successful? 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/bvn_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class BVNTest < Minitest::Test 4 | include FlutterWaveTestHelper 5 | 6 | attr_reader :client, :bvn_number, :otpoption, :response_data, :url 7 | 8 | def setup 9 | @bvn_number = Faker::Number.number(10) 10 | @otpoption = 'SMS' 11 | merchant_key = "tk_#{Faker::Crypto.md5[0, 10]}" 12 | api_key = "tk_#{Faker::Crypto.md5[0, 20]}" 13 | @client = Flutterwave::Client.new(merchant_key, api_key) 14 | end 15 | 16 | def set_response 17 | @response_data = { 18 | 'data' => { 19 | 'responseCode' => '00', 20 | 'responseMessage' => Faker::Lorem.sentence, 21 | 'transactionReference' => Faker::Crypto.md5[0, 7].upcase 22 | }, 23 | 'status' => 'success' 24 | } 25 | end 26 | 27 | def sample_verify_body 28 | { 29 | otpoption: otpoption, 30 | bvn: bvn_number 31 | } 32 | end 33 | 34 | def sample_verify_response 35 | { 36 | 'data' => { 37 | 'responseCode' => '00', 38 | 'responseMessage' => Faker::Lorem.sentence, 39 | 'transactionReference' => Faker::Crypto.md5[0, 7].upcase 40 | }, 41 | 'status' => 'success' 42 | } 43 | end 44 | 45 | def sample_resend_or_validate_response 46 | { 47 | 'data' => { 48 | 'responseCode' => '00', 49 | 'responseMessage' => Faker::Lorem.sentence, 50 | }, 51 | 'status' => 'success' 52 | } 53 | end 54 | 55 | def sample_resend_body 56 | { 57 | validateoption: otpoption, 58 | transactionreference: Faker::Crypto.md5[0, 7].upcase 59 | } 60 | end 61 | 62 | def sample_validate_body 63 | { 64 | otp: otpoption, 65 | transactionreference: Faker::Crypto.md5[0, 7].upcase, 66 | bvn: bvn_number 67 | } 68 | end 69 | 70 | def test_verify 71 | @response_data = sample_verify_response 72 | @url = Flutterwave::Utils::Constants::BVN[:verify_url] 73 | 74 | stub_flutterwave 75 | 76 | response = client.bvn.verify(sample_verify_body) 77 | assert response.successful? 78 | end 79 | 80 | def test_resend 81 | @response_data = sample_resend_or_validate_response 82 | @url = Flutterwave::Utils::Constants::BVN[:resend_url] 83 | 84 | stub_flutterwave 85 | 86 | response = @client.bvn.resend(sample_resend_body) 87 | assert response.successful? 88 | end 89 | 90 | def test_validate 91 | @response_data = sample_resend_or_validate_response 92 | @url = Flutterwave::Utils::Constants::BVN[:validate_url] 93 | 94 | stub_flutterwave 95 | 96 | response = @client.bvn.validate(sample_validate_body) 97 | assert response.successful? 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /test/card_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CardTest < Minitest::Test 4 | include FlutterWaveTestHelper 5 | 6 | attr_reader :client, :response_data, :url 7 | 8 | def setup 9 | merchant_key = "tk_#{Faker::Crypto.md5[0, 10]}" 10 | api_key = "tk_#{Faker::Crypto.md5[0, 20]}" 11 | @client = Flutterwave::Client.new(merchant_key, api_key) 12 | end 13 | 14 | def sample_verify_body 15 | { 16 | trxreference: "FLW#{Faker::Number.number(8)}" 17 | } 18 | end 19 | 20 | def sample_verify_response 21 | { 22 | 'data' => { 23 | 'responsecode' => '00', 24 | 'batchno' => Time.now.strftime('%Y%m%d'), 25 | 'avsresponsecode' => nil, 26 | 'responsemessage' => 'Approved', 27 | 'transactionno' => Faker::Number.number(10), 28 | 'transactionIdentifier' => "FLWT#{Faker::Number.number(8)}", 29 | 'orderinfo' => 'O' << "FLW#{Faker::Number.number(8)}", 30 | 'responsetoken' => nil, 31 | 'avsresponsemessage' => nil, 32 | 'authorizeId' => Faker::Number.number(6), 33 | 'receiptno' => Faker::Number.number(12), 34 | 'otptransactionidentifier' => nil, 35 | 'merchtransactionreference' => 'Kudi_test%2F' << ''\ 36 | "FLW#{Faker::Number.number(8)}", 37 | 'transactionreference' => "FLW#{Faker::Number.number(8)}", 38 | 'responsehtml' => nil 39 | }, 40 | 'status' => 'success' 41 | } 42 | end 43 | 44 | def sample_charge_response 45 | { 46 | 'data' => { 47 | 'responsecode' => '00', 48 | 'avsresponsemessage' => 'Address Verification is Unsupported.', 49 | 'avsresponsecode' => 'Unsupported', 50 | 'responsemessage' => 'Approved', 51 | 'otptransactionidentifier' => nil, 52 | 'transactionreference' => "FLW#{Faker::Number.number(8)}", 53 | 'responsehtml' => nil, 54 | 'responsetoken' => Faker::Crypto.md5[0, 19].to_s 55 | }, 56 | 'status' => 'success' 57 | } 58 | end 59 | 60 | def sample_charge_body 61 | { 62 | authmodel: 'NOAUTH', 63 | cardno: Faker::Number.number(16), 64 | cvv: Faker::Number.number(3), 65 | expirymonth: Faker::Number.number(2), 66 | expiryyear: Faker::Number.number(2), 67 | amount: 1, 68 | currency: 'NGN', 69 | custid: 1, 70 | narration: 'Sample debit' 71 | } 72 | end 73 | 74 | def sample_recurrent_charge_body 75 | { 76 | chargetoken: Faker::Crypto.md5[0, 19].to_s, 77 | amount: 1, 78 | currency: 'NGN', 79 | custid: 1, 80 | narration: 'Sample debit' 81 | } 82 | end 83 | 84 | def sample_validate_enquiry_response 85 | { 86 | 'data' => { 87 | 'responsecode' => '00', 88 | 'balance' => '43.79', 89 | 'responsemessage' => 'Successful', 90 | 'cardType' => 'MasterCard', 91 | 'chargetoken' => nil, 92 | 'transactionref' => "TST/FLW#{Faker::Number.number(8)}", 93 | 'otpref' => nil 94 | }, 95 | 'status' => 'success' 96 | } 97 | end 98 | 99 | def sample_validate_enquiry_body 100 | { 101 | otp: Faker::Number.number(6), 102 | trxreference: "FLW#{Faker::Number.number(8)}", 103 | otptransactionidentifier: "FLW#{Faker::Number.number(8)}" 104 | } 105 | end 106 | 107 | def sample_enquiry_response 108 | { 109 | 'data' => { 110 | 'responsecode' => '02', 111 | 'balance' => nil, 112 | 'avsresponsecode' => nil, 113 | 'responsemessage' => 'Press the white button on your GTB token and type the transaction code generated.', 114 | 'cardType' => nil, 115 | 'chargetoken' => nil, 116 | 'transactionref' => "FLW#{Faker::Number.number(8)}", 117 | 'otpref' => "TST/FLW#{Faker::Number.number(8)}" 118 | }, 119 | 'status' => 'success' 120 | } 121 | end 122 | 123 | 124 | def sample_enquiry_body 125 | { 126 | cardno: Faker::Number.number(16), 127 | cvv: Faker::Number.number(3), 128 | expirymonth: Faker::Number.number(2), 129 | expiryyear: Faker::Number.number(2), 130 | pin: Faker::Number.number(4), 131 | trxreference: "FLW#{Faker::Number.number(8)}" 132 | } 133 | end 134 | 135 | def sample_refund_or_void_response 136 | { 137 | 'data' => { 138 | 'responsecode' => '00', 139 | 'avsresponsemessage' => nil, 140 | 'avsresponsecode' => nil, 141 | 'responsemessage' => 'Approved', 142 | 'otptransactionidentifier' => nil, 143 | 'transactionreference' => "FLW#{Faker::Number.number(8)}", 144 | 'responsehtml' => nil, 145 | 'responsetoken' => nil 146 | }, 147 | 'status' => 'success' 148 | } 149 | end 150 | 151 | def sample_refund_or_void_body 152 | { 153 | amount: '50', 154 | currency: 'NGN', 155 | trxauthorizeid: Faker::Number.number(5).to_s, 156 | trxreference: "FLW#{Faker::Number.number(8)}" 157 | } 158 | end 159 | 160 | def sample_capture_response 161 | { 162 | 'data' => { 163 | 'responsecode' => '00', 164 | 'avsresponsemessage' => nil, 165 | 'avsresponsecode' => nil, 166 | 'authorizeId' => '', 167 | 'responsemessage' => 'Approved', 168 | 'otptransactionidentifier' => nil, 169 | 'transactionreference' => "FLW#{Faker::Number.number(8)}", 170 | 'responsehtml' => nil, 171 | 'responsetoken' => nil 172 | }, 173 | 'status' => 'success' 174 | } 175 | end 176 | 177 | def sample_capture_body 178 | { 179 | amount: '50', 180 | currency: 'NGN', 181 | trxauthorizeid: Faker::Number.number(5).to_s, 182 | trxreference: "FLW#{Faker::Number.number(8)}", 183 | chargetoken: Faker::Crypto.md5[0, 19].to_s 184 | } 185 | end 186 | 187 | def sample_preauthorize_response 188 | { 189 | 'data' => { 190 | 'responsecode' => '00', 191 | 'avsresponsemessage' => nil, 192 | 'avsresponsecode' => nil, 193 | 'responsemessage' => Faker::Lorem.sentence, 194 | 'otptransactionidentifier' => nil, 195 | 'transactionreference' => "FLW#{Faker::Number.number(8)}", 196 | 'responsetoken' => nil, 197 | 'responsehtml' => nil 198 | }, 199 | 'status' => 'success' 200 | } 201 | end 202 | 203 | def sample_preauthorize_body 204 | { 205 | amount: '50', 206 | currency: 'NGN', 207 | chargetoken: Faker::Crypto.md5[0, 19].to_s 208 | } 209 | end 210 | 211 | def sample_tokenize_response 212 | { 213 | 'data' => { 214 | 'responsecode' => '00', 215 | 'avsresponsemessage' => nil, 216 | 'avsresponsecode' => nil, 217 | 'authorizeId' => Faker::Number.number(5).to_s, 218 | 'responsemessage' => 'Approved', 219 | 'otptransactionidentifier' => nil, 220 | 'transactionreference' => nil, 221 | 'responsetoken' => Faker::Crypto.md5[0, 19].to_s 222 | }, 223 | 'status' => 'success' 224 | } 225 | end 226 | 227 | def sample_tokenize_body 228 | { 229 | validateoption: 'SMS', 230 | authmodel: 'NOAUTH', 231 | cardno: Faker::Number.number(16).to_s, 232 | cvv: Faker::Number.number(3).to_s, 233 | expirymonth: Faker::Number.number(2).to_s, 234 | expiryyear: Faker::Number.number(2).to_s 235 | } 236 | end 237 | 238 | def test_tokenize 239 | @response_data = sample_tokenize_response 240 | @url = Flutterwave::Utils::Constants::CARD[:tokenize_url] 241 | 242 | stub_flutterwave 243 | 244 | response = client.card.tokenize(sample_tokenize_body) 245 | assert response.successful? 246 | end 247 | 248 | def test_preauthorize 249 | @response_data = sample_preauthorize_response 250 | @url = Flutterwave::Utils::Constants::CARD[:preauthorize_url] 251 | 252 | stub_flutterwave 253 | 254 | response = client.card.preauthorize(sample_preauthorize_body) 255 | assert response.successful? 256 | end 257 | 258 | def test_capture 259 | @response_data = sample_capture_response 260 | @url = Flutterwave::Utils::Constants::CARD[:capture_url] 261 | 262 | stub_flutterwave 263 | 264 | response = client.card.capture(sample_capture_body) 265 | assert response.successful? 266 | end 267 | 268 | def test_refund 269 | @response_data = sample_refund_or_void_response 270 | @url = Flutterwave::Utils::Constants::CARD[:refund_url] 271 | 272 | stub_flutterwave 273 | 274 | response = client.card.refund(sample_refund_or_void_body) 275 | assert response.successful? 276 | end 277 | 278 | def test_void 279 | @response_data = sample_refund_or_void_response 280 | @url = Flutterwave::Utils::Constants::CARD[:void_url] 281 | 282 | stub_flutterwave 283 | 284 | response = client.card.void(sample_refund_or_void_body) 285 | assert response.successful? 286 | end 287 | 288 | def test_enquiry 289 | @response_data = sample_enquiry_response 290 | @url = Flutterwave::Utils::Constants::CARD[:enquiry_url] 291 | 292 | stub_flutterwave 293 | 294 | response = client.card.enquiry(sample_enquiry_body) 295 | assert response.responsecode.eql? '02' 296 | end 297 | 298 | def test_validate_enquiry 299 | @response_data = sample_validate_enquiry_response 300 | @url = Flutterwave::Utils::Constants::CARD[:validate_enquiry_url] 301 | 302 | stub_flutterwave 303 | 304 | response = client.card.validate_enquiry(sample_validate_enquiry_body) 305 | assert response.successful? 306 | end 307 | 308 | def test_charge 309 | @response_data = sample_charge_response 310 | @url = Flutterwave::Utils::Constants::CARD[:charge_url] 311 | 312 | stub_flutterwave 313 | 314 | response = client.card.charge(sample_charge_body) 315 | assert response.successful? 316 | end 317 | 318 | def test_recurrent_charge 319 | @response_data = sample_charge_response 320 | @url = Flutterwave::Utils::Constants::CARD[:charge_url] 321 | 322 | stub_flutterwave 323 | 324 | response = client.card.recurrent_charge(sample_recurrent_charge_body) 325 | assert response.successful? 326 | end 327 | 328 | def test_verify 329 | @response_data = sample_verify_response 330 | @url = Flutterwave::Utils::Constants::CARD[:verify_url] 331 | 332 | stub_flutterwave 333 | 334 | response = client.card.verify(sample_verify_body) 335 | assert response.successful? 336 | end 337 | end 338 | -------------------------------------------------------------------------------- /test/client_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ClientTest < Minitest::Test 4 | attr_reader :client 5 | 6 | def setup 7 | merchant_key = "tk_#{Faker::Crypto.md5[0, 10]}" 8 | api_key = "tk_#{Faker::Crypto.md5[0, 20]}" 9 | @client = Flutterwave::Client.new(merchant_key, api_key) 10 | end 11 | 12 | def test_that_it_has_bvn_instance 13 | refute_nil client.instance_variable_get(:@bvn) 14 | end 15 | 16 | def test_that_it_has_bin_instance 17 | refute_nil client.instance_variable_get(:@bin) 18 | end 19 | 20 | def test_that_it_has_ip_instance 21 | refute_nil client.instance_variable_get(:@ip) 22 | end 23 | 24 | def test_that_it_has_bank_instance 25 | refute_nil client.instance_variable_get(:@bank) 26 | end 27 | 28 | def test_that_it_has_card_instance 29 | refute_nil client.instance_variable_get(:@card) 30 | end 31 | 32 | def test_that_it_has_account_instance 33 | refute_nil client.instance_variable_get(:@account) 34 | end 35 | 36 | def test_that_it_has_ach_instance 37 | refute_nil client.instance_variable_get(:@ach) 38 | end 39 | 40 | def test_that_it_has_pay_instance 41 | refute_nil client.instance_variable_get(:@pay) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/flutterwave_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class FlutterwaveTest < Minitest::Test 4 | def test_that_it_has_a_version_number 5 | refute_nil ::Flutterwave::VERSION 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/ip_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class IPTest < Minitest::Test 4 | include FlutterWaveTestHelper 5 | 6 | attr_reader :client, :ip, :response_data, :url 7 | 8 | def setup 9 | merchant_key = "tk_#{Faker::Crypto.md5[0, 10]}" 10 | api_key = "tk_#{Faker::Crypto.md5[0, 20]}" 11 | @client = Flutterwave::Client.new(merchant_key, api_key) 12 | @ip = Faker::Internet.ip_v4_address 13 | end 14 | 15 | def sample_check_body 16 | { 17 | ip: ip 18 | } 19 | end 20 | 21 | def sample_check_response 22 | { 23 | 'data' => { 24 | 'responsecode' => '00', 25 | 'ipaddress' => ip, 26 | 'alpha2code' => Faker::Address.country_code, 27 | 'alpha3code' => Faker::Address.country_code << 'X', 28 | 'responsemessage' => Faker::Lorem.sentence, 29 | 'country_name' => Faker::Address.country, 30 | 'transactionreference' => "FLW#{Faker::Number.number(8)}" 31 | }, 32 | 'status' => 'success' 33 | } 34 | end 35 | 36 | def test_check 37 | @response_data = sample_check_response 38 | @url = Flutterwave::Utils::Constants::IP[:check_url] 39 | 40 | stub_flutterwave 41 | 42 | response = client.ip.check(sample_check_body) 43 | assert response.successful? 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/pay_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PayTest < Minitest::Test 4 | include FlutterWaveTestHelper 5 | 6 | attr_reader :client, :url, :response_data 7 | 8 | def setup 9 | merchant_key = "tk_#{Faker::Crypto.md5[0, 10]}" 10 | api_key = "tk_#{Faker::Crypto.md5[0, 20]}" 11 | @client = Flutterwave::Client.new(merchant_key, api_key) 12 | end 13 | 14 | def sample_linked_accounts_response 15 | { 16 | 'data' => { 17 | 'responsecode' => '00', 18 | 'linkedaccounts' => [ 19 | { 20 | 'accountnumber' => Faker::Number.number(10), 21 | 'added' => Time.now.to_s, 22 | 'status' => 'VALIDATED' 23 | }, 24 | { 25 | 'accountnumber' => Faker::Number.number(10), 26 | 'added' => Time.now.to_s, 27 | 'status' => 'VALIDATED' 28 | } 29 | ] 30 | }, 31 | 'status' => 'success' 32 | } 33 | end 34 | 35 | def sample_link_body 36 | { 37 | accountnumber: Faker::Number.number(10), 38 | country: Faker::Address.country_code 39 | } 40 | end 41 | 42 | def sample_link_response 43 | { 44 | 'data' => { 45 | 'responsecode' => '02', 46 | 'responsemessage' => Faker::Lorem.sentence, 47 | 'uniquereference' => "FLWT#{Faker::Number.number(8)}" 48 | }, 49 | 'status' => 'success' 50 | } 51 | end 52 | 53 | def sample_unlink_body 54 | { 55 | accountnumber: Faker::Number.number(10), 56 | country: Faker::Address.country_code 57 | } 58 | end 59 | 60 | def sample_unlink_response 61 | { 62 | 'data' => { 63 | 'responsecode' => '00', 64 | 'responsemessage' => Faker::Lorem.sentence, 65 | 'uniquereference' => Faker::Number.number(10) 66 | }, 67 | 'status' => 'success' 68 | } 69 | end 70 | 71 | def sample_status_body 72 | { 73 | uniquereference: Faker::Number.number(10), 74 | country: Faker::Address.country_code 75 | } 76 | end 77 | 78 | def sample_status_response 79 | { 80 | 'data' => { 81 | 'responsecode' => '00', 82 | 'responsemessage' => Faker::Lorem.sentence, 83 | 'uniquereference' => Faker::Number.number(10) 84 | }, 85 | 'status' => 'success' 86 | } 87 | end 88 | 89 | def sample_validate_body 90 | { 91 | otp: Faker::Number.number(6), 92 | relatedreference: "FLWT#{Faker::Number.number(8)}", 93 | otptype: 'PHONE_OTP' 94 | } 95 | end 96 | 97 | def sample_validate_response 98 | { 99 | 'data' => { 100 | 'responsecode' => '00', 101 | 'responsemessage' => Faker::Lorem.sentence, 102 | 'uniquereference' => "FLWT#{Faker::Number.number(8)}", 103 | }, 104 | 'status' => 'success' 105 | } 106 | end 107 | 108 | def sample_send_body 109 | { 110 | accounttoken: Faker::Number.number(6), 111 | destbankcode: Faker::Number.number(3), 112 | currency: 'NGN', 113 | transferamount: 1, 114 | narration: Faker::Lorem.sentence, 115 | recipientname: Faker::Name.name, 116 | sendername: Faker::Name.name, 117 | recipientaccount: Faker::Number.number(10), 118 | uniquereference: "FLWT#{Faker::Number.number(8)}", 119 | otptype: 'PHONE_OTP' 120 | } 121 | end 122 | 123 | def sample_send_response 124 | { 125 | 'data' => { 126 | 'responsecode' => '00', 127 | 'responsemessage' => Faker::Lorem.sentence, 128 | 'uniquereference' => Faker::Number.number(10) 129 | }, 130 | 'status' => 'success' 131 | } 132 | end 133 | 134 | def test_link 135 | @response_data = sample_link_response 136 | @url = Flutterwave::Utils::Constants::PAY[:link_url] 137 | 138 | stub_flutterwave 139 | 140 | response = client.pay.link(sample_link_body) 141 | assert response.responsecode.eql? '02' 142 | end 143 | 144 | def test_validate 145 | @response_data = sample_validate_response 146 | @url = Flutterwave::Utils::Constants::PAY[:validate_url] 147 | 148 | stub_flutterwave 149 | 150 | response = client.pay.validate(sample_validate_body) 151 | assert response.successful? 152 | end 153 | 154 | def test_send 155 | @response_data = sample_send_response 156 | @url = Flutterwave::Utils::Constants::PAY[:send_url] 157 | 158 | stub_flutterwave 159 | 160 | response = client.pay.send(sample_send_body) 161 | assert response.successful? 162 | end 163 | 164 | def test_linked_accounts 165 | @response_data = sample_linked_accounts_response 166 | @url = Flutterwave::Utils::Constants::PAY[:linked_accounts_url] 167 | 168 | stub_flutterwave 169 | 170 | response = client.pay.linked_accounts 171 | 172 | assert response.successful? 173 | assert response.linkedaccounts.is_a? Array 174 | assert response.linkedaccounts.length == 2 175 | end 176 | 177 | def test_unlink 178 | @response_data = sample_unlink_response 179 | @url = Flutterwave::Utils::Constants::PAY[:unlink_url] 180 | 181 | stub_flutterwave 182 | 183 | response = client.pay.unlink(sample_unlink_body) 184 | assert response.successful? 185 | end 186 | 187 | def test_status 188 | @response_data = sample_status_response 189 | @url = Flutterwave::Utils::Constants::PAY[:status_url] 190 | 191 | stub_flutterwave 192 | 193 | response = client.pay.status(sample_status_body) 194 | assert response.successful? 195 | end 196 | end 197 | -------------------------------------------------------------------------------- /test/response_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ResponseTest < Minitest::Test 4 | attr_reader :success_response, :failure_response 5 | 6 | def setup 7 | @success_response = { 8 | 'data' => { 9 | 'responseCode' => '00', 10 | 'responseMessage' => Faker::Lorem.sentence 11 | }, 12 | 'status' => 'success' 13 | } 14 | 15 | @failure_response = { 16 | 'data' => { 17 | 'responseCode' => 'B02', 18 | 'responseMessage' => Faker::Lorem.sentence 19 | }, 20 | 'status' => 'success' 21 | } 22 | end 23 | 24 | def test_for_successful_response 25 | assert Flutterwave::Response.new(success_response).successful? 26 | end 27 | 28 | def test_for_failed_response 29 | refute Flutterwave::Response.new(failure_response).successful? 30 | end 31 | 32 | def test_for_method_delegation 33 | assert_equal success_response['data']['responseMessage'], 34 | Flutterwave::Response.new( 35 | success_response).responseMessage 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | require 'flutterwave' 3 | require 'minitest/autorun' 4 | require 'webmock/minitest' 5 | require 'faker' 6 | 7 | module FlutterWaveTestHelper 8 | def stub_flutterwave 9 | stub_request( 10 | :post, "#{Flutterwave::Utils::Constants::BASE_URL}"\ 11 | "#{url}" 12 | ).to_return(status: 200, body: response_data.to_json) 13 | end 14 | end 15 | 16 | -------------------------------------------------------------------------------- /test/utils/encryption_manager_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'base64' 3 | 4 | class EncryptionManagerTest < Minitest::Test 5 | def setup 6 | @plain_text = 'flutterwave' 7 | @cipher_text = 'WmbH0cemRWB8zy/ZQS7gbA==' 8 | @key = 'tk_0f86a4ef436f76faab1d3' 9 | end 10 | 11 | def test_that_it_encrypts_appropriately 12 | assert_equal Flutterwave::Utils::EncryptionManager.encrypt( 13 | @plain_text, @key), @cipher_text 14 | end 15 | 16 | def test_that_it_decrypts_appropriately 17 | assert_equal Flutterwave::Utils::EncryptionManager.decrypt( 18 | @cipher_text, @key), @plain_text 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/utils/network_manager_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class NetworkManagerTest < Minitest::Test 4 | def setup 5 | @url = "/#{Faker::Lorem.word}" 6 | @body = { 7 | Faker::Lorem.word => Faker::Lorem.word, 8 | Faker::Lorem.word => Faker::Lorem.word 9 | } 10 | @response = { 11 | Faker::Lorem.word => Faker::Lorem.word, 12 | Faker::Lorem.word => Faker::Lorem.word 13 | } 14 | end 15 | 16 | def stub_flutterwave 17 | stub_request(:post, /#{Flutterwave::Utils::Constants::BASE_URL}/) 18 | end 19 | 20 | def test_that_it_returns_nil_when_network_connection_is_disabled 21 | stub_flutterwave.to_raise(SocketError) 22 | assert_nil Flutterwave::Utils::NetworkManager.post(@url, @body) 23 | end 24 | 25 | def test_that_it_returns_nil_when_response_does_not_match_json_specs 26 | stub_flutterwave.to_return(body: '') 27 | assert_nil Flutterwave::Utils::NetworkManager.post(@url, @body) 28 | end 29 | 30 | def test_that_it_makes_network_calls_successfully 31 | response_data = { 'data' => @response, 'status' => 'success' } 32 | stub_flutterwave.to_return(status: 200, body: response_data.to_json) 33 | 34 | assert_equal response_data, 35 | Flutterwave::Utils::NetworkManager.post(@url, @body) 36 | end 37 | end 38 | --------------------------------------------------------------------------------