fawry 0.1.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -2,7 +2,24 @@
2
2
 
3
3
  require 'bundler/gem_tasks'
4
4
  require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
5
6
 
6
7
  RSpec::Core::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new(:rubocop)
7
9
 
8
- task default: :spec
10
+ task default: :rspec_rubocop
11
+
12
+ task :rspec_rubocop do
13
+ Rake::Task['rubocop'].invoke
14
+ Rake::Task['spec'].invoke
15
+ end
16
+
17
+ desc 'Start a console session with Fawry gem loaded'
18
+ task :console do
19
+ require 'irb'
20
+ require 'irb/completion'
21
+ require 'fawry'
22
+
23
+ ARGV.clear
24
+ IRB.start
25
+ end
data/fawry.gemspec CHANGED
@@ -7,13 +7,14 @@ require 'fawry/version'
7
7
  Gem::Specification.new do |spec|
8
8
  spec.name = 'fawry'
9
9
  spec.version = Fawry::VERSION
10
- spec.authors = ['Amr Bakry']
11
- spec.email = ['amrrbakry17@gmail.com']
10
+ spec.authors = ['Amr El Bakry']
11
+ spec.email = ['amrr@hey.com']
12
12
 
13
13
  spec.summary = "A library to interface with Fawry's payment gateway API (charge, refund, payment status,
14
14
  service callback)."
15
15
  spec.homepage = 'https://github.com/amrrbakry/fawry'
16
16
  spec.license = 'MIT'
17
+ spec.required_ruby_version = '>= 2.6'
17
18
 
18
19
  spec.metadata['homepage_uri'] = 'https://github.com/amrrbakry/fawry'
19
20
  spec.metadata['source_code_uri'] = 'https://github.com/amrrbakry/fawry'
@@ -31,8 +32,10 @@ Gem::Specification.new do |spec|
31
32
  spec.add_dependency 'faraday', '~> 0.17.0'
32
33
 
33
34
  spec.add_development_dependency 'bundler', '~> 2.0'
34
- spec.add_development_dependency 'byebug', '~> 11.0', '>= 11.0.1'
35
- spec.add_development_dependency 'rake', '~> 10.0'
35
+ spec.add_development_dependency 'byebug', '~> 11.0'
36
+ spec.add_development_dependency 'rake', '~> 13.0'
36
37
  spec.add_development_dependency 'rspec', '~> 3.0'
37
- spec.add_development_dependency 'webmock', '~> 3.7', '>= 3.7.6'
38
+ spec.add_development_dependency 'rspec_junit_formatter'
39
+ spec.add_development_dependency 'rubocop', '~> 1.11'
40
+ spec.add_development_dependency 'webmock', '~> 3.12'
38
41
  end
data/lib/fawry.rb CHANGED
@@ -3,12 +3,22 @@
3
3
  require 'fawry/version'
4
4
  require 'fawry/connection'
5
5
  require 'fawry/errors'
6
+ require 'fawry/utils'
6
7
  require 'fawry/fawry_request'
7
8
  require 'fawry/fawry_response'
9
+ require 'fawry/fawry_callback'
8
10
  require 'fawry/requests/charge_request'
9
11
  require 'fawry/requests/refund_request'
12
+ require 'fawry/requests/payment_status_request'
13
+ require 'fawry/requests/create_card_token_request'
14
+ require 'fawry/requests/list_tokens_request'
15
+ require 'fawry/requests/delete_token_request'
10
16
  require 'fawry/contracts/charge_request_contract'
11
17
  require 'fawry/contracts/refund_request_contract'
18
+ require 'fawry/contracts/payment_status_request_contract'
19
+ require 'fawry/contracts/create_card_token_request_contract'
20
+ require 'fawry/contracts/list_tokens_request_contract'
21
+ require 'fawry/contracts/delete_token_request_contract'
12
22
 
13
23
  module Fawry
14
24
  class << self
@@ -17,19 +27,19 @@ module Fawry
17
27
  # the request signature
18
28
  #
19
29
  # @param params [Hash] list of params to send to fawry
20
- # required(:merchant_code).value(:string)
21
30
  # required(:merchant_ref_num).value(:string)
22
31
  # required(:customer_profile_id).value(:string)
23
32
  # required(:amount).value(:decimal)
24
33
  # required(:description).value(:string)
25
34
  # required(:customer_mobile).value(:string)
26
- # required(:fawry_secure_key).value(:string)
27
35
  # required(:charge_items).array(:hash) do
28
36
  # required(:item_id).value(:string)
29
37
  # required(:description).value(:string)
30
38
  # required(:price).value(:decimal)
31
39
  # required(:quantity).value(:integer)
32
40
  # end
41
+ # optional(:merchant_code).value(:string)
42
+ # optional(:fawry_secure_key).value(:string)
33
43
  # optional(:currency_code).value(:string)
34
44
  # optional(:card_token).value(:string)
35
45
  # optional(:customer_email).value(:string)
@@ -43,7 +53,7 @@ module Fawry
43
53
  # false by default
44
54
  # Example: `Fawry.charge(params, sandbox: true)`
45
55
  #
46
- # @raise [Fawry::InvalidFawryRequest] raised when one
56
+ # @raise [Fawry::InvalidFawryRequestError] raised when one
47
57
  # or more of the params are invalid. the message
48
58
  # specifices which params and why are they invalid
49
59
  #
@@ -51,7 +61,7 @@ module Fawry
51
61
  # has Fawry API response keys as instance methods
52
62
  # plus some convenience methods e.g. success?
53
63
  def charge(params, opts = {})
54
- FawryRequest.new('charge', params, opts).fire
64
+ FawryRequest.new('charge', params, opts).fire_charge_request
55
65
  end
56
66
 
57
67
  # Sends a refund request to Fawry API
@@ -59,10 +69,10 @@ module Fawry
59
69
  # the request signature
60
70
  #
61
71
  # @param params [Hash] list of params to send to fawry
62
- # required(:merchant_code).value(:string)
63
72
  # required(:reference_number).value(:string)
64
73
  # required(:refund_amount).value(:decimal)
65
- # required(:fawry_secure_key).value(:string)
74
+ # optional(:merchant_code).value(:string)
75
+ # optional(:fawry_secure_key).value(:string)
66
76
  # optional(:reason).value(:string)
67
77
  #
68
78
  # @param opts [Hash] list of options to
@@ -71,7 +81,7 @@ module Fawry
71
81
  # send the request to fawry sandbox env or not
72
82
  # false by default
73
83
  #
74
- # @raise [Fawry::InvalidFawryRequest] raised when one
84
+ # @raise [Fawry::InvalidFawryRequestError] raised when one
75
85
  # or more of the params are invalid. the message
76
86
  # specifices which params and why are they invalid
77
87
  #
@@ -79,7 +89,139 @@ module Fawry
79
89
  # has Fawry API response keys as instance methods
80
90
  # plus some convenience methods e.g. success?
81
91
  def refund(params, opts = {})
82
- FawryRequest.new('refund', params, opts).fire
92
+ FawryRequest.new('refund', params, opts).fire_refund_request
93
+ end
94
+
95
+ # Sends a payment status request to Fawry API
96
+ # performs param validation and builds
97
+ # the request signature
98
+ #
99
+ # @param params [Hash] list of params to send to fawry
100
+ # required(:merchant_ref_number).value(:string)
101
+ # optional(:merchant_code).value(:string)
102
+ # optional(:fawry_secure_key).value(:string)
103
+ #
104
+ # @param opts [Hash] list of options to
105
+ # configure the request
106
+ # @option opts :sandbox [Boolean] whether to
107
+ # send the request to fawry sandbox env or not
108
+ # false by default
109
+ #
110
+ # @raise [Fawry::InvalidFawryRequestError] raised when one
111
+ # or more of the params are invalid. the message
112
+ # specifices which params and why are they invalid
113
+ #
114
+ # @return [Fawry::FawryResponse] an object that
115
+ # has Fawry API response keys as instance methods
116
+ # plus some convenience methods e.g. success?
117
+ def payment_status(params, opts = {})
118
+ FawryRequest.new('payment_status', params, opts).fire_payment_status_request
119
+ end
120
+
121
+ # Sends a card token request to Fawry API
122
+ # performs param validation and builds
123
+ # the request signature
124
+ #
125
+ # @param params [Hash] list of params to send to fawry
126
+ # required(:customer_profile_id).value(:string)
127
+ # required(:customer_mobile).value(:string)
128
+ # required(:merchant_code).value(:string)
129
+ # required(:customer_email).value(:string)
130
+ # required(:card_number).value(:string)
131
+ # required(:expiry_year).value(:string)
132
+ # required(:expiry_month).value(:string)
133
+ # required(:cvv).value(:string)
134
+ #
135
+ # @param opts [Hash] list of options to
136
+ # configure the request
137
+ # @option opts :sandbox [Boolean] whether to
138
+ # send the request to fawry sandbox env or not
139
+ # false by default
140
+ #
141
+ # @raise [Fawry::InvalidFawryRequestError] raised when one
142
+ # or more of the params are invalid. the message
143
+ # specifices which params and why are they invalid
144
+ #
145
+ # @return [Fawry::FawryResponse] an object that
146
+ # has Fawry API response keys as instance methods
147
+ # plus some convenience methods e.g. success?
148
+
149
+ def create_card_token(params, opts = {})
150
+ FawryRequest.new('create_card_token', params, opts).fire_create_card_token_request
151
+ end
152
+
153
+ # Sends a list tokens request to Fawry API
154
+ # performs param validation and builds
155
+ # the request signature
156
+ #
157
+ # @param params [Hash] list of params to send to fawry
158
+ # required(:merchant_code).value(:string)
159
+ # required(:customer_profile_id).value(:string)
160
+ # optional(:fawry_secure_key).value(:string)
161
+ #
162
+ # @param opts [Hash] list of options to
163
+ # configure the request
164
+ # @option opts :sandbox [Boolean] whether to
165
+ # send the request to fawry sandbox env or not
166
+ # false by default
167
+ #
168
+ # @raise [Fawry::InvalidFawryRequestError] raised when one
169
+ # or more of the params are invalid. the message
170
+ # specifices which params and why are they invalid
171
+ #
172
+ # @return [Fawry::FawryResponse] an object that
173
+ # has Fawry API response keys as instance methods
174
+ # plus some convenience methods e.g. success?
175
+
176
+ def list_tokens(params, opts = {})
177
+ FawryRequest.new('list_tokens', params, opts).fire_list_tokens_request
178
+ end
179
+
180
+ # Sends delete token request to Fawry API
181
+ # performs param validation and builds
182
+ # the request signature
183
+ #
184
+ # @param params [Hash] list of params to send to fawry
185
+ # required(:customer_profile_id).value(:string)
186
+ # optional(:merchant_code).value(:string)
187
+ # optional(:fawry_secure_key).value(:string)
188
+ #
189
+ # @param opts [Hash] list of options to
190
+ # configure the request
191
+ # @option opts :sandbox [Boolean] whether to
192
+ # send the request to fawry sandbox env or not
193
+ # false by default
194
+ #
195
+ # @raise [Fawry::InvalidFawryRequestError] raised when one
196
+ # or more of the params are invalid. the message
197
+ # specifices which params and why are they invalid
198
+ #
199
+ # @return [Fawry::FawryResponse] an object that
200
+ # has Fawry API response keys as instance methods
201
+ # plus some convenience methods e.g. success?
202
+
203
+ def delete_token(params, opts = {})
204
+ FawryRequest.new('delete_token', params, opts).fire_delete_token_request
205
+ end
206
+
207
+ # Parses Fawry callback v2 into
208
+ # FawryCallback object with callback
209
+ # params as instance methods
210
+ #
211
+ # @param params [Hash] list of params sent
212
+ # from fawry server callback
213
+ #
214
+ # @param opts [Hash] list of options to
215
+ # configure the request. currently no
216
+ # options available
217
+ #
218
+ # @raise [Fawry::InvalidSignatureError] raised when
219
+ # request signature cannot be verified
220
+ #
221
+ # @return [Fawry::FawryCallback] an object that
222
+ # has Fawry server callback params' keys as instance methods
223
+ def parse_callback(params, opts = {})
224
+ FawryCallback.new(params, opts).parse
83
225
  end
84
226
  end
85
227
  end
@@ -5,9 +5,9 @@ require 'json'
5
5
 
6
6
  module Fawry
7
7
  class Connection
8
- FAWRY_BASE_URL = 'https://www.atfawry.com/ECommerceWeb/Fawry/payments/'
8
+ FAWRY_BASE_URL = 'https://www.atfawry.com/ECommerceWeb/Fawry/'
9
9
 
10
- FAWRY_SANDBOX_BASE_URL = 'https://atfawry.fawrystaging.com//ECommerceWeb/Fawry/payments/'
10
+ FAWRY_SANDBOX_BASE_URL = 'https://atfawry.fawrystaging.com//ECommerceWeb/Fawry/'
11
11
 
12
12
  class << self
13
13
  def post(path, params, body, options)
@@ -19,16 +19,53 @@ module Fawry
19
19
  end
20
20
  end
21
21
 
22
+ def get(path, params, body, options)
23
+ conn = options[:sandbox] ? sandbox_connection : connection
24
+
25
+ conn.get(path) do |request|
26
+ request.params = params
27
+ request.body = body.to_json
28
+ # Fawry doesn't understand encoded params
29
+ request.options = request.options.merge(params_encoder: ParamsSpecialEncoder)
30
+ end
31
+ end
32
+
33
+ def delete(path, params, body, options)
34
+ conn = options[:sandbox] ? sandbox_connection : connection
35
+
36
+ conn.delete(path) do |request|
37
+ request.params = params
38
+ request.body = body.to_json
39
+ # Fawry doesn't understand encoded params
40
+ request.options = request.options.merge(params_encoder: ParamsSpecialEncoder)
41
+ end
42
+ end
43
+
22
44
  private
23
45
 
24
46
  def connection
25
47
  @connection ||= Faraday.new(url: FAWRY_BASE_URL, headers: { 'Content-Type': 'application/json',
26
- 'Accept': 'application/json' })
48
+ Accept: 'application/json' })
27
49
  end
28
50
 
29
51
  def sandbox_connection
30
52
  @sandbox_connection ||= Faraday.new(url: FAWRY_SANDBOX_BASE_URL, headers: { 'Content-Type': 'application/json',
31
- 'Accept': 'application/json' })
53
+ Accept: 'application/json' })
54
+ end
55
+
56
+ # Fawry does not understand encoded params
57
+ # so we use this encoder to convert the params
58
+ # hash to a string of query params without encoding
59
+ # { a: 1, b: 2 } => a=1&b=2
60
+ class ParamsSpecialEncoder
61
+ def self.encode(hash)
62
+ hash.each_with_object([]) { |(k, v), arr| arr << "#{k}=#{v}" }.join('&')
63
+ end
64
+
65
+ def self.decode(string)
66
+ arr = string.split('&')
67
+ arr.map { |str| str.split('=') }.to_h
68
+ end
32
69
  end
33
70
  end
34
71
  end
@@ -6,19 +6,19 @@ module Fawry
6
6
  module Contracts
7
7
  class ChargeRequestContract < Dry::Validation::Contract
8
8
  params do
9
- required(:merchant_code).value(:string)
10
9
  required(:merchant_ref_num).value(:string)
11
10
  required(:customer_profile_id).value(:string)
12
11
  required(:amount).value(:decimal)
13
12
  required(:description).value(:string)
14
13
  required(:customer_mobile).value(:string)
15
- required(:fawry_secure_key).value(:string)
16
14
  required(:charge_items).array(:hash) do
17
15
  required(:item_id).value(:string)
18
16
  required(:description).value(:string)
19
17
  required(:price).value(:decimal)
20
18
  required(:quantity).value(:integer)
21
19
  end
20
+ optional(:merchant_code).value(:string)
21
+ optional(:fawry_secure_key).value(:string)
22
22
  optional(:currency_code).value(:string)
23
23
  optional(:card_token).value(:string)
24
24
  optional(:customer_email).value(:string)
@@ -37,6 +37,18 @@ module Fawry
37
37
  key.failure('card_token is required when payment_method is CARD')
38
38
  end
39
39
  end
40
+
41
+ rule(:fawry_secure_key) do
42
+ if ENV['FAWRY_SECURE_KEY'].nil? && value.nil?
43
+ key(:fawry_secure_key).failure('fawry secure key is required as a param or an env var')
44
+ end
45
+ end
46
+
47
+ rule(:merchant_code) do
48
+ if ENV['FAWRY_MERCHANT_CODE'].nil? && value.nil?
49
+ key(:merchant_code).failure('fawry merchant code is required as a param or an env var')
50
+ end
51
+ end
40
52
  end
41
53
  end
42
54
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-validation'
4
+
5
+ module Fawry
6
+ module Contracts
7
+ class CreateCardTokenRequestContract < Dry::Validation::Contract
8
+ params do
9
+ required(:customer_profile_id).value(:string)
10
+ required(:customer_mobile).value(:string)
11
+ required(:merchant_code).value(:string)
12
+ required(:customer_email).value(:string)
13
+ required(:card_number).value(:string)
14
+ required(:expiry_year).value(:string)
15
+ required(:expiry_month).value(:string)
16
+ required(:cvv).value(:string)
17
+ end
18
+
19
+ rule(:customer_email) do
20
+ unless /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i.match?(value)
21
+ key? && key.failure('has invalid format')
22
+ end
23
+ end
24
+
25
+ rule(:merchant_code) do
26
+ if ENV['FAWRY_MERCHANT_CODE'].nil? && value.nil?
27
+ key(:merchant_code).failure('fawry merchant code is required as a param or an env var')
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-validation'
4
+
5
+ module Fawry
6
+ module Contracts
7
+ class DeleteTokenRequestContract < Dry::Validation::Contract
8
+ params do
9
+ required(:merchant_code).value(:string)
10
+ required(:customer_profile_id).value(:string)
11
+ required(:fawry_secure_key).value(:string)
12
+ required(:card_token).value(:string)
13
+ end
14
+
15
+ rule(:fawry_secure_key) do
16
+ if ENV['FAWRY_SECURE_KEY'].nil? && value.nil?
17
+ key(:fawry_secure_key).failure('fawry secure key is required as a param or an env var')
18
+ end
19
+ end
20
+
21
+ rule(:merchant_code) do
22
+ if ENV['FAWRY_MERCHANT_CODE'].nil? && value.nil?
23
+ key(:merchant_code).failure('fawry merchant code is required as a param or an env var')
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end