spree_mollie_gateway 0.1.1 → 1.0.0.pre.beta3
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.
- checksums.yaml +5 -5
- data/.gitignore +3 -1
- data/.travis.yml +12 -1
- data/CONTRIBUTING.md +28 -0
- data/Gemfile.lock +322 -2
- data/README.md +29 -54
- data/Rakefile +19 -4
- data/app/controllers/spree/api/v1/mollie_controller.rb +16 -0
- data/app/controllers/spree/checkout_controller_decorator.rb +5 -6
- data/app/controllers/spree/mollie_controller.rb +20 -8
- data/app/models/mollie/client_decorator.rb +13 -0
- data/app/models/spree/gateway/mollie_gateway.rb +108 -53
- data/app/models/spree/mollie_logger.rb +9 -0
- data/app/models/spree/{mollie_transaction.rb → mollie_payment_source.rb} +7 -3
- data/app/models/spree/order_decorator.rb +24 -0
- data/app/models/spree/payment/processing_decorator.rb +8 -2
- data/app/models/spree/payment_decorator.rb +16 -0
- data/app/models/spree/user_decorator.rb +4 -2
- data/app/views/spree/checkout/payment/_molliegateway.html.erb +40 -3
- data/app/views/spree/shared/_payment.html.erb +1 -1
- data/config/routes.rb +11 -1
- data/db/migrate/{20180214133044_create_spree_mollie_transactions.rb → 20180214133044_create_spree_mollie_payment_sources.rb} +2 -2
- data/db/migrate/20180301084841_add_mollie_customer_id_to_spree_user.rb +2 -1
- data/docs/api/methods.md +61 -0
- data/docs/api/readme.md +7 -0
- data/docs/debugging.md +26 -0
- data/lib/spree_mollie_gateway/engine.rb +13 -3
- data/lib/spree_mollie_gateway/factories.rb +10 -0
- data/lib/spree_mollie_gateway/version.rb +1 -1
- data/spree_mollie_gateway.gemspec +27 -12
- metadata +231 -8
- data/app/models/mollie/provider.rb +0 -5
data/Rakefile
CHANGED
@@ -1,6 +1,21 @@
|
|
1
|
-
require
|
2
|
-
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
3
|
|
4
|
-
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'spree/testing_support/extension_rake'
|
5
6
|
|
6
|
-
|
7
|
+
RSpec::Core::RakeTask.new
|
8
|
+
|
9
|
+
task :default do
|
10
|
+
if Dir['spec/dummy'].empty?
|
11
|
+
Rake::Task[:test_app].invoke
|
12
|
+
Dir.chdir('../../')
|
13
|
+
end
|
14
|
+
Rake::Task[:spec].invoke
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'Generates a dummy app for testing'
|
18
|
+
task :test_app do
|
19
|
+
ENV['LIB_NAME'] = 'spree_mollie_gateway'
|
20
|
+
Rake::Task['extension:test_app'].invoke
|
21
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Spree
|
2
|
+
module Api
|
3
|
+
module V1
|
4
|
+
class MollieController < BaseController
|
5
|
+
def methods
|
6
|
+
mollie = Spree::PaymentMethod.find_by_type 'Spree::Gateway::MollieGateway'
|
7
|
+
payment_methods = mollie.available_payment_methods
|
8
|
+
|
9
|
+
puts payment_methods
|
10
|
+
|
11
|
+
render json: payment_methods
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,12 +1,15 @@
|
|
1
1
|
module Spree
|
2
2
|
module CheckoutWithMollie
|
3
|
+
# If we're currently in the checkout
|
3
4
|
def update
|
4
5
|
if payment_params_valid? && paying_with_mollie?
|
5
6
|
if @order.update_from_params(params, permitted_checkout_attributes, request.headers.env)
|
6
7
|
payment = @order.payments.last
|
7
|
-
payment.
|
8
|
+
payment.process!
|
8
9
|
mollie_payment_url = payment.payment_source.payment_url
|
9
10
|
|
11
|
+
MollieLogger.debug("For order #{@order.number} redirect user to payment URL: #{mollie_payment_url}")
|
12
|
+
|
10
13
|
redirect_to mollie_payment_url
|
11
14
|
else
|
12
15
|
render :edit
|
@@ -27,15 +30,11 @@ module Spree
|
|
27
30
|
|
28
31
|
def paying_with_mollie?
|
29
32
|
payment_method = PaymentMethod.find(payment_method_id_param)
|
30
|
-
|
33
|
+
payment_method.is_a? Gateway::MollieGateway
|
31
34
|
end
|
32
35
|
|
33
36
|
def payment_params_valid?
|
34
37
|
(params[:state] === 'payment') && params[:order][:payments_attributes]
|
35
38
|
end
|
36
|
-
|
37
|
-
def mollie_payment_method?(payment_method)
|
38
|
-
payment_method.is_a?(Gateway::MollieGateway)
|
39
|
-
end
|
40
39
|
end
|
41
40
|
end
|
@@ -5,10 +5,13 @@ module Spree
|
|
5
5
|
# When the user is redirected from Mollie back to the shop, we can check the
|
6
6
|
# mollie transaction status and set the Spree order state accordingly.
|
7
7
|
def validate_payment
|
8
|
-
|
9
|
-
payment =
|
10
|
-
|
11
|
-
mollie.
|
8
|
+
order_number, payment_number = split_payment_identifier params[:order_number]
|
9
|
+
payment = Spree::Payment.find_by_number payment_number
|
10
|
+
order = Spree::Order.find_by_number order_number
|
11
|
+
mollie = Spree::PaymentMethod.find_by_type 'Spree::Gateway::MollieGateway'
|
12
|
+
mollie.update_payment_status payment
|
13
|
+
|
14
|
+
MollieLogger.debug("Redirect URL visited for order #{params[:order_number]}")
|
12
15
|
|
13
16
|
redirect_to order.reload.paid? ? order_path(order) : checkout_state_path(:payment)
|
14
17
|
end
|
@@ -16,11 +19,20 @@ module Spree
|
|
16
19
|
# Mollie might send us information about a transaction through the webhook.
|
17
20
|
# We should update the payment state accordingly.
|
18
21
|
def update_payment_status
|
19
|
-
payment
|
20
|
-
|
21
|
-
|
22
|
+
MollieLogger.debug("Webhook called for payment #{params[:id]}")
|
23
|
+
|
24
|
+
payment = Spree::MolliePaymentSource.find_by_payment_id(params[:id]).payments.first
|
25
|
+
mollie = Spree::PaymentMethod.find_by_type 'Spree::Gateway::MollieGateway'
|
26
|
+
mollie.update_payment_status payment
|
27
|
+
|
28
|
+
head :ok
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
22
32
|
|
23
|
-
|
33
|
+
# Payment identifier is a combination of order_number and payment_id.
|
34
|
+
def split_payment_identifier(payment_identifier)
|
35
|
+
payment_identifier.split '-'
|
24
36
|
end
|
25
37
|
end
|
26
38
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Mollie::Client.class_eval do
|
2
|
+
attr_accessor :version_strings
|
3
|
+
|
4
|
+
def initialize(api_key = nil)
|
5
|
+
@api_endpoint = Mollie::Client::API_ENDPOINT
|
6
|
+
@api_key = api_key
|
7
|
+
@version_strings = []
|
8
|
+
|
9
|
+
add_version_string 'MollieSpreeCommerce/' << SpreeMollieGateway::VERSION
|
10
|
+
add_version_string 'Ruby/' << RUBY_VERSION
|
11
|
+
add_version_string OpenSSL::OPENSSL_VERSION.split(' ').slice(0, 2).join '/'
|
12
|
+
end
|
13
|
+
end
|
@@ -3,16 +3,26 @@ module Spree
|
|
3
3
|
preference :api_key, :string
|
4
4
|
preference :hostname, :string
|
5
5
|
|
6
|
-
has_many :
|
6
|
+
has_many :spree_mollie_payment_sources, class_name: 'Spree::MolliePaymentSource'
|
7
|
+
|
8
|
+
# Only enable one-click payments if spree_auth_devise is installed
|
9
|
+
def self.allow_one_click_payments?
|
10
|
+
Gem.loaded_specs.has_key?('spree_auth_devise')
|
11
|
+
end
|
7
12
|
|
8
13
|
def payment_source_class
|
9
|
-
Spree::
|
14
|
+
Spree::MolliePaymentSource
|
15
|
+
end
|
16
|
+
|
17
|
+
def actions
|
18
|
+
%w{credit}
|
10
19
|
end
|
11
20
|
|
12
21
|
def provider_class
|
13
|
-
::Mollie::
|
22
|
+
::Mollie::Client
|
14
23
|
end
|
15
24
|
|
25
|
+
# Always create a source which references to the selected Mollie payment method.
|
16
26
|
def source_required?
|
17
27
|
true
|
18
28
|
end
|
@@ -21,41 +31,59 @@ module Spree
|
|
21
31
|
true
|
22
32
|
end
|
23
33
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
transaction = ::Mollie::Payment.create(
|
28
|
-
prepare_transaction_params(payment.order, source)
|
29
|
-
)
|
30
|
-
|
31
|
-
invalidate_prev_transactions(payment.id)
|
32
|
-
|
33
|
-
payment.response_code = transaction.id
|
34
|
-
payment.save!
|
35
|
-
|
36
|
-
source.payment_id = transaction.id
|
37
|
-
source.payment_url = transaction.payment_url
|
38
|
-
source.save!
|
34
|
+
def auto_capture?
|
35
|
+
true
|
36
|
+
end
|
39
37
|
|
40
|
-
|
38
|
+
# Create a new Mollie payment.
|
39
|
+
def create_transaction(money_in_cents, source, gateway_options)
|
40
|
+
MollieLogger.debug("About to create payment for order #{gateway_options[:order_id]}")
|
41
|
+
|
42
|
+
begin
|
43
|
+
mollie_payment = ::Mollie::Payment.create(
|
44
|
+
prepare_payment_params(money_in_cents, source, gateway_options)
|
45
|
+
)
|
46
|
+
MollieLogger.debug("Payment #{mollie_payment.id} created for order #{gateway_options[:order_id]}")
|
47
|
+
|
48
|
+
source.status = mollie_payment.status
|
49
|
+
source.payment_id = mollie_payment.id
|
50
|
+
source.payment_url = mollie_payment.payment_url
|
51
|
+
source.save!
|
52
|
+
ActiveMerchant::Billing::Response.new(true, 'Payment created')
|
53
|
+
rescue Mollie::Exception => e
|
54
|
+
MollieLogger.debug("Could not create payment for order #{gateway_options[:order_id]}: #{e.message}")
|
55
|
+
ActiveMerchant::Billing::Response.new(false, "Payment could not be created: #{e.message}")
|
56
|
+
end
|
41
57
|
end
|
42
58
|
|
59
|
+
# Create a Mollie customer which can be passed with a payment.
|
60
|
+
# Required for one-click Mollie payments.
|
43
61
|
def create_customer(user)
|
44
|
-
Mollie::Customer.create(
|
62
|
+
customer = Mollie::Customer.create(
|
45
63
|
email: user.email,
|
46
64
|
api_key: get_preference(:api_key),
|
47
65
|
)
|
66
|
+
MollieLogger.debug("Created a Mollie Customer for Spree user with ID #{customer.id}")
|
67
|
+
customer
|
48
68
|
end
|
49
69
|
|
50
|
-
def
|
70
|
+
def prepare_payment_params(money_in_cents, source, gateway_options)
|
51
71
|
spree_routes = ::Spree::Core::Engine.routes.url_helpers
|
72
|
+
order_number = gateway_options[:order_id]
|
73
|
+
customer_id = gateway_options[:customer_id]
|
74
|
+
amount = money_in_cents / 100.0
|
52
75
|
|
53
|
-
order_number = order.number
|
54
76
|
order_params = {
|
55
|
-
amount:
|
77
|
+
amount: amount,
|
56
78
|
description: "Spree Order ID: #{order_number}",
|
57
|
-
redirectUrl: spree_routes.mollie_validate_payment_mollie_url(
|
58
|
-
|
79
|
+
redirectUrl: spree_routes.mollie_validate_payment_mollie_url(
|
80
|
+
order_number: order_number,
|
81
|
+
host: get_preference(:hostname)
|
82
|
+
),
|
83
|
+
webhookUrl: spree_routes.mollie_update_payment_status_mollie_url(
|
84
|
+
order_number: order_number,
|
85
|
+
host: get_preference(:hostname)
|
86
|
+
),
|
59
87
|
method: source.payment_method_name,
|
60
88
|
metadata: {
|
61
89
|
order_id: order_number
|
@@ -63,24 +91,54 @@ module Spree
|
|
63
91
|
api_key: get_preference(:api_key),
|
64
92
|
}
|
65
93
|
|
66
|
-
|
94
|
+
source.issuer.present?
|
95
|
+
order_params.merge! ({
|
96
|
+
issuer: source.issuer
|
97
|
+
})
|
98
|
+
|
99
|
+
if customer_id.present?
|
67
100
|
if source.payment_method_name.match(Regexp.union([::Mollie::Method::BITCOIN, ::Mollie::Method::BANKTRANSFER, ::Mollie::Method::GIFTCARD]))
|
68
101
|
order_params.merge! ({
|
69
|
-
billingEmail:
|
102
|
+
billingEmail: gateway_options[:email]
|
70
103
|
})
|
71
104
|
end
|
72
105
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
106
|
+
if Spree::Gateway::MollieGateway.allow_one_click_payments?
|
107
|
+
mollie_customer_id = Spree.user_class.find(customer_id).try(:mollie_customer_id)
|
108
|
+
|
109
|
+
# Allow one-click payments by passing Mollie customer ID.
|
110
|
+
if mollie_customer_id.present?
|
111
|
+
order_params.merge! ({
|
112
|
+
customerId: customer_id
|
113
|
+
})
|
114
|
+
end
|
78
115
|
end
|
79
116
|
end
|
80
117
|
|
81
118
|
order_params
|
82
119
|
end
|
83
120
|
|
121
|
+
# Create a new Mollie refund
|
122
|
+
def credit(credit_cents, payment_id, options)
|
123
|
+
order_number = options[:originator].try(:payment).try(:order).try(:number)
|
124
|
+
MollieLogger.debug("Starting refund for order #{order_number}")
|
125
|
+
|
126
|
+
begin
|
127
|
+
amount = credit_cents / 100.0
|
128
|
+
Mollie::Payment::Refund.create(
|
129
|
+
payment_id: payment_id,
|
130
|
+
amount: amount,
|
131
|
+
description: "Refund Spree Order ID: #{order_number}",
|
132
|
+
api_key: get_preference(:api_key)
|
133
|
+
)
|
134
|
+
MollieLogger.debug("Successfully refunded #{amount} for order #{order_number}")
|
135
|
+
ActiveMerchant::Billing::Response.new(true, 'Refund successful')
|
136
|
+
rescue Mollie::Exception => e
|
137
|
+
MollieLogger.debug("Refund failed for order #{order_number}: #{e.message}")
|
138
|
+
ActiveMerchant::Billing::Response.new(false, 'Refund unsuccessful')
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
84
142
|
def available_payment_methods
|
85
143
|
::Mollie::Method.all(
|
86
144
|
api_key: get_preference(:api_key),
|
@@ -89,35 +147,32 @@ module Spree
|
|
89
147
|
end
|
90
148
|
|
91
149
|
def update_payment_status(payment)
|
92
|
-
mollie_transaction_id = payment.
|
93
|
-
|
150
|
+
mollie_transaction_id = payment.source.payment_id
|
151
|
+
mollie_payment = ::Mollie::Payment.get(
|
94
152
|
mollie_transaction_id,
|
95
153
|
api_key: get_preference(:api_key)
|
96
154
|
)
|
97
155
|
|
98
|
-
|
99
|
-
case transaction.status
|
100
|
-
when 'paid'
|
101
|
-
payment.complete! unless payment.completed?
|
102
|
-
payment.order.finalize!
|
103
|
-
payment.order.update_attributes(:state => 'complete', :completed_at => Time.now)
|
104
|
-
when 'cancelled', 'expired', 'failed'
|
105
|
-
payment.failure! unless payment.failed?
|
106
|
-
else
|
107
|
-
logger.debug 'Unhandled Mollie payment state received. Therefore we did not update the payment state.'
|
108
|
-
end
|
109
|
-
end
|
156
|
+
MollieLogger.debug("Updating order state for payment. Payment has state #{mollie_payment.status}")
|
110
157
|
|
111
|
-
|
158
|
+
update_by_mollie_status!(mollie_payment, payment)
|
112
159
|
end
|
113
160
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
161
|
+
def update_by_mollie_status!(mollie_payment, payment)
|
162
|
+
case mollie_payment.status
|
163
|
+
when 'paid'
|
164
|
+
payment.complete! unless payment.completed?
|
165
|
+
payment.order.finalize!
|
166
|
+
payment.order.update_attributes(:state => 'complete', :completed_at => Time.now)
|
167
|
+
when 'cancelled', 'expired', 'failed'
|
168
|
+
payment.failure! unless payment.failed?
|
169
|
+
when 'refunded'
|
170
|
+
payment.void! unless payment.void?
|
171
|
+
else
|
172
|
+
MollieLogger.debug('Unhandled Mollie payment state received. Therefore we did not update the payment state.')
|
120
173
|
end
|
174
|
+
|
175
|
+
payment.source.update(status: payment.state)
|
121
176
|
end
|
122
177
|
end
|
123
178
|
end
|
@@ -1,14 +1,18 @@
|
|
1
1
|
module Spree
|
2
|
-
class
|
2
|
+
class MolliePaymentSource < Spree::Base
|
3
3
|
belongs_to :payment_method
|
4
|
-
has_many :
|
4
|
+
has_many :payments, as: :source
|
5
5
|
|
6
6
|
def actions
|
7
7
|
[]
|
8
8
|
end
|
9
9
|
|
10
|
+
def transaction_id
|
11
|
+
payment_id
|
12
|
+
end
|
13
|
+
|
10
14
|
def method_type
|
11
|
-
'
|
15
|
+
'mollie_payment_source'
|
12
16
|
end
|
13
17
|
|
14
18
|
def name
|
@@ -0,0 +1,24 @@
|
|
1
|
+
Spree::Order.class_eval do
|
2
|
+
# Make sure the order confirmation is delivered when the order has been paid for.
|
3
|
+
def finalize!
|
4
|
+
# lock all adjustments (coupon promotions, etc.)
|
5
|
+
all_adjustments.each(&:close)
|
6
|
+
|
7
|
+
# update payment and shipment(s) states, and save
|
8
|
+
updater.update_payment_state
|
9
|
+
shipments.each do |shipment|
|
10
|
+
shipment.update!(self)
|
11
|
+
shipment.finalize!
|
12
|
+
end
|
13
|
+
|
14
|
+
updater.update_shipment_state
|
15
|
+
save!
|
16
|
+
updater.run_hooks
|
17
|
+
|
18
|
+
touch :completed_at
|
19
|
+
|
20
|
+
deliver_order_confirmation_email unless confirmation_delivered? or !paid?
|
21
|
+
|
22
|
+
consider_risk
|
23
|
+
end
|
24
|
+
end
|
@@ -1,6 +1,12 @@
|
|
1
1
|
Spree::Payment::Processing.module_eval do
|
2
|
-
def
|
2
|
+
def process!(amount = nil)
|
3
|
+
amount ||= money.money.cents
|
3
4
|
started_processing!
|
4
|
-
|
5
|
+
response = payment_method.create_transaction(
|
6
|
+
amount,
|
7
|
+
source,
|
8
|
+
gateway_options
|
9
|
+
)
|
10
|
+
handle_response(response, :pend, :failure)
|
5
11
|
end
|
6
12
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Spree::Payment.class_eval do
|
2
|
+
delegate :transaction_id, to: :source
|
3
|
+
|
4
|
+
def build_source
|
5
|
+
return unless new_record?
|
6
|
+
if source_attributes.present? && source.blank? && payment_method.try(:payment_source_class)
|
7
|
+
self.source = payment_method.payment_source_class.new(source_attributes)
|
8
|
+
source.payment_method_id = payment_method.id
|
9
|
+
source.user_id = order.user_id if order
|
10
|
+
|
11
|
+
# Spree will not process payments if order is completed.
|
12
|
+
# We should call process! for completed orders to create a new Mollie payment.
|
13
|
+
process! if order.completed?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|