airvend 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/airvend.gemspec ADDED
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/airvend/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "airvend"
7
+ spec.version = Airvend::VERSION
8
+ spec.authors = ["Uchenna Mba"]
9
+ spec.email = ["hey@uche.io"]
10
+
11
+ spec.summary = "Gem that provides access to bill payment & subscription in Nigeria."
12
+ spec.description = "This gem makes it easy for businesses or individuals to implement vending of Airtime, Data, Electricity, Utilities & Television subscriptions to their application"
13
+ spec.homepage = "https://github.com/urchymanny/airvend-rails"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
16
+
17
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
18
+ #
19
+ # spec.metadata["homepage_uri"] = spec.homepage
20
+ # spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
21
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ # Uncomment to register a new dependency of your gem
33
+ spec.add_development_dependency 'dotenv-rails'
34
+ spec.add_development_dependency 'byebug'
35
+ spec.add_dependency 'httparty', '~> 0.18'
36
+ spec.add_dependency 'json', '~> 2.5'
37
+ spec.add_dependency 'faraday', '~> 1.4'
38
+ spec.add_dependency 'faraday-detailed_logger', '~> 2.3'
39
+ spec.add_dependency 'typhoeus', '~> 1.4'
40
+
41
+ # For more information and examples about making a new gem, checkout our
42
+ # guide at: https://bundler.io/guides/creating_gem.html
43
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "airvend"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ require "byebug"
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
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
data/lib/airvend.rb ADDED
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require_relative "airvend/version"
4
+ require "dotenv"
5
+ require 'json'
6
+ require 'digest'
7
+ require 'airvend/vend.rb'
8
+ require 'airvend/base_endpoints.rb'
9
+ require 'airvend/errors.rb'
10
+ require 'airvend/upper_case_string.rb'
11
+ require 'airvend/airvend_objects/airtime.rb'
12
+ require 'airvend/airvend_objects/internet.rb'
13
+ require 'airvend/airvend_objects/power.rb'
14
+ require 'airvend/airvend_objects/television.rb'
15
+
16
+ Dotenv.load(File.expand_path("../.env", __FILE__))
17
+
18
+ class Airvend
19
+ attr_accessor :username, :password, :api_key, :production, :url
20
+
21
+ def initialize(username=nil, password=nil, api_key=nil, production=false)
22
+
23
+ @username = username
24
+ @password = password
25
+ @api_key = api_key
26
+ @production = production
27
+ sandbox_url = BASE_ENDPOINTS::SANDBOX_URL
28
+ live_url = BASE_ENDPOINTS::LIVE_URL
29
+
30
+ if (ENV['RAILS_ENV'].nil?)
31
+ @production = production
32
+ warn "Warning: Make sure you set RAILS_ENV to production or development"
33
+ else
34
+ if ENV['RAILS_ENV'] == "production"
35
+ @production=true
36
+ else
37
+ @prodcution=false
38
+ end
39
+ end
40
+
41
+ # set rave url to sandbox or live if we are in production or development
42
+ if production == false
43
+ @url = sandbox_url
44
+ else
45
+ @url = live_url
46
+ end
47
+
48
+ # check if we set our public and secret keys to the environment variable
49
+ if (username.nil? && password.nil?)
50
+ @username = ENV['AIRVEND_USERNAME']
51
+ @password = ENV['AIRVEND_PASSWORD']
52
+ else
53
+ @username = username
54
+ @password = password
55
+ warn "Warning: To ensure your account credentials are safe, It is best to always set your password in the environment variable with AIRVEND_USERNAME & AIRVEND_PASSWORD"
56
+ end
57
+
58
+ # raise this error if no username is passed
59
+ unless !@username.nil?
60
+ raise AirvendBadUserError, "No Username supplied and couldn't find any in environment variables. Make sure to set public key as an environment variable AIRVEND_USERNAME"
61
+ end
62
+
63
+ # raise this error if no password is passed
64
+ unless !@password.nil?
65
+ raise AirvendBadPassError, "No password supplied and couldn't find any in environment variables. Make sure to set secret key as an environment variable AIRVEND_PASSWORD"
66
+ end
67
+
68
+
69
+ if (api_key.nil?)
70
+ @api_key = ENV['AIRVEND_API_KEY']
71
+ else
72
+ @api_key = api_key
73
+ warn "Warning: To ensure your account key is safe, It is best to always set your password in the environment variable with AIRVEND_API_KEY"
74
+ end
75
+
76
+ # raise this error if no username is passed
77
+ unless !@api_key.nil?
78
+ raise AirvendBadKeyError, "No Api Key supplied and couldn't find any in environment variables. Make sure to set public key as an environment variable AIRVEND_USERNAME"
79
+ end
80
+ end
81
+
82
+ # method to return the base url
83
+ def base_url
84
+ return url
85
+ end
86
+
87
+ def headers(hashkey)
88
+ return {
89
+ UpperCaseString.new('Content-Type') => 'application/json',
90
+ UpperCaseString.new('username') => self.username,
91
+ UpperCaseString.new('password') => self.password,
92
+ UpperCaseString.new('hash') => hashkey,
93
+ user_agent: "Airvend-0.1.0"
94
+ }
95
+ end
96
+
97
+ def hash_req(details)
98
+ api_hash = details.to_json+self.api_key
99
+ api_hash = Digest::SHA512.hexdigest api_hash
100
+ return api_hash
101
+ end
102
+
103
+
104
+
105
+ end
@@ -0,0 +1,28 @@
1
+ require "airvend/base/base.rb"
2
+ include Vend
3
+
4
+ class Vend::Airtime < Base
5
+
6
+ def buy(payload)
7
+ params_hash = { 'ref'=> payload[:ref], 'account'=> payload[:phone], 'networkid'=> mno_id(payload[:mno]), 'type'=> "1", 'amount'=> payload[:amount] }
8
+ details = {}
9
+ details.merge!({ 'details'=>params_hash })
10
+ api_hash = @airvendObj.hash_req(details)
11
+ response = vendAdapter(api_hash, details)
12
+ if response.status == 200
13
+ hash = rename_hash(JSON.parse(response.body, { symbolize_names: true }))
14
+ rename_hash(hash[:details])
15
+ hash
16
+ else
17
+ produce_error(response)
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+
24
+ # input = [ref: "usdibisdbsidbsd", mno = "MTN", amount]
25
+ # Dotenv.load(File.expand_path("../.env", __FILE__))
26
+ # a = Airvend.new
27
+ # airtime = Vend::Airtime.new(Airvend.new)
28
+ # payload = { ref: "YOUR-OWN-REF-HERE", account: "08138236694", mno: "mtn", amount: "200"}
@@ -0,0 +1,45 @@
1
+ require "airvend/base/base.rb"
2
+ include Vend
3
+
4
+ class Vend::Internet < Base
5
+
6
+ def buy(payload)
7
+ params_hash = { 'ref'=> payload[:ref], 'account'=> payload[:phone], 'networkid'=> mno_id(payload[:mno]), 'type'=> "2", 'amount'=> payload[:code] }
8
+ details = {}
9
+ details.merge!({ 'details'=>params_hash })
10
+ api_hash = @airvendObj.hash_req(details)
11
+ response = vendAdapter(api_hash, details)
12
+ if response.status == 200
13
+ hash = rename_hash(JSON.parse(response.body, { symbolize_names: true }))
14
+ rename_hash(hash[:details])
15
+ hash
16
+ else
17
+ produce_error(response)
18
+ end
19
+ end
20
+
21
+ def plans(provider)
22
+ data = serialize_plans(get_plans(provider_id(provider), "2")[:details][:message])
23
+ end
24
+
25
+ def serialize_plans(plans)
26
+ plans.sort_by{|e| e[:amount].to_i}.each do |bundle|
27
+ if bundle[:validity] == nil
28
+ bundle[:validity] = "Unlimited"
29
+ elsif bundle[:validity].include? 'Day'
30
+ bundle[:validity].insert(0, " for ")
31
+ bundle[:validity]['Day'] = ' Day'
32
+ elsif bundle[:validity].include? 'Hours'
33
+ bundle[:validity].insert(0, " for ")
34
+ bundle[:validity]['Hours'] = ' Hours'
35
+ elsif bundle[:validity].include? 'Month'
36
+ bundle[:validity].insert(0, " for ")
37
+ bundle[:validity]['Month'] = ' Month'
38
+ elsif bundle[:validity].include? 'Year'
39
+ bundle[:validity].insert(0, " for ")
40
+ bundle[:validity]['Year'] = ' Year'
41
+ end
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,26 @@
1
+ require "airvend/base/base.rb"
2
+ include Vend
3
+
4
+ class Vend::Power < Base
5
+
6
+ def buy(payload)
7
+ params_hash = { 'ref'=> payload[:ref], 'account'=> payload[:account], 'type'=> power_id(payload[:provider], payload[:account_type]), 'amount'=> payload[:amount], 'customernumber'=> payload[:customernumber] }
8
+ details = {}
9
+ details.merge!({ 'details'=>params_hash })
10
+ api_hash = @airvendObj.hash_req(details)
11
+ resp = vendAdapter(api_hash, details)
12
+ if resp.status == 200
13
+ hash = rename_hash(JSON.parse(response.body, { symbolize_names: true }))
14
+ rename_hash(hash[:details])
15
+ hash
16
+ else
17
+ produce_error(response)
18
+ end
19
+ end
20
+
21
+ def verify(payload)
22
+ product_type = power_id(payload[:provider], payload[:account_type])
23
+ verify_customer(product_type, payload[:account])[:details][:message]
24
+ end
25
+
26
+ end
@@ -0,0 +1,29 @@
1
+ require "airvend/base/base.rb"
2
+ include Vend
3
+
4
+ class Vend::Television < Base
5
+
6
+ def buy(payload)
7
+ params_hash = { 'ref'=> payload[:ref], 'account'=> payload[:account], 'type'=> tv_id(payload[:provider]), 'amount'=> payload[:amount], 'customernumber'=> payload[:customernumber], 'invoicePeriod'=>"1" }
8
+ details = {}
9
+ details.merge!({ 'details'=>params_hash })
10
+ api_hash = @airvendObj.hash_req(details)
11
+ resp = vendAdapter(api_hash, details)
12
+ if resp.status == 200
13
+ hash = rename_hash(JSON.parse(response.body, { symbolize_names: true }))
14
+ rename_hash(hash[:details])
15
+ hash
16
+ else
17
+ produce_error(response)
18
+ end
19
+ end
20
+
21
+ def verify(payload)
22
+ verify_customer(tv_id(payload[:provider]), payload[:account])[:details][:message]
23
+ end
24
+
25
+ def plans(provider)
26
+ plans = get_plans("", tv_id(provider))[:details][:message]
27
+ new_plans = plans.each { |p| p.delete(:descrition); p[:description] = p.delete :name; p[:amount] = p[:amount].to_s}
28
+ end
29
+ end
@@ -0,0 +1,180 @@
1
+ require 'faraday'
2
+ require 'faraday/detailed_logger'
3
+ require 'typhoeus'
4
+ class Base
5
+ def initialize(airvendObj)
6
+ @airvendObj = airvendObj
7
+ end
8
+
9
+ def connect(api_hash)
10
+ conn = Faraday.new(:url => @airvendObj.base_url, :headers => @airvendObj.headers(api_hash)) do |faraday|
11
+ faraday.request :url_encoded
12
+ faraday.response :detailed_logger
13
+ faraday.adapter :typhoeus
14
+ end
15
+ return conn
16
+ end
17
+
18
+
19
+ def produce_error(response)
20
+ status = response.status
21
+ if status == 400
22
+ raise AirvendBadRequestError, "There's something wrong with this request - #{response.status}"
23
+ elsif status == 401
24
+ raise AirvendUnauthorizedError, "You're not authorized to access this resource - #{response.status}"
25
+ elsif status == 404
26
+ raise AirvendNotFoundError, "The resource could not be found - #{response.status}"
27
+ elsif status == 409
28
+ raise AirvendConflictError, "The Reference you provided already exists, please use unique reference IDs for every transaction - #{response.status}"
29
+ elsif status == 417
30
+ raise AirvendIncorrectPayload, "Please confirm that your payload data is correct and standard - #{response.status}"
31
+ elsif (400..451).member?(status)
32
+ raise AirvendUnknownClientError, "Please send an email to hey@uche.io explaining how you got this error - #{response.status}"
33
+ elsif (500..511).member?(status)
34
+ raise AirvendServerError.new(response.body)
35
+ end
36
+ end
37
+
38
+ def rename_hash(hash)
39
+ hash.keys.each do |a|
40
+ hash[underscorelize(a.to_s).to_sym] = hash.delete a
41
+ end
42
+ hash
43
+ end
44
+
45
+ def underscorelize(text)
46
+ text.gsub(/::/, '/').
47
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
48
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
49
+ tr("-", "_").
50
+ downcase
51
+ end
52
+
53
+ def get_plans(provider, product_type)
54
+ params_hash = { 'networkid'=> provider, 'type'=> product_type }
55
+ details = {}
56
+ details.merge!({ 'details'=>params_hash })
57
+ api_hash = @airvendObj.hash_req(details)
58
+ begin
59
+ response = productAdapter(api_hash, details)
60
+ rescue
61
+ return response
62
+ else
63
+ hash = rename_hash(JSON.parse(response.body, { symbolize_names: true }))
64
+ details = rename_hash(hash[:details])
65
+ details[:message].each do |d|
66
+ rename_hash(d)
67
+ end
68
+ hash
69
+ end
70
+ end
71
+
72
+ def verify_customer(product_type, account_id)
73
+ params_hash = { 'type'=> product_type, 'account'=> account_id }
74
+ details = {}
75
+ details.merge!({ 'details'=>params_hash })
76
+ api_hash = @airvendObj.hash_req(details)
77
+ begin
78
+ response = verifyAdapter(api_hash, details)
79
+ rescue
80
+ return response
81
+ else
82
+ if response.status == 200
83
+ hash = rename_hash(JSON.parse(response.body, { symbolize_names: true }))
84
+ rename_hash(hash[:details])
85
+ hash
86
+ else
87
+ produce_error(response)
88
+ end
89
+ end
90
+ end
91
+
92
+ def mno_id(mno)
93
+ case mno.downcase
94
+ when "mtn"
95
+ 2
96
+ when "airtel"
97
+ 1
98
+ when "glo"
99
+ 3
100
+ when "9mobile"
101
+ 4
102
+ else
103
+ raise AirvendInvalidProvider, "Invalid Mobile Network Operator, mno can only be 'mtn', 'glo', 'airtel' or '9mobile'"
104
+ end
105
+ end
106
+
107
+ def provider_id(mno)
108
+ case mno.downcase
109
+ when "mtn"
110
+ 2
111
+ when "airtel"
112
+ 1
113
+ when "glo"
114
+ 3
115
+ when "9mobile"
116
+ 4
117
+ else
118
+ raise AirvendInvalidProvider, "Invalid Mobile Network Operator, mno can only be 'mtn', 'glo', 'airtel' or '9mobile'"
119
+ end
120
+ end
121
+
122
+ def power_id(account, account_type)
123
+ x = account.upcase
124
+ y = account_type.upcase
125
+ if x == "IE"
126
+ if y == "POSTPAID"
127
+ return "10"
128
+ elsif y == "PREPAID"
129
+ return "11"
130
+ end
131
+ elsif x == "EKO"
132
+ if y == "POSTPAID"
133
+ return "13"
134
+ elsif y == "PREPAID"
135
+ return "14"
136
+ end
137
+ elsif x == "PHED"
138
+ if y == "POSTPAID"
139
+ return "15"
140
+ elsif y == "PREPAID"
141
+ return "16"
142
+ end
143
+ elsif x == "EEDC"
144
+ if y == "POSTPAID"
145
+ return "22"
146
+ elsif y == "PREPAID"
147
+ return "21"
148
+ end
149
+ elsif x == "KEDCO"
150
+ return "20"
151
+ elsif x == "AEDC"
152
+ if y == "POSTPAID"
153
+ return "25"
154
+ elsif y == "PREPAID"
155
+ return "24"
156
+ end
157
+ elsif x == "IBEDC"
158
+ if y == "POSTPAID"
159
+ return "12"
160
+ elsif y == "PREPAID"
161
+ return "11"
162
+ end
163
+ else
164
+ raise AirvendInvalidProvider, "Invalid Power Provider, see documentation: https://github.com/urchymanny/airvend-ruby#electricity-vending"
165
+ end
166
+ end
167
+
168
+ def tv_id(provider)
169
+ case provider.downcase
170
+ when "dstv"
171
+ 30
172
+ when "gotv"
173
+ 40
174
+ when "startimes"
175
+ 70
176
+ else
177
+ raise AirvendInvalidProvider, "Invalid Power Provider, see documentation: https://github.com/urchymanny/airvend-ruby#electricity-vending"
178
+ end
179
+ end
180
+ end