Skip to content

Commit f372405

Browse files
Update to Mollie v2 API (#1133)
* feat: update to mollie v3 api * chore: run linter * fix: add ideal as method so we return to Koala on failure * chore: linter ✨ * chore: satisfy linter 🤡 * chore: satisfy linter 🤡 * feat: remove issuer * fix: resolved brakeman warning * fix: resolved linter warning * fix: second attempt at securing mollie redirect --------- Co-authored-by: Silas <[email protected]>
1 parent 8b35a67 commit f372405

File tree

8 files changed

+73
-62
lines changed

8 files changed

+73
-62
lines changed

Gemfile

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ gem 'impressionist', github: 'charlotte-ruby/impressionist'
2727
# rests calls for mailgun
2828
gem 'rest-client'
2929

30+
# mollie
31+
gem 'mollie-api-ruby'
32+
3033
gem 'responders'
3134

3235
# pagination

Gemfile.lock

+2
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ GEM
179179
mini_mime (1.1.2)
180180
mini_portile2 (2.7.1)
181181
minitest (5.15.0)
182+
mollie-api-ruby (4.13.0)
182183
netrc (0.11.0)
183184
nio4r (2.5.8)
184185
nokogiri (1.13.1)
@@ -352,6 +353,7 @@ DEPENDENCIES
352353
impressionist!
353354
listen
354355
mimemagic (= 0.3.9)
356+
mollie-api-ruby
355357
pagy
356358
pg
357359
pg_search

app/controllers/members/payments_controller.rb

+31-6
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,31 @@ def pay_activities
3535
redirect_to(member_payments_path)
3636
return
3737
end
38+
3839
payment = Payment.new(
3940
description: description,
4041
amount: amount,
41-
issuer: transaction_params[:bank],
4242
member: member,
4343
payment_type: :ideal,
4444
transaction_id: unpaid.pluck(:activity_id),
4545
transaction_type: :activity,
4646
redirect_uri: member_payments_path
4747
)
4848
if payment.save
49-
redirect_to(payment.payment_uri)
49+
# Check URI for safety (supresses brakeman warning)
50+
url = begin
51+
URI.parse(payment.payment_uri)
52+
rescue StandardError
53+
nil
54+
end
55+
56+
# Check if it's a valid URI and matches your whitelist of acceptable domains (e.g., only http(s)://example.com)
57+
if url.is_a?(URI::HTTP) && ['mollie.com'].include?(url.host)
58+
redirect_to(url)
59+
else
60+
# Fallback to a safe default redirect if the URI is invalid or not in the whitelist
61+
redirect_to(root_path)
62+
end
5063
else
5164
flash[:notice] = I18n.t('failed', scope: 'activerecord.errors.models.payment')
5265
redirect_to(member_payments_path)
@@ -97,15 +110,27 @@ def add_funds
97110
description: description,
98111
amount: amount,
99112
member: member,
100-
issuer: transaction_params[:bank],
101113
payment_type: :ideal,
102-
103114
transaction_id: nil,
104115
transaction_type: :checkout,
105116
redirect_uri: member_payments_path
106117
)
118+
107119
if payment.save
108-
redirect_to(payment.payment_uri)
120+
# Check URI for safety (supresses brakeman warning)
121+
url = begin
122+
URI.parse(payment.payment_uri)
123+
rescue StandardError
124+
nil
125+
end
126+
127+
# Check if it's a valid URI and matches your whitelist of acceptable domains (e.g., only http(s)://example.com)
128+
if url.is_a?(URI::HTTP) && ['mollie.com'].include?(url.host)
129+
redirect_to(url)
130+
else
131+
# Fallback to a safe default redirect if the URI is invalid or not in the whitelist
132+
redirect_to(root_path)
133+
end
109134
else
110135
flash[:warning] = I18n.t('failed', scope: 'activerecord.errors.models.payment')
111136
redirect_to(members_home_path)
@@ -115,6 +140,6 @@ def add_funds
115140
private
116141

117142
def transaction_params
118-
params.permit(:amount, :bank, :activity_ids, :payment_type)
143+
params.permit(:amount, :activity_ids, :payment_type)
119144
end
120145
end

app/models/payment.rb

+21-50
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
#:nodoc:
22
class Payment < ApplicationRecord
3-
require 'request'
4-
53
self.primary_key = :token
64

75
attr_accessor :issuer, :payment_uri, :message
@@ -12,6 +10,7 @@ class Payment < ApplicationRecord
1210
validates :payment_type, presence: true
1311

1412
enum status: { failed: 0, in_progress: 1, successful: 2 }
13+
1514
# Keep payconiq_online because it is still present in the database
1615
enum payment_type: { ideal: 0, payconiq_online: 1, pin: 3 }
1716
enum transaction_type: { checkout: 0, activity: 1 }
@@ -26,6 +25,7 @@ class Payment < ApplicationRecord
2625
after_validation :request_payment, on: :create
2726

2827
include PgSearch::Model
28+
2929
pg_search_scope :search_by_name,
3030
against: [:trxid],
3131
associated_against: {
@@ -47,54 +47,40 @@ def request_payment
4747

4848
case payment_type.to_sym
4949
when :ideal
50-
http = ConstipatedKoala::Request.new(ENV['MOLLIE_DOMAIN'])
5150
self.token = Digest::SHA256.hexdigest("#{ member.id }#{ Time.now.to_f }#{ redirect_uri }")
5251

5352
webhook_url = if Rails.env.development?
5453
"#{ ENV['NGROK_HOST'] }/api/hook/mollie"
5554
else
5655
Rails.application.routes.url_helpers.mollie_hook_url
5756
end
58-
59-
request = http.post("/#{ ENV['MOLLIE_VERSION'] }/payments",
60-
amount: amount,
61-
description: description,
62-
63-
method: 'ideal',
64-
issuer: issuer,
65-
66-
metadata: {
67-
member: member.name,
68-
transaction_type: transaction_type,
69-
transaction_id: transaction_id
70-
71-
},
72-
webhookUrl: webhook_url,
73-
redirectUrl: Rails.application.routes.url_helpers.payment_redirect_url(token: token))
74-
75-
request['Authorization'] = "Bearer #{ ENV['MOLLIE_TOKEN'] }"
76-
response = http.send!(request)
77-
78-
self.trxid = response.id
79-
self.payment_uri = response.links.paymentUrl
57+
redirect_url = Rails.application.routes.url_helpers.payment_redirect_url(token: token)
58+
59+
payment = Mollie::Payment.create(
60+
amount: { value: format('%.2f', amount), currency: 'EUR' },
61+
method: 'ideal', # only ideal for now
62+
description: description,
63+
webhook_url: webhook_url,
64+
redirect_url: redirect_url
65+
)
66+
67+
self.trxid = payment.id
68+
self.payment_uri = payment._links['checkout']['href']
8069
self.status = :in_progress
81-
# pin payment shouldn't have any extra work
70+
71+
# pin payment shouldn't have any extra work
8272
when :pin
8373
end
8474
end
8575

8676
def update_transaction!
8777
case payment_type.to_sym
8878
when :ideal
89-
http = ConstipatedKoala::Request.new(ENV['MOLLIE_DOMAIN'])
9079
@status = status
9180

92-
request = http.get("/#{ ENV['MOLLIE_VERSION'] }/payments/#{ trxid }")
93-
request['Authorization'] = "Bearer #{ ENV['MOLLIE_TOKEN'] }"
94-
95-
response = http.send!(request)
81+
payment = Mollie::Payment.get(trxid)
9682

97-
status_update(response.status)
83+
status_update(payment.status)
9884

9985
save!
10086

@@ -156,32 +142,17 @@ def transaction_fee
156142
end
157143
end
158144

159-
def self.ideal_issuers
160-
# cache the payment issuers for 12 hours, don't request it to often. Stored in tmp/cache
161-
return [] if ENV['MOLLIE_TOKEN'].blank?
162-
163-
Rails.cache.fetch('mollie_issuers', expires_in: 12.hours) do
164-
http = ConstipatedKoala::Request.new(ENV['MOLLIE_DOMAIN'])
165-
166-
request = http.get("/#{ ENV['MOLLIE_VERSION'] }/issuers")
167-
request['Authorization'] = "Bearer #{ ENV['MOLLIE_TOKEN'] }"
168-
169-
response = http.send!(request)
170-
response.data.map { |issuer| [issuer.name, issuer.id] }
171-
end
172-
end
173-
174145
def activities
175146
Activity.find(transaction_id) if activity?
176147
end
177148

178149
private
179150

180151
def status_update(new_status)
181-
self.status = case new_status.downcase
182-
when "succeeded", "paid"
152+
self.status = case new_status
153+
when "paid", "authorized"
183154
:successful
184-
when "expired", "canceled", "failed", "cancelled", "authorization_failed"
155+
when "expired", "failed", "canceled"
185156
:failed
186157
else
187158
:in_progress

app/views/members/payments/index.html.haml

+1-4
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,10 @@
4242
%tr
4343
%td
4444
%td
45-
.ideal-activities
46-
= I18n.t("members.payments.unpaid_activity.footer.bank")
47-
.float-xl-right= f.select :bank, options_for_select(Payment::ideal_issuers), {}, {style: '', class:'ideal-activities'}
4845
%td
4946
%div
5047
= I18n.t("members.payments.unpaid_activity.footer.transactioncosts")
51-
%span.transaction_cost_activities.ideal-activities{:price =>@transaction_costs}= number_to_currency(@transaction_costs, :unit => '€')
48+
%span.transaction_cost_activities.ideal-activities{:price => @transaction_costs}= number_to_currency(@transaction_costs, :unit => '€')
5249
%tr
5350
%td
5451
%td

config/application.rb

+5
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,8 @@ class Application < Rails::Application
7575
config.middleware.use(I18n::JS::Middleware)
7676
end
7777
end
78+
79+
# Mollie configuration
80+
Mollie::Client.configure do |config|
81+
config.api_key = ENV['MOLLIE_TOKEN']
82+
end

gemset.nix

+10
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,16 @@
713713
};
714714
version = "5.15.0";
715715
};
716+
mollie-api-ruby = {
717+
groups = ["default"];
718+
platforms = [];
719+
source = {
720+
remotes = ["https://rubygems.org"];
721+
sha256 = "0z4z0cf5lq3bmdbjsdzjj2spvg351b32nzwrn9rf3zqm9rldai6w";
722+
type = "gem";
723+
};
724+
version = "4.13.0";
725+
};
716726
netrc = {
717727
groups = ["default"];
718728
platforms = [];

sample.env

-2
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,6 @@ MAILCHIMP_TEACHER_ID=
7676
NGROK_HOST=http
7777

7878
# MOLLIE credentials for the iDEAL integration.
79-
MOLLIE_DOMAIN=https://api.mollie.nl
80-
MOLLIE_VERSION=v1
8179
MOLLIE_TOKEN=
8280

8381
# Secret for error reporting.

0 commit comments

Comments
 (0)