solidus_paypal_commerce_platform 0.0.1 → 0.3.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/stale.yml +4 -4
  3. data/.github_changelog_generator +2 -0
  4. data/.rubocop.yml +18 -5
  5. data/CHANGELOG.md +181 -0
  6. data/README.md +49 -28
  7. data/app/assets/javascripts/spree/frontend/solidus_paypal_commerce_platform/button_actions.js +14 -4
  8. data/app/assets/javascripts/spree/frontend/solidus_paypal_commerce_platform/buttons.js +6 -3
  9. data/app/controllers/solidus_paypal_commerce_platform/orders_controller.rb +8 -5
  10. data/app/controllers/solidus_paypal_commerce_platform/paypal_orders_controller.rb +3 -1
  11. data/app/decorators/models/solidus_paypal_commerce_platform/spree/address_decorator.rb +17 -0
  12. data/app/jobs/solidus_paypal_commerce_platform/webhook_job.rb +3 -3
  13. data/app/models/solidus_paypal_commerce_platform/gateway.rb +1 -2
  14. data/app/models/solidus_paypal_commerce_platform/payment_method.rb +4 -1
  15. data/app/models/solidus_paypal_commerce_platform/paypal_address.rb +27 -23
  16. data/app/models/solidus_paypal_commerce_platform/paypal_order.rb +3 -3
  17. data/app/models/solidus_paypal_commerce_platform/pricing_options.rb +1 -1
  18. data/app/models/solidus_paypal_commerce_platform/state_guesser.rb +28 -0
  19. data/bin/rails-sandbox +1 -3
  20. data/bin/sandbox +2 -2
  21. data/lib/generators/solidus_paypal_commerce_platform/install/install_generator.rb +14 -5
  22. data/lib/generators/solidus_paypal_commerce_platform/install/templates/initializer.rb +6 -0
  23. data/lib/solidus_paypal_commerce_platform.rb +7 -5
  24. data/lib/solidus_paypal_commerce_platform/client.rb +5 -4
  25. data/lib/solidus_paypal_commerce_platform/configuration.rb +14 -4
  26. data/lib/solidus_paypal_commerce_platform/{factories.rb → testing_support/factories.rb} +0 -0
  27. data/lib/solidus_paypal_commerce_platform/version.rb +1 -1
  28. data/lib/views/frontend/solidus_paypal_commerce_platform/shared/_javascript_sdk_tag.html.erb +6 -0
  29. data/lib/views/frontend/spree/checkout/payment/_paypal_commerce_platform.html.erb +6 -1
  30. data/lib/views/frontend/spree/orders/payment/_paypal_commerce_platform.html.erb +3 -2
  31. data/lib/views/frontend/spree/products/payment/_paypal_commerce_platform.html.erb +3 -2
  32. data/solidus_paypal_commerce_platform.gemspec +7 -6
  33. data/spec/features/frontend/cart_spec.rb +21 -13
  34. data/spec/features/frontend/checkout_spec.rb +21 -13
  35. data/spec/features/frontend/product_spec.rb +27 -15
  36. data/spec/lib/solidus_paypal_commerce_platform/client_spec.rb +1 -1
  37. data/spec/lib/solidus_paypal_commerce_platform/configuration_spec.rb +28 -4
  38. data/spec/models/solidus_paypal_commerce_platform/payment_method_spec.rb +19 -0
  39. data/spec/models/solidus_paypal_commerce_platform/paypal_address_spec.rb +17 -5
  40. data/spec/models/solidus_paypal_commerce_platform/state_guesser_spec.rb +38 -0
  41. data/spec/requests/solidus_paypal_commerce_platform/orders_controller_spec.rb +2 -2
  42. data/spec/requests/solidus_paypal_commerce_platform/shipping_rates_controller_spec.rb +3 -3
  43. data/spec/requests/solidus_paypal_commerce_platform/wizard_controller_spec.rb +1 -1
  44. data/spec/spec_helper.rb +10 -8
  45. data/spec/support/capybara.rb +11 -0
  46. data/spec/support/paypal_sdk_script_tag_helper.rb +13 -0
  47. metadata +46 -15
  48. data/app/decorators/solidus_paypal_commerce_platform/remove_required_phone_from_address.rb +0 -13
@@ -11,7 +11,8 @@ module SolidusPaypalCommercePlatform
11
11
 
12
12
  @order = ::Spree::Order.create!(
13
13
  user: try_spree_current_user,
14
- store: current_store
14
+ store: current_store,
15
+ currency: current_pricing_options.currency
15
16
  )
16
17
 
17
18
  if @order.contents.update_cart order_params
@@ -64,10 +65,12 @@ module SolidusPaypalCommercePlatform
64
65
  ],
65
66
  recipient: [
66
67
  :email_address,
67
- name: [
68
- :given_name,
69
- :surname,
70
- ]
68
+ {
69
+ name: [
70
+ :given_name,
71
+ :surname,
72
+ ]
73
+ }
71
74
  ]
72
75
  )
73
76
  end
@@ -7,7 +7,9 @@ module SolidusPaypalCommercePlatform
7
7
 
8
8
  def show
9
9
  authorize! :show, @order, order_token
10
- render json: @payment_method.gateway.create_order(@order, @payment_method.auto_capture), status: :ok
10
+ order_request = @payment_method.gateway.create_order(@order, @payment_method.auto_capture)
11
+
12
+ render json: order_request, status: order_request.status_code
11
13
  end
12
14
 
13
15
  private
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusPaypalCommercePlatform
4
+ module Spree
5
+ module AddressDecorator
6
+ # PayPal doesn't use the phone number, so in cases where the user checks out via
7
+ # PayPal, this field will be unpopulated. If you want to require phone numbers,
8
+ # you'll need to turn off cart & product page displays on the payment method edit
9
+ # page.
10
+ def require_phone?
11
+ super && false
12
+ end
13
+
14
+ ::Spree::Address.prepend self
15
+ end
16
+ end
17
+ end
@@ -6,15 +6,15 @@ module SolidusPaypalCommercePlatform
6
6
  case payload["resource_type"]
7
7
  when "checkout-order"
8
8
  payment_source = PaymentSource.find_by!(paypal_order_id: payload.dig("resource", "id"))
9
- payment = Spree::Payment.where(source: payment_source).last!
9
+ payment = ::Spree::Payment.where(source: payment_source).last!
10
10
  payment.log_entries.create!(details: payload.to_yaml)
11
11
  when "capture"
12
12
  payment_source = PaymentSource.find_by!(capture_id: payload.dig("resource", "id"))
13
- payment = Spree::Payment.where(source: payment_source).last!
13
+ payment = ::Spree::Payment.where(source: payment_source).last!
14
14
  payment.log_entries.create!(details: payload.to_yaml)
15
15
  when "refund"
16
16
  payment_source = PaymentSource.find_by!(refund_id: payload.dig("resource", "id"))
17
- payment = Spree::Payment.where(source: payment_source).last!
17
+ payment = ::Spree::Payment.where(source: payment_source).last!
18
18
  payment.log_entries.create!(details: payload.to_yaml)
19
19
  end
20
20
  end
@@ -77,8 +77,7 @@ module SolidusPaypalCommercePlatform
77
77
  request = OrdersCreateRequest.new
78
78
  paypal_order = SolidusPaypalCommercePlatform::PaypalOrder.new(order)
79
79
  request.request_body paypal_order.to_json(intent)
80
-
81
- @client.execute(request).result
80
+ @client.execute(request)
82
81
  end
83
82
 
84
83
  def get_order(order_id)
@@ -11,6 +11,7 @@ module SolidusPaypalCommercePlatform
11
11
  preference :paypal_button_layout, :paypal_select, default: "vertical"
12
12
  preference :display_on_cart, :boolean, default: true
13
13
  preference :display_on_product_page, :boolean, default: true
14
+ preference :display_credit_messaging, :boolean, default: true
14
15
 
15
16
  def partial_name
16
17
  "paypal_commerce_platform"
@@ -57,7 +58,7 @@ module SolidusPaypalCommercePlatform
57
58
  }
58
59
  end
59
60
 
60
- def javascript_sdk_url(order: nil)
61
+ def javascript_sdk_url(order: nil, currency: nil)
61
62
  # Both instance and class respond to checkout_steps.
62
63
  step_names = order ? order.checkout_steps : ::Spree::Order.checkout_steps.keys
63
64
 
@@ -67,6 +68,8 @@ module SolidusPaypalCommercePlatform
67
68
  'client-id': client_id,
68
69
  intent: auto_capture ? "capture" : "authorize",
69
70
  commit: commit_immediately ? "false" : "true",
71
+ components: options[:display_credit_messaging] ? "buttons,messages" : "buttons",
72
+ currency: currency
70
73
  }
71
74
 
72
75
  "https://www.paypal.com/sdk/js?#{parameters.to_query}"
@@ -19,7 +19,7 @@ module SolidusPaypalCommercePlatform
19
19
  end
20
20
 
21
21
  def update(paypal_address)
22
- formatted_address = format_address(paypal_address)
22
+ formatted_address = address_attributes(paypal_address[:updated_address], paypal_address[:recipient])
23
23
  new_address = @order.ship_address.dup || ::Spree::Address.new
24
24
  new_address.assign_attributes(formatted_address)
25
25
 
@@ -42,40 +42,44 @@ module SolidusPaypalCommercePlatform
42
42
  @order.update(email: recipient[:email_address])
43
43
  end
44
44
 
45
+ def find_state(state_name, country)
46
+ if state = country.states.find_by(abbr: state_name) || country.states.find_by(name: state_name)
47
+ state
48
+ else
49
+ SolidusPaypalCommercePlatform.config.state_guesser_class.new(state_name, country).guess
50
+ end
51
+ end
52
+
45
53
  def format_simulated_address(paypal_address)
46
- country = ::Spree::Country.find_by(iso: paypal_address[:country_code])
47
- # Also adds fake information for a few fields, so validations can run
54
+ # Adds fake information for a few fields, so validations can run
55
+ paypal_address[:address_line_1] = "123 Fake St."
56
+
48
57
  ::Spree::Address.new(
49
- city: paypal_address[:city],
50
- state: country.states.find_by(abbr: paypal_address[:state]),
51
- state_name: paypal_address[:state],
52
- zipcode: paypal_address[:postal_code],
53
- country: country,
54
- address1: "123 Fake St.",
55
- phone: "123456789",
56
- firstname: "Fake"
58
+ address_attributes(paypal_address, { name: { given_name: "Fake" } })
57
59
  )
58
60
  end
59
61
 
60
- def format_address(paypal_address)
61
- address = paypal_address[:updated_address]
62
- recipient = paypal_address[:recipient]
62
+ def address_attributes(address, recipient)
63
63
  country = ::Spree::Country.find_by(iso: address[:country_code])
64
- state = country.states.where(abbr: address[:admin_area_1]).or(
65
- country.states.where(name: address[:admin_area_1])
66
- ).first
67
64
 
68
- {
65
+ attributes = {
69
66
  address1: address[:address_line_1],
70
67
  address2: address[:address_line_2],
71
- state: state,
72
- state_name: address[:admin_area_1],
73
- city: address[:admin_area_2],
68
+ state: find_state(address[:admin_area_1] || address[:state], country),
69
+ state_name: address[:admin_area_1] || address[:state],
70
+ city: address[:admin_area_2] || address[:city],
74
71
  country: country,
75
72
  zipcode: address[:postal_code],
76
- firstname: recipient[:name][:given_name],
77
- lastname: recipient[:name][:surname]
78
73
  }
74
+
75
+ if SolidusSupport.combined_first_and_last_name_in_address?
76
+ attributes[:name] = "#{recipient[:name][:given_name]} #{recipient[:name][:surname]}"
77
+ else
78
+ attributes[:firstname] = recipient[:name][:given_name]
79
+ attributes[:lastname] = recipient[:name][:surname]
80
+ end
81
+
82
+ attributes
79
83
  end
80
84
  end
81
85
  end
@@ -18,7 +18,7 @@ module SolidusPaypalCommercePlatform
18
18
  {
19
19
  op: 'replace',
20
20
  path: '/purchase_units/@reference_id==\'default\'',
21
- value: purchase_units[0]
21
+ value: purchase_units(include_shipping_address: false)[0]
22
22
  }
23
23
  end
24
24
 
@@ -50,12 +50,12 @@ module SolidusPaypalCommercePlatform
50
50
  }
51
51
  end
52
52
 
53
- def purchase_units
53
+ def purchase_units(include_shipping_address: true)
54
54
  [
55
55
  {
56
56
  amount: amount,
57
57
  items: line_items,
58
- shipping: (shipping_info if @order.ship_address)
58
+ shipping: (shipping_info if @order.ship_address && include_shipping_address)
59
59
  }
60
60
  ]
61
61
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidusPaypalCommercePlatform
4
- class PricingOptions < Spree::Variant::PricingOptions
4
+ class PricingOptions < ::Spree::Variant::PricingOptions
5
5
  def cache_key
6
6
  SolidusPaypalCommercePlatform::PaymentMethod.active.map(&:cache_key_with_version).sort + [super]
7
7
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusPaypalCommercePlatform
4
+ class StateGuesser
5
+ def initialize(state_name, country)
6
+ @state_name = state_name
7
+ @country = country
8
+ end
9
+
10
+ def guess
11
+ carmen_state = state_list.find{ |s| s.name == @state_name || s.code == @state_name }
12
+ return if carmen_state.blank?
13
+
14
+ guessed_state = spree_state(carmen_state.name)
15
+ guessed_state || spree_state(carmen_state.parent.name)
16
+ end
17
+
18
+ private
19
+
20
+ def state_list
21
+ Carmen::Country.coded(@country.iso).subregions.map{ |s| [s, s.subregions] }.flatten
22
+ end
23
+
24
+ def spree_state(name)
25
+ ::Spree::State.find_by(name: name)
26
+ end
27
+ end
28
+ end
data/bin/rails-sandbox CHANGED
@@ -1,13 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # frozen_string_literal: true
4
-
5
3
  app_root = 'sandbox'
6
4
 
7
5
  unless File.exist? "#{app_root}/bin/rails"
8
6
  warn 'Creating the sandbox app...'
9
7
  Dir.chdir "#{__dir__}/.." do
10
- system "#{__dir__}/sandbox" or begin # rubocop:disable Style/AndOr
8
+ system "#{__dir__}/sandbox" or begin
11
9
  warn 'Automatic creation of the sandbox app failed'
12
10
  exit 1
13
11
  end
data/bin/sandbox CHANGED
@@ -72,11 +72,11 @@ unbundled bundle exec rails generate spree:install \
72
72
  --user_class=Spree::User \
73
73
  --enforce_available_locales=true \
74
74
  --with-authentication=false \
75
+ --payment-method=none \
75
76
  $@
76
77
 
77
78
  unbundled bundle exec rails generate solidus:auth:install
78
- unbundled bundle exec rails generate solidus_paypal_commerce_platform:install
79
- unbundled bundle exec rails db:migrate
79
+ unbundled bundle exec rails generate ${extension_name}:install
80
80
 
81
81
  echo
82
82
  echo "🚀 Sandbox app successfully created for $extension_name!"
@@ -4,15 +4,22 @@ module SolidusPaypalCommercePlatform
4
4
  module Generators
5
5
  class InstallGenerator < Rails::Generators::Base
6
6
  class_option :auto_run_migrations, type: :boolean, default: false
7
+ class_option :skip_migrations, type: :boolean, default: false
8
+
9
+ source_root File.expand_path('templates', __dir__)
10
+
11
+ def copy_initializer
12
+ template 'initializer.rb', 'config/initializers/solidus_paypal_commerce_platform.rb'
13
+ end
7
14
 
8
15
  def add_javascripts
9
- append_file 'vendor/assets/javascripts/spree/frontend/all.js', "//= require spree/frontend/solidus_paypal_commerce_platform\n" # rubocop:disable Metrics/LineLength
10
- append_file 'vendor/assets/javascripts/spree/backend/all.js', "//= require spree/backend/solidus_paypal_commerce_platform\n" # rubocop:disable Metrics/LineLength
16
+ append_file 'vendor/assets/javascripts/spree/frontend/all.js', "//= require spree/frontend/solidus_paypal_commerce_platform\n" # rubocop:disable Layout/LineLength
17
+ append_file 'vendor/assets/javascripts/spree/backend/all.js', "//= require spree/backend/solidus_paypal_commerce_platform\n" # rubocop:disable Layout/LineLength
11
18
  end
12
19
 
13
20
  def add_stylesheets
14
- inject_into_file 'vendor/assets/stylesheets/spree/frontend/all.css', " *= require spree/frontend/solidus_paypal_commerce_platform\n", before: %r{\*/}, verbose: true # rubocop:disable Metrics/LineLength
15
- inject_into_file 'vendor/assets/stylesheets/spree/backend/all.css', " *= require spree/backend/solidus_paypal_commerce_platform\n", before: %r{\*/}, verbose: true # rubocop:disable Metrics/LineLength
21
+ inject_into_file 'vendor/assets/stylesheets/spree/frontend/all.css', " *= require spree/frontend/solidus_paypal_commerce_platform\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength
22
+ inject_into_file 'vendor/assets/stylesheets/spree/backend/all.css', " *= require spree/backend/solidus_paypal_commerce_platform\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength
16
23
  end
17
24
 
18
25
  def add_migrations
@@ -26,7 +33,9 @@ module SolidusPaypalCommercePlatform
26
33
  end
27
34
 
28
35
  def run_migrations
29
- run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]')) # rubocop:disable Metrics/LineLength
36
+ return if options[:skip_migrations]
37
+
38
+ run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]')) # rubocop:disable Layout/LineLength
30
39
  if run_migrations
31
40
  run 'bin/rails db:migrate'
32
41
  else
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ SolidusPaypalCommercePlatform.configure do |config|
4
+ # TODO: Remember to change this with the actual preferences you have implemented!
5
+ # config.sample_preference = 'sample_value'
6
+ end
@@ -3,19 +3,21 @@
3
3
  require 'solidus_core'
4
4
  require 'solidus_support'
5
5
 
6
- require 'solidus_paypal_commerce_platform/version'
7
- require 'solidus_paypal_commerce_platform/configuration'
8
6
  require 'solidus_paypal_commerce_platform/client'
7
+ require 'solidus_paypal_commerce_platform/configuration'
8
+ require 'solidus_paypal_commerce_platform/version'
9
9
  require 'solidus_paypal_commerce_platform/engine'
10
10
 
11
11
  module SolidusPaypalCommercePlatform
12
12
  class << self
13
- def config
14
- @config ||= Configuration.new
13
+ def configuration
14
+ @configuration ||= Configuration.new
15
15
  end
16
16
 
17
+ alias config configuration
18
+
17
19
  def configure
18
- yield config
20
+ yield configuration
19
21
  end
20
22
  end
21
23
  end
@@ -10,12 +10,12 @@ module SolidusPaypalCommercePlatform
10
10
  SUCCESS_STATUS_CODES = [201, 204].freeze
11
11
 
12
12
  PARTNER_ATTRIBUTION_INJECTOR = ->(request) {
13
- request.headers["PayPal-Partner-Attribution-Id"] = "Solidus_PCP_SP"
13
+ request.headers["PayPal-Partner-Attribution-Id"] = SolidusPaypalCommercePlatform.config.partner_code
14
14
  }.freeze
15
15
 
16
16
  attr_reader :environment
17
17
 
18
- def initialize(test_mode: nil, client_id:, client_secret: "")
18
+ def initialize(client_id:, client_secret: "", test_mode: nil)
19
19
  test_mode = SolidusPaypalCommercePlatform.config.env.sandbox? if test_mode.nil?
20
20
  env_class = test_mode ? PayPal::SandboxEnvironment : PayPal::LiveEnvironment
21
21
 
@@ -27,8 +27,9 @@ module SolidusPaypalCommercePlatform
27
27
 
28
28
  def execute(request)
29
29
  @paypal_client.execute(request)
30
- rescue PayPalHttp::HttpError
31
- OpenStruct.new(status_code: nil)
30
+ rescue PayPalHttp::HttpError => e
31
+ Rails.logger.error e.result
32
+ OpenStruct.new(status_code: 422, error: e.result)
32
33
  end
33
34
 
34
35
  def execute_with_response(request, success_message: nil, failure_message: nil)
@@ -4,6 +4,8 @@ require 'paypal-checkout-sdk'
4
4
 
5
5
  module SolidusPaypalCommercePlatform
6
6
  class Configuration
7
+ attr_writer :state_guesser_class, :partner_id, :partner_client_id
8
+
7
9
  InvalidEnvironment = Class.new(StandardError)
8
10
 
9
11
  DEFAULT_PARTNER_ID = {
@@ -16,6 +18,12 @@ module SolidusPaypalCommercePlatform
16
18
  live: "ASOxaUMkeX5bv7PbXnWUDnqb3SVYkzRSosApmLGFih-eAhB_OS_Wo6juijE5t8NCmWDgpN2ugHMmQFWA",
17
19
  }.freeze
18
20
 
21
+ def state_guesser_class
22
+ self.state_guesser_class = "SolidusPaypalCommercePlatform::StateGuesser" unless @state_guesser_class
23
+
24
+ @state_guesser_class.constantize
25
+ end
26
+
19
27
  def env=(value)
20
28
  unless %w[live sandbox].include? value
21
29
  raise InvalidEnvironment, "#{value} is not a valid environment"
@@ -35,9 +43,9 @@ module SolidusPaypalCommercePlatform
35
43
 
36
44
  case Rails.env
37
45
  when 'production'
38
- return 'live'
46
+ 'live'
39
47
  when 'test', 'development'
40
- return 'sandbox'
48
+ 'sandbox'
41
49
  else
42
50
  raise InvalidEnvironment, "Unable to guess the PayPal env, please set #{self}.env= explicitly."
43
51
  end
@@ -51,8 +59,6 @@ module SolidusPaypalCommercePlatform
51
59
  env.live? ? "www.paypal.com" : "www.sandbox.paypal.com"
52
60
  end
53
61
 
54
- attr_writer :partner_id, :partner_client_id
55
-
56
62
  def partner_id
57
63
  @partner_id ||= ENV['PAYPAL_PARTNER_ID'] || DEFAULT_PARTNER_ID[env.to_sym]
58
64
  end
@@ -60,5 +66,9 @@ module SolidusPaypalCommercePlatform
60
66
  def partner_client_id
61
67
  @partner_client_id ||= ENV['PAYPAL_PARTNER_CLIENT_ID'] || DEFAULT_PARTNER_CLIENT_ID[env.to_sym]
62
68
  end
69
+
70
+ def partner_code
71
+ "Solidus_PCP_SP"
72
+ end
63
73
  end
64
74
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidusPaypalCommercePlatform
4
- VERSION = '0.0.1'
4
+ VERSION = '0.3.0'
5
5
  end