solidus_afterpay 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +41 -0
  3. data/.gem_release.yml +5 -0
  4. data/.github/stale.yml +17 -0
  5. data/.github_changelog_generator +2 -0
  6. data/.gitignore +20 -0
  7. data/.rspec +2 -0
  8. data/.rubocop.yml +14 -0
  9. data/CHANGELOG.md +1 -0
  10. data/Gemfile +33 -0
  11. data/LICENSE +202 -0
  12. data/README.md +175 -0
  13. data/Rakefile +6 -0
  14. data/app/assets/javascripts/solidus_afterpay/afterpay_checkout.js +131 -0
  15. data/app/assets/javascripts/spree/backend/solidus_afterpay.js +2 -0
  16. data/app/assets/javascripts/spree/frontend/solidus_afterpay.js +4 -0
  17. data/app/assets/stylesheets/spree/backend/solidus_afterpay.css +4 -0
  18. data/app/assets/stylesheets/spree/frontend/solidus_afterpay.css +4 -0
  19. data/app/controllers/solidus_afterpay/base_controller.rb +30 -0
  20. data/app/controllers/solidus_afterpay/callbacks_controller.rb +71 -0
  21. data/app/controllers/solidus_afterpay/checkouts_controller.rb +47 -0
  22. data/app/decorators/controllers/solidus_afterpay/spree/checkout_controller_decorator.rb +13 -0
  23. data/app/decorators/models/solidus_afterpay/spree/order_decorator.rb +15 -0
  24. data/app/helpers/solidus_afterpay/afterpay_helper.rb +15 -0
  25. data/app/models/solidus_afterpay/gateway.rb +157 -0
  26. data/app/models/solidus_afterpay/order_component_builder.rb +97 -0
  27. data/app/models/solidus_afterpay/payment_method.rb +56 -0
  28. data/app/models/solidus_afterpay/payment_source.rb +23 -0
  29. data/app/models/solidus_afterpay/user_agent_generator.rb +35 -0
  30. data/app/views/solidus_afterpay/_afterpay_javascript.html.erb +5 -0
  31. data/app/views/spree/admin/payments/source_forms/_afterpay.html.erb +1 -0
  32. data/app/views/spree/admin/payments/source_views/_afterpay.html.erb +0 -0
  33. data/app/views/spree/api/payments/source_views/_afterpay.json.jbuilder +3 -0
  34. data/app/views/spree/checkout/payment/_afterpay.html.erb +9 -0
  35. data/app/views/spree/shared/_afterpay_messaging.html.erb +13 -0
  36. data/bin/console +17 -0
  37. data/bin/rails +7 -0
  38. data/bin/rails-engine +13 -0
  39. data/bin/rails-sandbox +16 -0
  40. data/bin/rake +7 -0
  41. data/bin/sandbox +86 -0
  42. data/bin/setup +8 -0
  43. data/codecov.yml +2 -0
  44. data/config/locales/en.yml +12 -0
  45. data/config/routes.rb +7 -0
  46. data/db/migrate/20210813142725_create_solidus_afterpay_payment_sources.rb +12 -0
  47. data/lib/generators/solidus_afterpay/install/install_generator.rb +43 -0
  48. data/lib/generators/solidus_afterpay/install/templates/initializer.rb +5 -0
  49. data/lib/solidus_afterpay/configuration.rb +25 -0
  50. data/lib/solidus_afterpay/engine.rb +25 -0
  51. data/lib/solidus_afterpay/testing_support/factories.rb +20 -0
  52. data/lib/solidus_afterpay/version.rb +5 -0
  53. data/lib/solidus_afterpay.rb +5 -0
  54. data/solidus_afterpay.gemspec +38 -0
  55. data/spec/fixtures/vcr_casettes/create_checkout/invalid.yml +65 -0
  56. data/spec/fixtures/vcr_casettes/create_checkout/valid.yml +64 -0
  57. data/spec/fixtures/vcr_casettes/credit/invalid.yml +61 -0
  58. data/spec/fixtures/vcr_casettes/credit/valid.yml +63 -0
  59. data/spec/fixtures/vcr_casettes/deferred/authorize/declined_payment.yml +120 -0
  60. data/spec/fixtures/vcr_casettes/deferred/authorize/invalid.yml +61 -0
  61. data/spec/fixtures/vcr_casettes/deferred/authorize/valid.yml +120 -0
  62. data/spec/fixtures/vcr_casettes/deferred/capture/invalid.yml +61 -0
  63. data/spec/fixtures/vcr_casettes/deferred/capture/valid.yml +140 -0
  64. data/spec/fixtures/vcr_casettes/deferred/void/invalid.yml +61 -0
  65. data/spec/fixtures/vcr_casettes/deferred/void/valid.yml +137 -0
  66. data/spec/fixtures/vcr_casettes/find_payment/invalid.yml +61 -0
  67. data/spec/fixtures/vcr_casettes/find_payment/valid.yml +140 -0
  68. data/spec/fixtures/vcr_casettes/immediate/capture/declined_payment.yml +120 -0
  69. data/spec/fixtures/vcr_casettes/immediate/capture/invalid.yml +61 -0
  70. data/spec/fixtures/vcr_casettes/immediate/capture/valid.yml +134 -0
  71. data/spec/fixtures/vcr_casettes/retrieve_configuration/valid.yml +67 -0
  72. data/spec/helpers/solidus_afterpay/afterpay_helper_spec.rb +23 -0
  73. data/spec/models/solidus_afterpay/gateway_spec.rb +418 -0
  74. data/spec/models/solidus_afterpay/order_component_builder_spec.rb +137 -0
  75. data/spec/models/solidus_afterpay/payment_method_spec.rb +143 -0
  76. data/spec/models/solidus_afterpay/payment_source_spec.rb +61 -0
  77. data/spec/models/solidus_afterpay/user_agent_generator_spec.rb +22 -0
  78. data/spec/models/spree/order_spec.rb +158 -0
  79. data/spec/requests/solidus_afterpay/callbacks_controller_spec.rb +127 -0
  80. data/spec/requests/solidus_afterpay/checkouts_controller_spec.rb +190 -0
  81. data/spec/spec_helper.rb +31 -0
  82. data/spec/support/auth.rb +15 -0
  83. data/spec/support/preferences.rb +33 -0
  84. data/spec/support/vcr.rb +18 -0
  85. metadata +249 -0
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'solidus_dev_support/rake_tasks'
4
+ SolidusDevSupport::RakeTasks.install
5
+
6
+ task default: 'extension:specs'
@@ -0,0 +1,131 @@
1
+ $(function () {
2
+ function enableSubmit() {
3
+ /* If we're using jquery-ujs on the frontend, it will automatically disable
4
+ * the submit button, but do so in a setTimeout here:
5
+ * https://github.com/rails/jquery-rails/blob/master/vendor/assets/javascripts/jquery_ujs.js#L517
6
+ * The only way we can re-enable it is by delaying longer than that timeout
7
+ * or stopping propagation so their submit handler doesn't run. */
8
+ if ($.rails && typeof $.rails.enableFormElement !== "undefined") {
9
+ setTimeout(function () {
10
+ $.rails.enableFormElement($submitButton);
11
+ $submitButton
12
+ .attr("disabled", false)
13
+ .removeClass("disabled")
14
+ .addClass("primary");
15
+ }, 100);
16
+ } else if (
17
+ typeof Rails !== "undefined" &&
18
+ typeof Rails.enableElement !== "undefined"
19
+ ) {
20
+ /* Indicates that we have rails-ujs instead of jquery-ujs. Rails-ujs was added to rails
21
+ * core in Rails 5.1.0 */
22
+ setTimeout(function () {
23
+ Rails.enableElement($submitButton[0]);
24
+ $submitButton
25
+ .attr("disabled", false)
26
+ .removeClass("disabled")
27
+ .addClass("primary");
28
+ }, 100);
29
+ } else {
30
+ $submitButton
31
+ .attr("disabled", false)
32
+ .removeClass("disabled")
33
+ .addClass("primary");
34
+ }
35
+ }
36
+
37
+ function disableSubmit() {
38
+ $submitButton
39
+ .attr("disabled", true)
40
+ .removeClass("primary")
41
+ .addClass("disabled");
42
+ }
43
+
44
+ function addFormHook() {
45
+ $paymentForm.on("submit", function (event) {
46
+ var selectedPaymentMethodId = parseInt(
47
+ $("#payment-method-fields input[type='radio']:checked").val()
48
+ );
49
+
50
+ if (paymentMethodId === selectedPaymentMethodId) {
51
+ event.preventDefault();
52
+ disableSubmit();
53
+
54
+ Spree.ajax({
55
+ method: "POST",
56
+ url: "/solidus_afterpay/checkouts.json",
57
+ data: {
58
+ order_number: orderNumber,
59
+ payment_method_id: paymentMethodId,
60
+ },
61
+ })
62
+ .success(function (response) {
63
+ onSuccess(response);
64
+ })
65
+ .error(function (response) {
66
+ onError(response);
67
+ });
68
+ }
69
+ });
70
+ }
71
+
72
+ function onSuccess(response) {
73
+ AfterPay.initialize({ countryCode: "US" });
74
+ if (popupWindow) {
75
+ openAfterpayPopup(response);
76
+ } else {
77
+ AfterPay.redirect({ token: response.token });
78
+ }
79
+ }
80
+
81
+ function onError(response) {
82
+ enableSubmit();
83
+ }
84
+
85
+ function openAfterpayPopup(response) {
86
+ AfterPay.onComplete = function (event) {
87
+ if (event.data.status == "SUCCESS") {
88
+ onAfterpaySuccess(event);
89
+ } else {
90
+ onAfterpayCancel(event);
91
+ }
92
+ };
93
+ AfterPay.open();
94
+ AfterPay.transfer({ token: response.token });
95
+ }
96
+
97
+ function onAfterpaySuccess(event) {
98
+ Spree.ajax({
99
+ method: "POST",
100
+ url: "/solidus_afterpay/callbacks/confirm.json",
101
+ data: {
102
+ order_number: orderNumber,
103
+ payment_method_id: paymentMethodId,
104
+ order_token: event.data.orderToken,
105
+ },
106
+ })
107
+ .success(function (response) {
108
+ window.location.href = response.redirect_url;
109
+ })
110
+ .error(function (response) {
111
+ enableSubmit();
112
+ });
113
+ }
114
+
115
+ function onAfterpayCancel(event) {
116
+ enableSubmit();
117
+ }
118
+
119
+ if ($("#afterpay_checkout_payload").length > 0) {
120
+ var $paymentForm = $("#checkout_form_payment");
121
+ var $submitButton = $("input[type='submit']", $paymentForm);
122
+
123
+ var paymentMethodId = $("#afterpay_checkout_payload").data(
124
+ "payment-method-id"
125
+ );
126
+ var orderNumber = $("#afterpay_checkout_payload").data("order-number");
127
+ var popupWindow = $("#afterpay_checkout_payload").data("popup-window");
128
+
129
+ addFormHook();
130
+ }
131
+ });
@@ -0,0 +1,2 @@
1
+ // Placeholder manifest file.
2
+ // the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/backend/all.js'
@@ -0,0 +1,4 @@
1
+ // Placeholder manifest file.
2
+ // the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/frontend/all.js'
3
+
4
+ //= require solidus_afterpay/afterpay_checkout
@@ -0,0 +1,4 @@
1
+ /*
2
+ Placeholder manifest file.
3
+ the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/backend/all.css'
4
+ */
@@ -0,0 +1,4 @@
1
+ /*
2
+ Placeholder manifest file.
3
+ the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/frontend/all.css'
4
+ */
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusAfterpay
4
+ class BaseController < ::Spree::BaseController
5
+ protect_from_forgery unless: -> { request.format.json? }
6
+
7
+ rescue_from ::ActiveRecord::RecordNotFound, with: :resource_not_found
8
+ rescue_from ::CanCan::AccessDenied, with: :unauthorized
9
+
10
+ private
11
+
12
+ def order_token
13
+ cookies.signed[:guest_token]
14
+ end
15
+
16
+ def resource_not_found
17
+ respond_to do |format|
18
+ format.html { redirect_to spree.cart_path, notice: I18n.t('solidus_afterpay.resource_not_found') }
19
+ format.json { render json: { error: I18n.t('solidus_afterpay.resource_not_found') }, status: :not_found }
20
+ end
21
+ end
22
+
23
+ def unauthorized
24
+ respond_to do |format|
25
+ format.html { redirect_to spree.cart_path, notice: I18n.t('solidus_afterpay.unauthorized') }
26
+ format.json { render json: { error: I18n.t('solidus_afterpay.unauthorized') }, status: :unauthorized }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusAfterpay
4
+ class CallbacksController < SolidusAfterpay::BaseController
5
+ before_action :ensure_afterpay_order_token_presence, only: :confirm
6
+ before_action :ensure_order_not_completed, only: :confirm
7
+
8
+ def confirm
9
+ authorize! :update, order, order_token
10
+
11
+ if ::Spree::OrderUpdateAttributes.new(order, update_params, request_env: request.headers.env).apply
12
+ order.next
13
+ end
14
+
15
+ respond_to do |format|
16
+ format.html { redirect_to checkout_state_path(order.state) }
17
+ format.json { render json: { redirect_url: checkout_state_url(order.state) } }
18
+ end
19
+ end
20
+
21
+ def cancel
22
+ redirect_to checkout_state_path(order.state)
23
+ end
24
+
25
+ private
26
+
27
+ def update_params
28
+ {
29
+ payments_attributes: [{
30
+ payment_method: payment_method,
31
+ amount: order.total,
32
+ source_attributes: {
33
+ token: afterpay_order_token
34
+ }
35
+ }]
36
+ }
37
+ end
38
+
39
+ def order
40
+ @order ||= ::Spree::Order.find_by!(number: params[:order_number])
41
+ end
42
+
43
+ def payment_method
44
+ @payment_method ||= SolidusAfterpay::PaymentMethod.active.find(params[:payment_method_id])
45
+ end
46
+
47
+ def afterpay_order_token
48
+ params[:orderToken] || params[:order_token]
49
+ end
50
+
51
+ def ensure_afterpay_order_token_presence
52
+ return if afterpay_order_token
53
+
54
+ respond_to do |format|
55
+ format.html {
56
+ redirect_to checkout_state_path(order.state), notice: I18n.t('solidus_afterpay.order_token_not_found')
57
+ }
58
+ format.json { render json: { error: I18n.t('solidus_afterpay.order_token_not_found') }, status: :not_found }
59
+ end
60
+ end
61
+
62
+ def ensure_order_not_completed
63
+ return unless order.complete?
64
+
65
+ respond_to do |format|
66
+ format.html { redirect_to spree.order_path(order), notice: I18n.t('spree.order_already_completed') }
67
+ format.json { render json: { error: I18n.t('spree.order_already_completed') }, status: :unprocessable_entity }
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusAfterpay
4
+ class CheckoutsController < SolidusAfterpay.api_base_controller_parent_class
5
+ def create
6
+ authorize! :update, order, order_token
7
+
8
+ response = payment_method.gateway.create_checkout(
9
+ order,
10
+ redirect_confirm_url: redirect_confirm_url,
11
+ redirect_cancel_url: redirect_cancel_url
12
+ )
13
+
14
+ if response.success?
15
+ render json: {
16
+ token: response.params['token'],
17
+ expires: response.params['expires'],
18
+ redirectCheckoutUrl: response.params['redirectCheckoutUrl']
19
+ }, status: :created
20
+ else
21
+ render json: { error: response.message, errorCode: response.error_code }, status: :unprocessable_entity
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def order
28
+ @order ||= ::Spree::Order.find_by!(number: params[:order_number])
29
+ end
30
+
31
+ def payment_method
32
+ @payment_method ||= SolidusAfterpay::PaymentMethod.active.find(params[:payment_method_id])
33
+ end
34
+
35
+ def redirect_confirm_url
36
+ params[:redirect_confirm_url] || solidus_afterpay.callbacks_confirm_url(
37
+ order_number: order.number, payment_method_id: payment_method.id
38
+ )
39
+ end
40
+
41
+ def redirect_cancel_url
42
+ params[:redirect_cancel_url] || solidus_afterpay.callbacks_cancel_url(
43
+ order_number: order.number, payment_method_id: payment_method.id
44
+ )
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusAfterpay
4
+ module Spree
5
+ module CheckoutControllerDecorator
6
+ def self.prepended(base)
7
+ base.helper ::SolidusAfterpay::AfterpayHelper
8
+ end
9
+
10
+ ::Spree::CheckoutController.prepend(self) if SolidusSupport.frontend_available?
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusAfterpay
4
+ module Spree
5
+ module OrderDecorator
6
+ def available_payment_methods
7
+ @available_payment_methods ||= super.select do |payment_method|
8
+ !payment_method.respond_to?(:available_for_order?) || payment_method.available_for_order?(self)
9
+ end
10
+ end
11
+
12
+ ::Spree::Order.prepend self
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusAfterpay
4
+ module AfterpayHelper
5
+ def include_afterpay_js(test_mode: false)
6
+ afterpay_js_url = if test_mode
7
+ 'https://portal.sandbox.afterpay.com/afterpay.js'
8
+ else
9
+ 'https://portal.afterpay.com/afterpay.js'
10
+ end
11
+
12
+ javascript_include_tag afterpay_js_url
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'afterpay'
4
+
5
+ module SolidusAfterpay
6
+ class Gateway
7
+ VOIDABLE_STATUSES = ['AUTH_APPROVED', 'PARTIALLY_CAPTURED'].freeze
8
+
9
+ def initialize(options)
10
+ ::Afterpay.configure do |config|
11
+ config.merchant_id = options[:merchant_id]
12
+ config.secret_key = options[:secret_key]
13
+ config.server = 'https://global-api-sandbox.afterpay.com/' if options[:test_mode]
14
+ config.user_agent = SolidusAfterpay::UserAgentGenerator.new(merchant_id: options[:merchant_id]).generate
15
+ end
16
+ end
17
+
18
+ def authorize(_amount, payment_source, _gateway_options)
19
+ result = {}
20
+
21
+ if payment_source.payment_method.preferred_deferred
22
+ response = ::Afterpay::API::Payment::Auth.call(
23
+ payment: ::Afterpay::Components::Payment.new(token: payment_source.token)
24
+ )
25
+ result = response.body
26
+ end
27
+
28
+ ActiveMerchant::Billing::Response.new(true, 'Transaction approved', result, authorization: result[:id])
29
+ rescue ::Afterpay::BaseError => e
30
+ message = e.message
31
+ error_code = e.error_code
32
+ if message == 'Afterpay::PaymentRequiredError'
33
+ message = I18n.t('solidus_afterpay.payment_declined')
34
+ error_code = 'payment_declined'
35
+ end
36
+ ActiveMerchant::Billing::Response.new(false, message, {}, error_code: error_code)
37
+ end
38
+
39
+ def capture(amount, response_code, gateway_options)
40
+ payment_method = gateway_options[:originator].payment_method
41
+
42
+ response = if payment_method.preferred_deferred
43
+ deferred_capture(amount, response_code, gateway_options)
44
+ else
45
+ immediate_capture(amount, response_code, gateway_options)
46
+ end
47
+ result = response.body
48
+
49
+ if result.status != 'APPROVED'
50
+ raise ::Afterpay::BaseError.new('payment_declined'), I18n.t('solidus_afterpay.payment_declined')
51
+ end
52
+
53
+ ActiveMerchant::Billing::Response.new(true, 'Transaction captured', result, authorization: result.id)
54
+ rescue ::Afterpay::BaseError => e
55
+ ActiveMerchant::Billing::Response.new(false, e.message, {}, error_code: e.error_code)
56
+ end
57
+
58
+ def purchase(amount, payment_source, gateway_options)
59
+ result = authorize(amount, payment_source, gateway_options)
60
+ return result unless result.success?
61
+
62
+ capture(amount, result.authorization, gateway_options)
63
+ end
64
+
65
+ def credit(amount, response_code, gateway_options)
66
+ response = ::Afterpay::API::Payment::Refund.call(
67
+ order_id: response_code,
68
+ refund: ::Afterpay::Components::Refund.new(
69
+ amount: ::Afterpay::Components::Money.new(
70
+ amount: Money.from_cents(amount).amount.to_s,
71
+ currency: gateway_options[:originator].payment.currency
72
+ ),
73
+ merchant_reference: gateway_options[:originator].payment.id
74
+ )
75
+ )
76
+ result = response.body
77
+
78
+ ActiveMerchant::Billing::Response.new(true, "Transaction Credited with #{amount}", result,
79
+ authorization: result.refundId)
80
+ rescue ::Afterpay::BaseError => e
81
+ ActiveMerchant::Billing::Response.new(false, e.message, {}, error_code: e.error_code)
82
+ end
83
+
84
+ def void(response_code, gateway_options)
85
+ payment_method = gateway_options[:originator].payment_method
86
+
87
+ unless payment_method.preferred_deferred
88
+ return ActiveMerchant::Billing::Response.new(false, "Transaction can't be voided", {},
89
+ error_code: 'void_not_allowed')
90
+ end
91
+
92
+ response = ::Afterpay::API::Payment::Void.call(
93
+ order_id: response_code,
94
+ payment: ::Afterpay::Components::Payment.new(
95
+ amount: ::Afterpay::Components::Money.new(
96
+ amount: gateway_options[:originator].amount.to_s,
97
+ currency: gateway_options[:currency]
98
+ )
99
+ )
100
+ )
101
+ result = response.body
102
+
103
+ ActiveMerchant::Billing::Response.new(true, 'Transaction voided', result, authorization: result.id)
104
+ rescue ::Afterpay::BaseError => e
105
+ ActiveMerchant::Billing::Response.new(false, e.message, {}, error_code: e.error_code)
106
+ end
107
+
108
+ def create_checkout(order, gateway_options)
109
+ response = ::Afterpay::API::Order::Create.call(
110
+ order: SolidusAfterpay::OrderComponentBuilder.new(
111
+ order: order,
112
+ redirect_confirm_url: gateway_options[:redirect_confirm_url],
113
+ redirect_cancel_url: gateway_options[:redirect_cancel_url]
114
+ ).call
115
+ )
116
+ result = response.body
117
+
118
+ ActiveMerchant::Billing::Response.new(true, 'Checkout created', result)
119
+ rescue ::Afterpay::BaseError => e
120
+ ActiveMerchant::Billing::Response.new(false, e.message, {}, error_code: e.error_code)
121
+ end
122
+
123
+ def find_payment(order_id:)
124
+ ::Afterpay::API::Payment::Find.call(order_id: order_id).body
125
+ rescue ::Afterpay::BaseError
126
+ nil
127
+ end
128
+
129
+ def retrieve_configuration
130
+ ::Afterpay::API::Configuration::Retrieve.call.body
131
+ rescue ::Afterpay::BaseError
132
+ nil
133
+ end
134
+
135
+ private
136
+
137
+ def immediate_capture(_amount, _response_code, gateway_options)
138
+ payment_source = gateway_options[:originator].payment_source
139
+
140
+ ::Afterpay::API::Payment::Capture.call(
141
+ payment: ::Afterpay::Components::Payment.new(token: payment_source.token)
142
+ )
143
+ end
144
+
145
+ def deferred_capture(amount, response_code, gateway_options)
146
+ ::Afterpay::API::Payment::DeferredCapture.call(
147
+ order_id: response_code,
148
+ payment: ::Afterpay::Components::Payment.new(
149
+ amount: ::Afterpay::Components::Money.new(
150
+ amount: Money.from_cents(amount).amount.to_s,
151
+ currency: gateway_options[:currency]
152
+ )
153
+ )
154
+ )
155
+ end
156
+ end
157
+ end