solidus_afterpay 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +52 -23
  3. data/app/assets/javascripts/solidus_afterpay/afterpay_checkout.js +11 -11
  4. data/app/assets/javascripts/solidus_afterpay/afterpay_checkout_button.js +85 -0
  5. data/app/assets/javascripts/solidus_afterpay/afterpay_init.js +12 -0
  6. data/app/assets/javascripts/solidus_afterpay/backend/afterpay_autocomplete.js +9 -0
  7. data/app/assets/javascripts/spree/backend/solidus_afterpay.js +3 -1
  8. data/app/assets/javascripts/spree/frontend/solidus_afterpay.js +2 -0
  9. data/app/controllers/solidus_afterpay/checkouts_controller.rb +8 -2
  10. data/app/controllers/solidus_afterpay/express_callbacks_controller.rb +61 -0
  11. data/app/decorators/controllers/solidus_afterpay/spree/orders_controller_decorator.rb +13 -0
  12. data/app/helpers/solidus_afterpay/afterpay_helper.rb +5 -4
  13. data/app/models/solidus_afterpay/gateway.rb +37 -10
  14. data/app/models/solidus_afterpay/order_component_builder.rb +34 -5
  15. data/app/models/solidus_afterpay/payment_method.rb +43 -10
  16. data/app/models/solidus_afterpay/payment_source.rb +1 -1
  17. data/app/presentes/solidus_afterpay/order_presenter.rb +17 -0
  18. data/app/presentes/solidus_afterpay/shipping_rate_presenter.rb +28 -0
  19. data/app/services/solidus_afterpay/base_service.rb +13 -0
  20. data/app/services/solidus_afterpay/shipping_rate_builder_service.rb +32 -0
  21. data/app/services/solidus_afterpay/update_order_addresses_service.rb +45 -0
  22. data/app/services/solidus_afterpay/update_order_attributes_service.rb +49 -0
  23. data/app/views/solidus_afterpay/_afterpay_checkout_button.html.erb +9 -0
  24. data/app/views/solidus_afterpay/_afterpay_javascript.html.erb +4 -1
  25. data/app/views/spree/api/payments/source_views/_afterpay.json.jbuilder +1 -1
  26. data/app/views/spree/shared/_afterpay_messaging.html.erb +14 -12
  27. data/config/locales/en.yml +4 -0
  28. data/config/routes.rb +2 -0
  29. data/lib/generators/solidus_afterpay/install/templates/initializer.rb +12 -0
  30. data/lib/solidus_afterpay/configuration.rb +22 -0
  31. data/lib/solidus_afterpay/testing_support/factories.rb +20 -0
  32. data/lib/solidus_afterpay/version.rb +1 -1
  33. data/solidus_afterpay.gemspec +1 -1
  34. data/spec/fixtures/vcr_casettes/find_order/invalid.yml +64 -0
  35. data/spec/fixtures/vcr_casettes/find_order/valid.yml +120 -0
  36. data/spec/helpers/solidus_afterpay/afterpay_helper_spec.rb +18 -2
  37. data/spec/models/solidus_afterpay/gateway_spec.rb +86 -12
  38. data/spec/models/solidus_afterpay/order_component_builder_spec.rb +67 -6
  39. data/spec/models/solidus_afterpay/payment_method_spec.rb +103 -39
  40. data/spec/models/solidus_afterpay/payment_source_spec.rb +3 -3
  41. data/spec/presenters/solidus_afterpay/order_presenter_spec.rb +34 -0
  42. data/spec/presenters/solidus_afterpay/shipping_rate_presenter_spec.rb +28 -0
  43. data/spec/requests/solidus_afterpay/checkouts_controller_spec.rb +72 -4
  44. data/spec/requests/solidus_afterpay/express_callbacks_controller_spec.rb +167 -0
  45. data/spec/services/solidus_afterpay/base_service_spec.rb +13 -0
  46. data/spec/services/solidus_afterpay/shipping_rate_builder_service_spec.rb +34 -0
  47. data/spec/services/solidus_afterpay/update_order_addresses_service_spec.rb +82 -0
  48. data/spec/services/solidus_afterpay/update_order_attributes_service_spec.rb +58 -0
  49. data/spec/support/cache.rb +5 -0
  50. data/spec/support/solidus.rb +1 -0
  51. data/spec/views/solidus_afterpay/express_checkout_button_spec.rb +33 -0
  52. data/spec/views/spree/shared/afterpay_messaging_spec.rb +44 -0
  53. metadata +42 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff5d1583df1845d1d0f620355fe613076850dd3f8d7a534a9311b2c91aadf01f
4
- data.tar.gz: f657c4a2d45062e63333bb8ec8932fb1e6d45785df730e93a242ef95334778bc
3
+ metadata.gz: 8765915b73f132484c9c0a37e556f00be9d4ddebc9a366f58ad65ac735104851
4
+ data.tar.gz: cc860fb8245b3b727084ce0566bfb845b3f2addee262bd61d0978852ecc6f880
5
5
  SHA512:
6
- metadata.gz: 59b6adb36612219fdafa8b03fbce5b5315b8c14ca700af128075fb87979968c909f402e967beba8b494d3e03d091172b2c05fb2671b56d3332d0accb2dd6b770
7
- data.tar.gz: 02feaeda3db7d7403e0b67289b57c3b88ed945911da446de1fc546f48ba0a0cff08152f2aed37167dd22cf1d42faf99ce8863ec5050dd9840b04d566c2684bb1
6
+ metadata.gz: be2b0dce8edf3f39a249a986b406817de704080b8010788a87ab95b0ee6522fb6fbe7af47ca7c47fb7bf0cd7db69ea57120ae38dcd30ac4bfe18b7e12244876d
7
+ data.tar.gz: 645f595276aeb50bcdaae034a352aae98eea1e112b98315eb8d4a74da968ba23b2500be2588d0a4bd9f942715182089c90a071301a726ebe2ad3afd2e03debed
data/README.md CHANGED
@@ -1,8 +1,7 @@
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. -->
7
6
 
8
7
  ## Installation
@@ -18,68 +17,97 @@ Bundle your dependencies and run the installation generator:
18
17
  ```shell
19
18
  bin/rails generate solidus_afterpay:install
20
19
  ```
20
+
21
21
  ## Basic Setup
22
22
 
23
23
  ### Retrieve Afterpay account details
24
+
24
25
  You'll need the following account details:
26
+
25
27
  - `Merchant ID`
26
28
  - `Secret key`
27
29
 
28
30
  These values can be obtained by calling the `Merchant Support` [here](https://developers.afterpay.com/afterpay-online/docs/merchant-support).
29
31
 
30
32
  ### Create a new payment method
33
+
31
34
  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
35
 
33
36
  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
37
 
47
- 2. Visit `/admin/payment_methods/new`
38
+ ```ruby
39
+ # config/initializers/spree.rb
40
+ Spree::Config.configure do |config|
41
+ config.static_model_preferences.add(
42
+ SolidusAfterpay::PaymentMethod,
43
+ 'afterpay_credentials', {
44
+ merchant_id: ENV['AFTERPAY_MERCHANT_ID'],
45
+ secret_key: ENV['AFTERPAY_SECRET_KEY'],
46
+ }
47
+ )
48
+ end
49
+ ```
48
50
 
51
+ 2. Visit `/admin/payment_methods/new`
49
52
  3. Set `provider` to SolidusAfterpay::PaymentMethod
50
-
51
53
  4. Click "Save"
52
-
53
54
  5. Choose `afterpay_credentials` from the `Preference Source` select
54
-
55
55
  6. Click `Update` to save
56
56
 
57
57
  Alternatively, create a payment method from the Rails console with:
58
+
58
59
  ```ruby
59
60
  SolidusAfterpay::PaymentMethod.new(
60
61
  name: "Afterpay",
61
62
  preference_source: "afterpay_credentials"
62
63
  ).save
63
64
  ```
65
+
64
66
  ## Deferred Payment Flow
65
67
 
66
68
  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
69
 
68
- Simply check the deferred checkbox when creating the Afterpay payment_method to activate the deferred payment flow instead of the immediate payment flow.
70
+ 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
71
 
70
72
  For more info about the deferred payment flow click [here](https://developers.afterpay.com/afterpay-online/reference#deferred-payment-flow).
71
73
 
72
74
  ## Usage
73
75
 
76
+ ### Customizing shipping rate builder
77
+
78
+ By default, the extension will build the shipping rates based on the default Solidus shipments building the Afterpay array.
79
+
80
+ If you want to override this logic, you can provide your own `shipping_rate_calculator_class`.
81
+
82
+ ### Customizing update order attributes service
83
+
84
+ By default, the extension will update the order payment_attributes, order email attribute and shipments attributes based on the Afterpay returned data.
85
+
86
+ If you want to override this logic, adding/removing attributes, you can provide your own `update_order_attributes_service_class`.
87
+
88
+ ### Express checkout from the cart
89
+
90
+ An Afterpay button can also be included on the cart view to enable express checkouts:
91
+
92
+ ```ruby
93
+ <%= render "solidus_afterpay/afterpay_checkout_button" %>
94
+ ```
95
+
74
96
  ### Afterpay Messaging
75
97
 
76
98
  Afterpay offers an on-site messaging component to notify the customer that there are financing options available.
77
99
 
78
100
  To add the `Afterpay messaging` simply add the `Afterpay messaging partial` into your `html.erb` file, like this.
79
101
 
102
+ You need to provide the products as well, so you can exclude products from `Afterpay messaging`. It is required to
103
+ add the product in an array for example: `products: [<product>]`, or for multiple products: `products: [<product1>, <product2>]`.
104
+
105
+ If you only have the order you can do it like this `products: order.line_items.map { |item| item.variant.product }`.
106
+
80
107
  ```erb
81
- <%= render "spree/shared/afterpay_messaging", min: nil, max: nil, data: { amount: <Product price>, locale: "en_US", currency: "USD" } %>
108
+ <%= render "spree/shared/afterpay_messaging", min: nil, max: nil, products: [<Product>], data: { amount: <Product price>, locale: "en_US", currency: "USD" } %>
82
109
  ```
110
+
83
111
  The amount, locale and currency are required in order to work properly.
84
112
 
85
113
  This will automatically render an Afterpay messaging icon.
@@ -91,8 +119,9 @@ The min attribute is to configure from which amount Afterpay should be available
91
119
  For example if you would write...
92
120
 
93
121
  ```erb
94
- <%= render "spree/shared/afterpay_messaging", min: nil, max: 25, data: { amount: <Product price>, locale: "en_US", currency: "USD" } %>
122
+ <%= render "spree/shared/afterpay_messaging", min: nil, max: 25, products: [<Product>], data: { amount: <Product price>, locale: "en_US", currency: "USD" } %>
95
123
  ```
124
+
96
125
  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
126
 
98
127
  The default value for min is 1$.
@@ -104,7 +133,7 @@ Click [here](https://developers.afterpay.com/afterpay-online/docs/advanced-usage
104
133
  If you would like to change the size of the Afterpay messaging you simply add size to the `data` hash. For example...
105
134
 
106
135
  ```erb
107
- <%= render "spree/shared/afterpay_messaging", min: nil, max: nil, data: { amount: <Product price>, locale: "en_US", currency: "USD", size: "sm" } %>
136
+ <%= render "spree/shared/afterpay_messaging", min: nil, max: nil, product: [<Product>], data: { amount: <Product price>, locale: "en_US", currency: "USD", size: "sm" } %>
108
137
  ```
109
138
 
110
139
  ## 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
@@ -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,61 @@
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 ||= SolidusAfterpay::PaymentMethod.active.find(params[:payment_method_id])
59
+ end
60
+ end
61
+ 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
 
@@ -4,17 +4,20 @@ require 'afterpay'
4
4
 
5
5
  module SolidusAfterpay
6
6
  class OrderComponentBuilder
7
- attr_reader :order, :redirect_confirm_url, :redirect_cancel_url
7
+ attr_reader :order, :mode, :redirect_confirm_url, :redirect_cancel_url, :popup_origin_url
8
8
 
9
- def initialize(order:, redirect_confirm_url:, redirect_cancel_url:)
9
+ def initialize(order:, mode: nil, redirect_confirm_url: nil, redirect_cancel_url: nil, popup_origin_url: nil)
10
10
  @order = order
11
+ @mode = mode
11
12
  @redirect_confirm_url = redirect_confirm_url
12
13
  @redirect_cancel_url = redirect_cancel_url
14
+ @popup_origin_url = popup_origin_url
13
15
  end
14
16
 
15
17
  def call
16
18
  ::Afterpay::Components::Order.new(
17
19
  amount: amount,
20
+ mode: mode,
18
21
  consumer: consumer,
19
22
  billing: address(order.billing_address),
20
23
  shipping: address(order.shipping_address),
@@ -27,6 +30,8 @@ module SolidusAfterpay
27
30
  private
28
31
 
29
32
  def address(address)
33
+ return unless address
34
+
30
35
  name = if SolidusSupport.combined_first_and_last_name_in_address?
31
36
  address.name
32
37
  else
@@ -45,6 +50,8 @@ module SolidusAfterpay
45
50
  end
46
51
 
47
52
  def consumer
53
+ return unless order.billing_address
54
+
48
55
  if SolidusSupport.combined_first_and_last_name_in_address?
49
56
  name_components = ::Spree::Address::Name.new(order.billing_address.name)
50
57
  first_name = name_components.first_name
@@ -55,7 +62,7 @@ module SolidusAfterpay
55
62
  end
56
63
 
57
64
  ::Afterpay::Components::Consumer.new(
58
- email: order.user&.email || order.email,
65
+ email: order.user&.email || order.email || 'afterpay@guest.com',
59
66
  given_names: first_name,
60
67
  surname: last_name,
61
68
  phone: order.billing_address.phone
@@ -72,7 +79,8 @@ module SolidusAfterpay
72
79
  def merchant
73
80
  ::Afterpay::Components::Merchant.new(
74
81
  redirect_confirm_url: redirect_confirm_url,
75
- redirect_cancel_url: redirect_cancel_url
82
+ redirect_cancel_url: redirect_cancel_url,
83
+ popup_origin_url: popup_origin_url
76
84
  )
77
85
  end
78
86
 
@@ -90,8 +98,29 @@ module SolidusAfterpay
90
98
  price: ::Afterpay::Components::Money.new(
91
99
  amount: line_item.price.to_s,
92
100
  currency: line_item.currency
93
- )
101
+ ),
102
+ preorder: pre_order?(line_item),
103
+ estimated_shipment_date: estimated_shipment_date(line_item)
94
104
  )
95
105
  end
106
+
107
+ def estimated_shipment_date_product(line_item)
108
+ line_item.product.property("estimatedShipmentDate")
109
+ end
110
+
111
+ def estimated_shipment_date_variant(line_item)
112
+ line_item.variant.variant_properties.find{ |prop| prop.property.name == "estimatedShipmentDate" }&.value
113
+ end
114
+
115
+ def estimated_shipment_date(line_item)
116
+ @estimated_shipment_date ||=
117
+ (estimated_shipment_date_variant(line_item) || estimated_shipment_date_product(line_item))
118
+ end
119
+
120
+ def pre_order?(line_item)
121
+ return false if estimated_shipment_date(line_item).nil?
122
+
123
+ Time.zone.local(*estimated_shipment_date(line_item).split("-")) > Time.zone.now
124
+ end
96
125
  end
97
126
  end