workarea-sezzle 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  3. data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. data/.gitignore +23 -0
  6. data/.rubocop.yml +405 -0
  7. data/.scss-lint.yml +204 -0
  8. data/Gemfile +17 -0
  9. data/LICENSE +7 -0
  10. data/README.md +95 -0
  11. data/Rakefile +56 -0
  12. data/Workarea +0 -0
  13. data/app/assets/images/workarea/admin/sezzle/.keep +0 -0
  14. data/app/assets/images/workarea/sezzle_logo_white.svg +1 -0
  15. data/app/assets/images/workarea/storefront/sezzle/.keep +0 -0
  16. data/app/assets/javascripts/workarea/admin/sezzle/.keep +0 -0
  17. data/app/assets/javascripts/workarea/storefront/sezzle/.keep +0 -0
  18. data/app/assets/stylesheets/workarea/admin/sezzle/.keep +0 -0
  19. data/app/assets/stylesheets/workarea/admin/sezzle/components/_payment_icon.scss +13 -0
  20. data/app/assets/stylesheets/workarea/storefront/sezzle/.keep +0 -0
  21. data/app/assets/stylesheets/workarea/storefront/sezzle/components/_payment_icon.scss +13 -0
  22. data/app/assets/stylesheets/workarea/storefront/sezzle/components/_sezzle.scss +13 -0
  23. data/app/controllers/.keep +0 -0
  24. data/app/controllers/workarea/storefront/checkout/place_order_controller.decorator +11 -0
  25. data/app/controllers/workarea/storefront/sezzle_controller.rb +109 -0
  26. data/app/helpers/.keep +0 -0
  27. data/app/mailers/.keep +0 -0
  28. data/app/models/.keep +0 -0
  29. data/app/models/workarea/payment/authorize/sezzle.rb +32 -0
  30. data/app/models/workarea/payment/capture/sezzle.rb +85 -0
  31. data/app/models/workarea/payment/purchase/sezzle.rb +30 -0
  32. data/app/models/workarea/payment/refund/sezzle.rb +47 -0
  33. data/app/models/workarea/payment/tender/sezzle.rb +15 -0
  34. data/app/models/workarea/paymentl.decorator +32 -0
  35. data/app/services/workarea/sezzle/order_builder.rb +135 -0
  36. data/app/view_models/workarea/storefront/checkout/payment_view_model.decorator +11 -0
  37. data/app/views/.keep +0 -0
  38. data/app/views/workarea/admin/orders/tenders/_sezzle.html.haml +3 -0
  39. data/app/views/workarea/storefront/carts/_sezzle_widget.html.haml +5 -0
  40. data/app/views/workarea/storefront/checkouts/_sezzle_payment.html.haml +8 -0
  41. data/app/views/workarea/storefront/order_mailer/tenders/_sezzle.html.haml +1 -0
  42. data/app/views/workarea/storefront/order_mailer/tenders/_sezzle.text.erb +1 -0
  43. data/app/views/workarea/storefront/orders/tenders/_sezzle.html.haml +4 -0
  44. data/app/views/workarea/storefront/products/_sezzle_widget.html.haml +10 -0
  45. data/app/views/workarea/storefront/sezzle/_script.html.haml +22 -0
  46. data/bin/rails +25 -0
  47. data/config/initializers/appends.rb +30 -0
  48. data/config/initializers/configurations.rb +41 -0
  49. data/config/initializers/workarea.rb +21 -0
  50. data/config/locales/en.yml +43 -0
  51. data/config/routes.rb +5 -0
  52. data/lib/workarea/sezzle.rb +53 -0
  53. data/lib/workarea/sezzle/authentication.rb +49 -0
  54. data/lib/workarea/sezzle/bogus_gateway.rb +128 -0
  55. data/lib/workarea/sezzle/engine.rb +10 -0
  56. data/lib/workarea/sezzle/gateway.rb +90 -0
  57. data/lib/workarea/sezzle/response.rb +27 -0
  58. data/lib/workarea/sezzle/version.rb +5 -0
  59. data/module +0 -0
  60. data/test/dummy/.ruby-version +1 -0
  61. data/test/dummy/Rakefile +6 -0
  62. data/test/dummy/app/assets/config/manifest.js +2 -0
  63. data/test/dummy/app/assets/images/.keep +0 -0
  64. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  65. data/test/dummy/app/controllers/application_controller.rb +2 -0
  66. data/test/dummy/app/controllers/concerns/.keep +0 -0
  67. data/test/dummy/app/helpers/application_helper.rb +2 -0
  68. data/test/dummy/app/javascript/packs/application.js +14 -0
  69. data/test/dummy/app/jobs/application_job.rb +7 -0
  70. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  71. data/test/dummy/app/models/concerns/.keep +0 -0
  72. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  73. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  74. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  75. data/test/dummy/bin/rails +4 -0
  76. data/test/dummy/bin/rake +4 -0
  77. data/test/dummy/bin/setup +25 -0
  78. data/test/dummy/config.ru +5 -0
  79. data/test/dummy/config/application.rb +34 -0
  80. data/test/dummy/config/boot.rb +5 -0
  81. data/test/dummy/config/environment.rb +5 -0
  82. data/test/dummy/config/environments/development.rb +53 -0
  83. data/test/dummy/config/environments/production.rb +101 -0
  84. data/test/dummy/config/environments/test.rb +47 -0
  85. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  86. data/test/dummy/config/initializers/assets.rb +12 -0
  87. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  88. data/test/dummy/config/initializers/content_security_policy.rb +28 -0
  89. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  90. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  91. data/test/dummy/config/initializers/inflections.rb +16 -0
  92. data/test/dummy/config/initializers/mime_types.rb +4 -0
  93. data/test/dummy/config/initializers/workarea.rb +5 -0
  94. data/test/dummy/config/initializers/wrap_parameters.rb +9 -0
  95. data/test/dummy/config/locales/en.yml +33 -0
  96. data/test/dummy/config/puma.rb +38 -0
  97. data/test/dummy/config/routes.rb +5 -0
  98. data/test/dummy/config/spring.rb +6 -0
  99. data/test/dummy/db/seeds.rb +2 -0
  100. data/test/dummy/lib/assets/.keep +0 -0
  101. data/test/dummy/log/.keep +0 -0
  102. data/test/integration/workarea/storefront/sezzle_integration_test.rb +205 -0
  103. data/test/models/workarea/payment/sezzle_payment_integration_test.rb +82 -0
  104. data/test/services/workarea/sezzle/order_builder_test.rb +116 -0
  105. data/test/teaspoon_env.rb +6 -0
  106. data/test/test_helper.rb +10 -0
  107. data/workarea-sezzle.gemspec +20 -0
  108. metadata +163 -0
@@ -0,0 +1,11 @@
1
+ module Workarea
2
+ decorate Storefront::Checkout::PaymentViewModel, with: :sezzle do
3
+ decorated do
4
+ delegate :sezzle?, to: :payment
5
+ end
6
+
7
+ def using_new_card?
8
+ super && !sezzle?
9
+ end
10
+ end
11
+ end
File without changes
@@ -0,0 +1,3 @@
1
+ %li
2
+ = inline_svg('workarea/sezzle_logo_white.svg', class: 'payment-icon payment-icon--sezzle')
3
+ = number_to_currency tender.amount
@@ -0,0 +1,5 @@
1
+ - if Workarea.config.sezzle_merchant_id.present?
2
+ #sezzle.sezzle.sezzle--cart
3
+ .sezzle__prices
4
+ %span.sezzle__price
5
+ = number_to_currency(cart.total_price)
@@ -0,0 +1,8 @@
1
+ .checkout-payment__primary-method{ class: ('checkout-payment__primary-method--selected' if @step.sezzle?) }
2
+ .button-property
3
+ = hidden_field_tag 'from_checkout', 'from_checkout', id: nil
4
+ .value= radio_button_tag 'payment', 'sezzle', step.sezzle?
5
+ = label_tag 'payment[sezzle]', nil, class: 'button-property__name' do
6
+ %span.button-property__text= inline_svg('workarea/sezzle_logo_white.svg', class: 'payment-icon payment-icon--sezzle')
7
+ %p.checkout-payment__primary-method-description
8
+ %span= t('workarea.storefront.sezzle.on_continue')
@@ -0,0 +1 @@
1
+ %p= t('workarea.storefront.sezzle.paid_with_sezzle')
@@ -0,0 +1 @@
1
+ <%= t('workarea.sezzle.tender_description') %>
@@ -0,0 +1,4 @@
1
+ .data-card
2
+ .data-card__cell
3
+ %p.data-card__line.data-card__credit-card= inline_svg('workarea/sezzle_logo_white.svg', class: 'payment-icon payment-icon--sezzle')
4
+
@@ -0,0 +1,10 @@
1
+ - if Workarea.config.sezzle_merchant_id.present?
2
+ #sezzle.sezzle.sezzle--product
3
+ .sezzle__prices
4
+ - if product.show_sell_range?
5
+ %span.sezzle__price= number_to_currency(product.sell_min_price)
6
+
7
+ %span.sezzle__price= number_to_currency(product.sell_max_price)
8
+ - else
9
+ %span.sezzle__price
10
+ = number_to_currency(product.sell_min_price)
@@ -0,0 +1,22 @@
1
+ - if Workarea.config.sezzle_merchant_id.present?
2
+ :javascript
3
+ document.sezzleConfig = {
4
+ configGroups: [
5
+ {
6
+ targetXPath: "#{Workarea.config.sezzle.product_config[:target_xpath]}",
7
+ renderToPath: "#{Workarea.config.sezzle.product_config[:render_to_path]}",
8
+ urlMatch: "#{Workarea.config.sezzle.product_config[:url_match]}",
9
+ splitPriceElementsOn: "#{Workarea.config.sezzle.product_config[:split_price_elements_on]}"
10
+ },
11
+ {
12
+ targetXPath: "#{Workarea.config.sezzle.cart_config[:target_xpath]}",
13
+ renderToPath: "#{Workarea.config.sezzle.cart_config[:render_to_path]}",
14
+ urlMatch: "#{Workarea.config.sezzle.cart_config[:url_match]}",
15
+ altVersionTemplate: {
16
+ 'en': '4 interest-free payments of %%price%% with %%logo%% %%info%%',
17
+ 'fr': '4 paiements de %%price%% sans intérêts avec %%logo%% %%info%%'
18
+ }
19
+ }
20
+ ]
21
+ }
22
+ %script{ src: "https://widget.sezzle.com/v1/javascript/price-widget?uuid=#{Workarea.config.sezzle_merchant_id}" }
@@ -0,0 +1,25 @@
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/sezzle/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 'rails'
14
+ # Pick the frameworks you want:
15
+ require 'active_model/railtie'
16
+ require 'active_job/railtie'
17
+ # require "active_record/railtie"
18
+ # require "active_storage/engine"
19
+ require 'action_controller/railtie'
20
+ require 'action_mailer/railtie'
21
+ require 'action_view/railtie'
22
+ # require "action_cable/engine"
23
+ require 'sprockets/railtie'
24
+ require 'rails/test_unit/railtie'
25
+ require 'rails/engine/commands'
@@ -0,0 +1,30 @@
1
+ Workarea::Plugin.append_stylesheets(
2
+ 'admin.components',
3
+ 'workarea/storefront/sezzle/components/payment_icon'
4
+ )
5
+
6
+ Workarea::Plugin.append_stylesheets(
7
+ 'storefront.components',
8
+ 'workarea/storefront/sezzle/components/sezzle',
9
+ 'workarea/storefront/sezzle/components/payment_icon'
10
+ )
11
+
12
+ Workarea::Plugin.append_partials(
13
+ 'storefront.payment_method',
14
+ 'workarea/storefront/checkouts/sezzle_payment'
15
+ )
16
+
17
+ Workarea::Plugin.append_partials(
18
+ 'storefront.javascript',
19
+ 'workarea/storefront/sezzle/script'
20
+ )
21
+
22
+ Workarea::Plugin.append_partials(
23
+ 'storefront.product_pricing_details',
24
+ 'workarea/storefront/products/sezzle_widget'
25
+ )
26
+
27
+ Workarea::Plugin.append_partials(
28
+ 'storefront.cart_additional_information',
29
+ 'workarea/storefront/carts/sezzle_widget'
30
+ )
@@ -0,0 +1,41 @@
1
+ if Workarea::Configuration.respond_to?(:define_fields)
2
+ Workarea::Configuration.define_fields do
3
+ fieldset 'Sezzle', namespaced: true do
4
+ field 'Public Key',
5
+ type: :string,
6
+ description: 'Public Key from merchant dashboard.',
7
+ allow_blank: false,
8
+ encrypted: false
9
+
10
+ field 'Private Key',
11
+ type: :string,
12
+ description: 'Private Key from merchant dashboard.',
13
+ allow_blank: false,
14
+ encrypted: true
15
+
16
+ field 'Merchant ID',
17
+ type: :string,
18
+ default: 'TODO',
19
+ description: 'Merchant ID from merchant dashboard.',
20
+ allow_blank: false,
21
+ encrypted: false
22
+
23
+ field 'Payment Action',
24
+ type: :string,
25
+ description: 'Determines if the order payment is authorized or immediately catpured.',
26
+ allow_blank: false,
27
+ encrypted: false,
28
+ default: 'AUTH'
29
+
30
+ field 'Test Mode',
31
+ type: :boolean,
32
+ description: 'Uses the sandbox API endpoints if set to true. Contact your Sezzle representative for sandbox credentials.',
33
+ default: !Rails.env.production?
34
+ end
35
+ end
36
+ else
37
+
38
+ Workarea.config.sezzle_test_mode = !Rails.env.production?
39
+ Workarea.config.sezzle_payment_action = 'AUTH'
40
+ Workarea.config.sezzle_merchant_id = nil # Host apps are responsible for adding this configuration
41
+ end
@@ -0,0 +1,21 @@
1
+ Workarea.configure do |config|
2
+
3
+ config.tender_types.append(:sezzle)
4
+
5
+ config.sezzle = ActiveSupport::Configurable::Configuration.new
6
+ config.sezzle.api_timeout = 10
7
+ config.sezzle.open_timeout = 10
8
+
9
+ config.sezzle.product_config = {
10
+ target_xpath: '#sezzle/.sezzle__prices',
11
+ render_to_path: './',
12
+ url_match: 'products',
13
+ split_price_elements_on: '–'
14
+ }
15
+
16
+ config.sezzle.cart_config = {
17
+ target_xpath: '#sezzle/.sezzle__prices',
18
+ render_to_path: './',
19
+ url_match: 'cart'
20
+ }
21
+ end
@@ -0,0 +1,43 @@
1
+ ---
2
+ en:
3
+ workarea:
4
+ admin:
5
+ orders:
6
+ tenders:
7
+ sezzle:
8
+ title: Sezzle
9
+ storefront:
10
+ orders:
11
+ tenders:
12
+ sezzle: Sezzle
13
+ carts:
14
+ sezzle: "Pay %{installment_count} interest free payments of %{installment_price} with"
15
+ checkouts:
16
+ sezzle: Pay With Sezzle
17
+ products:
18
+ sezzle: "Or %{installment_count} payments of %{installment_price} with"
19
+ sezzle:
20
+ address_error: We can not ship to this address. Please enter a new address.
21
+ cancel_message: Your Sezzle order has been cancelled. Please select another payment method.
22
+ check_out: Check Out with sezzle
23
+ checkout_submit_text: Continue to Sezzle
24
+ on_continue: When you continue, you will be taken to the Sezzle website to complete your order.
25
+ paid_with_sezzle: Paid with Sezzle
26
+ payment_error: There was a problem processing your Sezzle payment, you have not been charged. Please try again or select
27
+ another payment method.
28
+ sezzle: Sezzle
29
+ learn_more: Learn More
30
+ sezzle:
31
+ authorize: "%{amount} has been captured"
32
+ pre_authorized_message: "Authorization happens in the Sezzle checkout process. No API response to record. %{amount} was authorized by Sezzle."
33
+ pre_captured_message: "Capture occurred in the Sezzle checkout process. No API response to record. %{amount} was captured by Sezzle."
34
+ pre_purchased_message: "Authorize and Capture occurred in the Sezzle checkout process. No API response to record. %{amount} was captured by Sezzle."
35
+ capture: "%{amount} has been captured"
36
+ purchase: "%{amount} has been purchased"
37
+ refund: "%{amount} has been refunded"
38
+ void: Sezzle Transaction has been voided
39
+ authorize_failure: Sezzle authorize has failed
40
+ capture_failure: Sezzle capture has failed
41
+ purchase_failure: Sezzle purchase has failed
42
+ refund_failure: Sezzle refund has failed
43
+
@@ -0,0 +1,5 @@
1
+ Workarea::Storefront::Engine.routes.draw do
2
+ get 'sezzle/start' => 'sezzle#start', as: :start_sezzle
3
+ get 'sezzle/complete' => 'sezzle#complete', as: :complete_sezzle
4
+ get 'sezzle/cancel' => 'sezzle#cancel', as: :cancel_sezzle
5
+ end
@@ -0,0 +1,53 @@
1
+ require 'workarea'
2
+ require 'workarea/storefront'
3
+ require 'workarea/admin'
4
+
5
+ require 'workarea/sezzle/engine'
6
+ require 'workarea/sezzle/version'
7
+
8
+ require 'workarea/sezzle/bogus_gateway'
9
+ require 'workarea/sezzle/authentication'
10
+ require 'workarea/sezzle/gateway'
11
+ require 'workarea/sezzle/response'
12
+
13
+ module Workarea
14
+ module Sezzle
15
+ def self.config
16
+ Workarea.config.sezzle
17
+ end
18
+
19
+ # used for <= 3.5, these creds come from secrets isntead of encrypted in the
20
+ # 3.5 configurations system.
21
+ def self.credentials
22
+ (Rails.application.secrets.sezzle || {}).deep_symbolize_keys
23
+ end
24
+
25
+ def self.api_public_key
26
+ Workarea.config.sezzle_public_key || credentials[:api_public_key]
27
+ end
28
+
29
+ def self.api_private_key
30
+ Workarea.config.sezzle_private_key || credentials[:api_private_key]
31
+ end
32
+
33
+ def self.api_configured?
34
+ api_public_key.present? && api_private_key.present?
35
+ end
36
+
37
+ def self.test?
38
+ Workarea.config.sezzle_test_mode || credentials[:test]
39
+ end
40
+
41
+ def self.gateway
42
+ if Rails.env.test?
43
+ Sezzle::BogusGateway.new
44
+ else
45
+ Sezzle::Gateway.new(
46
+ api_public_key: api_public_key,
47
+ api_private_key: api_private_key,
48
+ test: test?
49
+ )
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,49 @@
1
+ module Workarea
2
+ module Sezzle
3
+ module Authentication
4
+ class AuthenticationError < StandardError; end
5
+ def token
6
+ response = get_token
7
+ body = JSON.parse(response.body)
8
+
9
+ raise AuthenticationError, response.body.to_s unless response.success?
10
+ body['token']
11
+ end
12
+
13
+ private
14
+
15
+ def get_token
16
+ Rails.cache.fetch(token_cache_key, expires_in: 5.minutes) do
17
+
18
+ body = {
19
+ public_key: api_public_key,
20
+ private_key: api_private_key
21
+ }
22
+
23
+ conn = Faraday.new(url: rest_endpoint)
24
+ conn.post do |req|
25
+ req.url 'v2/authentication'
26
+ req.headers['Content-Type'] = 'application/json'
27
+ req.body = body.to_json
28
+ end
29
+ end
30
+ end
31
+
32
+ def api_public_key
33
+ options[:api_public_key]
34
+ end
35
+
36
+ def api_private_key
37
+ options[:api_private_key]
38
+ end
39
+
40
+ def test
41
+ options[:test]
42
+ end
43
+
44
+ def token_cache_key
45
+ Digest::MD5.hexdigest "#{api_public_key}#{api_private_key}#{test}"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,128 @@
1
+ module Workarea
2
+ module Sezzle
3
+ class BogusGateway
4
+ def initialize(*)
5
+ end
6
+
7
+ def create_session(order)
8
+ b = {
9
+ "uuid": 'fadbc642-05a4-4e38-9e74-80e325623af9',
10
+ "links": [
11
+ {
12
+ "href": 'https://gateway.sezzle.com/v2/session/fadbc642-05a4-4e38-9e74-80e325623af9',
13
+ "method": 'GET',
14
+ "rel": 'self'
15
+ }
16
+ ],
17
+ "order": {
18
+ "uuid": '12a34bc5-6de7-890f-g123-4hi1238jk902',
19
+ "intent": 'CAPTURE',
20
+ "checkout_url": 'https://checkout.sezzle.com/?id=12a34bc5-6de7-890f-g123-4hi1238jk902',
21
+ "links": [
22
+ {
23
+ "href": 'https://gateway.sezzle.com/v2/order/12a34bc5-6de7-890f-g123-4hi1238jk902',
24
+ "method": 'GET',
25
+ "rel": 'self'
26
+ },
27
+ {
28
+ "href": 'https://gateway.sezzle.com/v2/order/12a34bc5-6de7-890f-g123-4hi1238jk902',
29
+ "method": 'PATCH',
30
+ "rel": 'self'
31
+ },
32
+ {
33
+ "href": 'https://gateway.sezzle.com/v2/order/12a34bc5-6de7-890f-g123-4hi1238jk902/release',
34
+ "method": 'POST',
35
+ "rel": 'release'
36
+ },
37
+ {
38
+ "href": 'https://gateway.sezzle.com/v2/order/12a34bc5-6de7-890f-g123-4hi1238jk902/capture',
39
+ "method": 'POST',
40
+ "rel": 'capture'
41
+ },
42
+ {
43
+ "href": 'https://gateway.sezzle.com/v2/order/12a34bc5-6de7-890f-g123-4hi1238jk902/refund',
44
+ "method": 'POST',
45
+ "rel": 'refund'
46
+ }
47
+ ]
48
+ },
49
+ "tokenize": {
50
+ "token": '7ec98824-67cc-469c-86ab-f9e047f9cf1a',
51
+ "expiration": '2020-04-27T14:46:59Z',
52
+ "approval_url": 'https://dashboard.sezzle.com/customer/checkout-approval?merchant-request-id=3f3244fd-78ce-4994-af0c-b8c760d47794',
53
+ "links": [
54
+ {
55
+ "href": 'https://gateway.sezzle.com/v2/token/7ec98824-67cc-469c-86ab-f9e047f9cf1a/session ',
56
+ "method": 'GET',
57
+ "rel": 'token'
58
+ }
59
+ ]
60
+ }
61
+ }
62
+
63
+ Response.new(response(b))
64
+ end
65
+
66
+ def capture(id, attrs)
67
+ if id == 'error'
68
+ Response.new(response(error_response_body, 400))
69
+ else
70
+ Response.new(response(payment_response_body, 200))
71
+ end
72
+ end
73
+
74
+ def authorize(id, attrs)
75
+ if id == 'error'
76
+ Response.new(response(error_response_body, 400))
77
+ elsif id == 'timeout_token'
78
+ Response.new(response(nil, 502))
79
+ else
80
+ Response.new(response(payment_response_body, 200))
81
+ end
82
+ end
83
+
84
+ def purchase(id, attrs)
85
+ if id == 'error'
86
+ Response.new(response(error_response_body, 402))
87
+ else
88
+ Response.new(response(payment_response_body, 200))
89
+ end
90
+ end
91
+
92
+ def refund(id, attrs)
93
+ Response.new(response(payment_response_body, 200))
94
+ end
95
+
96
+ def void(payment_id)
97
+ Response.new(response(payment_response_body))
98
+ end
99
+
100
+ private
101
+
102
+ def response(body, status = 200)
103
+ response = Faraday.new do |builder|
104
+ builder.adapter :test do |stub|
105
+ stub.get('/v1/bogus') { |env| [ status, {}, body.to_json ] }
106
+ end
107
+ end
108
+ response.get('/v1/bogus')
109
+ end
110
+
111
+ def error_response_body
112
+ [
113
+ {
114
+ "code": 'invalid',
115
+ "message": 'Order must have a valid intent',
116
+ "location": 'order.intent'
117
+ }
118
+ ]
119
+ end
120
+
121
+ def payment_response_body(status: 'APPROVED')
122
+ {
123
+ "uuid": '6c9db5d4-d09a-4224-860a-b5438ac32ca8'
124
+ }
125
+ end
126
+ end
127
+ end
128
+ end