spree_emerchantpay_genesis 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +52 -0
  3. data/LICENSE +21 -0
  4. data/README.md +269 -0
  5. data/Rakefile +50 -0
  6. data/app/assets/images/spree/emerchantpay_logo.png +0 -0
  7. data/app/assets/javascripts/spree/emerchantpay_threeds.js +126 -0
  8. data/app/assets/javascripts/spree/frontend/card.min.js +3 -0
  9. data/app/assets/stylesheets/spree/emerchantpay_threeds.css +40 -0
  10. data/app/assets/stylesheets/spree/frontend/card.css +35 -0
  11. data/app/controllers/spree/api/v2/storefront/checkout_controller_decorator.rb +66 -0
  12. data/app/controllers/spree/api/v2/storefront/emerchantpay_notification_controller.rb +47 -0
  13. data/app/controllers/spree/api/v2/storefront/emerchantpay_threeds_controller.rb +61 -0
  14. data/app/controllers/spree/emerchantpay_threeds_controller.rb +31 -0
  15. data/app/helpers/spree/admin/payment_methods_helper.rb +110 -0
  16. data/app/helpers/spree_emerchantpay_genesis/mappers/genesis.rb +332 -0
  17. data/app/helpers/spree_emerchantpay_genesis/mappers/order.rb +70 -0
  18. data/app/helpers/spree_emerchantpay_genesis/mappers/transaction.rb +45 -0
  19. data/app/helpers/spree_emerchantpay_genesis/threeds_helper.rb +116 -0
  20. data/app/helpers/spree_emerchantpay_genesis/transaction_helper.rb +200 -0
  21. data/app/models/spree/gateway/emerchantpay_direct.rb +66 -0
  22. data/app/models/spree/payment_decorator.rb +13 -0
  23. data/app/models/spree/payment_processing_decorator.rb +28 -0
  24. data/app/models/spree_emerchantpay_genesis/base/data.rb +33 -0
  25. data/app/models/spree_emerchantpay_genesis/base/gateway.rb +65 -0
  26. data/app/models/spree_emerchantpay_genesis/data/address.rb +23 -0
  27. data/app/models/spree_emerchantpay_genesis/data/provider.rb +23 -0
  28. data/app/models/spree_emerchantpay_genesis/data/user.rb +32 -0
  29. data/app/models/spree_emerchantpay_genesis/db/application_record.rb +10 -0
  30. data/app/models/spree_emerchantpay_genesis/db/emerchantpay_payment.rb +35 -0
  31. data/app/models/spree_emerchantpay_genesis/genesis_provider.rb +203 -0
  32. data/app/repositories/spree_emerchantpay_genesis/emerchantpay_payments_repository.rb +120 -0
  33. data/app/repositories/spree_emerchantpay_genesis/spree_order_repository.rb +51 -0
  34. data/app/repositories/spree_emerchantpay_genesis/spree_payments_repository.rb +58 -0
  35. data/app/services/spree/payments/create_decorator.rb +63 -0
  36. data/app/services/spree_emerchantpay_genesis/base/payment_service.rb +83 -0
  37. data/app/services/spree_emerchantpay_genesis/notifications/service_handler.rb +33 -0
  38. data/app/services/spree_emerchantpay_genesis/sources/create_credit_card.rb +47 -0
  39. data/app/services/spree_emerchantpay_genesis/threeds/callback.rb +62 -0
  40. data/app/services/spree_emerchantpay_genesis/threeds/method_continue.rb +57 -0
  41. data/app/views/layouts/spree/method_continue.html.erb +17 -0
  42. data/app/views/spree/admin/payments/source_views/_emerchantpay_direct.html.erb +80 -0
  43. data/app/views/spree/checkout/payment/_emerchantpay_direct.html.erb +51 -0
  44. data/app/views/spree/emerchantpay_threeds/method_continue.html.erb +26 -0
  45. data/config/locales/en.yml +45 -0
  46. data/config/routes.rb +24 -0
  47. data/db/migrate/20230927072418_add_emerchantpay_payments.rb +34 -0
  48. data/db/migrate/20231128153140_add_callback_status_to_emerchantpay_payments.rb +8 -0
  49. data/lib/generators/spree_emerchantpay_genesis/install/install_generator.rb +37 -0
  50. data/lib/spree_emerchantpay_genesis/engine.rb +38 -0
  51. data/lib/spree_emerchantpay_genesis/factories.rb +11 -0
  52. data/lib/spree_emerchantpay_genesis/version.rb +7 -0
  53. data/lib/spree_emerchantpay_genesis.rb +8 -0
  54. metadata +421 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f3f946f18fcb121abeb5227927b4d8480f73aedfd549cca1eae4a069a322b34b
4
+ data.tar.gz: 675f4efd4755f39eca482ef49dcb4c75555eb98e8cd89217cd2b713f90074d59
5
+ SHA512:
6
+ metadata.gz: eda4500c230e18a04b44eb67613c7911666e27730dd8f9eafc19fe6e43d636673479b3f47c9762465ee839ccbff026acb2e4b6dad5cde36f0b7fd07c2fff216f
7
+ data.tar.gz: 958cdcf91ac6ce0cd617e14d17c8cc09bde302fb829197e1b74c77ff9829f8e961b5847c419710b2372149baf1b1e72eb4956b0ce7ffaaa7e94121304af46895
data/CHANGELOG.md ADDED
@@ -0,0 +1,52 @@
1
+ 0.1.3
2
+ -----
3
+ **Features**
4
+
5
+ * Added tests to the library
6
+ * Added Appraisals for Spree 4.4 and Spree main GitHub Branch
7
+ * Added `spree_emerchantpay_genesis` to [RubyGems](https://rubygems.org/gems/spree_emerchantpay_genesis)
8
+
9
+ **Fixes**:
10
+
11
+ * Fixed 3DSv2 parameters handling
12
+ * Fixed engine installation without Spree Rails Frontend
13
+ * Fixed minor issues
14
+
15
+ 0.1.2
16
+ -----
17
+
18
+ **Features**:
19
+
20
+ * Updated project license to MIT
21
+ * Added support for the following transaction types:
22
+ * Authorize 3D
23
+ * Sale 3D
24
+ * Added 3DSv2 parameters support to the 3D payments
25
+ * Added support for 3DSv2 payment flow
26
+ * Added `emerchantpay_payment` inside Spree API V2 Create Payment response containing payment state and redirect_url
27
+ * Added Gateway Notifications handling used for asynchronous payments
28
+ * Updated Genesis Ruby SDK to version 0.1.3
29
+
30
+ 0.1.1
31
+ -----
32
+
33
+ **Features**:
34
+
35
+ * Added support for the following reference payment actions via Genesis Gateway:
36
+ * Capture
37
+ * Void
38
+ * Refund
39
+
40
+ 0.1.0
41
+ -----
42
+
43
+ **Features**:
44
+
45
+ * Added initial code base for emerchantpay Gateway Module for Spree payment method
46
+ * Added EmerchantpayDirect Spree Gateway
47
+ * Added Spree Payment Creation decoration
48
+ * Added Spree Payment Methods settings decorator
49
+ * Added Payment Source View decorator
50
+ * Added Spree V2 Checkout controller decorator
51
+ * Added Emerchantpay Payments database migration
52
+ * Added Spree Payment Processing decorator
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 emerchantpay ltd.
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,269 @@
1
+ # emerchantpay Genesis Gateway for Spree
2
+ This is a Payment Module for Spree eCommerce that gives you the ability to process payments through emerchantpay's Payment Gateway - Genesis.
3
+
4
+ # Requirements
5
+ * Spree Core 4.x (Tested up to 4.4.0)
6
+ * Spree Backend 4.x (Tested up to 4.4.0)
7
+ * Spree FrontEnd - Optional (Tested up to 4.4.0)
8
+ * Ruby >= 2.7
9
+ * Ruby on Rails >= 6.1.4
10
+ * [GenesisRuby v0.1.3](https://github.com/GenesisGateway/genesis_ruby/releases/tag/0.1.3)
11
+ * PCI-certified server in order to use emerchantpay Direct
12
+
13
+ ## Installation
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'spree_emerchantpay_genesis'
18
+ ```
19
+
20
+ And then execute:
21
+ ```bash
22
+ bundle
23
+ ```
24
+
25
+ Or install it yourself as:
26
+ ```bash
27
+ gem install spree_emerchantpay_genesis
28
+ ```
29
+
30
+ Copy & run migrations
31
+ ```bash
32
+ bundle exec rails g spree_emerchantpay_genesis:install
33
+ ```
34
+
35
+ Restart your server
36
+
37
+ ## Create Payment Method
38
+ * Sign in to Spree Admin BackEnd
39
+ * Navigate to Configurations -> Payment Methods -> New Payment Method
40
+ * For Provider `Choose Spree::Gateway::Emerchantpay*`
41
+ * Fill in Name, Description and Stores
42
+ * Click Create
43
+
44
+ # Usage
45
+
46
+ ## Configuration
47
+ * Navigate to the `Spree::Gateway::Emerchantpay*` payment method in the Configurations
48
+ * Fill in Username, Password and Token
49
+ * Fill in `hostname` used for the generation of the notification webhook. In most cases, the hostname should be the hostname of the Spree backend.
50
+ * Fill in `return_success_url` and `return_failure_url`. Those endpoints will be returned to the `create_payment` response
51
+ * If you add `|:ORDER:|` pattern in the URLs. The pattern will be replaced with the Spree Order Number
52
+
53
+ ## Spree Storefront API V2
54
+
55
+ 1. Create Cart
56
+
57
+ ```bash
58
+ curl --request 'POST' \
59
+ --url 'http://127.0.0.1:4000/api/v2/storefront/cart'
60
+ ```
61
+
62
+ Response:
63
+
64
+ ```text
65
+ {
66
+ "data":{
67
+ "id":"59",
68
+ "type":"cart",
69
+ "attributes":{
70
+ "number":"R702094375",
71
+ ...
72
+ "currency":"USD",
73
+ "state":"cart",
74
+ "token":"EsDjq1oXEgKI6kuujgfvFw1694531383712",
75
+ ...
76
+ },
77
+ ...
78
+ }
79
+ }
80
+ ```
81
+
82
+ 2. Add Items
83
+ <details>
84
+ <summary>List Products</summary>
85
+
86
+ ```bash
87
+ curl --request 'GET' \
88
+ --url 'https://demo.spreecommerce.org/api/v2/storefront/products' \
89
+ --header 'Accept: application/vnd.api+json'
90
+ ```
91
+
92
+ </details>
93
+
94
+ ```bash
95
+ curl --request 'POST' \
96
+ --url 'http://localhost:4000/api/v2/storefront/cart/add_item' \
97
+ --header 'Accept: application/vnd.api+json' \
98
+ --header 'X-Spree-Order-Token: EsDjq1oXEgKI6kuujgfvFw1694531383712' \
99
+ --header 'Content-Type: application/vnd.api+json' \
100
+ --data '{
101
+ "variant_id": "130",
102
+ "quantity": "1"
103
+ }'
104
+ ```
105
+
106
+ 3. Add Shipping
107
+ <details>
108
+ <summary>List Shipping Rates</summary>
109
+
110
+ ```bash
111
+ curl --request 'GET' \
112
+ --url 'https://demo.spreecommerce.org/api/v2/storefront/checkout/shipping_rates' \
113
+ --header 'Accept: application/vnd.api+json' \
114
+ --header 'X-Spree-Order-Token: EsDjq1oXEgKI6kuujgfvFw1694531383712'
115
+ ```
116
+
117
+ </details>
118
+
119
+ ```bash
120
+ curl --request 'PATCH' \
121
+ --url 'http://localhost:4000/api/v2/storefront/checkout/select_shipping_method' \
122
+ --header 'Accept: application/vnd.api+json' \
123
+ --header 'X-Spree-Order-Token: EsDjq1oXEgKI6kuujgfvFw1694531383712' \
124
+ --header 'Content-Type: application/vnd.api+json' \
125
+ --data '{
126
+ "shipping_method_id": "1"
127
+ }'
128
+ ```
129
+
130
+ 4. Update Order
131
+ ```bash
132
+ curl --request 'PATCH' \
133
+ --header 'Accept: application/vnd.api+json' \
134
+ --header 'X-Spree-Order-Token: EsDjq1oXEgKI6kuujgfvFw1694531383712' \
135
+ --header 'Content-Type: application/vnd.api+json' \
136
+ --url 'http://localhost:4000/api/v2/storefront/checkout' \
137
+ --data '{
138
+ "order": {
139
+ "email": "john.smith@example.com",
140
+ "bill_address_attributes": {
141
+ "firstname": "John",
142
+ "lastname": "Smith",
143
+ "address1": "1 Area",
144
+ "city": "Louisville",
145
+ "phone": "01234567891",
146
+ "zipcode": "40202",
147
+ "country_iso": "US",
148
+ "state_id": 500
149
+ },
150
+ "ship_address_attributes": {
151
+ "firstname": "John",
152
+ "lastname": "Smith",
153
+ "address1": "1 Area",
154
+ "city": "Louisville",
155
+ "phone": "01234567891",
156
+ "zipcode": "40202",
157
+ "country_iso": "US",
158
+ "state_id": 500
159
+ },
160
+ "payments_attributes":[
161
+ {
162
+ "payment_method_id":"5"
163
+ }
164
+ ]
165
+ },
166
+ "payment_source": {
167
+ "5": {
168
+ "name": "John Smith",
169
+ "number": "4200000000000000",
170
+ "month": 1,
171
+ "year": 2040,
172
+ "verification_value": "123",
173
+ "cc_type": "visa"
174
+ }
175
+ }
176
+ }'
177
+ ```
178
+ 5. Create Payment
179
+ **CAUTION** Create Payment endpoint will Complete the order! Call this endpoint in order to finish the order!
180
+ Accept Header, Java Enabled, Language, Color Depth, Screen height, Screen Width, Time Zone Offset, User Agent parameters must be retrieved from the customer browser. More info [here](https://emerchantpay.github.io/gateway-api-docs/?shell#3ds-v2-request-params).
181
+
182
+ ```bash
183
+ curl --request 'POST' \
184
+ --header 'Accept: application/vnd.api+json' \
185
+ --header 'X-Spree-Order-Token: EsDjq1oXEgKI6kuujgfvFw1694531383712' \
186
+ --header 'Content-Type: application/vnd.api+json' \
187
+ --url 'http://localhost:4000/api/v2/storefront/checkout/create_payment' \
188
+ --data '{
189
+ "payment_method_id": "5",
190
+ "source_attributes": {
191
+ "name": "John Smith",
192
+ "number": "4200000000000000",
193
+ "month": 1,
194
+ "year": 2040,
195
+ "verification_value": "123",
196
+ "cc_type": "visa",
197
+ "accept_header": "*/*",
198
+ "java_enabled": "true",
199
+ "language": "en-GB",
200
+ "color_depth": "32",
201
+ "screen_height": "400",
202
+ "screen_width": "400",
203
+ "time_zone_offset": "+0",
204
+ "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"
205
+ }
206
+ }'
207
+ ```
208
+
209
+ Response:
210
+ **CAUTION** If a redirect URL exists in the response object you MUST redirect the customer for payment completion
211
+
212
+ Create Payment response will contain `emerchantpay_payment` object. It will contain the current status of the payment.
213
+ Redirect URL will give you the next step.
214
+
215
+ States:
216
+ * error or declined - redirect URL will be the Failure URL filled in plugin settings
217
+ * approved - redirect URL will be the Success URL filled in the plugin settings
218
+ * pending_async - redirect URL will be the 3DSecure Method Continue endpoint for the next step of the payment
219
+
220
+ ```json
221
+ {
222
+ "data": {
223
+ "id": "XXX",
224
+ "type": "cart",
225
+ "attributes": {...},
226
+ "relationships": {...},
227
+ "emerchantpay_payment": {
228
+ "state": "pending_async",
229
+ "redirect_url": "<customer-redirect-url>"
230
+ }
231
+ }
232
+ }
233
+ ```
234
+
235
+ ## Reference Actions
236
+ **CAUTION** the following transaction actions must be executed via Spree Admin backend:
237
+ * Capture
238
+ * Refund
239
+ * Void
240
+
241
+ ## Contributing
242
+ Contribution directions go here.
243
+
244
+ ## Development
245
+
246
+ ### Running Specs
247
+
248
+ `rake test`
249
+
250
+ ### Running Linters
251
+
252
+ `rake styles`
253
+
254
+ ### Appraisals
255
+
256
+ #### Spree 4.4
257
+
258
+ `bundle exec appraisal spree-4.4 rake test`
259
+
260
+ #### Spree Master
261
+
262
+ `bundle exec appraisal spree-master rake test`
263
+
264
+ #### Configure
265
+
266
+ `bundle exec appraisal install`
267
+
268
+ ## License
269
+ The gem is available as open source under the terms of the [GPL-2.0 License](https://opensource.org/license/gpl-2-0/).
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ require 'spree/testing_support/extension_rake'
6
+
7
+ desc 'Generates a dummy app for testing'
8
+ task :test_app do
9
+ ENV['LIB_NAME'] = 'spree_emerchantpay_genesis'
10
+ Rake::Task['extension:test_app'].invoke
11
+ end
12
+
13
+ RSpec::Core::RakeTask.new(:spec)
14
+ RSpec::Core::RakeTask.new(:spec_junit) do |t|
15
+ t.rspec_opts = ['--format RspecJunitFormatter', '--out rspec_report.xml']
16
+ t.pattern = '**/*_spec.rb'
17
+ end
18
+
19
+ task :rspec, [:format] do |_task, args|
20
+ if Dir['spec/dummy'].empty?
21
+ Rake::Task[:test_app].invoke
22
+ Dir.chdir('../../')
23
+ end
24
+
25
+ case args.format
26
+ when 'junit'
27
+ Rake::Task[:spec_junit].invoke
28
+ else
29
+ Rake::Task[:spec].invoke
30
+ end
31
+ end
32
+
33
+ require 'rubocop/rake_task'
34
+ RuboCop::RakeTask.new(:rubocop_default)
35
+ RuboCop::RakeTask.new(:rubocop_progress) do |t|
36
+ t.formatters = %w(progress)
37
+ t.options = %w(--out rubocop_report.txt)
38
+ end
39
+
40
+ task :test do
41
+ Rake::Task[:rspec].invoke('')
42
+ end
43
+
44
+ task :test_junit do
45
+ Rake::Task[:rspec].invoke('junit')
46
+ end
47
+
48
+ task default: %i[test rubocop_default]
49
+ task styles: :rubocop_default
50
+ task style_progress: :rubocop_progress
@@ -0,0 +1,126 @@
1
+ (function(document) {
2
+ document.body.onload = function (){
3
+ empThreedsSecureMethod('threedsMethodForm').execute()
4
+ }
5
+ })(document);
6
+
7
+ const empThreedsSecureMethod = function(form_id) {
8
+ let interval = null
9
+ let delay = 500
10
+ let retries = 0
11
+ let max_retries = 12
12
+ let status_complete = 'completed'
13
+ let internal_error = 'internal_error'
14
+
15
+ // ThreedsForm HTML Element
16
+ const threedsForm = function() {
17
+ return document.getElementById(form_id)
18
+ }
19
+
20
+ // 3DSv2 Callback Status endpoint
21
+ const getCallbackStatusUrl = function() {
22
+ return threedsForm().dataset.statusUrl
23
+ }
24
+
25
+ // 3DSv2 Method Continue endpoint
26
+ const getMethodContinueUrl = function() {
27
+ return threedsForm().dataset.methodSecureUrl
28
+ }
29
+
30
+ const getFailureUrl = function() {
31
+ return threedsForm().dataset.failureUrl
32
+ }
33
+
34
+ // Execute 3DSv2 submission
35
+ const submitThreedsMethod = function(){
36
+ threedsForm().submit()
37
+ startLoop()
38
+ }
39
+
40
+ // 3DSv2 Secure Method Handler
41
+ const startLoop = function() {
42
+ initInterval(function() {
43
+ checkCallbackStatus(function(data) {
44
+ handleInternalError(data)
45
+
46
+ if (retries >= max_retries || data.status === status_complete) {
47
+ clearInterval(interval)
48
+
49
+ sendBackEndData(function(data) {
50
+ handleInternalError(data)
51
+
52
+ redirectTo(data.redirect_url)
53
+ })
54
+ }
55
+
56
+ retries++;
57
+ })
58
+ })
59
+ }
60
+
61
+ const handleInternalError = function(data) {
62
+ if (data.status === internal_error) {
63
+ clearInterval(interval)
64
+ redirectTo(getFailureUrl())
65
+ }
66
+ }
67
+
68
+ // Redirect to the given url
69
+ const redirectTo = function(url) {
70
+ parent.location.href = url
71
+ }
72
+
73
+ // Initialize the Interval
74
+ const initInterval = function(callback) {
75
+ interval = setInterval(callback, delay)
76
+ }
77
+
78
+ // Check Callback status
79
+ const checkCallbackStatus = function(callback) {
80
+ ajaxCall(
81
+ { method: 'GET', url: getCallbackStatusUrl(), async: false },
82
+ (data) => { callback(data) },
83
+ (status, response) => { callback({ status: internal_error }) }
84
+ )
85
+ }
86
+
87
+ function sendBackEndData(callback) {
88
+ ajaxCall(
89
+ { method: 'POST', url: getMethodContinueUrl(), async: false, form_data: new FormData(threedsForm()) },
90
+ (data) => { callback(data) },
91
+ (status, response) => { callback({ status: internal_error }) }
92
+ )
93
+ }
94
+
95
+ const ajaxCall = function(options, success, failure) {
96
+ let xhr = new XMLHttpRequest()
97
+ xhr.open(options.method, options.url, options.async)
98
+
99
+ try {
100
+ xhr.onload = function() {
101
+ if (xhr.status === 200) {
102
+ return success(parseData(xhr.responseText))
103
+ }
104
+
105
+ return failure(xhr.status, xhr.responseText)
106
+ };
107
+
108
+ xhr.onerror = () => { failure(500, 'Network Error') }
109
+
110
+ xhr.send(options.form_data)
111
+ } catch(err) {
112
+ failure(0, 'System error')
113
+ }
114
+ }
115
+
116
+ const parseData = function(data) {
117
+ try {
118
+ return JSON.parse(data)
119
+ } catch (error) {
120
+ return {}
121
+ }
122
+ }
123
+
124
+
125
+ return { execute: submitThreedsMethod }
126
+ };