fawry 0.1.0 → 1.2.1

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 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