solidus_paypal_commerce_platform 0.5.0 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +21 -9
  3. data/.github/stale.yml +1 -17
  4. data/.gitignore +1 -0
  5. data/.rspec +0 -1
  6. data/.rubocop.yml +3 -2
  7. data/CHANGELOG.md +42 -9
  8. data/Gemfile +0 -41
  9. data/README.md +9 -12
  10. data/Rakefile +6 -1
  11. data/app/models/solidus_paypal_commerce_platform/payment_method.rb +6 -5
  12. data/app/models/solidus_paypal_commerce_platform/paypal_address.rb +6 -2
  13. data/app/models/solidus_paypal_commerce_platform/paypal_order.rb +22 -15
  14. data/bin/dummy-app +37 -0
  15. data/bin/rails-dummy-app +17 -0
  16. data/bin/rspec +11 -0
  17. data/bin/sandbox +3 -29
  18. data/lib/generators/solidus_paypal_commerce_platform/install/install_generator.rb +62 -23
  19. data/lib/generators/solidus_paypal_commerce_platform/install/templates/app/assets/javascripts/spree/frontend/solidus_paypal_commerce_platform/button_actions.js +272 -0
  20. data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/assets/javascripts/spree/frontend/solidus_paypal_commerce_platform/buttons.js +0 -0
  21. data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/assets/javascripts/spree/frontend/solidus_paypal_commerce_platform/namespace.js +0 -0
  22. data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/assets/javascripts/spree/frontend/solidus_paypal_commerce_platform.js +7 -0
  23. data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/assets/stylesheets/spree/frontend/solidus_paypal_commerce_platform.css +0 -0
  24. data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/controllers/solidus_paypal_commerce_platform/orders_controller.rb +2 -1
  25. data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/controllers/solidus_paypal_commerce_platform/payments_controller.rb +0 -0
  26. data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/controllers/solidus_paypal_commerce_platform/paypal_orders_controller.rb +1 -1
  27. data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/controllers/solidus_paypal_commerce_platform/shipping_rates_controller.rb +0 -0
  28. data/{app → lib/generators/solidus_paypal_commerce_platform/install/templates/app}/controllers/solidus_paypal_commerce_platform/wizard_controller.rb +0 -0
  29. data/lib/{views/frontend/spree/checkout → generators/solidus_paypal_commerce_platform/install/templates/app/views/checkouts}/payment/_paypal_commerce_platform.html.erb +5 -4
  30. data/lib/{views/frontend/spree → generators/solidus_paypal_commerce_platform/install/templates/app/views}/orders/payment/_paypal_commerce_platform.html.erb +4 -3
  31. data/lib/{views/frontend/spree → generators/solidus_paypal_commerce_platform/install/templates/app/views}/products/payment/_paypal_commerce_platform.html.erb +1 -1
  32. data/lib/{views/frontend → generators/solidus_paypal_commerce_platform/install/templates/app/views}/solidus_paypal_commerce_platform/cart/_cart_buttons.html.erb +0 -0
  33. data/lib/{views/frontend → generators/solidus_paypal_commerce_platform/install/templates/app/views}/solidus_paypal_commerce_platform/payments/_payment.html.erb +0 -0
  34. data/lib/{views/frontend → generators/solidus_paypal_commerce_platform/install/templates/app/views}/solidus_paypal_commerce_platform/product/_product_buttons.html.erb +0 -0
  35. data/lib/{views/frontend → generators/solidus_paypal_commerce_platform/install/templates/app/views}/solidus_paypal_commerce_platform/shared/_javascript_sdk_tag.html.erb +4 -2
  36. data/lib/generators/solidus_paypal_commerce_platform/install/templates/{initializer.rb → config/initializers/solidus_paypal_commerce_platform.rb} +0 -0
  37. data/lib/paypal/paypal_checkout_sdk/orders/orders_authorize_request.rb +0 -2
  38. data/lib/paypal/paypal_checkout_sdk/orders/orders_capture_request.rb +0 -2
  39. data/lib/paypal/paypal_checkout_sdk/orders/orders_create_request.rb +0 -2
  40. data/lib/paypal/paypal_checkout_sdk/orders/orders_get_request.rb +0 -2
  41. data/lib/paypal/paypal_checkout_sdk/orders/orders_patch_request.rb +0 -52
  42. data/lib/paypal/paypal_checkout_sdk/orders/orders_validate_request.rb +0 -2
  43. data/lib/paypal/paypal_checkout_sdk/payments/authorizations_capture_request.rb +0 -2
  44. data/lib/paypal/paypal_checkout_sdk/payments/authorizations_get_request.rb +0 -2
  45. data/lib/paypal/paypal_checkout_sdk/payments/authorizations_reauthorize_request.rb +0 -2
  46. data/lib/paypal/paypal_checkout_sdk/payments/authorizations_void_request.rb +0 -2
  47. data/lib/paypal/paypal_checkout_sdk/payments/captures_get_request.rb +0 -2
  48. data/lib/paypal/paypal_checkout_sdk/payments/captures_refund_request.rb +0 -2
  49. data/lib/paypal/paypal_checkout_sdk/payments/refunds_get_request.rb +0 -2
  50. data/lib/paypal/paypal_http_client.rb +4 -4
  51. data/lib/solidus_paypal_commerce_platform/client.rb +6 -3
  52. data/lib/solidus_paypal_commerce_platform/paypal_checkout_sdk/orders/orders_patch_request.rb +1 -54
  53. data/lib/solidus_paypal_commerce_platform/version.rb +1 -1
  54. data/solidus_paypal_commerce_platform.gemspec +1 -2
  55. data/spec/jobs/solidus_paypal_commerce_platform/fixtures/CHECKOUT.ORDER.COMPLETED.v2.json +121 -0
  56. data/spec/jobs/solidus_paypal_commerce_platform/fixtures/CHECKOUT.ORDER.PROCESSED.v2.json +121 -0
  57. data/spec/jobs/solidus_paypal_commerce_platform/fixtures/PAYMENT.CAPTURE.COMPLETED.v1.json +50 -0
  58. data/spec/jobs/solidus_paypal_commerce_platform/fixtures/PAYMENT.CAPTURE.COMPLETED.v2.json +72 -0
  59. data/spec/jobs/solidus_paypal_commerce_platform/fixtures/PAYMENT.CAPTURE.DENIED.v1.json +50 -0
  60. data/spec/jobs/solidus_paypal_commerce_platform/fixtures/PAYMENT.CAPTURE.DENIED.v2.json +68 -0
  61. data/spec/jobs/solidus_paypal_commerce_platform/fixtures/PAYMENT.CAPTURE.REFUNDED.v1.json +51 -0
  62. data/spec/jobs/solidus_paypal_commerce_platform/fixtures/PAYMENT.CAPTURE.REFUNDED.v2.json +63 -0
  63. data/spec/jobs/solidus_paypal_commerce_platform/webhook_job_spec.rb +44 -0
  64. data/spec/lib/solidus_paypal_commerce_platform/client_spec.rb +21 -0
  65. data/spec/lib/solidus_paypal_commerce_platform/configuration_spec.rb +91 -0
  66. data/spec/models/solidus_paypal_commerce_platform/payment_method_spec.rb +200 -0
  67. data/spec/models/solidus_paypal_commerce_platform/payment_source_spec.rb +119 -0
  68. data/spec/models/solidus_paypal_commerce_platform/paypal_address_spec.rb +67 -0
  69. data/spec/models/solidus_paypal_commerce_platform/paypal_order_spec.rb +80 -0
  70. data/spec/models/solidus_paypal_commerce_platform/state_guesser_spec.rb +38 -0
  71. data/spec/models/solidus_paypal_commerce_platform/wizard_spec.rb +9 -0
  72. data/spec/requests/solidus_paypal_commerce_platform/orders_controller_spec.rb +36 -0
  73. data/spec/requests/solidus_paypal_commerce_platform/shipping_rates_controller_spec.rb +44 -0
  74. data/spec/requests/solidus_paypal_commerce_platform/wizard_controller_spec.rb +59 -0
  75. data/spec/solidus_paypal_commerce_platform_spec_helper.rb +5 -0
  76. data/spec/support/solidus_paypal_commerce_platform/factories.rb +5 -0
  77. data/spec/support/solidus_paypal_commerce_platform/paypal_sdk_script_tag_helper.rb +13 -0
  78. data/spec/system/backend/new_payment_method_spec.rb +40 -0
  79. data/spec/system/frontend/cart_spec.rb +53 -0
  80. data/spec/system/frontend/checkout_spec.rb +104 -0
  81. data/spec/system/frontend/product_spec.rb +98 -0
  82. metadata +53 -37
  83. data/app/assets/javascripts/spree/frontend/solidus_paypal_commerce_platform/button_actions.js +0 -203
@@ -0,0 +1,51 @@
1
+ {
2
+ "id": "WH-2JG62852X9350413N-40V56408FN023574K",
3
+ "create_time": "2014-10-22T23:49:03Z",
4
+ "resource_type": "capture",
5
+ "event_type": "PAYMENT.CAPTURE.REFUNDED",
6
+ "summary": "A $ 0.15 USD capture payment was refunded",
7
+ "resource": {
8
+ "parent_payment": "PAY-7PD36489JY0066936KREEERQ",
9
+ "update_time": "2014-10-22T23:48:57Z",
10
+ "amount": {
11
+ "total": "-0.15",
12
+ "currency": "USD"
13
+ },
14
+ "create_time": "2014-10-22T23:48:57Z",
15
+ "links": [
16
+ {
17
+ "href": "https://api.paypal.com/v1/payments/refund/8VC964970N548345V",
18
+ "rel": "self",
19
+ "method": "GET"
20
+ },
21
+ {
22
+ "href": "https://api.paypal.com/v1/payments/payment/PAY-7PD36489JY0066936KREEERQ",
23
+ "rel": "parent_payment",
24
+ "method": "GET"
25
+ },
26
+ {
27
+ "href": "https://api.paypal.com/v1/payments/capture/5LK72275CG489890D",
28
+ "rel": "capture",
29
+ "method": "GET"
30
+ }
31
+ ],
32
+ "id": "8VC964970N548345V",
33
+ "state": "completed",
34
+ "capture_id": "5LK72275CG489890D"
35
+ },
36
+ "links": [
37
+ {
38
+ "href": "https://api.paypal.com/v1/notifications/webhooks-events/WH-2JG62852X9350413N-40V56408FN023574K",
39
+ "rel": "self",
40
+ "method": "GET",
41
+ "encType": "application/json"
42
+ },
43
+ {
44
+ "href": "https://api.paypal.com/v1/notifications/webhooks-events/WH-2JG62852X9350413N-40V56408FN023574K/resend",
45
+ "rel": "resend",
46
+ "method": "POST",
47
+ "encType": "application/json"
48
+ }
49
+ ],
50
+ "event_version": "1.0"
51
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "id": "WH-1GE84257G0350133W-6RW800890C634293G",
3
+ "create_time": "2018-08-15T19:14:04.543Z",
4
+ "resource_type": "refund",
5
+ "event_type": "PAYMENT.CAPTURE.REFUNDED",
6
+ "summary": "A $ 0.99 USD capture payment was refunded",
7
+ "resource": {
8
+ "seller_payable_breakdown": {
9
+ "gross_amount": {
10
+ "currency_code": "USD",
11
+ "value": "0.99"
12
+ },
13
+ "paypal_fee": {
14
+ "currency_code": "USD",
15
+ "value": "0.02"
16
+ },
17
+ "net_amount": {
18
+ "currency_code": "USD",
19
+ "value": "0.97"
20
+ },
21
+ "total_refunded_amount": {
22
+ "currency_code": "USD",
23
+ "value": "1.98"
24
+ }
25
+ },
26
+ "amount": {
27
+ "currency_code": "USD",
28
+ "value": "0.99"
29
+ },
30
+ "update_time": "2018-08-15T12:13:29-07:00",
31
+ "create_time": "2018-08-15T12:13:29-07:00",
32
+ "links": [
33
+ {
34
+ "href": "https://api.paypal.com/v2/payments/refunds/1Y107995YT783435V",
35
+ "rel": "self",
36
+ "method": "GET"
37
+ },
38
+ {
39
+ "href": "https://api.paypal.com/v2/payments/captures/0JF852973C016714D",
40
+ "rel": "up",
41
+ "method": "GET"
42
+ }
43
+ ],
44
+ "id": "1Y107995YT783435V",
45
+ "status": "COMPLETED"
46
+ },
47
+ "links": [
48
+ {
49
+ "href": "https://api.paypal.com/v1/notifications/webhooks-events/WH-1GE84257G0350133W-6RW800890C634293G",
50
+ "rel": "self",
51
+ "method": "GET",
52
+ "encType": "application/json"
53
+ },
54
+ {
55
+ "href": "https://api.paypal.com/v1/notifications/webhooks-events/WH-1GE84257G0350133W-6RW800890C634293G/resend",
56
+ "rel": "resend",
57
+ "method": "POST",
58
+ "encType": "application/json"
59
+ }
60
+ ],
61
+ "event_version": "1.0",
62
+ "resource_version": "2.0"
63
+ }
@@ -0,0 +1,44 @@
1
+ require 'solidus_paypal_commerce_platform_spec_helper'
2
+
3
+ RSpec.describe SolidusPaypalCommercePlatform::WebhookJob do
4
+ let(:paypal_order_id) { SecureRandom.uuid }
5
+ let(:capture_id) { SecureRandom.uuid }
6
+ let(:refund_id) { SecureRandom.uuid }
7
+
8
+ let(:payment_source) {
9
+ SolidusPaypalCommercePlatform::PaymentSource.new(
10
+ paypal_order_id: paypal_order_id,
11
+ capture_id: capture_id,
12
+ refund_id: refund_id,
13
+ payment_method: create(:paypal_payment_method),
14
+ )
15
+ }
16
+
17
+ let(:payment) {
18
+ create(:order).payments.create!(
19
+ payment_method: payment_source.payment_method,
20
+ source: payment_source,
21
+ )
22
+ }
23
+
24
+ [
25
+ ["CHECKOUT.ORDER.COMPLETED.v2", "paypal_order_id"],
26
+ ["CHECKOUT.ORDER.PROCESSED.v2", "paypal_order_id"],
27
+ ["PAYMENT.CAPTURE.COMPLETED.v1", "capture_id"],
28
+ ["PAYMENT.CAPTURE.COMPLETED.v2", "capture_id"],
29
+ ["PAYMENT.CAPTURE.DENIED.v1", "capture_id"],
30
+ ["PAYMENT.CAPTURE.DENIED.v2", "capture_id"],
31
+ ["PAYMENT.CAPTURE.REFUNDED.v1", "capture_id"],
32
+ ["PAYMENT.CAPTURE.REFUNDED.v2", "refund_id"],
33
+ ].each do |(event, source_attr)|
34
+ describe event do
35
+ let(:payload) { JSON.parse(File.read("#{__dir__}/fixtures/#{event}.json")) }
36
+ let(source_attr) { payload.dig("resource", "id") }
37
+
38
+ it 'inserts a log entry for the corresponding payment source' do
39
+ expect { described_class.perform_now(payload) }.to change(payment.log_entries, :count).by(1)
40
+ expect(YAML.load(payment.log_entries.last.details)).to eq(payload)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,21 @@
1
+ require 'solidus_paypal_commerce_platform_spec_helper'
2
+
3
+ RSpec.describe SolidusPaypalCommercePlatform::Client do
4
+ subject(:client) { described_class.new(client_id: "1234") }
5
+
6
+ describe '#execute_with_response' do
7
+ let(:request_class) { SolidusPaypalCommercePlatform::Gateway::OrdersCaptureRequest }
8
+ let(:paypal_request) { double(:request, class: request_class) }
9
+ let(:paypal_response) { double(:response, status_code: status_code, result: nil, headers: {}) }
10
+ let(:status_code) { 201 }
11
+
12
+ it 'forwards to the upstream client adding i18n response messages' do
13
+ allow_any_instance_of(PayPal::PayPalHttpClient)
14
+ .to receive(:execute).with(paypal_request).and_return(paypal_response)
15
+
16
+ response = subject.execute_with_response(paypal_request)
17
+
18
+ expect(response.message).to eq("Payment captured")
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,91 @@
1
+ require 'solidus_paypal_commerce_platform_spec_helper'
2
+
3
+ RSpec.describe SolidusPaypalCommercePlatform::Configuration do
4
+ subject(:test_subject) { described_class.new }
5
+
6
+ describe '#default_env' do
7
+ it "uses ENV['PAYPAL_ENV'] when present" do
8
+ expect(ENV).to receive(:[]).with("PAYPAL_ENV").and_return("foo").at_least(:once)
9
+ expect(test_subject.default_env).to eq("foo")
10
+ end
11
+
12
+ it "falls back to Rails.env if ENV['PAYPAL_ENV'] is not set" do
13
+ expect(ENV).to receive(:[]).with("PAYPAL_ENV").and_return(nil).at_least(:once)
14
+
15
+ allow(Rails).to receive(:env).and_return("development".inquiry)
16
+ expect(test_subject.default_env).to eq("sandbox")
17
+
18
+ allow(Rails).to receive(:env).and_return("test".inquiry)
19
+ expect(test_subject.default_env).to eq("sandbox")
20
+
21
+ allow(Rails).to receive(:env).and_return("production".inquiry)
22
+ expect(test_subject.default_env).to eq("live")
23
+
24
+ allow(Rails).to receive(:env).and_return("staging".inquiry)
25
+ expect{ test_subject.default_env }.to raise_error(described_class::InvalidEnvironment)
26
+ end
27
+ end
28
+
29
+ describe '#env' do
30
+ it 'returns a string inquirer' do
31
+ expect(test_subject.env).to eq("sandbox")
32
+ expect(test_subject.env).to be_sandbox
33
+
34
+ test_subject.env = "live"
35
+ expect(test_subject.env).to eq("live")
36
+ expect(test_subject.env).to be_live
37
+
38
+ test_subject.env = "sandbox"
39
+ expect(test_subject.env).to eq("sandbox")
40
+ expect(test_subject.env).to be_sandbox
41
+ end
42
+
43
+ it 'raises an error when assigned an unsupported value' do
44
+ expect{ test_subject.env = "foo" }.to raise_error(described_class::InvalidEnvironment)
45
+ end
46
+ end
47
+
48
+ describe '#env_class' do
49
+ it 'changes based on the current env' do
50
+ test_subject.env = "live"
51
+ expect(test_subject.env_class).to eq(PayPal::LiveEnvironment)
52
+
53
+ test_subject.env = "sandbox"
54
+ expect(test_subject.env_class).to eq(PayPal::SandboxEnvironment)
55
+ end
56
+ end
57
+
58
+ describe '#env_domain' do
59
+ it 'changes based on the current env' do
60
+ test_subject.env = "live"
61
+ expect(test_subject.env_domain).to eq("www.paypal.com")
62
+
63
+ test_subject.env = "sandbox"
64
+ expect(test_subject.env_domain).to eq("www.sandbox.paypal.com")
65
+ end
66
+ end
67
+
68
+ describe "#state_guesser_class" do
69
+ before do
70
+ stub_const('SolidusPaypalCommercePlatform::BetterStateGuesser', Class.new)
71
+ end
72
+
73
+ it "returns a class" do
74
+ expect(test_subject.state_guesser_class).to be_a(Class)
75
+ end
76
+
77
+ it "is settable" do
78
+ expect(test_subject.state_guesser_class).to eq(SolidusPaypalCommercePlatform::StateGuesser)
79
+
80
+ test_subject.state_guesser_class = "SolidusPaypalCommercePlatform::BetterStateGuesser"
81
+
82
+ expect(test_subject.state_guesser_class).to eq(SolidusPaypalCommercePlatform::BetterStateGuesser)
83
+ end
84
+ end
85
+
86
+ describe "#partner_code" do
87
+ it "returns the correct code" do
88
+ expect(test_subject.partner_code).to eq("Solidus_PCP_SP")
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,200 @@
1
+ require 'solidus_paypal_commerce_platform_spec_helper'
2
+
3
+ RSpec.describe SolidusPaypalCommercePlatform::PaymentMethod, type: :model do
4
+ let(:paypal_payment_method) { create(:paypal_payment_method) }
5
+ let(:payment) { create(:payment) }
6
+ let(:completed_payment) { create(:payment, :completed) }
7
+ let(:response) { Struct(status_code: status_code, result: result, headers: headers) }
8
+ let(:status_code) { 201 }
9
+ let(:result) { nil }
10
+ let(:headers) { {} }
11
+
12
+ def Struct(data) # rubocop:disable Naming/MethodName
13
+ Struct.new(*data.keys, keyword_init: true).new(data)
14
+ end
15
+
16
+ before { allow_any_instance_of(PayPal::PayPalHttpClient).to receive(:execute) { response } }
17
+
18
+ describe 'preferences' do
19
+ context 'with paypal_button_color' do
20
+ before do
21
+ paypal_payment_method.preferences.update(paypal_button_color: 'gold')
22
+ paypal_payment_method.save
23
+ end
24
+
25
+ it 'cannot be gold when Venmo standalone is enabled' do
26
+ expect(paypal_payment_method).to be_valid
27
+ paypal_payment_method.preferences.update(venmo_standalone: 'enabled')
28
+ expect(paypal_payment_method).to be_invalid
29
+ paypal_payment_method.preferences.update(venmo_standalone: 'only render standalone')
30
+ expect(paypal_payment_method).to be_invalid
31
+ end
32
+ end
33
+ end
34
+
35
+ describe "#purchase" do
36
+ let(:result) { Struct(purchase_units: [Struct(payments: payments)]) }
37
+ let(:payments) { Struct(captures: [Struct(id: SecureRandom.hex(4))]) }
38
+
39
+ it "sends a purchase request to paypal" do
40
+ paypal_order_id = SecureRandom.hex(8)
41
+ source = paypal_payment_method.payment_source_class.create(paypal_order_id: paypal_order_id)
42
+ expect_request(:OrdersCaptureRequest).to receive(:new).with(paypal_order_id).and_call_original
43
+ paypal_payment_method.purchase(1000, source, {})
44
+ end
45
+ end
46
+
47
+ describe "#authorize" do
48
+ let(:result) { Struct(purchase_units: [Struct(payments: payments)]) }
49
+ let(:payments) { Struct(authorizations: [Struct(id: SecureRandom.hex(4))]) }
50
+
51
+ it "sends an authorize request to paypal" do
52
+ paypal_order_id = SecureRandom.hex(8)
53
+ source = paypal_payment_method.payment_source_class.create(paypal_order_id: paypal_order_id)
54
+ expect_request(:OrdersAuthorizeRequest).to receive(:new).with(paypal_order_id)
55
+ paypal_payment_method.authorize(1000, source, {})
56
+ end
57
+ end
58
+
59
+ describe "#capture" do
60
+ let(:result) { Struct(id: SecureRandom.hex(4), status: status) }
61
+
62
+ context "when payment COMPLETED" do
63
+ let(:status) { "COMPLETED" }
64
+
65
+ it "sends a capture request to paypal" do
66
+ authorization_id = SecureRandom.hex(8)
67
+ source = paypal_payment_method.payment_source_class.create(authorization_id: authorization_id)
68
+ payment.source = source
69
+ expect_request(:AuthorizationsCaptureRequest).to receive(:new).with(authorization_id).and_call_original
70
+ billing_response = paypal_payment_method.capture(1000, {}, originator: payment)
71
+ expect(billing_response.message).to eq("Authorization captured")
72
+ end
73
+ end
74
+
75
+ context "when payment PENDING" do
76
+ let(:status) { "PENDING" }
77
+
78
+ it "sends a capture request to paypal" do
79
+ authorization_id = SecureRandom.hex(8)
80
+ source = paypal_payment_method.payment_source_class.create(authorization_id: authorization_id)
81
+ payment.source = source
82
+ expect_request(:AuthorizationsCaptureRequest).to receive(:new).with(authorization_id).and_call_original
83
+ billing_response = paypal_payment_method.capture(1000, {}, originator: payment)
84
+ expect(billing_response.message).to eq("Payment is awaiting processing on PayPal's side")
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "#void" do
90
+ it "sends a void request to paypal" do
91
+ authorization_id = SecureRandom.hex(8)
92
+ source = paypal_payment_method.payment_source_class.create(authorization_id: authorization_id)
93
+ payment.source = source
94
+ expect_request(:AuthorizationsVoidRequest).to receive(:new).with(authorization_id)
95
+ paypal_payment_method.void(nil, originator: payment)
96
+ end
97
+ end
98
+
99
+ describe "#credit" do
100
+ let(:result) { Struct(id: SecureRandom.hex(4)) }
101
+
102
+ it "sends a refund request to paypal" do
103
+ capture_id = SecureRandom.hex(4)
104
+ source = paypal_payment_method.payment_source_class.create(capture_id: capture_id)
105
+ completed_payment.source = source
106
+ expect_request(:CapturesRefundRequest).to receive(:new).with(capture_id).and_call_original
107
+ paypal_payment_method.credit(1000, {}, originator: completed_payment.refunds.new(amount: 12))
108
+ expect(source.refund_id).not_to be_blank
109
+ end
110
+ end
111
+
112
+ describe '.javascript_sdk_url' do
113
+ subject(:url) { URI(paypal_payment_method.javascript_sdk_url(order: order)) }
114
+
115
+ let(:order) { build_stubbed(:order) }
116
+
117
+ context 'when checkout_steps include "confirm"' do
118
+ it 'sets autocommit' do
119
+ expect(url.query.split("&")).to include("commit=false")
120
+ end
121
+ end
122
+
123
+ context 'when checkout_steps does not include "confirm"' do
124
+ it 'disables autocommit' do
125
+ allow(order).to receive(:checkout_steps).and_return([:address, :delivery, :payment])
126
+ expect(url.query.split("&")).to include("commit=true")
127
+ end
128
+ end
129
+
130
+ context 'when messaging is turned on' do
131
+ it 'includes messaging component' do
132
+ paypal_payment_method.preferences.update(display_credit_messaging: true)
133
+ expect(url.query.split("&")).to include("components=buttons%2Cmessages")
134
+ end
135
+ end
136
+
137
+ context 'when messaging is turned off' do
138
+ it 'only includes buttons components' do
139
+ paypal_payment_method.preferences.update(display_credit_messaging: false)
140
+ expect(url.query.split("&")).not_to include("messages")
141
+ expect(url.query.split("&")).to include("components=buttons")
142
+ end
143
+ end
144
+
145
+ context 'when venmo_standalone is "only render standalone"' do
146
+ before { paypal_payment_method.preferences.update(venmo_standalone: 'only render standalone') }
147
+
148
+ it 'includes "enable-funding=venmo" as a parameter' do
149
+ expect(url.query.split('&')).to include('enable-funding=venmo')
150
+ end
151
+ end
152
+
153
+ context 'when venmo_standalone is "enabled"' do
154
+ before { paypal_payment_method.preferences.update(venmo_standalone: 'enabled') }
155
+
156
+ it 'includes "enable-funding=venmo" as a parameter' do
157
+ expect(url.query.split('&')).to include('enable-funding=venmo')
158
+ end
159
+ end
160
+
161
+ context 'when venmo_standalone is "disabled"' do
162
+ before { paypal_payment_method.preferences.update(venmo_standalone: 'disabled') }
163
+
164
+ it 'does not include the "enable-funding" parameter' do
165
+ expect(url.query.split('&')).not_to include(match 'enable-funding')
166
+ end
167
+ end
168
+
169
+ context 'when force_buyer_country is an empty string' do
170
+ it 'does not include the "buyer-country" parameter' do
171
+ expect(url.query.split('&')).not_to include(match 'buyer-country')
172
+ end
173
+ end
174
+
175
+ context 'when force_buyer_country is "US"' do
176
+ before { paypal_payment_method.preferences.update(force_buyer_country: 'US') }
177
+
178
+ it 'includes "buyer-country=US" as a parameter' do
179
+ expect(url.query.split('&')).to include('buyer-country=US')
180
+ end
181
+ end
182
+
183
+ context 'when force_buyer_country is "US" but the environment is production' do
184
+ before {
185
+ allow(Rails.env).to receive(:production?).and_return(true)
186
+ paypal_payment_method.preferences.update(force_buyer_country: 'US')
187
+ }
188
+
189
+ it 'includes "buyer-country=US" as a parameter' do
190
+ expect(url.query.split('&')).not_to include(match 'buyer-country')
191
+ end
192
+ end
193
+ end
194
+
195
+ private
196
+
197
+ def expect_request(name)
198
+ expect(SolidusPaypalCommercePlatform::Gateway.const_get(name))
199
+ end
200
+ end
@@ -0,0 +1,119 @@
1
+ require 'solidus_paypal_commerce_platform_spec_helper'
2
+
3
+ RSpec.describe SolidusPaypalCommercePlatform::PaymentSource, type: :model do
4
+ let(:order) { Spree::TestingSupport::OrderWalkthrough.up_to(:payment) }
5
+ let(:paypal_payment_method) { create(:paypal_payment_method) }
6
+ let(:paypal_order_id) { "foo-123" }
7
+ let(:payment_source) {
8
+ described_class.new(
9
+ paypal_order_id: paypal_order_id,
10
+ payment_method: paypal_payment_method,
11
+ )
12
+ }
13
+ let(:payment) {
14
+ order.payments.create!(
15
+ payment_method: paypal_payment_method,
16
+ source: payment_source
17
+ )
18
+ }
19
+ let(:paypal_order_status) { "COMPLETED" }
20
+
21
+ before do
22
+ allow_any_instance_of(SolidusPaypalCommercePlatform::Client).to receive(:execute) do |_client, request|
23
+ expect(request).to be_a(SolidusPaypalCommercePlatform::Gateway::OrdersGetRequest) # rubocop:disable RSpec/ExpectInHook
24
+
25
+ # rubocop:disable RSpec/VerifiedDoubles
26
+ double(
27
+ 'response',
28
+ result: double('result', status: paypal_order_status)
29
+ )
30
+ # rubocop:enable RSpec/VerifiedDoubles
31
+ end
32
+ end
33
+
34
+ describe '#actions' do
35
+ context 'when the payment is not yet completed' do
36
+ it 'shows "capture" and "void"' do
37
+ expect(payment.actions).to contain_exactly("capture", "void")
38
+ end
39
+ end
40
+
41
+ context 'when the payment is completed and captured' do
42
+ before do
43
+ payment.update!(state: :completed, amount: 123)
44
+ payment_source.update!(capture_id: 1234)
45
+ end
46
+
47
+ it 'shows "credit"' do
48
+ expect(payment.actions).to contain_exactly("credit")
49
+ end
50
+
51
+ context 'when the PayPal order status is not COMPLETED' do
52
+ let(:paypal_order_status) { "FOOBAR" }
53
+
54
+ it 'hides the "credit" action' do
55
+ expect(payment.actions).not_to include("credit")
56
+ end
57
+ end
58
+ end
59
+
60
+ context 'when it cannot capture' do
61
+ it 'also cannot void' do
62
+ # One for "capture", and one for "void"
63
+ expect(payment_source).to receive(:can_capture?).and_return(false).twice
64
+ expect(payment.actions).not_to include("void")
65
+ end
66
+ end
67
+
68
+ context 'with #display_paypal_funding_source' do
69
+ context 'when the EN locale exists' do
70
+ it 'translates the funding source' do
71
+ payment_source.paypal_funding_source = 'card'
72
+
73
+ result = payment_source.display_paypal_funding_source
74
+
75
+ expect(result).to eq('Credit or debit card')
76
+ end
77
+ end
78
+
79
+ context "when the locale doesn't exist" do
80
+ it 'returns the paypal_funding_source as the default' do
81
+ allow(payment_source).to receive(:paypal_funding_source).and_return('non-existent')
82
+
83
+ result = payment_source.display_paypal_funding_source
84
+
85
+ expect(result).to eq('non-existent')
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ describe 'attributes' do
92
+ context 'with paypal_funding_source' do
93
+ it 'can be nil' do
94
+ payment_source.paypal_funding_source = nil
95
+
96
+ expect(payment_source).to be_valid
97
+ end
98
+
99
+ it 'makes empty strings nil' do
100
+ payment_source.paypal_funding_source = ''
101
+
102
+ result = payment_source.save
103
+
104
+ expect(result).to be(true)
105
+ expect(payment_source.paypal_funding_source).to be_nil
106
+ end
107
+
108
+ it 'gets correctly mapped as an enum' do
109
+ payment_source.paypal_funding_source = 'applepay'
110
+
111
+ result = payment_source.save
112
+
113
+ expect(result).to be(true)
114
+ expect(payment_source.paypal_funding_source).to eq('applepay')
115
+ expect(payment_source.applepay_funding?).to be(true)
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,67 @@
1
+ require 'solidus_paypal_commerce_platform_spec_helper'
2
+
3
+ RSpec.describe SolidusPaypalCommercePlatform::PaypalAddress, type: :model do
4
+ let(:order) { create(:order) }
5
+ let(:original_address) { create(:address) }
6
+ let(:address) { create(:address, name_attributes) }
7
+ let(:params) {
8
+ {
9
+ updated_address: {
10
+ admin_area_1: address.state.abbr,
11
+ admin_area_2: address.city,
12
+ address_line_1: address.address1,
13
+ address_line_2: address.address2,
14
+ postal_code: address.zipcode,
15
+ country_code: address.country.iso
16
+ },
17
+ recipient: {
18
+ name: {
19
+ given_name: "Alexander",
20
+ surname: "Hamilton"
21
+ }
22
+ }
23
+ }
24
+ }
25
+
26
+ describe "#update_address" do
27
+ subject{ order.ship_address }
28
+
29
+ it "formats PayPal addresses correctly" do
30
+ order.ship_address = original_address
31
+
32
+ described_class.new(order).update(params)
33
+
34
+ expect(subject.state).to eq address.state
35
+ expect(subject.city).to eq address.city
36
+ expect(subject.address1).to eq address.address1
37
+ expect(subject.address2).to eq address.address2
38
+ expect(subject.zipcode).to eq address.zipcode
39
+ expect(subject.country).to eq address.country
40
+ if SolidusSupport.combined_first_and_last_name_in_address?
41
+ expect(subject.name).to eq address.name
42
+ else
43
+ expect(subject.firstname).to eq address.firstname
44
+ expect(subject.lastname).to eq address.lastname
45
+ end
46
+ expect(subject.phone).to eq original_address.phone
47
+ end
48
+
49
+ context "when no address exists" do
50
+ it "produce a valid address" do
51
+ order.ship_address = nil
52
+
53
+ described_class.new(order).update(params)
54
+
55
+ expect(subject).to be_present
56
+ end
57
+ end
58
+ end
59
+
60
+ def name_attributes
61
+ if SolidusSupport.combined_first_and_last_name_in_address?
62
+ { name: "Alexander Hamilton" }
63
+ else
64
+ { firstname: "Alexander", lastname: "Hamilton" }
65
+ end
66
+ end
67
+ end