workarea-klarna 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +20 -0
  3. data/.eslintrc.json +36 -0
  4. data/.github/workflows/ci.yml +61 -0
  5. data/.gitignore +24 -0
  6. data/.rubocop.yml +2 -0
  7. data/.stylelintrc.json +8 -0
  8. data/.yardopts +1 -0
  9. data/CHANGELOG.md +81 -0
  10. data/Gemfile +17 -0
  11. data/LICENSE +52 -0
  12. data/README.md +79 -0
  13. data/Rakefile +56 -0
  14. data/app/assets/javascripts/workarea/storefront/klarna/modules/klarna_widget.js +99 -0
  15. data/app/controllers/workarea/storefront/checkout/addresses_controller.decorator +8 -0
  16. data/app/controllers/workarea/storefront/checkout/place_order_controller.decorator +9 -0
  17. data/app/controllers/workarea/storefront/checkouts_controller.decorator +13 -0
  18. data/app/models/workarea/checkout/steps/payment.decorator +22 -0
  19. data/app/models/workarea/order.decorator +7 -0
  20. data/app/models/workarea/payment.decorator +34 -0
  21. data/app/models/workarea/payment/authorize/klarna.rb +21 -0
  22. data/app/models/workarea/payment/capture/klarna.rb +21 -0
  23. data/app/models/workarea/payment/klarna_session.rb +18 -0
  24. data/app/models/workarea/payment/purchase/klarna.rb +21 -0
  25. data/app/models/workarea/payment/refund/klarna.rb +18 -0
  26. data/app/models/workarea/payment/tender/klarna.rb +53 -0
  27. data/app/services/workarea/setup_klarna_session.rb +24 -0
  28. data/app/view_models/workarea/storefront/checkout/payment_view_model.decorator +41 -0
  29. data/app/views/workarea/admin/orders/tenders/_klarna.html.haml +4 -0
  30. data/app/views/workarea/storefront/checkouts/_klarna_payments.html.haml +22 -0
  31. data/app/views/workarea/storefront/klarna/_sdk.html.haml +2 -0
  32. data/app/views/workarea/storefront/order_mailer/tenders/_klarna.html.haml +2 -0
  33. data/app/views/workarea/storefront/orders/tenders/_klarna.html.haml +6 -0
  34. data/bin/rails +20 -0
  35. data/config/initializers/appends.rb +14 -0
  36. data/config/initializers/configuration.rb +32 -0
  37. data/config/initializers/fields.rb +43 -0
  38. data/config/locales/en.yml +30 -0
  39. data/config/routes.rb +2 -0
  40. data/lib/workarea/klarna.rb +30 -0
  41. data/lib/workarea/klarna/bogus_gateway.rb +19 -0
  42. data/lib/workarea/klarna/engine.rb +10 -0
  43. data/lib/workarea/klarna/gateway.rb +92 -0
  44. data/lib/workarea/klarna/gateway/cancel_request.rb +16 -0
  45. data/lib/workarea/klarna/gateway/capture_request.rb +24 -0
  46. data/lib/workarea/klarna/gateway/create_session_request.rb +22 -0
  47. data/lib/workarea/klarna/gateway/order.rb +249 -0
  48. data/lib/workarea/klarna/gateway/place_order_request.rb +34 -0
  49. data/lib/workarea/klarna/gateway/refund_request.rb +24 -0
  50. data/lib/workarea/klarna/gateway/release_request.rb +16 -0
  51. data/lib/workarea/klarna/gateway/request.rb +91 -0
  52. data/lib/workarea/klarna/gateway/response.rb +38 -0
  53. data/lib/workarea/klarna/gateway/update_session_request.rb +30 -0
  54. data/lib/workarea/klarna/version.rb +5 -0
  55. data/package.json +9 -0
  56. data/test/dummy/Rakefile +6 -0
  57. data/test/dummy/app/assets/config/manifest.js +4 -0
  58. data/test/dummy/app/assets/javascripts/application.js +13 -0
  59. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  60. data/test/dummy/app/controllers/application_controller.rb +3 -0
  61. data/test/dummy/app/helpers/application_helper.rb +2 -0
  62. data/test/dummy/app/jobs/application_job.rb +2 -0
  63. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  64. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  65. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  66. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  67. data/test/dummy/bin/bundle +3 -0
  68. data/test/dummy/bin/rails +4 -0
  69. data/test/dummy/bin/rake +4 -0
  70. data/test/dummy/bin/setup +34 -0
  71. data/test/dummy/bin/update +29 -0
  72. data/test/dummy/config.ru +5 -0
  73. data/test/dummy/config/application.rb +24 -0
  74. data/test/dummy/config/boot.rb +5 -0
  75. data/test/dummy/config/cable.yml +9 -0
  76. data/test/dummy/config/environment.rb +5 -0
  77. data/test/dummy/config/environments/development.rb +54 -0
  78. data/test/dummy/config/environments/production.rb +86 -0
  79. data/test/dummy/config/environments/test.rb +43 -0
  80. data/test/dummy/config/initializers/application_controller_renderer.rb +6 -0
  81. data/test/dummy/config/initializers/assets.rb +11 -0
  82. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  83. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  84. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  85. data/test/dummy/config/initializers/inflections.rb +16 -0
  86. data/test/dummy/config/initializers/mime_types.rb +4 -0
  87. data/test/dummy/config/initializers/new_framework_defaults.rb +21 -0
  88. data/test/dummy/config/initializers/workarea.rb +5 -0
  89. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  90. data/test/dummy/config/locales/en.yml +23 -0
  91. data/test/dummy/config/puma.rb +47 -0
  92. data/test/dummy/config/routes.rb +5 -0
  93. data/test/dummy/config/secrets.yml +22 -0
  94. data/test/dummy/config/spring.rb +6 -0
  95. data/test/dummy/db/seeds.rb +2 -0
  96. data/test/dummy/log/.keep +0 -0
  97. data/test/factories/workarea/klarna.rb +146 -0
  98. data/test/integration/workarea/storefront/klarna_session_integration_test.rb +40 -0
  99. data/test/lib/workarea/klarna/gateway/capture_request_test.rb +26 -0
  100. data/test/lib/workarea/klarna/gateway/create_session_request_test.rb +26 -0
  101. data/test/lib/workarea/klarna/gateway/order_test.rb +135 -0
  102. data/test/lib/workarea/klarna/gateway/place_order_request_test.rb +34 -0
  103. data/test/lib/workarea/klarna/gateway/refund_request_test.rb +26 -0
  104. data/test/lib/workarea/klarna/gateway/request_test.rb +87 -0
  105. data/test/lib/workarea/klarna/gateway/response_test.rb +75 -0
  106. data/test/lib/workarea/klarna/gateway/update_session_request_test.rb +27 -0
  107. data/test/lib/workarea/klarna/gateway_test.rb +58 -0
  108. data/test/models/workarea/checkout/steps/klarna_payment_test.rb +79 -0
  109. data/test/models/workarea/klarna_payment_test.rb +45 -0
  110. data/test/models/workarea/payment/authorize/klarna_test.rb +34 -0
  111. data/test/models/workarea/payment/capture/klarna_test.rb +51 -0
  112. data/test/models/workarea/payment/tender/klarna_test.rb +81 -0
  113. data/test/services/workarea/setup_klarna_session_test.rb +42 -0
  114. data/test/teaspoon_env.rb +6 -0
  115. data/test/test_helper.rb +10 -0
  116. data/test/vcr_cassettes/klarna_authorize.yml +62 -0
  117. data/test/vcr_cassettes/klarna_capture.yml +60 -0
  118. data/test/vcr_cassettes/klarna_create_session.yml +61 -0
  119. data/test/vcr_cassettes/klarna_refund.yml +60 -0
  120. data/test/view_models/workarea/storefront/checkout/klarna_payment_view_model_test.rb +62 -0
  121. data/workarea-klarna.gemspec +20 -0
  122. metadata +177 -0
@@ -0,0 +1,2 @@
1
+ - if Workarea.config.load_klarna
2
+ = javascript_include_tag 'https://x.klarnacdn.net/kp/lib/v1/api.js'
@@ -0,0 +1,2 @@
1
+ %p
2
+ = "#{t('workarea.storefront.orders.klarna')}: #{number_to_currency(tender.amount)}"
@@ -0,0 +1,6 @@
1
+ .data-card
2
+ .data-card__cell
3
+ %p.data-card__line
4
+ = t('workarea.storefront.orders.klarna')
5
+ %p.data-card__line
6
+ = number_to_currency(tender.amount)
@@ -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.
@@ -0,0 +1,2 @@
1
+ Rails.application.routes.draw do
2
+ end
@@ -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,10 @@
1
+ require 'workarea/klarna'
2
+
3
+ module Workarea
4
+ module Klarna
5
+ class Engine < ::Rails::Engine
6
+ include Workarea::Plugin
7
+ isolate_namespace Workarea::Klarna
8
+ end
9
+ end
10
+ 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