spree_emerchantpay_genesis 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +52 -0
- data/LICENSE +21 -0
- data/README.md +269 -0
- data/Rakefile +50 -0
- data/app/assets/images/spree/emerchantpay_logo.png +0 -0
- data/app/assets/javascripts/spree/emerchantpay_threeds.js +126 -0
- data/app/assets/javascripts/spree/frontend/card.min.js +3 -0
- data/app/assets/stylesheets/spree/emerchantpay_threeds.css +40 -0
- data/app/assets/stylesheets/spree/frontend/card.css +35 -0
- data/app/controllers/spree/api/v2/storefront/checkout_controller_decorator.rb +66 -0
- data/app/controllers/spree/api/v2/storefront/emerchantpay_notification_controller.rb +47 -0
- data/app/controllers/spree/api/v2/storefront/emerchantpay_threeds_controller.rb +61 -0
- data/app/controllers/spree/emerchantpay_threeds_controller.rb +31 -0
- data/app/helpers/spree/admin/payment_methods_helper.rb +110 -0
- data/app/helpers/spree_emerchantpay_genesis/mappers/genesis.rb +332 -0
- data/app/helpers/spree_emerchantpay_genesis/mappers/order.rb +70 -0
- data/app/helpers/spree_emerchantpay_genesis/mappers/transaction.rb +45 -0
- data/app/helpers/spree_emerchantpay_genesis/threeds_helper.rb +116 -0
- data/app/helpers/spree_emerchantpay_genesis/transaction_helper.rb +200 -0
- data/app/models/spree/gateway/emerchantpay_direct.rb +66 -0
- data/app/models/spree/payment_decorator.rb +13 -0
- data/app/models/spree/payment_processing_decorator.rb +28 -0
- data/app/models/spree_emerchantpay_genesis/base/data.rb +33 -0
- data/app/models/spree_emerchantpay_genesis/base/gateway.rb +65 -0
- data/app/models/spree_emerchantpay_genesis/data/address.rb +23 -0
- data/app/models/spree_emerchantpay_genesis/data/provider.rb +23 -0
- data/app/models/spree_emerchantpay_genesis/data/user.rb +32 -0
- data/app/models/spree_emerchantpay_genesis/db/application_record.rb +10 -0
- data/app/models/spree_emerchantpay_genesis/db/emerchantpay_payment.rb +35 -0
- data/app/models/spree_emerchantpay_genesis/genesis_provider.rb +203 -0
- data/app/repositories/spree_emerchantpay_genesis/emerchantpay_payments_repository.rb +120 -0
- data/app/repositories/spree_emerchantpay_genesis/spree_order_repository.rb +51 -0
- data/app/repositories/spree_emerchantpay_genesis/spree_payments_repository.rb +58 -0
- data/app/services/spree/payments/create_decorator.rb +63 -0
- data/app/services/spree_emerchantpay_genesis/base/payment_service.rb +83 -0
- data/app/services/spree_emerchantpay_genesis/notifications/service_handler.rb +33 -0
- data/app/services/spree_emerchantpay_genesis/sources/create_credit_card.rb +47 -0
- data/app/services/spree_emerchantpay_genesis/threeds/callback.rb +62 -0
- data/app/services/spree_emerchantpay_genesis/threeds/method_continue.rb +57 -0
- data/app/views/layouts/spree/method_continue.html.erb +17 -0
- data/app/views/spree/admin/payments/source_views/_emerchantpay_direct.html.erb +80 -0
- data/app/views/spree/checkout/payment/_emerchantpay_direct.html.erb +51 -0
- data/app/views/spree/emerchantpay_threeds/method_continue.html.erb +26 -0
- data/config/locales/en.yml +45 -0
- data/config/routes.rb +24 -0
- data/db/migrate/20230927072418_add_emerchantpay_payments.rb +34 -0
- data/db/migrate/20231128153140_add_callback_status_to_emerchantpay_payments.rb +8 -0
- data/lib/generators/spree_emerchantpay_genesis/install/install_generator.rb +37 -0
- data/lib/spree_emerchantpay_genesis/engine.rb +38 -0
- data/lib/spree_emerchantpay_genesis/factories.rb +11 -0
- data/lib/spree_emerchantpay_genesis/version.rb +7 -0
- data/lib/spree_emerchantpay_genesis.rb +8 -0
- metadata +421 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
body {
|
2
|
+
padding-top: 50px;
|
3
|
+
}
|
4
|
+
.center {
|
5
|
+
max-width: 819px;
|
6
|
+
height: 423px;
|
7
|
+
background-color: #ffffff;
|
8
|
+
border-radius: 10px;
|
9
|
+
color: #000000;
|
10
|
+
margin: 0 auto;
|
11
|
+
display: flex;
|
12
|
+
justify-content: center;
|
13
|
+
align-items: center;
|
14
|
+
}
|
15
|
+
.center h3 {
|
16
|
+
font-family: 'Segoe UI', Arial, sans-serif;
|
17
|
+
text-align: center;
|
18
|
+
font-weight: 500;
|
19
|
+
font-size: 2em;
|
20
|
+
}
|
21
|
+
.center h3 span {
|
22
|
+
font-family: 'Segoe UI', Arial, sans-serif;
|
23
|
+
display: block;
|
24
|
+
text-align: center;
|
25
|
+
font-weight: 400;
|
26
|
+
font-size: 16px;
|
27
|
+
padding-top: 30px;
|
28
|
+
}
|
29
|
+
.hidden {
|
30
|
+
display: none;
|
31
|
+
}
|
32
|
+
.screen-logo
|
33
|
+
{
|
34
|
+
width: 152px;
|
35
|
+
height: 50px;
|
36
|
+
margin: 0 auto;
|
37
|
+
display: flex;
|
38
|
+
justify-content: center;
|
39
|
+
align-items: center;
|
40
|
+
}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright (C) 2023 emerchantpay Ltd.
|
3
|
+
*
|
4
|
+
* This program is free software; you can redistribute it and/or
|
5
|
+
* modify it under the terms of the GNU General Public License
|
6
|
+
* as published by the Free Software Foundation; either version 2
|
7
|
+
* of the License, or (at your option) any later version.
|
8
|
+
*
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
* GNU General Public License for more details.
|
13
|
+
*
|
14
|
+
* @author emerchantpay
|
15
|
+
* @copyright 2023 emerchantpay Ltd.
|
16
|
+
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License, version 2 (GPL-2.0)
|
17
|
+
*/
|
18
|
+
|
19
|
+
.emerchantpay-card-wrapper {
|
20
|
+
text-align: center;
|
21
|
+
}
|
22
|
+
|
23
|
+
.emerchantpay-payment-gateway .payment-gateway-fields {
|
24
|
+
text-align: center;
|
25
|
+
width: 80%;
|
26
|
+
margin: 0px auto;
|
27
|
+
}
|
28
|
+
|
29
|
+
.emerchantpay-payment-gateway .payment-gateway-fields input {
|
30
|
+
padding: 15px;
|
31
|
+
}
|
32
|
+
|
33
|
+
.emerchantpay-payment-gateway .payment-gateway-fields input::-webkit-input-placeholder {
|
34
|
+
text-transform: none;
|
35
|
+
}
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Spree
|
2
|
+
module Api
|
3
|
+
module V2
|
4
|
+
module Storefront
|
5
|
+
# Decorate Create Payment action
|
6
|
+
module CheckoutControllerDecorator
|
7
|
+
|
8
|
+
def create_payment
|
9
|
+
result = create_payment_service.call(order: spree_current_order, params: params)
|
10
|
+
|
11
|
+
if result.success?
|
12
|
+
return emerchantpay_direct_payment_handler if
|
13
|
+
result.value.payment_method.type == Spree::Gateway::EmerchantpayDirect.name
|
14
|
+
|
15
|
+
render_serialized_payload(201) { serialize_resource(spree_current_order.reload) }
|
16
|
+
else
|
17
|
+
render_error_payload(result.error)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Handle EmerchantpayDirect Payment Method Create Payment API Call
|
24
|
+
def emerchantpay_direct_payment_handler
|
25
|
+
return render_error_payload('You must authenticate in order to create Emerchanptpay payment') if
|
26
|
+
order_token.empty?
|
27
|
+
|
28
|
+
spree_authorize! :update, spree_current_order, order_token
|
29
|
+
|
30
|
+
# Complete the order. This will call the purchase method with source containing credit card number
|
31
|
+
loop { break unless spree_current_order.next }
|
32
|
+
|
33
|
+
handle_order_state
|
34
|
+
end
|
35
|
+
|
36
|
+
# Generate Spree Response
|
37
|
+
def handle_order_state
|
38
|
+
# spree_current_order.payments.last.source
|
39
|
+
|
40
|
+
if spree_current_order.completed?
|
41
|
+
return render_serialized_payload(201) do
|
42
|
+
response = serialize_resource(spree_current_order.reload)
|
43
|
+
|
44
|
+
response[:data].merge!(build_genesis_response_parameters)
|
45
|
+
|
46
|
+
response
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
render_error_payload(spree_current_order.errors[:base].join('|'))
|
51
|
+
end
|
52
|
+
|
53
|
+
# Build additional response parameters
|
54
|
+
def build_genesis_response_parameters
|
55
|
+
spree_payment = spree_current_order.payments.last.private_metadata
|
56
|
+
|
57
|
+
{ emerchantpay_payment: { state: spree_payment[:state], redirect_url: spree_payment[:redirect_url] } }
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
::Spree::Api::V2::Storefront::CheckoutController.prepend(Spree::Api::V2::Storefront::CheckoutControllerDecorator)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Spree
|
2
|
+
module Api
|
3
|
+
module V2
|
4
|
+
module Storefront
|
5
|
+
# Emerchantpay IPN Controller handler
|
6
|
+
class EmerchantpayNotificationController < ::Spree::Api::V2::BaseController
|
7
|
+
|
8
|
+
before_action :validate_params
|
9
|
+
|
10
|
+
attr_reader :permitted_params
|
11
|
+
|
12
|
+
# Notification handler action
|
13
|
+
def index
|
14
|
+
process_params
|
15
|
+
|
16
|
+
notification = SpreeEmerchantpayGenesis::Notifications::ServiceHandler.call permitted_params
|
17
|
+
|
18
|
+
render xml: notification.generate_response
|
19
|
+
rescue StandardError => e
|
20
|
+
render status: 422, plain: "Spree notification handling exited with: #{e.message}"
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Validate params
|
26
|
+
def validate_params
|
27
|
+
params.require [:unique_id, :signature]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Permit params
|
31
|
+
def process_params
|
32
|
+
@permitted_params = params.permit(
|
33
|
+
:transaction_id, :terminal_token, :unique_id, :transaction_type, :status, :signature, :amount,
|
34
|
+
:eci, :cvv_result_code, :retrieval_reference_number, :authorization_code, :scheme_transaction_identifier,
|
35
|
+
:scheme_settlement_date, :threeds_authentication_flow, :threeds_target_protocol_version,
|
36
|
+
:threeds_concrete_protocol_version, :threeds_method_status, :scheme_response_code, :avs_response_code,
|
37
|
+
:avs_response_text, :reference_transaction_unique_id, :threeds_authentication_status_reason_code,
|
38
|
+
:card_brand, :card_number, :card_type, :card_subtype, :card_issuing_bank, :card_holder, :expiration_year,
|
39
|
+
:expiration_month
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Spree
|
2
|
+
module Api
|
3
|
+
module V2
|
4
|
+
module Storefront
|
5
|
+
# 3D Secure Helper methods
|
6
|
+
class EmerchantpayThreedsController < ::Spree::Api::V2::BaseController
|
7
|
+
|
8
|
+
before_action :validate_params, except: [:callback_status]
|
9
|
+
before_action :initialize_service
|
10
|
+
after_action :allow_iframe, only: :callback_handler
|
11
|
+
|
12
|
+
attr_reader :permitted_params
|
13
|
+
|
14
|
+
# 3DSv2 Callback webhook handler
|
15
|
+
def callback_handler
|
16
|
+
@service.store_callback_status
|
17
|
+
|
18
|
+
render status: 200, json: { status: @service.fetch_callback_status }
|
19
|
+
end
|
20
|
+
|
21
|
+
# 3DSv2 Callback webhook status
|
22
|
+
def callback_status
|
23
|
+
render status: 200, json: { status: @service.fetch_callback_status }
|
24
|
+
end
|
25
|
+
|
26
|
+
# 3DSv2 Secure Method Continue Request
|
27
|
+
def method_continue
|
28
|
+
render status: 200, json: { status: 'OK', redirect_url: @service.process_method_continue }
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Initialize 3DSv2 Callback Service
|
34
|
+
def initialize_service
|
35
|
+
process_params
|
36
|
+
|
37
|
+
@service = SpreeEmerchantpayGenesis::Threeds::Callback.call @permitted_params
|
38
|
+
rescue StandardError
|
39
|
+
render status: 422, json: { error: 'Given request is invalid!' }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Permit params
|
43
|
+
def process_params
|
44
|
+
@permitted_params = params.permit :unique_id, :signature, :threeds_method_status
|
45
|
+
end
|
46
|
+
|
47
|
+
# Validate Params
|
48
|
+
def validate_params
|
49
|
+
params.require [:unique_id, :signature]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Allow iFrame execution
|
53
|
+
def allow_iframe
|
54
|
+
response.headers.except! 'X-Frame-Options'
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Spree
|
2
|
+
# 3DSv2 Secure Method Continue customer controller
|
3
|
+
class EmerchantpayThreedsController < ApplicationController
|
4
|
+
|
5
|
+
after_action :allow_iframe, only: :index
|
6
|
+
|
7
|
+
layout 'spree/method_continue'
|
8
|
+
|
9
|
+
def index
|
10
|
+
service = SpreeEmerchantpayGenesis::Threeds::MethodContinue.call permitted_params
|
11
|
+
|
12
|
+
render 'method_continue', locals: service.build_secure_method_params
|
13
|
+
rescue StandardError => e
|
14
|
+
Rails.logger.error "Emerchantpay Threeds Controller: #{e.message}"
|
15
|
+
|
16
|
+
render plain: 'Error during Emerchantpay 3DSv2 execution. Contact administrator!'
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# Allow iFrame execution
|
22
|
+
def allow_iframe
|
23
|
+
response.headers.except! 'X-Frame-Options'
|
24
|
+
end
|
25
|
+
|
26
|
+
def permitted_params
|
27
|
+
params.permit :unique_id, :checksum
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Spree
|
2
|
+
module Admin
|
3
|
+
# Spree Payment Methods Helper for adding HTML elements (ex. select) for the Payment Method
|
4
|
+
module PaymentMethodsHelper
|
5
|
+
|
6
|
+
def preference_field_for(form, field, options) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
7
|
+
case options[:type]
|
8
|
+
when :integer, :string
|
9
|
+
form.text_field(field, preference_field_options(options))
|
10
|
+
when :boolean
|
11
|
+
form.check_box(field, preference_field_options(options))
|
12
|
+
when :password
|
13
|
+
form.password_field(field, preference_field_options(options))
|
14
|
+
when :text
|
15
|
+
form.text_area(field, preference_field_options(options))
|
16
|
+
when :boolean_select
|
17
|
+
label_tag(field, Spree.t(field))
|
18
|
+
form.select(field, { Spree.t(:enabled) => true, Spree.t(:disabled) => false }, {}, class: 'select2')
|
19
|
+
when :select
|
20
|
+
label_tag(field, Spree.t(field))
|
21
|
+
form.select(
|
22
|
+
field,
|
23
|
+
options_for_select(
|
24
|
+
options[:values].map { |key| [I18n.t(key, scope: 'emerchantpay.preferences'), key] }, options[:selected]
|
25
|
+
),
|
26
|
+
{},
|
27
|
+
class: 'select2'
|
28
|
+
)
|
29
|
+
else
|
30
|
+
form.text_field(field, preference_field_options(options))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def preference_field_tag(name, value, options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
35
|
+
case options[:type]
|
36
|
+
when :integer, :string
|
37
|
+
text_field_tag(name, value, preference_field_options(options))
|
38
|
+
when :boolean
|
39
|
+
hidden_field_tag(name, 0, id: "#{name}_hidden") +
|
40
|
+
check_box_tag(name, 1, value, preference_field_options(options))
|
41
|
+
when :password
|
42
|
+
password_field_tag(name, value, preference_field_options(options))
|
43
|
+
when :text
|
44
|
+
text_area_tag(name, value, preference_field_options(options))
|
45
|
+
when :boolean_select
|
46
|
+
select_tag(name, value, preference_field_options(options))
|
47
|
+
when :select
|
48
|
+
select_tag(name, value, preference_field_options(options))
|
49
|
+
else
|
50
|
+
text_field_tag(name, value, preference_field_options(options))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def preference_fields(object, form)
|
55
|
+
return unless object.respond_to?(:preferences)
|
56
|
+
|
57
|
+
get_preference_fields(object, object.preferences.keys, form)
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_preference_fields(object, keys, form)
|
61
|
+
keys.reject { |k| k == :currency_merchant_accounts }.map do |key|
|
62
|
+
next if !object.has_preference?(key) || default_preference_support?(object, key)
|
63
|
+
|
64
|
+
if object.preference_type(key) == :boolean
|
65
|
+
add_boolean_preference(object, key, form)
|
66
|
+
else
|
67
|
+
add_common_preference(object, key, form)
|
68
|
+
end
|
69
|
+
end.join(' ').html_safe
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def add_boolean_preference(object, key, form)
|
75
|
+
content_tag(
|
76
|
+
:div,
|
77
|
+
preference_field_for(
|
78
|
+
form,
|
79
|
+
"preferred_#{key}", type: object.preference_type(key)
|
80
|
+
) + form.label("preferred_#{key}", Spree.t(key), class: 'form-check-label'),
|
81
|
+
class: 'form-group form-check',
|
82
|
+
id: [object.class.to_s.parameterize, 'preference', key].join('-')
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
def add_common_preference(object, key, form)
|
87
|
+
content_tag(:div, class: 'form-group', 'data-hook' => "preferred_#{key}") do
|
88
|
+
form.label("preferred_#{key}", "#{Spree.t(key)}: ") + preference_field_for(
|
89
|
+
form,
|
90
|
+
"preferred_#{key}",
|
91
|
+
type: object.preference_type(key),
|
92
|
+
values: (object.__send__("preferred_#{key}_default").is_a?(Hash) ? object.__send__("preferred_#{key}_default")[:values] : nil), # rubocop:disable Layout/LineLength
|
93
|
+
selected: object.preferences[key]
|
94
|
+
)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def default_preference_support?(object, preference)
|
99
|
+
unsupported_default_preferences.include?(preference.to_s) &&
|
100
|
+
object.instance_of?(Spree::Gateway::EmerchantpayDirect)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Not Supported preferences from Emerchantpay Gateway
|
104
|
+
def unsupported_default_preferences
|
105
|
+
%w(server)
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|