workarea-klarna 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +20 -0
- data/.eslintrc.json +36 -0
- data/.github/workflows/ci.yml +61 -0
- data/.gitignore +24 -0
- data/.rubocop.yml +2 -0
- data/.stylelintrc.json +8 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +81 -0
- data/Gemfile +17 -0
- data/LICENSE +52 -0
- data/README.md +79 -0
- data/Rakefile +56 -0
- data/app/assets/javascripts/workarea/storefront/klarna/modules/klarna_widget.js +99 -0
- data/app/controllers/workarea/storefront/checkout/addresses_controller.decorator +8 -0
- data/app/controllers/workarea/storefront/checkout/place_order_controller.decorator +9 -0
- data/app/controllers/workarea/storefront/checkouts_controller.decorator +13 -0
- data/app/models/workarea/checkout/steps/payment.decorator +22 -0
- data/app/models/workarea/order.decorator +7 -0
- data/app/models/workarea/payment.decorator +34 -0
- data/app/models/workarea/payment/authorize/klarna.rb +21 -0
- data/app/models/workarea/payment/capture/klarna.rb +21 -0
- data/app/models/workarea/payment/klarna_session.rb +18 -0
- data/app/models/workarea/payment/purchase/klarna.rb +21 -0
- data/app/models/workarea/payment/refund/klarna.rb +18 -0
- data/app/models/workarea/payment/tender/klarna.rb +53 -0
- data/app/services/workarea/setup_klarna_session.rb +24 -0
- data/app/view_models/workarea/storefront/checkout/payment_view_model.decorator +41 -0
- data/app/views/workarea/admin/orders/tenders/_klarna.html.haml +4 -0
- data/app/views/workarea/storefront/checkouts/_klarna_payments.html.haml +22 -0
- data/app/views/workarea/storefront/klarna/_sdk.html.haml +2 -0
- data/app/views/workarea/storefront/order_mailer/tenders/_klarna.html.haml +2 -0
- data/app/views/workarea/storefront/orders/tenders/_klarna.html.haml +6 -0
- data/bin/rails +20 -0
- data/config/initializers/appends.rb +14 -0
- data/config/initializers/configuration.rb +32 -0
- data/config/initializers/fields.rb +43 -0
- data/config/locales/en.yml +30 -0
- data/config/routes.rb +2 -0
- data/lib/workarea/klarna.rb +30 -0
- data/lib/workarea/klarna/bogus_gateway.rb +19 -0
- data/lib/workarea/klarna/engine.rb +10 -0
- data/lib/workarea/klarna/gateway.rb +92 -0
- data/lib/workarea/klarna/gateway/cancel_request.rb +16 -0
- data/lib/workarea/klarna/gateway/capture_request.rb +24 -0
- data/lib/workarea/klarna/gateway/create_session_request.rb +22 -0
- data/lib/workarea/klarna/gateway/order.rb +249 -0
- data/lib/workarea/klarna/gateway/place_order_request.rb +34 -0
- data/lib/workarea/klarna/gateway/refund_request.rb +24 -0
- data/lib/workarea/klarna/gateway/release_request.rb +16 -0
- data/lib/workarea/klarna/gateway/request.rb +91 -0
- data/lib/workarea/klarna/gateway/response.rb +38 -0
- data/lib/workarea/klarna/gateway/update_session_request.rb +30 -0
- data/lib/workarea/klarna/version.rb +5 -0
- data/package.json +9 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/config/manifest.js +4 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/jobs/application_job.rb +2 -0
- data/test/dummy/app/mailers/application_mailer.rb +4 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +34 -0
- data/test/dummy/bin/update +29 -0
- data/test/dummy/config.ru +5 -0
- data/test/dummy/config/application.rb +24 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/cable.yml +9 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +54 -0
- data/test/dummy/config/environments/production.rb +86 -0
- data/test/dummy/config/environments/test.rb +43 -0
- data/test/dummy/config/initializers/application_controller_renderer.rb +6 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/new_framework_defaults.rb +21 -0
- data/test/dummy/config/initializers/workarea.rb +5 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/puma.rb +47 -0
- data/test/dummy/config/routes.rb +5 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/config/spring.rb +6 -0
- data/test/dummy/db/seeds.rb +2 -0
- data/test/dummy/log/.keep +0 -0
- data/test/factories/workarea/klarna.rb +146 -0
- data/test/integration/workarea/storefront/klarna_session_integration_test.rb +40 -0
- data/test/lib/workarea/klarna/gateway/capture_request_test.rb +26 -0
- data/test/lib/workarea/klarna/gateway/create_session_request_test.rb +26 -0
- data/test/lib/workarea/klarna/gateway/order_test.rb +135 -0
- data/test/lib/workarea/klarna/gateway/place_order_request_test.rb +34 -0
- data/test/lib/workarea/klarna/gateway/refund_request_test.rb +26 -0
- data/test/lib/workarea/klarna/gateway/request_test.rb +87 -0
- data/test/lib/workarea/klarna/gateway/response_test.rb +75 -0
- data/test/lib/workarea/klarna/gateway/update_session_request_test.rb +27 -0
- data/test/lib/workarea/klarna/gateway_test.rb +58 -0
- data/test/models/workarea/checkout/steps/klarna_payment_test.rb +79 -0
- data/test/models/workarea/klarna_payment_test.rb +45 -0
- data/test/models/workarea/payment/authorize/klarna_test.rb +34 -0
- data/test/models/workarea/payment/capture/klarna_test.rb +51 -0
- data/test/models/workarea/payment/tender/klarna_test.rb +81 -0
- data/test/services/workarea/setup_klarna_session_test.rb +42 -0
- data/test/teaspoon_env.rb +6 -0
- data/test/test_helper.rb +10 -0
- data/test/vcr_cassettes/klarna_authorize.yml +62 -0
- data/test/vcr_cassettes/klarna_capture.yml +60 -0
- data/test/vcr_cassettes/klarna_create_session.yml +61 -0
- data/test/vcr_cassettes/klarna_refund.yml +60 -0
- data/test/view_models/workarea/storefront/checkout/klarna_payment_view_model_test.rb +62 -0
- data/workarea-klarna.gemspec +20 -0
- metadata +177 -0
data/bin/rails
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails gems
|
3
|
+
# installed from the root of your application.
|
4
|
+
|
5
|
+
ENGINE_ROOT = File.expand_path('..', __dir__)
|
6
|
+
ENGINE_PATH = File.expand_path('../lib/workarea/klarna/engine', __dir__)
|
7
|
+
APP_PATH = File.expand_path('../test/dummy/config/application', __dir__)
|
8
|
+
|
9
|
+
# Set up gems listed in the Gemfile.
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
11
|
+
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
12
|
+
|
13
|
+
require 'action_controller/railtie'
|
14
|
+
require 'action_view/railtie'
|
15
|
+
require 'action_mailer/railtie'
|
16
|
+
require 'rails/test_unit/railtie'
|
17
|
+
require 'sprockets/railtie'
|
18
|
+
require 'teaspoon-mocha'
|
19
|
+
|
20
|
+
require 'rails/engine/commands'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Workarea::Plugin.append_partials(
|
2
|
+
'storefront.javascript',
|
3
|
+
'workarea/storefront/klarna/sdk'
|
4
|
+
)
|
5
|
+
|
6
|
+
Workarea::Plugin.append_partials(
|
7
|
+
'storefront.payment_method',
|
8
|
+
'workarea/storefront/checkouts/klarna_payments'
|
9
|
+
)
|
10
|
+
|
11
|
+
Workarea::Plugin.append_javascripts(
|
12
|
+
'storefront.modules',
|
13
|
+
'workarea/storefront/klarna/modules/klarna_widget'
|
14
|
+
)
|
@@ -0,0 +1,32 @@
|
|
1
|
+
Workarea.configure do |config|
|
2
|
+
config.tender_types << 'klarna'
|
3
|
+
|
4
|
+
# If this is false, the javascript file will not be included on the
|
5
|
+
# storefront, and a bogus gateway will be used.
|
6
|
+
config.load_klarna = !Rails.env.test?
|
7
|
+
|
8
|
+
# How long a klarna session is valid for. This is defined by Klarna's API
|
9
|
+
# and should not be extended beyond 48 hours.
|
10
|
+
config.klarn_session_expiration = 48.hours
|
11
|
+
|
12
|
+
# This controls whether requests go to the live or sandbox API endpoints.
|
13
|
+
config.klarna_playground = !Rails.env.production?
|
14
|
+
|
15
|
+
# Continents supported by KLarna and keys used to generate paths to
|
16
|
+
# credentials via environment variables and/or rails credentials. Also used
|
17
|
+
# to get the keys for the subdomain configuration.
|
18
|
+
config.klarna_continent_keys = {
|
19
|
+
'Europe' => 'EUR',
|
20
|
+
'North America' => 'NA',
|
21
|
+
'Oceania' => 'OC',
|
22
|
+
'Australia' => 'AUS'
|
23
|
+
}
|
24
|
+
|
25
|
+
# Subdomains to use for API requests depending on the user's continent.
|
26
|
+
config.klarna_subdomains = {
|
27
|
+
'EUR' => 'api',
|
28
|
+
'NA' => 'api-na',
|
29
|
+
'OC' => 'api-oc',
|
30
|
+
'AUS' => 'api-oc'
|
31
|
+
}
|
32
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
Workarea::Configuration.define_fields do
|
2
|
+
fieldset 'Klarna', namespaced: false do
|
3
|
+
# Klarna credentials can be configured via environment variables per
|
4
|
+
# continent:
|
5
|
+
# WORKAREA_KLARNA_EUR_USERNAME
|
6
|
+
# WORKAREA_KLARNA_EUR_PASSWORD
|
7
|
+
# WORKAREA_KLARNA_NA_USERNAME
|
8
|
+
# WORKAREA_KLARNA_NA_PASSWORD
|
9
|
+
#
|
10
|
+
# via rails credentials:
|
11
|
+
# klarna:
|
12
|
+
# eur:
|
13
|
+
# username: YOUR_USERNAME
|
14
|
+
# password: YOUR_CLIENT_SECRET
|
15
|
+
# na:
|
16
|
+
# username: YOUR_USERNAME
|
17
|
+
# password: YOUR_CLIENT_SECRET
|
18
|
+
#
|
19
|
+
# Or through the workarea admin configuration. Setting credentials through
|
20
|
+
# the configuration allows for dyanmically changing credentials if, for
|
21
|
+
# example, you are using the multi-site plugin and wish to use different
|
22
|
+
# Klarna accounts for some or all sites.
|
23
|
+
#
|
24
|
+
field 'Europe Username',
|
25
|
+
type: :string,
|
26
|
+
id: 'klarna_eur_username',
|
27
|
+
description: 'Your Klarna API username for Europe'
|
28
|
+
field 'Europe Password',
|
29
|
+
type: :string,
|
30
|
+
id: 'klarna_eur_password',
|
31
|
+
encrypted: true,
|
32
|
+
description: 'Your Klarna API password for Europe'
|
33
|
+
field 'North America Username',
|
34
|
+
type: :string,
|
35
|
+
id: 'klarna_na_username',
|
36
|
+
description: 'Your Klarna API username for North America'
|
37
|
+
field 'North America Password',
|
38
|
+
type: :string,
|
39
|
+
id: 'klarna_na_password',
|
40
|
+
encrypted: true,
|
41
|
+
description: 'Your Klarna API password for North America'
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
en:
|
2
|
+
workarea:
|
3
|
+
admin:
|
4
|
+
orders:
|
5
|
+
tenders:
|
6
|
+
klarna:
|
7
|
+
title: Klarna
|
8
|
+
storefront:
|
9
|
+
checkouts:
|
10
|
+
klarna_payment_received: Your Klarna payment has been approved. Click 'Place Order' to purchase.
|
11
|
+
klarna_payment_expired: Your Klarna payment approval has expired.
|
12
|
+
orders:
|
13
|
+
klarna: Klarna
|
14
|
+
widget:
|
15
|
+
invalid_payment: The payment selected is not valid for this order. Please select a different payment option.
|
16
|
+
klarna:
|
17
|
+
gateway:
|
18
|
+
request:
|
19
|
+
base: 'Klarna request'
|
20
|
+
create_session: 'Klarna session creation'
|
21
|
+
update_session: 'Klarna session update'
|
22
|
+
place_order: 'Klarna payment authorization for %{amount}'
|
23
|
+
capture: 'Klarna payment capture for %{amount}'
|
24
|
+
refund: 'Klarna payment refund for %{amount}'
|
25
|
+
cancel: 'Klarna order cancellation'
|
26
|
+
release: 'Klarna order authorization release'
|
27
|
+
response:
|
28
|
+
success: '%{summary} was successful'
|
29
|
+
failure: Request to Klarna was not successful. %{summary}
|
30
|
+
unsupported_continent: The continent "%{continent}" associated to this request is either unknown or not supported.
|
data/config/routes.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'workarea'
|
2
|
+
require 'workarea/storefront'
|
3
|
+
require 'workarea/admin'
|
4
|
+
|
5
|
+
require 'workarea/klarna/gateway'
|
6
|
+
require 'workarea/klarna/gateway/request'
|
7
|
+
require 'workarea/klarna/gateway/response'
|
8
|
+
require 'workarea/klarna/gateway/order'
|
9
|
+
require 'workarea/klarna/gateway/create_session_request'
|
10
|
+
require 'workarea/klarna/gateway/update_session_request'
|
11
|
+
require 'workarea/klarna/gateway/place_order_request'
|
12
|
+
require 'workarea/klarna/gateway/capture_request'
|
13
|
+
require 'workarea/klarna/gateway/refund_request'
|
14
|
+
require 'workarea/klarna/gateway/cancel_request'
|
15
|
+
require 'workarea/klarna/gateway/release_request'
|
16
|
+
require 'workarea/klarna/bogus_gateway'
|
17
|
+
|
18
|
+
module Workarea
|
19
|
+
module Klarna
|
20
|
+
class << self
|
21
|
+
def gateway
|
22
|
+
return Klarna::BogusGateway.new unless Workarea.config.load_klarna
|
23
|
+
Klarna::Gateway.new
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
require 'workarea/klarna/engine'
|
30
|
+
require 'workarea/klarna/version'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Klarna
|
3
|
+
class BogusGateway
|
4
|
+
def respond_to_missing?(method_name, include_private = false)
|
5
|
+
Gateway.new.respond_to?(method_name)
|
6
|
+
end
|
7
|
+
|
8
|
+
def method_missing(method_name, *args)
|
9
|
+
return super unless Gateway.new.respond_to?(method_name)
|
10
|
+
|
11
|
+
OpenStruct.new(
|
12
|
+
success?: false,
|
13
|
+
message: 'This is a bogus gateway',
|
14
|
+
params: {}
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Klarna
|
3
|
+
class Gateway
|
4
|
+
class ContinentNotSupported < StandardError; end
|
5
|
+
|
6
|
+
def create_session(order)
|
7
|
+
request = CreateSessionRequest.new(order)
|
8
|
+
send_request(request)
|
9
|
+
end
|
10
|
+
|
11
|
+
def update_session(order, session_id = nil)
|
12
|
+
request = UpdateSessionRequest.new(order, session_id)
|
13
|
+
send_request(request)
|
14
|
+
end
|
15
|
+
|
16
|
+
def authorize(tender, amount)
|
17
|
+
request = PlaceOrderRequest.new(tender, amount)
|
18
|
+
send_transaction_request(request)
|
19
|
+
end
|
20
|
+
|
21
|
+
def purchase(tender, amount)
|
22
|
+
request = PlaceOrderRequest.new(order, amount, auto_capture: true)
|
23
|
+
send_transaction_request(request)
|
24
|
+
end
|
25
|
+
|
26
|
+
def capture(tender, amount)
|
27
|
+
request = CaptureRequest.new(tender, amount)
|
28
|
+
send_transaction_request(request)
|
29
|
+
end
|
30
|
+
|
31
|
+
def refund(tender, amount)
|
32
|
+
request = RefundRequest.new(tender, amount)
|
33
|
+
send_transaction_request(request)
|
34
|
+
end
|
35
|
+
|
36
|
+
def cancel(tender)
|
37
|
+
request = CancelRequest.new(tender)
|
38
|
+
send_transaction_request(request)
|
39
|
+
end
|
40
|
+
|
41
|
+
def release_authorization(tender)
|
42
|
+
request = ReleaseRequest.new(tender)
|
43
|
+
send_transaction_request(request)
|
44
|
+
end
|
45
|
+
|
46
|
+
def send_request(request)
|
47
|
+
request.validate!
|
48
|
+
|
49
|
+
raw_response = Faraday.send(request.method, request.url) do |req|
|
50
|
+
req.headers['Authorization'] = auth_header(request)
|
51
|
+
req.headers['User-Agent'] = "Workarea/#{Workarea::VERSION::STRING}"
|
52
|
+
|
53
|
+
if request.body.present?
|
54
|
+
req.headers['Content-Type'] = 'application/json'
|
55
|
+
req.body = request.body.to_json
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Response.new(request, raw_response)
|
60
|
+
rescue ContinentNotSupported
|
61
|
+
unsupported_continent_response(request)
|
62
|
+
end
|
63
|
+
|
64
|
+
def send_transaction_request(request)
|
65
|
+
response = send_request(request)
|
66
|
+
|
67
|
+
ActiveMerchant::Billing::Response.new(
|
68
|
+
response.success?,
|
69
|
+
response.message,
|
70
|
+
response.params
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def auth_header(request)
|
77
|
+
"Basic #{Base64.encode64([request.username, request.password].join(':'))}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def unsupported_continent_response(request)
|
81
|
+
OpenStruct.new(
|
82
|
+
success?: false,
|
83
|
+
message: I18n.t(
|
84
|
+
'workarea.klarna.gateway.unsupported_continent',
|
85
|
+
continent: request.continent
|
86
|
+
),
|
87
|
+
params: {}
|
88
|
+
)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Klarna
|
3
|
+
class Gateway
|
4
|
+
class CancelRequest < Request
|
5
|
+
def initialize(tender)
|
6
|
+
@tender = tender
|
7
|
+
@payment = tender.payment
|
8
|
+
|
9
|
+
@path = "/ordermanagement/v1/orders/#{tender.order_id}/cancel"
|
10
|
+
@method = 'post'
|
11
|
+
@summary = I18n.t('workarea.klarna.gateway.request.cancel')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Klarna
|
3
|
+
class Gateway
|
4
|
+
class CaptureRequest < Request
|
5
|
+
def initialize(tender, amount)
|
6
|
+
@tender = tender
|
7
|
+
@payment = tender.payment
|
8
|
+
@amount = amount
|
9
|
+
|
10
|
+
@path = "/ordermanagement/v1/orders/#{tender.order_id}/captures"
|
11
|
+
@method = 'post'
|
12
|
+
@summary = I18n.t(
|
13
|
+
'workarea.klarna.gateway.request.capture',
|
14
|
+
amount: @amount.format
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def body
|
19
|
+
{ captured_amount: @amount.cents }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Klarna
|
3
|
+
class Gateway
|
4
|
+
class CreateSessionRequest < Request
|
5
|
+
def initialize(order)
|
6
|
+
super
|
7
|
+
|
8
|
+
@path = 'payments/v1/sessions'
|
9
|
+
@method = 'post'
|
10
|
+
@summary = I18n.t('workarea.klarna.gateway.request.create_session')
|
11
|
+
end
|
12
|
+
|
13
|
+
def body
|
14
|
+
Gateway::Order
|
15
|
+
.new(order, payment: payment)
|
16
|
+
.to_h
|
17
|
+
.except(:shipping_address, :billing_address)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Klarna
|
3
|
+
class Gateway
|
4
|
+
class Order
|
5
|
+
attr_reader :order
|
6
|
+
|
7
|
+
def initialize(order, payment: nil, shippings: nil)
|
8
|
+
@order = order
|
9
|
+
@payment = payment
|
10
|
+
@shippings = shippings
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_h
|
14
|
+
{
|
15
|
+
purchase_country: payment.address.country.alpha2,
|
16
|
+
purchase_currency: order.total_price.currency.iso_code,
|
17
|
+
locale: I18n.locale,
|
18
|
+
order_amount: order_amount,
|
19
|
+
order_tax_amount: order.tax_total.cents,
|
20
|
+
order_lines: order_lines,
|
21
|
+
billing_address: billing_address,
|
22
|
+
shipping_address: shipping_address,
|
23
|
+
merchant_urls: merchant_urls,
|
24
|
+
merchant_reference1: order.id,
|
25
|
+
attachment: extra_merchant_data
|
26
|
+
}
|
27
|
+
.compact
|
28
|
+
.tap { |hash| remove_inline_tax(hash) if include_tax_line? }
|
29
|
+
end
|
30
|
+
|
31
|
+
def payment
|
32
|
+
@payment ||= Workarea::Payment.find(order.id)
|
33
|
+
end
|
34
|
+
|
35
|
+
def shippings
|
36
|
+
@shippings ||= Workarea::Shipping.by_order(order.id)
|
37
|
+
end
|
38
|
+
|
39
|
+
def price_adjustments
|
40
|
+
@price_adjustments ||=
|
41
|
+
order.price_adjustments + shippings.flat_map(&:price_adjustments)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def order_amount
|
47
|
+
raw_total = order.total_price
|
48
|
+
tenders = gift_cards + [store_credit]
|
49
|
+
|
50
|
+
(raw_total - tenders.compact.sum(&:amount).to_m).cents
|
51
|
+
end
|
52
|
+
|
53
|
+
def order_lines
|
54
|
+
[
|
55
|
+
*order.items.map(&method(:format_item)),
|
56
|
+
shipping_line,
|
57
|
+
discount_line,
|
58
|
+
tax_line,
|
59
|
+
store_credit_line,
|
60
|
+
gift_card_line,
|
61
|
+
].compact
|
62
|
+
end
|
63
|
+
|
64
|
+
def billing_address
|
65
|
+
format_address(payment.address)
|
66
|
+
end
|
67
|
+
|
68
|
+
def shipping_address
|
69
|
+
format_address(shippings.first.address)
|
70
|
+
end
|
71
|
+
|
72
|
+
def format_address(address)
|
73
|
+
return unless address.present?
|
74
|
+
|
75
|
+
{
|
76
|
+
given_name: address.first_name,
|
77
|
+
family_name: address.last_name,
|
78
|
+
email: order.email,
|
79
|
+
street_address: address.street,
|
80
|
+
street_address2: address.street_2,
|
81
|
+
postal_code: address.postal_code,
|
82
|
+
city: address.city,
|
83
|
+
region: address.region,
|
84
|
+
phone: address.phone_number,
|
85
|
+
country: address.country.alpha2
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def format_item(item)
|
90
|
+
view_model = Storefront::OrderItemViewModel.new(item)
|
91
|
+
breadcrumbs = Navigation::Breadcrumbs.from_global_id(item.via) if item.via.present?
|
92
|
+
tax_amount, tax_rate = item_tax_data(item)
|
93
|
+
|
94
|
+
{
|
95
|
+
name: view_model.product_name,
|
96
|
+
type: item.shipping? ? 'physical' : 'digital',
|
97
|
+
reference: item.sku,
|
98
|
+
quantity: item.quantity,
|
99
|
+
unit_price: item.current_unit_price.cents,
|
100
|
+
total_amount: item.total_price.cents,
|
101
|
+
total_tax_amount: tax_amount,
|
102
|
+
tax_rate: tax_rate,
|
103
|
+
product_url: Workarea::Storefront::Engine.routes.url_helpers.product_url(
|
104
|
+
host: Workarea.config.host,
|
105
|
+
id: view_model.product.slug
|
106
|
+
),
|
107
|
+
product_identifiers: { category_path: breadcrumbs&.join(' > ') }.compact
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
def item_tax_data(item)
|
112
|
+
tax_amount =
|
113
|
+
price_adjustments
|
114
|
+
.adjusting('tax')
|
115
|
+
.select { |pa| pa.data['order_item_id'].to_s == item.id.to_s }
|
116
|
+
.sum(&:amount)
|
117
|
+
|
118
|
+
tax_rate = (tax_amount.cents.to_f / item.total_price.cents).round(2) * 10000
|
119
|
+
|
120
|
+
[tax_amount.cents, tax_rate.to_i]
|
121
|
+
end
|
122
|
+
|
123
|
+
def shipping_line
|
124
|
+
return unless shippings.present? && shippings.all?(&:shipping_service)
|
125
|
+
|
126
|
+
shipping_total = order.shipping_total.cents
|
127
|
+
return if shipping_total.zero?
|
128
|
+
|
129
|
+
shipping_tax = price_adjustments
|
130
|
+
.adjusting('tax')
|
131
|
+
.select { |pa| !!pa.data['shipping_service_tax'] }
|
132
|
+
.sum(&:amount)
|
133
|
+
.cents
|
134
|
+
|
135
|
+
{
|
136
|
+
name: 'Shipping',
|
137
|
+
type: 'shipping_fee',
|
138
|
+
quantity: 1,
|
139
|
+
unit_price: shipping_total,
|
140
|
+
total_amount: shipping_total,
|
141
|
+
total_tax_amount: shipping_tax,
|
142
|
+
tax_rate: ((shipping_tax * 1.0 / shipping_total).round(2) * 10000).to_i
|
143
|
+
}
|
144
|
+
end
|
145
|
+
|
146
|
+
def discount_line
|
147
|
+
order_discount = price_adjustments.adjusting('order').discounts.sum.cents
|
148
|
+
return if order_discount.zero?
|
149
|
+
|
150
|
+
{
|
151
|
+
name: 'Order Discounts',
|
152
|
+
type: 'discount',
|
153
|
+
quantity: 1,
|
154
|
+
unit_price: order_discount,
|
155
|
+
total_amount: order_discount,
|
156
|
+
total_tax_amount: 0,
|
157
|
+
tax_rate: 0
|
158
|
+
}
|
159
|
+
end
|
160
|
+
|
161
|
+
def tax_line
|
162
|
+
return unless include_tax_line?
|
163
|
+
|
164
|
+
{
|
165
|
+
name: 'Tax',
|
166
|
+
type: 'sales_tax',
|
167
|
+
quantity: 1,
|
168
|
+
unit_price: order.tax_total.cents,
|
169
|
+
total_amount: order.tax_total.cents
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
def store_credit
|
174
|
+
payment.store_credit
|
175
|
+
end
|
176
|
+
|
177
|
+
def store_credit_line
|
178
|
+
return unless store_credit&.amount&.positive?
|
179
|
+
|
180
|
+
{
|
181
|
+
name: 'Store Credit',
|
182
|
+
type: 'store_credit',
|
183
|
+
quantity: 1,
|
184
|
+
unit_price: -1 * store_credit.amount.cents,
|
185
|
+
tax_rate: 0,
|
186
|
+
total_tax_amount: 0,
|
187
|
+
total_amount: -1 * store_credit.amount.cents
|
188
|
+
}
|
189
|
+
end
|
190
|
+
|
191
|
+
def gift_cards
|
192
|
+
if payment.respond_to?(:gift_card) &&
|
193
|
+
[payment.gift_card].compact
|
194
|
+
elsif payment.respond_to?(:gift_cards)
|
195
|
+
payment.gift_cards
|
196
|
+
else
|
197
|
+
[]
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def gift_card_line
|
202
|
+
amount = gift_cards.sum(&:amount)
|
203
|
+
|
204
|
+
return unless amount.to_i.positive?
|
205
|
+
|
206
|
+
{
|
207
|
+
name: 'Gift Card',
|
208
|
+
type: 'gift_card',
|
209
|
+
quantity: 1,
|
210
|
+
unit_price: -1 * amount,
|
211
|
+
tax_rate: 0,
|
212
|
+
total_tax_amount: 0,
|
213
|
+
total_amount: -1 * amount
|
214
|
+
}
|
215
|
+
end
|
216
|
+
|
217
|
+
def merchant_urls
|
218
|
+
{
|
219
|
+
confirmation:
|
220
|
+
Workarea::Storefront::Engine
|
221
|
+
.routes
|
222
|
+
.url_helpers
|
223
|
+
.checkout_confirmation_url(host: Workarea.config.host)
|
224
|
+
}
|
225
|
+
end
|
226
|
+
|
227
|
+
def include_tax_line?
|
228
|
+
payment.address.country.alpha2 == 'US'
|
229
|
+
end
|
230
|
+
|
231
|
+
def remove_inline_tax(hash)
|
232
|
+
hash[:order_lines].each do |line|
|
233
|
+
line.delete(:total_tax_amount)
|
234
|
+
line.delete(:tax_rate)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def extra_merchant_data
|
239
|
+
return unless order.extra_merchant_data.present?
|
240
|
+
|
241
|
+
{
|
242
|
+
body: order.extra_merchant_data.to_json,
|
243
|
+
content_type: 'application/vnd.klarna.internal.emd-v2+json'
|
244
|
+
}
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|