workarea-paypal 2.0.10 → 3.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc.json +2 -1
  3. data/.gitignore +16 -11
  4. data/CHANGELOG.md +86 -2
  5. data/README.md +105 -6
  6. data/Rakefile +1 -2
  7. data/UPGRADE.md +46 -0
  8. data/app/assets/javascripts/workarea/storefront/paypal/config.js.erb +44 -0
  9. data/app/assets/javascripts/workarea/storefront/paypal/modules/paypal_buttons.js +71 -0
  10. data/app/assets/javascripts/workarea/storefront/paypal/modules/paypal_hosted_fields.js +96 -0
  11. data/app/assets/javascripts/workarea/storefront/paypal/modules/update_checkout_submit_text.js +11 -5
  12. data/app/assets/javascripts/workarea/storefront/paypal/templates/paypal_fields.jst.ejs +43 -0
  13. data/app/controllers/workarea/storefront/checkout/place_order_controller.decorator +6 -4
  14. data/app/controllers/workarea/storefront/paypal_controller.rb +32 -26
  15. data/app/helpers/workarea/storefront/paypal_helper.rb +38 -0
  16. data/app/models/workarea/payment.decorator +17 -3
  17. data/app/models/workarea/payment/authorize/paypal.rb +13 -13
  18. data/app/models/workarea/payment/capture/paypal.rb +10 -20
  19. data/app/models/workarea/payment/null_address.rb +37 -0
  20. data/app/models/workarea/payment/purchase/paypal.rb +1 -25
  21. data/app/models/workarea/payment/refund/paypal.rb +3 -6
  22. data/app/models/workarea/payment/tender/paypal.rb +14 -2
  23. data/app/services/workarea/paypal/approve_order.rb +104 -0
  24. data/app/services/workarea/paypal/create_order.rb +177 -0
  25. data/app/services/workarea/paypal/update_order.rb +44 -0
  26. data/app/views/workarea/admin/orders/tenders/_paypal.html.haml +5 -2
  27. data/app/views/workarea/api/orders/tenders/_paypal.json.jbuilder +7 -1
  28. data/app/views/workarea/storefront/carts/_paypal_checkout.html.haml +6 -1
  29. data/app/views/workarea/storefront/checkouts/_paypal_payment.html.haml +17 -6
  30. data/app/views/workarea/storefront/order_mailer/tenders/_paypal.html.haml +5 -3
  31. data/app/views/workarea/storefront/orders/tenders/_paypal.html.haml +9 -2
  32. data/app/views/workarea/storefront/paypal/_paypal_sdk.html.haml +1 -0
  33. data/app/workers/workarea/paypal/handle_webhook_event.rb +64 -0
  34. data/config/initializers/append_points.rb +17 -5
  35. data/config/initializers/fields.rb +27 -0
  36. data/config/initializers/workarea.rb +41 -5
  37. data/config/locales/en.yml +15 -5
  38. data/config/routes.rb +5 -2
  39. data/lib/tasks/workarea/create_webhooks.rake +29 -0
  40. data/lib/workarea/paypal.rb +22 -18
  41. data/lib/workarea/paypal/engine.rb +4 -0
  42. data/lib/workarea/paypal/gateway.rb +200 -0
  43. data/lib/workarea/paypal/requests/create_webhook.rb +21 -0
  44. data/lib/workarea/paypal/requests/delete_webhook.rb +17 -0
  45. data/lib/workarea/paypal/requests/generate_token.rb +21 -0
  46. data/lib/workarea/paypal/requests/list_webhooks.rb +17 -0
  47. data/lib/workarea/paypal/version.rb +1 -1
  48. data/package.json +9 -0
  49. data/test/dummy/config/initializers/workarea.rb +1 -1
  50. data/test/factories/workarea/capture_completed_webhook.json +70 -0
  51. data/test/factories/workarea/capture_denied_webhook.json +68 -0
  52. data/test/factories/workarea/paypal.rb +34 -0
  53. data/test/helpers/workarea/storefront/paypal_helper_test.rb +35 -0
  54. data/test/integration/workarea/storefront/paypal_integration_test.rb +104 -294
  55. data/test/integration/workarea/storefront/paypal_place_order_integration_test.rb +42 -0
  56. data/test/lib/workarea/paypal/gateway_test.rb +236 -0
  57. data/test/models/workarea/payment/authorize/paypal_test.rb +57 -46
  58. data/test/models/workarea/payment/capture/paypal_test.rb +9 -51
  59. data/test/models/workarea/payment/null_address_test.rb +53 -0
  60. data/test/models/workarea/payment/refund/paypal_test.rb +39 -38
  61. data/test/models/workarea/paypal_payment_test.rb +65 -0
  62. data/test/models/workarea/search/paypal_order_text_test.rb +14 -0
  63. data/test/services/workarea/paypal/approve_order_test.rb +35 -0
  64. data/test/services/workarea/paypal/create_order_test.rb +127 -0
  65. data/test/services/workarea/paypal/update_order_test.rb +73 -0
  66. data/test/support/workarea/paypal_setup.rb +49 -0
  67. data/test/system/workarea/storefront/cart_system_test.decorator +1 -1
  68. data/test/system/workarea/storefront/logged_in_checkout_system_test.decorator +1 -1
  69. data/test/vcr_cassettes/paypal_approve_order.yml +106 -0
  70. data/test/vcr_cassettes/paypal_create_order.yml +110 -0
  71. data/test/vcr_cassettes/paypal_gateway_create_order.yml +105 -0
  72. data/test/vcr_cassettes/paypal_gateway_generate_token.yml +103 -0
  73. data/test/vcr_cassettes/paypal_gateway_get_order.yml +103 -0
  74. data/test/vcr_cassettes/paypal_gateway_update_order.yml +199 -0
  75. data/test/vcr_cassettes/paypal_gateway_webhooks.yml +403 -0
  76. data/test/vcr_cassettes/paypal_update_order.yml +204 -0
  77. data/test/workers/workarea/paypal/handle_webhook_event_test.rb +60 -0
  78. data/workarea-paypal.gemspec +2 -1
  79. metadata +64 -13
  80. data/app/services/workarea/paypal/setup.rb +0 -114
  81. data/app/services/workarea/paypal/update.rb +0 -69
  82. data/app/views/workarea/storefront/checkouts/_paypal_error.html.haml +0 -6
  83. data/app/views/workarea/storefront/order_mailer/tenders/_paypal.text.haml +0 -2
  84. data/test/dummy/config/initializers/session_store.rb +0 -3
  85. data/test/integration/workarea/storefront/place_order_integration_test.decorator +0 -11
  86. data/test/models/workarea/payment/purchase/paypal_test.rb +0 -94
  87. data/test/models/workarea/payment_test.decorator +0 -34
  88. data/test/models/workarea/search/admin/order_test.decorator +0 -32
  89. data/test/services/workarea/paypal/setup_test.rb +0 -120
  90. data/test/services/workarea/paypal/update_test.rb +0 -221
@@ -0,0 +1,42 @@
1
+ require 'test_helper'
2
+
3
+ module Workarea
4
+ module Storefront
5
+ class PaypalPlaceOrderIntegrationTest < Workarea::IntegrationTest
6
+ def test_place_order_with_paypal
7
+ product = create_product(variants: [{ sku: 'SKU1', regular: 5.to_m }])
8
+
9
+ post storefront.cart_items_path, params: {
10
+ product_id: product.id,
11
+ sku: product.skus.first,
12
+ quantity: 2
13
+ }
14
+
15
+ patch storefront.checkout_addresses_path,
16
+ params: {
17
+ email: 'test@workarea.com',
18
+ billing_address: factory_defaults(:billing_address),
19
+ shipping_address: factory_defaults(:shipping_address)
20
+ }
21
+
22
+ patch storefront.checkout_place_order_path, params: { payment: 'paypal' }
23
+ assert_redirected_to(storefront.checkout_payment_path)
24
+ assert_equal(t('workarea.storefront.paypal.errors.place_order'), flash[:info])
25
+
26
+ Paypal::UpdateOrder.any_instance.expects(:perform).returns(true)
27
+ Workarea::Checkout.any_instance.stubs(:place_order).returns(true)
28
+
29
+ jar = ActionDispatch::Cookies::CookieJar.build(request, cookies.to_hash)
30
+ payment = Workarea::Payment.find_or_create_by(id: jar.signed[:order_id])
31
+ payment.set_paypal(
32
+ paypal_id: '123',
33
+ payer_id: 'abc',
34
+ approved: true
35
+ )
36
+
37
+ patch storefront.checkout_place_order_path, params: { payment: 'paypal' }
38
+ assert_redirected_to(storefront.checkout_confirmation_path)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,236 @@
1
+ require 'test_helper'
2
+
3
+ module Workarea
4
+ module Paypal
5
+ class GatewayTest < TestCase
6
+ include PaypalSetup
7
+
8
+ def gateway
9
+ Paypal::Gateway.new
10
+ end
11
+
12
+ def body
13
+ {
14
+ intent: 'CAPTURE',
15
+ purchase_units: [{
16
+ amount: {
17
+ currency_code: 'USD',
18
+ value: '100.00'
19
+ }
20
+ }]
21
+ }
22
+ end
23
+
24
+ def order_id
25
+ VCR.use_cassette('paypal_gateway_create_order') do
26
+ gateway.create_order(body: body).id
27
+ end
28
+ end
29
+
30
+ def test_generate_token
31
+ response = VCR.use_cassette('paypal_gateway_generate_token') do
32
+ gateway.generate_token
33
+ end
34
+
35
+ assert(response.result.client_token.present?)
36
+ end
37
+
38
+ def test_get_order
39
+ response = VCR.use_cassette('paypal_gateway_get_order') do
40
+ gateway.get_order(order_id)
41
+ end
42
+
43
+ assert_equal(200, response.status_code)
44
+ assert_equal(order_id, response.result.id)
45
+ assert(response.result.purchase_units.first.present?)
46
+ end
47
+
48
+ def test_create_order
49
+ response = VCR.use_cassette('paypal_gateway_create_order') do
50
+ gateway.create_order(body: body)
51
+ end
52
+
53
+ assert_equal('CREATED', response.status)
54
+ assert(response.id.present?)
55
+ end
56
+
57
+ def test_update_order
58
+ body = [
59
+ {
60
+ op: 'replace',
61
+ path: "/purchase_units/@reference_id=='default'/amount",
62
+ value: {
63
+ currency_code: 'USD',
64
+ value: '125.00'
65
+ }
66
+ }
67
+ ]
68
+
69
+ VCR.use_cassette('paypal_gateway_update_order') do
70
+ response = gateway.update_order(order_id, body: body)
71
+
72
+ response = gateway.get_order(order_id)
73
+ assert_equal(200, response.status_code)
74
+ assert_equal('125.00', response.result.purchase_units.first.amount.value)
75
+ end
76
+ end
77
+
78
+
79
+ def test_capture
80
+ stubbed_response = OpenStruct.new(
81
+ status_code: 201,
82
+ result: PayPalHttp::HttpClient.new(nil)._parse_values(
83
+ purchase_units: [
84
+ {
85
+ payments: {
86
+ captures: [{ id: 'CAPTURE_0001', status: 'COMPLETED' }]
87
+ }
88
+ }
89
+ ]
90
+ )
91
+ )
92
+
93
+ PayPal::PayPalHttpClient.any_instance.expects(:execute).returns(stubbed_response)
94
+
95
+ result = gateway.capture('1234')
96
+ assert(result.success?)
97
+ assert_equal('PayPal capture succeeded', result.message)
98
+ assert_equal({ 'id' => 'CAPTURE_0001', 'status' => 'COMPLETED' }, result.params)
99
+
100
+ stubbed_response.result.purchase_units.first.payments.captures.first.status = 'DECLINED'
101
+ PayPal::PayPalHttpClient.any_instance.unstub
102
+ PayPal::PayPalHttpClient.any_instance.expects(:execute).returns(stubbed_response)
103
+
104
+ result = gateway.capture('1234')
105
+ refute(result.success?)
106
+ assert_equal('PayPal capture failed', result.message)
107
+
108
+ stubbed_response.status_code = 422
109
+ stubbed_response.result.purchase_units.first.payments.captures.first.status = 'COMPLETED'
110
+ PayPal::PayPalHttpClient.any_instance.unstub
111
+ PayPal::PayPalHttpClient.any_instance.expects(:execute).returns(stubbed_response)
112
+
113
+ result = gateway.capture('1234')
114
+ refute(result.success?)
115
+ assert_equal('PayPal capture failed', result.message)
116
+ assert_equal(
117
+ { 'purchase_units' => [
118
+ { payments: { captures: [{ id: "CAPTURE_0001", status: 'COMPLETED' }] } }
119
+ ]
120
+ },
121
+ result.params
122
+ )
123
+
124
+ PayPal::PayPalHttpClient.any_instance.unstub
125
+ PayPal::PayPalHttpClient.any_instance.expects(:execute).raises(
126
+ PayPalHttp::HttpError.new(422, {}, { 'paypal-debug-id' => '123' })
127
+ )
128
+
129
+ result = gateway.capture('1234')
130
+ refute(result.success?)
131
+ assert_equal({ 'status_code' => 422, 'debug_id' => '123' }, result.params)
132
+
133
+ PayPal::PayPalHttpClient.any_instance.unstub
134
+ PayPal::PayPalHttpClient.any_instance.expects(:execute).raises(
135
+ StandardError.new('failed')
136
+ )
137
+
138
+ result = gateway.capture('1234')
139
+ refute(result.success?)
140
+ assert_equal('failed', result.message)
141
+ assert(result.params.blank?)
142
+ end
143
+
144
+
145
+ def test_refund
146
+ stubbed_response = OpenStruct.new(
147
+ status_code: 201,
148
+ result: OpenStruct.new(
149
+ id: 'REFUND_0001',
150
+ status: 'COMPLETED',
151
+ status_details: { reason: 'test test' }
152
+ )
153
+ )
154
+
155
+ PayPal::PayPalHttpClient
156
+ .any_instance
157
+ .expects(:execute)
158
+ .returns(stubbed_response)
159
+
160
+ result = gateway.refund('1234')
161
+ assert(result.success?)
162
+ assert_equal('PayPal refund succeeded', result.message)
163
+ assert_equal(
164
+ {
165
+ 'id' => 'REFUND_0001',
166
+ 'status' => 'COMPLETED',
167
+ 'status_details' => { reason: 'test test' },
168
+ 'capture_id' => '1234',
169
+ 'amount' => ''
170
+ },
171
+ result.params
172
+ )
173
+
174
+ PayPal::PayPalHttpClient.any_instance.unstub
175
+ PayPal::PayPalHttpClient
176
+ .any_instance
177
+ .expects(:execute)
178
+ .with { |req| assert_equal('50.00', req.body[:amount][:value]) }
179
+ .returns(stubbed_response)
180
+
181
+ result = gateway.refund('1234', amount: 50.to_m)
182
+ assert(result.success?)
183
+ assert_equal('PayPal refund succeeded', result.message)
184
+ assert_equal(
185
+ {
186
+ 'id' => 'REFUND_0001',
187
+ 'status' => 'COMPLETED',
188
+ 'status_details' => { reason: 'test test' },
189
+ 'capture_id' => '1234',
190
+ 'amount' => '50.00'
191
+ },
192
+ result.params
193
+ )
194
+ end
195
+
196
+ def test_webhooks
197
+ VCR.use_cassette('paypal_gateway_webhooks') do
198
+ webhook = gateway.create_webhook(
199
+ url: 'https://example.com/my-webhooks',
200
+ event_types: %w(
201
+ PAYMENT.CAPTURE.COMPLETED
202
+ PAYMENT.CAPTURE.DENIED
203
+ )
204
+ ).result
205
+
206
+ assert(webhook.id.present?)
207
+ assert_equal('https://example.com/my-webhooks', webhook.url)
208
+
209
+ list = gateway.list_webhooks.result.webhooks
210
+
211
+ assert_equal(1, list.length)
212
+ assert_equal(webhook.id, list.first.id)
213
+
214
+ gateway.delete_webhook(webhook.id)
215
+
216
+ list = gateway.list_webhooks.result.webhooks
217
+ assert_equal(0, list.length)
218
+ end
219
+ end
220
+
221
+ def test_client_id
222
+ ENV['WORKAREA_PAYPAL_CLIENT_ID'] = nil
223
+
224
+ if Rails.application.credentials.paypal.present?
225
+ assert(Rails.application.credentials.paypal[:client_id], gateway.client_id)
226
+ elsif Workarea.config.paypal.try(:[], :client_id).present?
227
+ assert(Workarea.config.paypal[:client_id], gateway.client_id)
228
+ end
229
+
230
+ ENV['WORKAREA_PAYPAL_CLIENT_ID'] = 'abc123'
231
+
232
+ assert('abc123', gateway.client_id)
233
+ end
234
+ end
235
+ end
236
+ end
@@ -4,21 +4,14 @@ module Workarea
4
4
  class Payment
5
5
  module Authorize
6
6
  class PaypalTest < Workarea::TestCase
7
- delegate :gateway, to: Workarea::Paypal
8
-
9
7
  def payment
10
8
  @payment ||= create_payment
11
9
  end
12
10
 
13
- def authorization
14
- @authorization ||= ActiveMerchant::Billing::BogusGateway::AUTHORIZATION
15
- end
16
-
17
11
  def tender
18
12
  @tender ||=
19
13
  begin
20
- payment.set_address(first_name: 'Ben', last_name: 'Crouse')
21
- payment.build_paypal(token: '1234', payer_id: 'pid')
14
+ payment.build_paypal(paypal_id: '1234', payer_id: 'pid')
22
15
  payment.paypal
23
16
  end
24
17
  end
@@ -30,63 +23,81 @@ module Workarea
30
23
  )
31
24
  end
32
25
 
33
- def test_complete_authorizes_on_the_paypal_gateway
34
- operation = Paypal.new(tender, transaction)
35
-
36
- gateway.expects(:authorize)
26
+ def stubbed_capture
27
+ @stubbed_capture ||= OpenStruct.new(
28
+ status_code: 201,
29
+ result: PayPalHttp::HttpClient.new(nil)._parse_values(
30
+ purchase_units: [
31
+ {
32
+ payments: {
33
+ captures: [{ id: 'CAPTURE_0001', status: 'COMPLETED' }]
34
+ }
35
+ }
36
+ ]
37
+ )
38
+ )
39
+ end
37
40
 
38
- operation.complete!
41
+ def stubbed_refund
42
+ @stubbed_refund ||= OpenStruct.new(
43
+ status_code: 201,
44
+ result: OpenStruct.new(
45
+ id: 'REFUND_0001',
46
+ status: 'COMPLETED',
47
+ )
48
+ )
39
49
  end
40
50
 
41
- def test_complete_sets_the_response_on_the_transaction
51
+ def test_complete_success
52
+ PayPal::PayPalHttpClient.any_instance.expects(:execute).returns(stubbed_capture)
53
+
42
54
  operation = Paypal.new(tender, transaction)
43
55
  operation.complete!
44
56
 
45
- assert_instance_of(
46
- ActiveMerchant::Billing::Response,
47
- transaction.response
48
- )
49
- end
57
+ assert_equal('purchase', transaction.action)
58
+ assert(transaction.response.success?)
59
+ assert(transaction.response.message.present?)
60
+ assert(transaction.response.params.present?)
50
61
 
51
- def test_cancel_does_nothing_if_the_transaction_was_a_failure
52
- transaction.success = false
53
- operation = Paypal.new(tender, transaction)
62
+ PayPal::PayPalHttpClient.any_instance.unstub
63
+ PayPal::PayPalHttpClient.any_instance.expects(:execute).returns(stubbed_refund)
54
64
 
55
- operation.gateway.expects(:void).never
56
65
  operation.cancel!
66
+ assert(transaction.cancellation.success?)
67
+ assert(transaction.cancellation.message.present?)
68
+ assert_equal('CAPTURE_0001', transaction.cancellation.params['capture_id'])
57
69
  end
58
70
 
59
- def test_cancel_voids_with_the_authorization_from_the_transaction
60
- transaction.response = ActiveMerchant::Billing::Response.new(
61
- true,
62
- 'Message',
63
- 'transaction_id' => authorization
64
- )
71
+ def test_complete_success_but_pending
72
+ stubbed_capture.result.purchase_units.first.payments.captures.first.status = 'PENDING'
73
+ PayPal::PayPalHttpClient.any_instance.expects(:execute).returns(stubbed_capture)
65
74
 
66
75
  operation = Paypal.new(tender, transaction)
76
+ operation.complete!
67
77
 
68
- operation
69
- .gateway
70
- .expects(:void)
71
- .with(authorization)
72
-
73
- operation.cancel!
78
+ assert_equal('authorize', transaction.action)
79
+ assert(transaction.response.success?)
80
+ assert(transaction.response.message.present?)
81
+ assert(transaction.response.params.present?)
74
82
  end
75
83
 
76
- def test_cancel_sets_cancellation_params_on_the_transaction
77
- transaction.response = ActiveMerchant::Billing::Response.new(
78
- true,
79
- 'Message',
80
- 'transaction_id' => authorization
81
- )
84
+ def test_complete_failure
85
+ stubbed_capture.status_code = 500
86
+ PayPal::PayPalHttpClient.any_instance.expects(:execute).returns(stubbed_capture)
82
87
 
83
88
  operation = Paypal.new(tender, transaction)
84
- operation.cancel!
89
+ operation.complete!
85
90
 
86
- assert_instance_of(
87
- ActiveMerchant::Billing::Response,
88
- transaction.cancellation
89
- )
91
+ assert_equal('purchase', transaction.action)
92
+ refute(transaction.response.success?)
93
+ assert(transaction.response.message.present?)
94
+ assert(transaction.response.params.present?)
95
+
96
+ PayPal::PayPalHttpClient.any_instance.unstub
97
+ PayPal::PayPalHttpClient.any_instance.expects(:execute).never
98
+
99
+ operation.cancel!
100
+ assert_nil(transaction.cancellation)
90
101
  end
91
102
  end
92
103
  end
@@ -4,63 +4,21 @@ module Workarea
4
4
  class Payment
5
5
  class Capture
6
6
  class PaypalTest < Workarea::TestCase
7
- delegate :gateway, to: Workarea::Paypal
8
-
9
- def authorization
10
- @authorization ||= ActiveMerchant::Billing::BogusGateway::AUTHORIZATION
11
- end
12
-
13
7
  def payment
14
- @payment ||=
15
- begin
16
- result = create_payment
17
- result.set_paypal(token: '1234', payer_id: 'pid')
18
- result
19
- end
20
- end
21
-
22
- def reference
23
- @reference ||= Transaction.new(
24
- amount: 5.to_m,
25
- response: ActiveMerchant::Billing::Response.new(
26
- true,
27
- 'Message',
28
- 'transaction_id' => authorization
29
- )
30
- )
31
- end
32
-
33
- def transaction
34
- @transaction ||= payment.paypal.transactions.build(
35
- amount: 5.to_m,
36
- reference: reference
8
+ @payment ||= create_payment(
9
+ paypal: { paypal_id: '123', approved: true }
37
10
  )
38
11
  end
39
12
 
40
- def test_complate_raises_if_the_reference_transaction_is_blank
41
- transaction.reference = nil
42
- operation = Paypal.new(payment.paypal, transaction)
43
-
44
- assert_raises(Payment::MissingReference) { operation.complete! }
45
- end
46
-
47
- def test_complete_captures_on_the_credit_card_gateway
48
- operation = Paypal.new(payment.paypal, transaction)
49
-
50
- gateway
51
- .expects(:capture)
52
- .with(500, authorization, currency: 'USD')
53
-
54
- operation.complete!
55
- end
13
+ def test_complete!
14
+ transaction = payment.paypal.build_transaction(action: 'capture')
56
15
 
57
- def test_complete_sets_the_response_on_the_transaction
58
- operation = Paypal.new(payment.paypal, transaction)
59
- operation.complete!
16
+ Paypal.new(payment.paypal, transaction).complete!
60
17
 
61
- assert_instance_of(
62
- ActiveMerchant::Billing::Response,
63
- transaction.response
18
+ refute(transaction.success?)
19
+ assert_includes(
20
+ transaction.message,
21
+ I18n.t('workarea.payment.paypal_capture')
64
22
  )
65
23
  end
66
24
  end