solidus_afterpay 0.1.0

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.
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