solidus_paypal_commerce_platform 0.0.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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