solidus_afterpay 0.1.0 → 0.3.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +9 -0
  3. data/Gemfile +6 -3
  4. data/README.md +55 -23
  5. data/app/assets/javascripts/solidus_afterpay/afterpay_checkout.js +11 -11
  6. data/app/assets/javascripts/solidus_afterpay/afterpay_checkout_button.js +85 -0
  7. data/app/assets/javascripts/solidus_afterpay/afterpay_init.js +12 -0
  8. data/app/assets/javascripts/solidus_afterpay/backend/afterpay_autocomplete.js +9 -0
  9. data/app/assets/javascripts/spree/backend/solidus_afterpay.js +3 -1
  10. data/app/assets/javascripts/spree/frontend/solidus_afterpay.js +2 -0
  11. data/app/controllers/solidus_afterpay/callbacks_controller.rb +5 -1
  12. data/app/controllers/solidus_afterpay/checkouts_controller.rb +8 -2
  13. data/app/controllers/solidus_afterpay/express_callbacks_controller.rb +65 -0
  14. data/app/decorators/controllers/solidus_afterpay/spree/orders_controller_decorator.rb +13 -0
  15. data/app/helpers/solidus_afterpay/afterpay_helper.rb +5 -4
  16. data/app/models/solidus_afterpay/gateway.rb +37 -10
  17. data/app/models/solidus_afterpay/order_component_builder.rb +34 -5
  18. data/app/models/solidus_afterpay/payment_method.rb +43 -10
  19. data/app/models/solidus_afterpay/payment_source.rb +1 -1
  20. data/app/presentes/solidus_afterpay/order_presenter.rb +17 -0
  21. data/app/presentes/solidus_afterpay/shipping_rate_presenter.rb +28 -0
  22. data/app/services/solidus_afterpay/base_service.rb +13 -0
  23. data/app/services/solidus_afterpay/shipping_rate_builder_service.rb +32 -0
  24. data/app/services/solidus_afterpay/update_order_addresses_service.rb +45 -0
  25. data/app/services/solidus_afterpay/update_order_attributes_service.rb +49 -0
  26. data/app/views/solidus_afterpay/_afterpay_checkout_button.html.erb +9 -0
  27. data/app/views/solidus_afterpay/_afterpay_javascript.html.erb +4 -1
  28. data/app/views/spree/api/payments/source_views/_afterpay.json.jbuilder +1 -1
  29. data/app/views/spree/shared/_afterpay_messaging.html.erb +14 -12
  30. data/bin/sandbox +1 -1
  31. data/config/locales/en.yml +4 -0
  32. data/config/routes.rb +2 -0
  33. data/lib/generators/solidus_afterpay/install/templates/initializer.rb +12 -0
  34. data/lib/solidus_afterpay/configuration.rb +29 -0
  35. data/lib/solidus_afterpay/engine.rb +1 -1
  36. data/lib/solidus_afterpay/testing_support/factories.rb +20 -0
  37. data/lib/solidus_afterpay/version.rb +1 -1
  38. data/solidus_afterpay.gemspec +4 -4
  39. metadata +23 -73
  40. data/spec/fixtures/vcr_casettes/create_checkout/invalid.yml +0 -65
  41. data/spec/fixtures/vcr_casettes/create_checkout/valid.yml +0 -64
  42. data/spec/fixtures/vcr_casettes/credit/invalid.yml +0 -61
  43. data/spec/fixtures/vcr_casettes/credit/valid.yml +0 -63
  44. data/spec/fixtures/vcr_casettes/deferred/authorize/declined_payment.yml +0 -120
  45. data/spec/fixtures/vcr_casettes/deferred/authorize/invalid.yml +0 -61
  46. data/spec/fixtures/vcr_casettes/deferred/authorize/valid.yml +0 -120
  47. data/spec/fixtures/vcr_casettes/deferred/capture/invalid.yml +0 -61
  48. data/spec/fixtures/vcr_casettes/deferred/capture/valid.yml +0 -140
  49. data/spec/fixtures/vcr_casettes/deferred/void/invalid.yml +0 -61
  50. data/spec/fixtures/vcr_casettes/deferred/void/valid.yml +0 -137
  51. data/spec/fixtures/vcr_casettes/find_payment/invalid.yml +0 -61
  52. data/spec/fixtures/vcr_casettes/find_payment/valid.yml +0 -140
  53. data/spec/fixtures/vcr_casettes/immediate/capture/declined_payment.yml +0 -120
  54. data/spec/fixtures/vcr_casettes/immediate/capture/invalid.yml +0 -61
  55. data/spec/fixtures/vcr_casettes/immediate/capture/valid.yml +0 -134
  56. data/spec/fixtures/vcr_casettes/retrieve_configuration/valid.yml +0 -67
  57. data/spec/helpers/solidus_afterpay/afterpay_helper_spec.rb +0 -23
  58. data/spec/models/solidus_afterpay/gateway_spec.rb +0 -418
  59. data/spec/models/solidus_afterpay/order_component_builder_spec.rb +0 -137
  60. data/spec/models/solidus_afterpay/payment_method_spec.rb +0 -143
  61. data/spec/models/solidus_afterpay/payment_source_spec.rb +0 -61
  62. data/spec/models/solidus_afterpay/user_agent_generator_spec.rb +0 -22
  63. data/spec/models/spree/order_spec.rb +0 -158
  64. data/spec/requests/solidus_afterpay/callbacks_controller_spec.rb +0 -127
  65. data/spec/requests/solidus_afterpay/checkouts_controller_spec.rb +0 -190
  66. data/spec/spec_helper.rb +0 -31
  67. data/spec/support/auth.rb +0 -15
  68. data/spec/support/preferences.rb +0 -33
  69. data/spec/support/vcr.rb +0 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff5d1583df1845d1d0f620355fe613076850dd3f8d7a534a9311b2c91aadf01f
4
- data.tar.gz: f657c4a2d45062e63333bb8ec8932fb1e6d45785df730e93a242ef95334778bc
3
+ metadata.gz: 7c625c84548f8f458a709562e6886ef98373fb169590ea41c251a9aeb39e2566
4
+ data.tar.gz: '08c9f16f2da6d99d019729855d47a024bcf9894f568e22cd2758b56df17c83fe'
5
5
  SHA512:
6
- metadata.gz: 59b6adb36612219fdafa8b03fbce5b5315b8c14ca700af128075fb87979968c909f402e967beba8b494d3e03d091172b2c05fb2671b56d3332d0accb2dd6b770
7
- data.tar.gz: 02feaeda3db7d7403e0b67289b57c3b88ed945911da446de1fc546f48ba0a0cff08152f2aed37167dd22cf1d42faf99ce8863ec5050dd9840b04d566c2684bb1
6
+ metadata.gz: 46d7d8c3ed552931481af46fe657ca9af47370d80e220bb98b22162fb2d1ca322b818350341913a624b1ef09782804306055b4460e9ff9c5a8749943145288cf
7
+ data.tar.gz: 63c693f3abbd4c1f43b823899253a69cf02a216b3524ad5f48ee66922cc33e7dcc6150d6f743f6f50748b7ffa06be587926e6c58e57de70bd5e6fc33fce98784
data/.rubocop.yml CHANGED
@@ -12,3 +12,12 @@ RSpec/MultipleMemoizedHelpers:
12
12
 
13
13
  RSpec/NestedGroups:
14
14
  Enabled: false
15
+
16
+ Rspec/RemoveConst:
17
+ Enabled: false
18
+
19
+ RSpec/DescribeClass:
20
+ Enabled: false
21
+
22
+ Gemspec/DevelopmentDependencies:
23
+ Enabled: false
data/Gemfile CHANGED
@@ -3,7 +3,7 @@
3
3
  source 'https://rubygems.org'
4
4
  git_source(:github) { |repo| "https://github.com/#{repo}.git" }
5
5
 
6
- branch = ENV.fetch('SOLIDUS_BRANCH', 'master')
6
+ branch = ENV.fetch('SOLIDUS_BRANCH', 'main')
7
7
  gem 'solidus', github: 'solidusio/solidus', branch: branch
8
8
 
9
9
  # Needed to help Bundler figure out how to resolve dependencies,
@@ -14,13 +14,16 @@ gem 'rails', '>0.a'
14
14
  # Provides basic authentication functionality for testing parts of your engine
15
15
  gem 'solidus_auth_devise'
16
16
 
17
- case ENV['DB']
17
+ # The inclusion of this gem has been removed from the dev support gem in recent versions.
18
+ gem 'solidus_frontend'
19
+
20
+ case ENV.fetch('DB', nil)
18
21
  when 'mysql'
19
22
  gem 'mysql2'
20
23
  when 'postgresql'
21
24
  gem 'pg'
22
25
  else
23
- gem 'sqlite3'
26
+ gem 'sqlite3', '~> 1.4'
24
27
  end
25
28
 
26
29
  gemspec
data/README.md CHANGED
@@ -1,9 +1,10 @@
1
+ [![CircleCI](https://circleci.com/gh/nebulab/solidus_afterpay.svg?style=shield)](https://circleci.com/gh/nebulab/solidus_afterpay)
2
+ [![codecov](https://codecov.io/gh/nebulab/solidus_afterpay/branch/main/graph/badge.svg)](https://codecov.io/gh/solidusio/solidus_afterpay)
1
3
  # Solidus Afterpay
2
4
 
3
- [![CircleCI](https://circleci.com/gh/solidusio-contrib/solidus_afterpay.svg?style=shield)](https://circleci.com/gh/solidusio-contrib/solidus_afterpay)
4
- [![codecov](https://codecov.io/gh/solidusio-contrib/solidus_afterpay/branch/master/graph/badge.svg)](https://codecov.io/gh/solidusio-contrib/solidus_afterpay)
5
-
6
5
  <!-- Explain what your extension does. -->
6
+ > [!IMPORTANT]
7
+ > This gem still depends on the legacy [solidus_frontend](https://github.com/solidusio/solidus_frontend) gem being available in the Solidus application.
7
8
 
8
9
  ## Installation
9
10
 
@@ -18,68 +19,98 @@ Bundle your dependencies and run the installation generator:
18
19
  ```shell
19
20
  bin/rails generate solidus_afterpay:install
20
21
  ```
22
+
21
23
  ## Basic Setup
22
24
 
23
25
  ### Retrieve Afterpay account details
26
+
24
27
  You'll need the following account details:
28
+
25
29
  - `Merchant ID`
26
30
  - `Secret key`
27
31
 
28
32
  These values can be obtained by calling the `Merchant Support` [here](https://developers.afterpay.com/afterpay-online/docs/merchant-support).
29
33
 
30
34
  ### Create a new payment method
35
+
31
36
  Payment methods can accept preferences either directly entered in admin, or from a static source in code. For most projects we recommend using a static source, so that sensitive account credentials are not stored in the database.
32
37
 
33
38
  1. Set static preferences in an initializer
34
- ```ruby
35
- # config/initializers/spree.rb
36
- Spree::Config.configure do |config|
37
- config.static_model_preferences.add(
38
- SolidusAfterpay::PaymentMethod,
39
- 'afterpay_credentials', {
40
- merchant_id: ENV['AFTERPAY_MERCHANT_ID'],
41
- secret_key: ENV['AFTERPAY_SECRET_KEY'],
42
- }
43
- )
44
- end
45
- ```
46
39
 
47
- 2. Visit `/admin/payment_methods/new`
40
+ ```ruby
41
+ # config/initializers/spree.rb
42
+ Spree::Config.configure do |config|
43
+ config.static_model_preferences.add(
44
+ SolidusAfterpay::PaymentMethod,
45
+ 'afterpay_credentials', {
46
+ merchant_id: ENV['AFTERPAY_MERCHANT_ID'],
47
+ secret_key: ENV['AFTERPAY_SECRET_KEY'],
48
+ test_mode: ENV.fetch('AFTERPAY_ENVIRONMENT', '') != 'production'
49
+ }
50
+ )
51
+ end
52
+ ```
48
53
 
54
+ 2. Visit `/admin/payment_methods/new`
49
55
  3. Set `provider` to SolidusAfterpay::PaymentMethod
50
-
51
56
  4. Click "Save"
52
-
53
57
  5. Choose `afterpay_credentials` from the `Preference Source` select
54
-
55
58
  6. Click `Update` to save
56
59
 
57
60
  Alternatively, create a payment method from the Rails console with:
61
+
58
62
  ```ruby
59
63
  SolidusAfterpay::PaymentMethod.new(
60
64
  name: "Afterpay",
61
65
  preference_source: "afterpay_credentials"
62
66
  ).save
63
67
  ```
68
+
64
69
  ## Deferred Payment Flow
65
70
 
66
71
  This flow completes the payment approval and starts the consumer's payment plan, but does not initiate the settlement process. This flow allows settlement of merchant funds to be deferred until order fulfilment can be confirmed.
67
72
 
68
- Simply check the deferred checkbox when creating the Afterpay payment_method to activate the deferred payment flow instead of the immediate payment flow.
73
+ Simply set the auto_capture to false when creating the Afterpay payment_method to activate the deferred payment flow instead of the immediate payment flow.
69
74
 
70
75
  For more info about the deferred payment flow click [here](https://developers.afterpay.com/afterpay-online/reference#deferred-payment-flow).
71
76
 
72
77
  ## Usage
73
78
 
79
+ ### Customizing shipping rate builder
80
+
81
+ By default, the extension will build the shipping rates based on the default Solidus shipments building the Afterpay array.
82
+
83
+ If you want to override this logic, you can provide your own `shipping_rate_calculator_class`.
84
+
85
+ ### Customizing update order attributes service
86
+
87
+ By default, the extension will update the order payment_attributes, order email attribute and shipments attributes based on the Afterpay returned data.
88
+
89
+ If you want to override this logic, adding/removing attributes, you can provide your own `update_order_attributes_service_class`.
90
+
91
+ ### Express checkout from the cart
92
+
93
+ An Afterpay button can also be included on the cart view to enable express checkouts:
94
+
95
+ ```ruby
96
+ <%= render "solidus_afterpay/afterpay_checkout_button" %>
97
+ ```
98
+
74
99
  ### Afterpay Messaging
75
100
 
76
101
  Afterpay offers an on-site messaging component to notify the customer that there are financing options available.
77
102
 
78
103
  To add the `Afterpay messaging` simply add the `Afterpay messaging partial` into your `html.erb` file, like this.
79
104
 
105
+ You need to provide the products as well, so you can exclude products from `Afterpay messaging`. It is required to
106
+ add the product in an array for example: `products: [<product>]`, or for multiple products: `products: [<product1>, <product2>]`.
107
+
108
+ If you only have the order you can do it like this `products: order.line_items.map { |item| item.variant.product }`.
109
+
80
110
  ```erb
81
- <%= render "spree/shared/afterpay_messaging", min: nil, max: nil, data: { amount: <Product price>, locale: "en_US", currency: "USD" } %>
111
+ <%= render "spree/shared/afterpay_messaging", min: nil, max: nil, products: [<Product>], data: { amount: <Product price>, locale: "en_US", currency: "USD" } %>
82
112
  ```
113
+
83
114
  The amount, locale and currency are required in order to work properly.
84
115
 
85
116
  This will automatically render an Afterpay messaging icon.
@@ -91,8 +122,9 @@ The min attribute is to configure from which amount Afterpay should be available
91
122
  For example if you would write...
92
123
 
93
124
  ```erb
94
- <%= render "spree/shared/afterpay_messaging", min: nil, max: 25, data: { amount: <Product price>, locale: "en_US", currency: "USD" } %>
125
+ <%= render "spree/shared/afterpay_messaging", min: nil, max: 25, products: [<Product>], data: { amount: <Product price>, locale: "en_US", currency: "USD" } %>
95
126
  ```
127
+
96
128
  And a product price is `28.99`, Afterpay will display on that product that Afterpay is only available for orders between 1$ and 25$.
97
129
 
98
130
  The default value for min is 1$.
@@ -104,7 +136,7 @@ Click [here](https://developers.afterpay.com/afterpay-online/docs/advanced-usage
104
136
  If you would like to change the size of the Afterpay messaging you simply add size to the `data` hash. For example...
105
137
 
106
138
  ```erb
107
- <%= render "spree/shared/afterpay_messaging", min: nil, max: nil, data: { amount: <Product price>, locale: "en_US", currency: "USD", size: "sm" } %>
139
+ <%= render "spree/shared/afterpay_messaging", min: nil, max: nil, product: [<Product>], data: { amount: <Product price>, locale: "en_US", currency: "USD", size: "sm" } %>
108
140
  ```
109
141
 
110
142
  ## Development
@@ -1,4 +1,4 @@
1
- $(function () {
1
+ $(document).bind("afterpay.loaded", function () {
2
2
  function enableSubmit() {
3
3
  /* If we're using jquery-ujs on the frontend, it will automatically disable
4
4
  * the submit button, but do so in a setTimeout here:
@@ -58,13 +58,13 @@ $(function () {
58
58
  order_number: orderNumber,
59
59
  payment_method_id: paymentMethodId,
60
60
  },
61
- })
62
- .success(function (response) {
61
+ success: function (response) {
63
62
  onSuccess(response);
64
- })
65
- .error(function (response) {
63
+ },
64
+ error: function (response) {
66
65
  onError(response);
67
- });
66
+ }
67
+ })
68
68
  }
69
69
  });
70
70
  }
@@ -103,13 +103,13 @@ $(function () {
103
103
  payment_method_id: paymentMethodId,
104
104
  order_token: event.data.orderToken,
105
105
  },
106
- })
107
- .success(function (response) {
106
+ success: function (response) {
108
107
  window.location.href = response.redirect_url;
109
- })
110
- .error(function (response) {
108
+ },
109
+ error: function (response) {
111
110
  enableSubmit();
112
- });
111
+ }
112
+ })
113
113
  }
114
114
 
115
115
  function onAfterpayCancel(event) {
@@ -0,0 +1,85 @@
1
+ $(document).bind("afterpay.loaded", function () {
2
+ function initializeExpressCheckout(selector) {
3
+ AfterPay.initializeForPopup({
4
+ countryCode: "US",
5
+ onCommenceCheckout: function (actions) {
6
+ Spree.ajax({
7
+ method: "POST",
8
+ url: "/solidus_afterpay/checkouts.json",
9
+ data: { order_number: orderNumber, mode: "express" },
10
+ success: function (response) {
11
+ actions.resolve(response.token);
12
+ },
13
+ error: function () {
14
+ actions.reject(AfterPay.CONSTANTS.SERVICE_UNAVAILABLE);
15
+ }
16
+ })
17
+ },
18
+ onShippingAddressChange: function (data, actions) {
19
+ Spree.ajax({
20
+ method: "PATCH",
21
+ url: `/solidus_afterpay/express_callbacks/${orderNumber}.json`,
22
+ data: { address: data },
23
+ success: function (response) {
24
+ let results = response.data.map((shipping) =>
25
+ shippingMethod(shipping)
26
+ );
27
+
28
+ if (results.length > 0) {
29
+ actions.resolve(results);
30
+ } else {
31
+ actions.reject(AfterPay.CONSTANTS.SHIPPING_ADDRESS_UNSUPPORTED);
32
+ }
33
+ },
34
+ error: function (error) {
35
+ actions.reject(AfterPay.CONSTANTS.BAD_RESPONSE);
36
+ console.error(error);
37
+ }
38
+ })
39
+ },
40
+ onComplete: function (data) {
41
+ if (data.data.status == "SUCCESS") {
42
+ Spree.ajax({
43
+ method: "POST",
44
+ url: `/solidus_afterpay/express_callbacks/${orderNumber}.json`,
45
+ data: {
46
+ token: data.data.orderToken,
47
+ payment_method_id: paymentMethodId,
48
+ },
49
+ success: function (response) {
50
+ window.location.href = response.redirect_url;
51
+ },
52
+ error: function (error) {
53
+ const errorBody = JSON.parse(error.responseText);
54
+ showError(errorBody.error);
55
+ }
56
+ })
57
+ }
58
+ },
59
+ target: selector,
60
+ buyNow: false,
61
+ pickup: false,
62
+ shippingOptionRequired: true,
63
+ });
64
+ }
65
+
66
+ if ($("#afterpay-button").length > 0) {
67
+ var orderNumber = $("#afterpay-button").data("order-number");
68
+ var paymentMethodId = $("#afterpay-button").data("payment-method-id");
69
+
70
+ initializeExpressCheckout("#afterpay-button");
71
+ }
72
+ });
73
+
74
+ function shippingMethod(shipping) {
75
+ return {
76
+ id: shipping.id,
77
+ name: shipping.name,
78
+ description: shipping.description,
79
+ shippingAmount: {
80
+ amount: shipping.shipping_amount,
81
+ currency: shipping.currency,
82
+ },
83
+ orderAmount: { amount: shipping.order_amount, currency: shipping.currency },
84
+ };
85
+ }
@@ -0,0 +1,12 @@
1
+ function initAfterpay() {
2
+ $(function() {
3
+ $(document).trigger('afterpay.loaded');
4
+ });
5
+ }
6
+
7
+ function showError(errorMessage) {
8
+ if (!$("#content").is("*")) return;
9
+
10
+ $(".flash").remove();
11
+ $("#content").prepend(`<div class="flash error">${errorMessage}</div>`);
12
+ }
@@ -0,0 +1,9 @@
1
+ Spree.ready(function () {
2
+ if (
3
+ $(
4
+ 'form#edit_payment_method option[value="SolidusAfterpay::PaymentMethod"]:selected'
5
+ ).length > 0
6
+ ) {
7
+ $("#payment_method_preferred_excluded_products").productAutocomplete();
8
+ }
9
+ });
@@ -1,2 +1,4 @@
1
1
  // Placeholder manifest file.
2
- // the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/backend/all.js'
2
+ // the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/backend/all.js'
3
+
4
+ //= require solidus_afterpay/backend/afterpay_autocomplete
@@ -1,4 +1,6 @@
1
1
  // Placeholder manifest file.
2
2
  // the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/frontend/all.js'
3
3
 
4
+ //= require solidus_afterpay/afterpay_init
4
5
  //= require solidus_afterpay/afterpay_checkout
6
+ //= require solidus_afterpay/afterpay_checkout_button
@@ -41,7 +41,11 @@ module SolidusAfterpay
41
41
  end
42
42
 
43
43
  def payment_method
44
- @payment_method ||= SolidusAfterpay::PaymentMethod.active.find(params[:payment_method_id])
44
+ @payment_method ||= if params[:payment_method_id]
45
+ SolidusAfterpay::PaymentMethod.active.find(params[:payment_method_id])
46
+ else
47
+ SolidusAfterpay::PaymentMethod.active.first
48
+ end
45
49
  end
46
50
 
47
51
  def afterpay_order_token
@@ -8,7 +8,9 @@ module SolidusAfterpay
8
8
  response = payment_method.gateway.create_checkout(
9
9
  order,
10
10
  redirect_confirm_url: redirect_confirm_url,
11
- redirect_cancel_url: redirect_cancel_url
11
+ redirect_cancel_url: redirect_cancel_url,
12
+ mode: params[:mode],
13
+ popup_origin_url: request.referer
12
14
  )
13
15
 
14
16
  if response.success?
@@ -29,7 +31,11 @@ module SolidusAfterpay
29
31
  end
30
32
 
31
33
  def payment_method
32
- @payment_method ||= SolidusAfterpay::PaymentMethod.active.find(params[:payment_method_id])
34
+ @payment_method ||= if params[:payment_method_id]
35
+ SolidusAfterpay::PaymentMethod.active.find(params[:payment_method_id])
36
+ else
37
+ SolidusAfterpay::PaymentMethod.active.first
38
+ end
33
39
  end
34
40
 
35
41
  def redirect_confirm_url
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusAfterpay
4
+ class ExpressCallbacksController < SolidusAfterpay::BaseController
5
+ def create
6
+ authorize! :update, order, order_token
7
+
8
+ unless SolidusAfterpay.update_order_attributes_service_class.call(
9
+ order: order,
10
+ afterpay_order_token: params[:token],
11
+ payment_method: payment_method,
12
+ request_env: request.headers.env
13
+ )
14
+ render(
15
+ json: {
16
+ error: I18n.t('solidus_afterpay.express_checkout.errors.unable_place_order'),
17
+ errorCode: :internal_server_error
18
+ },
19
+ status: :internal_server_error
20
+ )
21
+ return
22
+ end
23
+
24
+ order.next!
25
+ order.next!
26
+
27
+ render json: { redirect_url: checkout_state_url(order.state) }, status: :ok
28
+ end
29
+
30
+ def update
31
+ authorize! :update, order, order_token
32
+
33
+ unless SolidusAfterpay::UpdateOrderAddressesService.call(order: order, address_params: params[:address])
34
+ render json: { errorCode: :internal_server_error }, status: :internal_server_error
35
+ return
36
+ end
37
+
38
+ order.next!
39
+ # rubocop:disable Rails/SkipsModelValidations
40
+ order.update_columns(email: nil) if order.email == SolidusAfterpay.configuration.dummy_email
41
+ # rubocop:enable Rails/SkipsModelValidations
42
+
43
+ render(
44
+ json: {
45
+ data: ::SolidusAfterpay.shipping_rate_builder_service_class.call(order: order)
46
+ },
47
+ status: :ok
48
+ )
49
+ end
50
+
51
+ private
52
+
53
+ def order
54
+ @order ||= ::Spree::Order.find_by!(number: params[:order_number])
55
+ end
56
+
57
+ def payment_method
58
+ @payment_method ||= if params[:payment_method_id]
59
+ SolidusAfterpay::PaymentMethod.active.find(params[:payment_method_id])
60
+ else
61
+ SolidusAfterpay::PaymentMethod.active.first
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusAfterpay
4
+ module Spree
5
+ module OrdersControllerDecorator
6
+ def self.prepended(base)
7
+ base.helper ::SolidusAfterpay::AfterpayHelper
8
+ end
9
+
10
+ ::Spree::OrdersController.prepend(self) if SolidusSupport.frontend_available?
11
+ end
12
+ end
13
+ end
@@ -2,14 +2,15 @@
2
2
 
3
3
  module SolidusAfterpay
4
4
  module AfterpayHelper
5
- def include_afterpay_js(test_mode: false)
5
+ def include_afterpay_js(test_mode: false, merchant_key: nil)
6
+ js_name = merchant_key ? "afterpay.js?merchant_key=#{merchant_key}" : 'afterpay-async.js'
6
7
  afterpay_js_url = if test_mode
7
- 'https://portal.sandbox.afterpay.com/afterpay.js'
8
+ "https://portal.sandbox.afterpay.com/#{js_name}"
8
9
  else
9
- 'https://portal.afterpay.com/afterpay.js'
10
+ "https://portal.afterpay.com/#{js_name}"
10
11
  end
11
12
 
12
- javascript_include_tag afterpay_js_url
13
+ javascript_include_tag afterpay_js_url, async: true, defer: true, onload: 'initAfterpay()'
13
14
  end
14
15
  end
15
16
  end
@@ -15,18 +15,26 @@ module SolidusAfterpay
15
15
  end
16
16
  end
17
17
 
18
- def authorize(_amount, payment_source, _gateway_options)
18
+ def authorize(amount, payment_source, gateway_options)
19
19
  result = {}
20
20
 
21
- if payment_source.payment_method.preferred_deferred
21
+ unless payment_source.payment_method.auto_capture
22
22
  response = ::Afterpay::API::Payment::Auth.call(
23
- payment: ::Afterpay::Components::Payment.new(token: payment_source.token)
23
+ payment: ::Afterpay::Components::Payment.new(
24
+ token: payment_source.token,
25
+ amount: ::Afterpay::Components::Money.new(
26
+ amount: Money.from_cents(amount).amount.to_s,
27
+ currency: gateway_options[:currency]
28
+ )
29
+ )
24
30
  )
25
31
  result = response.body
26
32
  end
27
33
 
28
34
  ActiveMerchant::Billing::Response.new(true, 'Transaction approved', result, authorization: result[:id])
29
35
  rescue ::Afterpay::BaseError => e
36
+ ::Afterpay::API::Payment::Reversal.call(token: payment_source.token) if e.is_a?(::Afterpay::RequestTimeoutError)
37
+
30
38
  message = e.message
31
39
  error_code = e.error_code
32
40
  if message == 'Afterpay::PaymentRequiredError'
@@ -39,10 +47,10 @@ module SolidusAfterpay
39
47
  def capture(amount, response_code, gateway_options)
40
48
  payment_method = gateway_options[:originator].payment_method
41
49
 
42
- response = if payment_method.preferred_deferred
43
- deferred_capture(amount, response_code, gateway_options)
44
- else
50
+ response = if payment_method.auto_capture
45
51
  immediate_capture(amount, response_code, gateway_options)
52
+ else
53
+ deferred_capture(amount, response_code, gateway_options)
46
54
  end
47
55
  result = response.body
48
56
 
@@ -52,6 +60,11 @@ module SolidusAfterpay
52
60
 
53
61
  ActiveMerchant::Billing::Response.new(true, 'Transaction captured', result, authorization: result.id)
54
62
  rescue ::Afterpay::BaseError => e
63
+ if e.is_a?(::Afterpay::RequestTimeoutError)
64
+ payment_source = gateway_options[:originator].payment_source
65
+ ::Afterpay::API::Payment::Reversal.call(token: payment_source.token)
66
+ end
67
+
55
68
  ActiveMerchant::Billing::Response.new(false, e.message, {}, error_code: e.error_code)
56
69
  end
57
70
 
@@ -84,7 +97,7 @@ module SolidusAfterpay
84
97
  def void(response_code, gateway_options)
85
98
  payment_method = gateway_options[:originator].payment_method
86
99
 
87
- unless payment_method.preferred_deferred
100
+ if payment_method.auto_capture
88
101
  return ActiveMerchant::Billing::Response.new(false, "Transaction can't be voided", {},
89
102
  error_code: 'void_not_allowed')
90
103
  end
@@ -109,8 +122,10 @@ module SolidusAfterpay
109
122
  response = ::Afterpay::API::Order::Create.call(
110
123
  order: SolidusAfterpay::OrderComponentBuilder.new(
111
124
  order: order,
125
+ mode: gateway_options[:mode],
112
126
  redirect_confirm_url: gateway_options[:redirect_confirm_url],
113
- redirect_cancel_url: gateway_options[:redirect_cancel_url]
127
+ redirect_cancel_url: gateway_options[:redirect_cancel_url],
128
+ popup_origin_url: gateway_options[:popup_origin_url]
114
129
  ).call
115
130
  )
116
131
  result = response.body
@@ -126,6 +141,12 @@ module SolidusAfterpay
126
141
  nil
127
142
  end
128
143
 
144
+ def find_order(token:)
145
+ ::Afterpay::API::Order::Find.call(token: token).body
146
+ rescue ::Afterpay::BaseError
147
+ nil
148
+ end
149
+
129
150
  def retrieve_configuration
130
151
  ::Afterpay::API::Configuration::Retrieve.call.body
131
152
  rescue ::Afterpay::BaseError
@@ -134,11 +155,17 @@ module SolidusAfterpay
134
155
 
135
156
  private
136
157
 
137
- def immediate_capture(_amount, _response_code, gateway_options)
158
+ def immediate_capture(amount, _response_code, gateway_options)
138
159
  payment_source = gateway_options[:originator].payment_source
139
160
 
140
161
  ::Afterpay::API::Payment::Capture.call(
141
- payment: ::Afterpay::Components::Payment.new(token: payment_source.token)
162
+ payment: ::Afterpay::Components::Payment.new(
163
+ token: payment_source.token,
164
+ amount: ::Afterpay::Components::Money.new(
165
+ amount: Money.from_cents(amount).amount.to_s,
166
+ currency: gateway_options[:currency]
167
+ )
168
+ )
142
169
  )
143
170
  end
144
171