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.
- checksums.yaml +4 -4
- data/README.md +52 -23
- data/app/assets/javascripts/solidus_afterpay/afterpay_checkout.js +11 -11
- data/app/assets/javascripts/solidus_afterpay/afterpay_checkout_button.js +85 -0
- data/app/assets/javascripts/solidus_afterpay/afterpay_init.js +12 -0
- data/app/assets/javascripts/solidus_afterpay/backend/afterpay_autocomplete.js +9 -0
- data/app/assets/javascripts/spree/backend/solidus_afterpay.js +3 -1
- data/app/assets/javascripts/spree/frontend/solidus_afterpay.js +2 -0
- data/app/controllers/solidus_afterpay/checkouts_controller.rb +8 -2
- data/app/controllers/solidus_afterpay/express_callbacks_controller.rb +61 -0
- data/app/decorators/controllers/solidus_afterpay/spree/orders_controller_decorator.rb +13 -0
- data/app/helpers/solidus_afterpay/afterpay_helper.rb +5 -4
- data/app/models/solidus_afterpay/gateway.rb +37 -10
- data/app/models/solidus_afterpay/order_component_builder.rb +34 -5
- data/app/models/solidus_afterpay/payment_method.rb +43 -10
- data/app/models/solidus_afterpay/payment_source.rb +1 -1
- data/app/presentes/solidus_afterpay/order_presenter.rb +17 -0
- data/app/presentes/solidus_afterpay/shipping_rate_presenter.rb +28 -0
- data/app/services/solidus_afterpay/base_service.rb +13 -0
- data/app/services/solidus_afterpay/shipping_rate_builder_service.rb +32 -0
- data/app/services/solidus_afterpay/update_order_addresses_service.rb +45 -0
- data/app/services/solidus_afterpay/update_order_attributes_service.rb +49 -0
- data/app/views/solidus_afterpay/_afterpay_checkout_button.html.erb +9 -0
- data/app/views/solidus_afterpay/_afterpay_javascript.html.erb +4 -1
- data/app/views/spree/api/payments/source_views/_afterpay.json.jbuilder +1 -1
- data/app/views/spree/shared/_afterpay_messaging.html.erb +14 -12
- data/config/locales/en.yml +4 -0
- data/config/routes.rb +2 -0
- data/lib/generators/solidus_afterpay/install/templates/initializer.rb +12 -0
- data/lib/solidus_afterpay/configuration.rb +22 -0
- data/lib/solidus_afterpay/testing_support/factories.rb +20 -0
- data/lib/solidus_afterpay/version.rb +1 -1
- data/solidus_afterpay.gemspec +1 -1
- data/spec/fixtures/vcr_casettes/find_order/invalid.yml +64 -0
- data/spec/fixtures/vcr_casettes/find_order/valid.yml +120 -0
- data/spec/helpers/solidus_afterpay/afterpay_helper_spec.rb +18 -2
- data/spec/models/solidus_afterpay/gateway_spec.rb +86 -12
- data/spec/models/solidus_afterpay/order_component_builder_spec.rb +67 -6
- data/spec/models/solidus_afterpay/payment_method_spec.rb +103 -39
- data/spec/models/solidus_afterpay/payment_source_spec.rb +3 -3
- data/spec/presenters/solidus_afterpay/order_presenter_spec.rb +34 -0
- data/spec/presenters/solidus_afterpay/shipping_rate_presenter_spec.rb +28 -0
- data/spec/requests/solidus_afterpay/checkouts_controller_spec.rb +72 -4
- data/spec/requests/solidus_afterpay/express_callbacks_controller_spec.rb +167 -0
- data/spec/services/solidus_afterpay/base_service_spec.rb +13 -0
- data/spec/services/solidus_afterpay/shipping_rate_builder_service_spec.rb +34 -0
- data/spec/services/solidus_afterpay/update_order_addresses_service_spec.rb +82 -0
- data/spec/services/solidus_afterpay/update_order_attributes_service_spec.rb +58 -0
- data/spec/support/cache.rb +5 -0
- data/spec/support/solidus.rb +1 -0
- data/spec/views/solidus_afterpay/express_checkout_button_spec.rb +33 -0
- data/spec/views/spree/shared/afterpay_messaging_spec.rb +44 -0
- metadata +42 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8765915b73f132484c9c0a37e556f00be9d4ddebc9a366f58ad65ac735104851
|
4
|
+
data.tar.gz: cc860fb8245b3b727084ce0566bfb845b3f2addee262bd61d0978852ecc6f880
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
+
}
|
@@ -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 ||=
|
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
|
-
|
8
|
+
"https://portal.sandbox.afterpay.com/#{js_name}"
|
8
9
|
else
|
9
|
-
|
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(
|
18
|
+
def authorize(amount, payment_source, gateway_options)
|
19
19
|
result = {}
|
20
20
|
|
21
|
-
|
21
|
+
unless payment_source.payment_method.auto_capture
|
22
22
|
response = ::Afterpay::API::Payment::Auth.call(
|
23
|
-
payment: ::Afterpay::Components::Payment.new(
|
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.
|
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
|
-
|
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(
|
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(
|
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
|
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
|