solidus_afterpay 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +41 -0
  3. data/.gem_release.yml +5 -0
  4. data/.github/stale.yml +17 -0
  5. data/.github_changelog_generator +2 -0
  6. data/.gitignore +20 -0
  7. data/.rspec +2 -0
  8. data/.rubocop.yml +14 -0
  9. data/CHANGELOG.md +1 -0
  10. data/Gemfile +33 -0
  11. data/LICENSE +202 -0
  12. data/README.md +175 -0
  13. data/Rakefile +6 -0
  14. data/app/assets/javascripts/solidus_afterpay/afterpay_checkout.js +131 -0
  15. data/app/assets/javascripts/spree/backend/solidus_afterpay.js +2 -0
  16. data/app/assets/javascripts/spree/frontend/solidus_afterpay.js +4 -0
  17. data/app/assets/stylesheets/spree/backend/solidus_afterpay.css +4 -0
  18. data/app/assets/stylesheets/spree/frontend/solidus_afterpay.css +4 -0
  19. data/app/controllers/solidus_afterpay/base_controller.rb +30 -0
  20. data/app/controllers/solidus_afterpay/callbacks_controller.rb +71 -0
  21. data/app/controllers/solidus_afterpay/checkouts_controller.rb +47 -0
  22. data/app/decorators/controllers/solidus_afterpay/spree/checkout_controller_decorator.rb +13 -0
  23. data/app/decorators/models/solidus_afterpay/spree/order_decorator.rb +15 -0
  24. data/app/helpers/solidus_afterpay/afterpay_helper.rb +15 -0
  25. data/app/models/solidus_afterpay/gateway.rb +157 -0
  26. data/app/models/solidus_afterpay/order_component_builder.rb +97 -0
  27. data/app/models/solidus_afterpay/payment_method.rb +56 -0
  28. data/app/models/solidus_afterpay/payment_source.rb +23 -0
  29. data/app/models/solidus_afterpay/user_agent_generator.rb +35 -0
  30. data/app/views/solidus_afterpay/_afterpay_javascript.html.erb +5 -0
  31. data/app/views/spree/admin/payments/source_forms/_afterpay.html.erb +1 -0
  32. data/app/views/spree/admin/payments/source_views/_afterpay.html.erb +0 -0
  33. data/app/views/spree/api/payments/source_views/_afterpay.json.jbuilder +3 -0
  34. data/app/views/spree/checkout/payment/_afterpay.html.erb +9 -0
  35. data/app/views/spree/shared/_afterpay_messaging.html.erb +13 -0
  36. data/bin/console +17 -0
  37. data/bin/rails +7 -0
  38. data/bin/rails-engine +13 -0
  39. data/bin/rails-sandbox +16 -0
  40. data/bin/rake +7 -0
  41. data/bin/sandbox +86 -0
  42. data/bin/setup +8 -0
  43. data/codecov.yml +2 -0
  44. data/config/locales/en.yml +12 -0
  45. data/config/routes.rb +7 -0
  46. data/db/migrate/20210813142725_create_solidus_afterpay_payment_sources.rb +12 -0
  47. data/lib/generators/solidus_afterpay/install/install_generator.rb +43 -0
  48. data/lib/generators/solidus_afterpay/install/templates/initializer.rb +5 -0
  49. data/lib/solidus_afterpay/configuration.rb +25 -0
  50. data/lib/solidus_afterpay/engine.rb +25 -0
  51. data/lib/solidus_afterpay/testing_support/factories.rb +20 -0
  52. data/lib/solidus_afterpay/version.rb +5 -0
  53. data/lib/solidus_afterpay.rb +5 -0
  54. data/solidus_afterpay.gemspec +38 -0
  55. data/spec/fixtures/vcr_casettes/create_checkout/invalid.yml +65 -0
  56. data/spec/fixtures/vcr_casettes/create_checkout/valid.yml +64 -0
  57. data/spec/fixtures/vcr_casettes/credit/invalid.yml +61 -0
  58. data/spec/fixtures/vcr_casettes/credit/valid.yml +63 -0
  59. data/spec/fixtures/vcr_casettes/deferred/authorize/declined_payment.yml +120 -0
  60. data/spec/fixtures/vcr_casettes/deferred/authorize/invalid.yml +61 -0
  61. data/spec/fixtures/vcr_casettes/deferred/authorize/valid.yml +120 -0
  62. data/spec/fixtures/vcr_casettes/deferred/capture/invalid.yml +61 -0
  63. data/spec/fixtures/vcr_casettes/deferred/capture/valid.yml +140 -0
  64. data/spec/fixtures/vcr_casettes/deferred/void/invalid.yml +61 -0
  65. data/spec/fixtures/vcr_casettes/deferred/void/valid.yml +137 -0
  66. data/spec/fixtures/vcr_casettes/find_payment/invalid.yml +61 -0
  67. data/spec/fixtures/vcr_casettes/find_payment/valid.yml +140 -0
  68. data/spec/fixtures/vcr_casettes/immediate/capture/declined_payment.yml +120 -0
  69. data/spec/fixtures/vcr_casettes/immediate/capture/invalid.yml +61 -0
  70. data/spec/fixtures/vcr_casettes/immediate/capture/valid.yml +134 -0
  71. data/spec/fixtures/vcr_casettes/retrieve_configuration/valid.yml +67 -0
  72. data/spec/helpers/solidus_afterpay/afterpay_helper_spec.rb +23 -0
  73. data/spec/models/solidus_afterpay/gateway_spec.rb +418 -0
  74. data/spec/models/solidus_afterpay/order_component_builder_spec.rb +137 -0
  75. data/spec/models/solidus_afterpay/payment_method_spec.rb +143 -0
  76. data/spec/models/solidus_afterpay/payment_source_spec.rb +61 -0
  77. data/spec/models/solidus_afterpay/user_agent_generator_spec.rb +22 -0
  78. data/spec/models/spree/order_spec.rb +158 -0
  79. data/spec/requests/solidus_afterpay/callbacks_controller_spec.rb +127 -0
  80. data/spec/requests/solidus_afterpay/checkouts_controller_spec.rb +190 -0
  81. data/spec/spec_helper.rb +31 -0
  82. data/spec/support/auth.rb +15 -0
  83. data/spec/support/preferences.rb +33 -0
  84. data/spec/support/vcr.rb +18 -0
  85. metadata +249 -0
@@ -0,0 +1,61 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://global-api-sandbox.afterpay.com/v2/payments/capture
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"token":"INVALID_TOKEN","merchantReference":null,"amount":null}'
9
+ headers:
10
+ User-Agent:
11
+ - SolidusAfterpay/0.0.1 (Solidus/3.0.1; Ruby/2.6.6; Merchant/100101481) https://
12
+ Authorization:
13
+ - Basic <ENCODED_AUTH_HEADER>
14
+ Content-Type:
15
+ - application/json
16
+ Accept-Encoding:
17
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
18
+ Accept:
19
+ - "*/*"
20
+ response:
21
+ status:
22
+ code: 402
23
+ message: Payment Required
24
+ headers:
25
+ Date:
26
+ - Wed, 25 Aug 2021 14:55:10 GMT
27
+ Content-Type:
28
+ - application/json
29
+ Content-Length:
30
+ - '163'
31
+ Connection:
32
+ - keep-alive
33
+ Http-Correlation-Id:
34
+ - 2m54dxammo5yivs22xpqy5ymp4
35
+ X-Envoy-Upstream-Service-Time:
36
+ - '65'
37
+ Cf-Cache-Status:
38
+ - DYNAMIC
39
+ Expect-Ct:
40
+ - max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
41
+ Set-Cookie:
42
+ - __cf_bm=59dde99429147353c566297400188a205dd1c569-1629903310-1800-AW+vk29NN4QPXNzSzYsYy0du6wT4ILfy1NY9yfxHlmnG/9bR/OuBxCflDZ2b/f2kBF4GjBQ3d9y4Tov4NdIy990scvazqSnXwt/2X2iaTMBt;
43
+ path=/; expires=Wed, 25-Aug-21 15:25:10 GMT; domain=.afterpay.com; HttpOnly;
44
+ Secure; SameSite=None
45
+ Strict-Transport-Security:
46
+ - max-age=31536000; includeSubDomains; preload
47
+ Server:
48
+ - cloudflare
49
+ Cf-Ray:
50
+ - 6845a8e67fc5cd42-FCO
51
+ body:
52
+ encoding: UTF-8
53
+ string: |-
54
+ {
55
+ "errorCode" : "invalid_token",
56
+ "errorId" : "da76c8c79d41e92b",
57
+ "message" : "Cannot complete payment, expired or invalid token.",
58
+ "httpStatusCode" : 402
59
+ }
60
+ recorded_at: Wed, 25 Aug 2021 14:55:10 GMT
61
+ recorded_with: VCR 6.0.0
@@ -0,0 +1,134 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://global-api-sandbox.afterpay.com/v2/payments/capture
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"token":"002.nt7e0ioqj00fh0ua1nbqcj6vcn9obtfsglqvrj9ijpo3edfc","merchantReference":null,"amount":null}'
9
+ headers:
10
+ User-Agent:
11
+ - SolidusAfterpay/0.0.1 (Solidus/3.0.1; Ruby/2.6.6; Merchant/100101481) https://
12
+ Authorization:
13
+ - Basic <ENCODED_AUTH_HEADER>
14
+ Content-Type:
15
+ - application/json
16
+ Accept-Encoding:
17
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
18
+ Accept:
19
+ - "*/*"
20
+ response:
21
+ status:
22
+ code: 201
23
+ message: Created
24
+ headers:
25
+ Date:
26
+ - Wed, 25 Aug 2021 14:57:00 GMT
27
+ Content-Type:
28
+ - application/json
29
+ Content-Length:
30
+ - '1853'
31
+ Connection:
32
+ - keep-alive
33
+ Http-Correlation-Id:
34
+ - aybxyh56w3cheadll4m2tdy7q4
35
+ X-Envoy-Upstream-Service-Time:
36
+ - '52'
37
+ Cf-Cache-Status:
38
+ - DYNAMIC
39
+ Expect-Ct:
40
+ - max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
41
+ Set-Cookie:
42
+ - __cf_bm=559bbd7450eb18d65d238a3f883de3d462989653-1629903420-1800-AZAsHum/8AJvXaC7SsSFuucq7UTebHQq8yI3EfbLOvY3g5R5nTPNo4NpNe4AJsWcz0yS5nit62GxjFvF2gp2C0JXbMys7O8SzRIgMSaMo0Lz;
43
+ path=/; expires=Wed, 25-Aug-21 15:27:00 GMT; domain=.afterpay.com; HttpOnly;
44
+ Secure; SameSite=None
45
+ Strict-Transport-Security:
46
+ - max-age=31536000; includeSubDomains; preload
47
+ Server:
48
+ - cloudflare
49
+ Cf-Ray:
50
+ - 6845ab96ea29cd2e-FCO
51
+ body:
52
+ encoding: UTF-8
53
+ string: |-
54
+ {
55
+ "id" : "100101768366",
56
+ "token" : "002.nt7e0ioqj00fh0ua1nbqcj6vcn9obtfsglqvrj9ijpo3edfc",
57
+ "status" : "APPROVED",
58
+ "created" : "2021-08-24T15:30:14.497Z",
59
+ "originalAmount" : {
60
+ "amount" : "22.04",
61
+ "currency" : "USD"
62
+ },
63
+ "openToCaptureAmount" : {
64
+ "amount" : "0.00",
65
+ "currency" : "USD"
66
+ },
67
+ "paymentState" : "CAPTURED",
68
+ "merchantReference" : "R903458715",
69
+ "refunds" : [ {
70
+ "amount" : {
71
+ "amount" : "0.10",
72
+ "currency" : "USD"
73
+ },
74
+ "refundId" : "1933891",
75
+ "refundedAt" : "2021-08-25T10:09:30.819Z"
76
+ }, {
77
+ "amount" : {
78
+ "amount" : "10.00",
79
+ "currency" : "USD"
80
+ },
81
+ "refundId" : "1934419",
82
+ "refundedAt" : "2021-08-25T14:56:33.753Z"
83
+ } ],
84
+ "orderDetails" : {
85
+ "consumer" : {
86
+ "phoneNumber" : "",
87
+ "givenNames" : "Christian",
88
+ "surname" : "Rimondi",
89
+ "email" : "admin@example.com"
90
+ },
91
+ "billing" : {
92
+ "name" : "Christian Rimondi",
93
+ "line1" : "1313 Broadway",
94
+ "line2" : "",
95
+ "area1" : "New York",
96
+ "region" : "NY",
97
+ "postcode" : "10001",
98
+ "phoneNumber" : "12524264421"
99
+ },
100
+ "shipping" : {
101
+ "name" : "Christian Rimondi",
102
+ "line1" : "1313 Broadway",
103
+ "line2" : "",
104
+ "area1" : "New York",
105
+ "region" : "NY",
106
+ "postcode" : "10001",
107
+ "phoneNumber" : "12524264421"
108
+ },
109
+ "courier" : { },
110
+ "items" : [ {
111
+ "name" : "Ruby Tote",
112
+ "sku" : "RUB-TOT01",
113
+ "quantity" : 1,
114
+ "price" : {
115
+ "amount" : "15.99",
116
+ "currency" : "USD"
117
+ }
118
+ } ],
119
+ "discounts" : [ ]
120
+ },
121
+ "events" : [ {
122
+ "id" : "1xBCPLdc9Opx4SQY8PeMXDtemsV",
123
+ "created" : "2021-08-24T15:31:09.859Z",
124
+ "expires" : null,
125
+ "type" : "CAPTURED",
126
+ "amount" : {
127
+ "amount" : "22.04",
128
+ "currency" : "USD"
129
+ },
130
+ "paymentEventMerchantReference" : null
131
+ } ]
132
+ }
133
+ recorded_at: Wed, 25 Aug 2021 14:57:00 GMT
134
+ recorded_with: VCR 6.0.0
@@ -0,0 +1,67 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://global-api-sandbox.afterpay.com/v2/configuration
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ User-Agent:
11
+ - SolidusAfterpay/0.0.1 (Solidus/3.0.1; Ruby/2.6.6; Merchant/100101481) https://
12
+ Authorization:
13
+ - Basic <ENCODED_AUTH_HEADER>
14
+ Accept-Encoding:
15
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
16
+ Accept:
17
+ - "*/*"
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ Date:
24
+ - Wed, 25 Aug 2021 14:56:02 GMT
25
+ Content-Type:
26
+ - application/json
27
+ Content-Length:
28
+ - '151'
29
+ Connection:
30
+ - keep-alive
31
+ Cache-Control:
32
+ - private, no-transform, max-age=1800
33
+ Http-Correlation-Id:
34
+ - u73bnoiwo265mfjp7rdrdzfsxu
35
+ Vary:
36
+ - Accept-Encoding
37
+ X-Envoy-Upstream-Service-Time:
38
+ - '10'
39
+ Cf-Cache-Status:
40
+ - DYNAMIC
41
+ Expect-Ct:
42
+ - max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
43
+ Set-Cookie:
44
+ - __cf_bm=ebd26f3256f7c877137bf42087fa5550fed5965e-1629903362-1800-AcvTlx8l0pTK7CNPb9AuSNR0Gno8NZxAq/ln/lsb1QfRDTKngcaw3pe7umDu3S2LL583bb0jFHJGa/Lbzh3XP31WX5N+vTDZeMa33NSOwYtV;
45
+ path=/; expires=Wed, 25-Aug-21 15:26:02 GMT; domain=.afterpay.com; HttpOnly;
46
+ Secure; SameSite=None
47
+ Strict-Transport-Security:
48
+ - max-age=31536000; includeSubDomains; preload
49
+ Server:
50
+ - cloudflare
51
+ Cf-Ray:
52
+ - 6845aa2e5871cd32-FCO
53
+ body:
54
+ encoding: UTF-8
55
+ string: |-
56
+ {
57
+ "minimumAmount" : {
58
+ "amount" : "1.00",
59
+ "currency" : "USD"
60
+ },
61
+ "maximumAmount" : {
62
+ "amount" : "1000.00",
63
+ "currency" : "USD"
64
+ }
65
+ }
66
+ recorded_at: Wed, 25 Aug 2021 14:56:02 GMT
67
+ recorded_with: VCR 6.0.0
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe SolidusAfterpay::AfterpayHelper, type: :helper do
4
+ describe '#include_afterpay_js' do
5
+ subject { helper.include_afterpay_js(test_mode: test_mode) }
6
+
7
+ context 'when test_mode is false' do
8
+ let(:test_mode) { false }
9
+
10
+ it 'includes the production javascript' do
11
+ is_expected.to eq('<script src="https://portal.afterpay.com/afterpay.js"></script>')
12
+ end
13
+ end
14
+
15
+ context 'when test_mode is true' do
16
+ let(:test_mode) { true }
17
+
18
+ it 'includes the sandbox javascript' do
19
+ is_expected.to eq('<script src="https://portal.sandbox.afterpay.com/afterpay.js"></script>')
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,418 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe SolidusAfterpay::Gateway do
4
+ let(:gateway) { described_class.new(options) }
5
+ let(:options) do
6
+ {
7
+ merchant_id: ENV.fetch('AFTERPAY_MERCHANT_ID', 'dummy_merchant_id'),
8
+ secret_key: ENV.fetch('AFTERPAY_SECRET_KEY', 'dummy_secret_key'),
9
+ test_mode: true
10
+ }
11
+ end
12
+
13
+ describe '#authorize' do
14
+ subject(:response) { gateway.authorize(amount, payment_source, gateway_options) }
15
+
16
+ let(:order_token) { '002.m6d9jkrtv1p0j4jqslklhfq9k4nl54jo2530d58kf6snpqq1' }
17
+ let(:deferred?) { false }
18
+ let(:payment_method) { build(:afterpay_payment_method, preferred_deferred: deferred?) }
19
+
20
+ let(:amount) { 1000 }
21
+ let(:payment_source) { build(:afterpay_payment_source, token: order_token, payment_method: payment_method) }
22
+ let(:gateway_options) { {} }
23
+
24
+ context 'with the immediate flow' do
25
+ it 'returns a successful response' do
26
+ is_expected.to be_success
27
+ end
28
+ end
29
+
30
+ context 'with the deferred flow' do
31
+ let(:deferred?) { true }
32
+
33
+ context 'with valid params', vcr: 'deferred/authorize/valid' do
34
+ it 'authorize the afterpay payment with the order_token' do
35
+ is_expected.to be_success
36
+ end
37
+ end
38
+
39
+ context 'with an invalid token', vcr: 'deferred/authorize/invalid' do
40
+ let(:order_token) { 'INVALID_TOKEN' }
41
+
42
+ it 'returns an unsuccesfull response' do
43
+ is_expected.not_to be_success
44
+ end
45
+
46
+ it 'returns the error message from Afterpay in the response' do
47
+ expect(response.message).to eq('Cannot complete payment, expired or invalid token.')
48
+ end
49
+
50
+ it 'returns the error_code from Afterpay in the response' do
51
+ expect(response.error_code).to eq('invalid_token')
52
+ end
53
+ end
54
+
55
+ context 'with an invalid credit card', vcr: 'deferred/authorize/declined_payment' do
56
+ let(:order_token) { '002.ijlqnvko1o4ou45uabplrl9pqao8u2v52njs2972r24hje65' }
57
+
58
+ it 'returns an unsuccesfull response' do
59
+ is_expected.not_to be_success
60
+ end
61
+
62
+ it 'returns the error message from Afterpay in the response' do
63
+ expect(response.message).to eq(
64
+ 'Payment declined. Please contact the Afterpay Customer Service team for more information.'
65
+ )
66
+ end
67
+
68
+ it 'returns the error_code from Afterpay in the response' do
69
+ expect(response.error_code).to eq('payment_declined')
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ describe '#capture' do
76
+ subject(:response) { gateway.capture(amount, response_code, gateway_options) }
77
+
78
+ let(:order_token) { '002.nt7e0ioqj00fh0ua1nbqcj6vcn9obtfsglqvrj9ijpo3edfc' }
79
+ let(:deferred?) { false }
80
+ let(:payment_source) { build(:afterpay_payment_source, token: order_token) }
81
+ let(:payment_method) { build(:afterpay_payment_method, preferred_deferred: deferred?) }
82
+ let(:payment) { build(:afterpay_payment, source: payment_source, payment_method: payment_method) }
83
+
84
+ let(:amount) { 1000 }
85
+ let(:response_code) { '100101782114' }
86
+ let(:gateway_options) { { originator: payment, currency: 'USD' } }
87
+
88
+ context 'with the immediate flow' do
89
+ context 'with valid params', vcr: 'immediate/capture/valid' do
90
+ it 'captures the afterpay payment with the order_token' do
91
+ is_expected.to be_success
92
+ end
93
+ end
94
+
95
+ context 'with an invalid token', vcr: 'immediate/capture/invalid' do
96
+ let(:order_token) { 'INVALID_TOKEN' }
97
+
98
+ it 'returns an unsuccesfull response' do
99
+ is_expected.not_to be_success
100
+ end
101
+
102
+ it 'returns the error message from Afterpay in the response' do
103
+ expect(response.message).to eq('Cannot complete payment, expired or invalid token.')
104
+ end
105
+
106
+ it 'returns the error_code from Afterpay in the response' do
107
+ expect(response.error_code).to eq('invalid_token')
108
+ end
109
+ end
110
+
111
+ context 'with an invalid credit card', vcr: 'immediate/capture/declined_payment' do
112
+ let(:order_token) { '002.kj16plsn63eqfacueg767cp7l34e9ph5tms4ql14o2iid7l1' }
113
+
114
+ it 'returns an unsuccesfull response' do
115
+ is_expected.not_to be_success
116
+ end
117
+
118
+ it 'returns the error message from Afterpay in the response' do
119
+ expect(response.message).to eq(
120
+ 'Payment declined. Please contact the Afterpay Customer Service team for more information.'
121
+ )
122
+ end
123
+
124
+ it 'returns the error_code from Afterpay in the response' do
125
+ expect(response.error_code).to eq('payment_declined')
126
+ end
127
+ end
128
+ end
129
+
130
+ context 'with the deferred flow' do
131
+ let(:deferred?) { true }
132
+
133
+ context 'with valid params', vcr: 'deferred/capture/valid' do
134
+ it 'captures the afterpay payment with the order_id' do
135
+ is_expected.to be_success
136
+ end
137
+ end
138
+
139
+ context 'with an invalid payment ID', vcr: 'deferred/capture/invalid' do
140
+ let(:response_code) { 'INVALID_RESPONSE_CODE' }
141
+
142
+ it 'returns an unsuccesfull response' do
143
+ is_expected.not_to be_success
144
+ end
145
+
146
+ it 'returns the error message from Afterpay in the response' do
147
+ expect(response.message).to eq('Afterpay payment ID not found.')
148
+ end
149
+
150
+ it 'returns the error_code from Afterpay in the response' do
151
+ expect(response.error_code).to eq('not_found')
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+ describe '#purchase' do
158
+ subject(:response) { gateway.purchase(amount, payment_source, gateway_options) }
159
+
160
+ let(:order_token) { '002.nt7e0ioqj00fh0ua1nbqcj6vcn9obtfsglqvrj9ijpo3edfc' }
161
+ let(:deferred?) { false }
162
+ let(:payment_method) { build(:afterpay_payment_method, preferred_deferred: deferred?) }
163
+ let(:payment) { build(:afterpay_payment, source: payment_source, payment_method: payment_method) }
164
+
165
+ let(:amount) { 1000 }
166
+ let(:payment_source) { build(:afterpay_payment_source, token: order_token, payment_method: payment_method) }
167
+ let(:gateway_options) { { originator: payment, currency: 'USD' } }
168
+
169
+ context 'with the immediate flow' do
170
+ context 'with valid params', vcr: 'immediate/capture/valid' do
171
+ it 'authorize and captures the afterpay payment with the order_token' do
172
+ is_expected.to be_success
173
+ end
174
+ end
175
+
176
+ context 'with an invalid token', vcr: 'immediate/capture/invalid' do
177
+ let(:order_token) { 'INVALID_TOKEN' }
178
+
179
+ it 'returns an unsuccesfull response' do
180
+ is_expected.not_to be_success
181
+ end
182
+
183
+ it 'returns the error message from Afterpay in the response' do
184
+ expect(response.message).to eq('Cannot complete payment, expired or invalid token.')
185
+ end
186
+
187
+ it 'returns the error_code from Afterpay in the response' do
188
+ expect(response.error_code).to eq('invalid_token')
189
+ end
190
+ end
191
+
192
+ context 'with an invalid credit card', vcr: 'immediate/capture/declined_payment' do
193
+ let(:order_token) { '002.kj16plsn63eqfacueg767cp7l34e9ph5tms4ql14o2iid7l1' }
194
+
195
+ it 'returns an unsuccesfull response' do
196
+ is_expected.not_to be_success
197
+ end
198
+
199
+ it 'returns the error message from Afterpay in the response' do
200
+ expect(response.message).to eq(
201
+ 'Payment declined. Please contact the Afterpay Customer Service team for more information.'
202
+ )
203
+ end
204
+
205
+ it 'returns the error_code from Afterpay in the response' do
206
+ expect(response.error_code).to eq('payment_declined')
207
+ end
208
+ end
209
+ end
210
+
211
+ context 'with the deferred flow' do
212
+ let(:deferred?) { true }
213
+
214
+ context 'with valid params', vcr: 'deferred/authorize/valid' do
215
+ it 'authorize and captures the afterpay payment with the order_token' do
216
+ VCR.use_cassette('deferred/capture/valid') do
217
+ is_expected.to be_success
218
+ end
219
+ end
220
+ end
221
+
222
+ context 'with an invalid token', vcr: 'deferred/authorize/invalid' do
223
+ let(:order_token) { 'INVALID_TOKEN' }
224
+
225
+ it 'returns an unsuccesfull response' do
226
+ is_expected.not_to be_success
227
+ end
228
+
229
+ it 'returns the error message from Afterpay in the response' do
230
+ expect(response.message).to eq('Cannot complete payment, expired or invalid token.')
231
+ end
232
+
233
+ it 'returns the error_code from Afterpay in the response' do
234
+ expect(response.error_code).to eq('invalid_token')
235
+ end
236
+ end
237
+
238
+ context 'with an invalid credit card', vcr: 'deferred/authorize/declined_payment' do
239
+ let(:order_token) { '002.kj16plsn63eqfacueg767cp7l34e9ph5tms4ql14o2iid7l1' }
240
+
241
+ it 'returns an unsuccesfull response' do
242
+ is_expected.not_to be_success
243
+ end
244
+
245
+ it 'returns the error message from Afterpay in the response' do
246
+ expect(response.message).to eq(
247
+ 'Payment declined. Please contact the Afterpay Customer Service team for more information.'
248
+ )
249
+ end
250
+
251
+ it 'returns the error_code from Afterpay in the response' do
252
+ expect(response.error_code).to eq('payment_declined')
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+ describe '#credit' do
259
+ subject(:response) { gateway.credit(amount, response_code, gateway_options) }
260
+
261
+ let(:payment) { build(:afterpay_payment) }
262
+ let(:refund) { build(:refund, payment: payment) }
263
+
264
+ let(:amount) { 1000 }
265
+ let(:response_code) { '100101768366' }
266
+ let(:gateway_options) { { originator: refund } }
267
+
268
+ context 'with valid params', vcr: 'credit/valid' do
269
+ it 'refunds the amount using the response_code' do
270
+ is_expected.to be_success
271
+ end
272
+ end
273
+
274
+ context 'with an invalid response_code', vcr: 'credit/invalid' do
275
+ let(:response_code) { 'INVALID_RESPONSE_CODE' }
276
+
277
+ it 'returns an unsuccesfull response' do
278
+ is_expected.not_to be_success
279
+ end
280
+
281
+ it 'returns the error message from Afterpay in the response' do
282
+ expect(response.message).to eq('Afterpay payment ID not found.')
283
+ end
284
+
285
+ it 'returns the error_code from Afterpay in the response' do
286
+ expect(response.error_code).to eq('not_found')
287
+ end
288
+ end
289
+ end
290
+
291
+ describe '#void' do
292
+ subject(:response) { gateway.void(response_code, gateway_options) }
293
+
294
+ let(:deferred?) { false }
295
+ let(:amount) { 10 }
296
+ let(:payment_method) { build(:afterpay_payment_method, preferred_deferred: deferred?) }
297
+ let(:payment) { build(:afterpay_payment, payment_method: payment_method, amount: amount) }
298
+
299
+ let(:response_code) { '100101785223' }
300
+ let(:gateway_options) { { originator: payment, currency: 'USD' } }
301
+
302
+ context 'with the immediate flow' do
303
+ it 'returns an unsuccessful response' do
304
+ is_expected.not_to be_success
305
+ end
306
+
307
+ it 'returns the error message from Afterpay in the response' do
308
+ expect(response.message).to eq("Transaction can't be voided")
309
+ end
310
+
311
+ it 'returns the error_code from Afterpay in the response' do
312
+ expect(response.error_code).to eq('void_not_allowed')
313
+ end
314
+ end
315
+
316
+ context 'with the deferred flow' do
317
+ let(:deferred?) { true }
318
+
319
+ context 'with valid params', vcr: 'deferred/void/valid' do
320
+ it 'voids the payment using the response_code' do
321
+ is_expected.to be_success
322
+ end
323
+ end
324
+
325
+ context 'with an invalid response_code', vcr: 'deferred/void/invalid' do
326
+ let(:response_code) { 'INVALID_RESPONSE_CODE' }
327
+
328
+ it 'returns an unsuccesfull response' do
329
+ is_expected.not_to be_success
330
+ end
331
+
332
+ it 'returns the error message from Afterpay in the response' do
333
+ expect(response.message).to eq('Afterpay payment ID not found.')
334
+ end
335
+
336
+ it 'returns the error_code from Afterpay in the response' do
337
+ expect(response.error_code).to eq('not_found')
338
+ end
339
+ end
340
+ end
341
+ end
342
+
343
+ describe '#create_checkout' do
344
+ subject(:response) { gateway.create_checkout(order, gateway_options) }
345
+
346
+ let(:redirect_confirm_url) { 'https://merchantsite.com/confirm' }
347
+ let(:redirect_cancel_url) { 'https://merchantsite.com/cancel' }
348
+
349
+ let(:order) { build(:order_with_line_items) }
350
+ let(:gateway_options) { { redirect_confirm_url: redirect_confirm_url, redirect_cancel_url: redirect_cancel_url } }
351
+
352
+ context 'with valid params', vcr: 'create_checkout/valid' do
353
+ it 'creates the checkout' do
354
+ is_expected.to be_success
355
+ end
356
+
357
+ it 'returns the order_token' do
358
+ expect(response.params).to include('token')
359
+ end
360
+ end
361
+
362
+ context 'with an invalid params', vcr: 'create_checkout/invalid' do
363
+ let(:redirect_confirm_url) { 'INVALID_URL' }
364
+
365
+ it 'returns an unsuccesfull response' do
366
+ is_expected.not_to be_success
367
+ end
368
+
369
+ it 'returns the error message from Afterpay in the response' do
370
+ expect(response.message).to eq('merchant.redirectConfirmUrl must be a valid URL')
371
+ end
372
+
373
+ it 'returns the error_code from Afterpay in the response' do
374
+ expect(response.error_code).to eq('invalid_object')
375
+ end
376
+ end
377
+ end
378
+
379
+ describe '#find_payment' do
380
+ subject(:response) { gateway.find_payment(order_id: order_id) }
381
+
382
+ let(:order_id) { '100101785223' }
383
+
384
+ context 'with valid params', vcr: 'find_payment/valid' do
385
+ it 'retrieves the Afterpay payment' do
386
+ expect(response).to include('id' => order_id)
387
+ end
388
+ end
389
+
390
+ context 'with an invalid params', vcr: 'find_payment/invalid' do
391
+ let(:order_id) { 'INVALID_ORDER_ID' }
392
+
393
+ it 'returns nil' do
394
+ is_expected.to be_nil
395
+ end
396
+ end
397
+ end
398
+
399
+ describe '#retrieve_configuration' do
400
+ subject(:response) { gateway.retrieve_configuration }
401
+
402
+ context 'with valid response', vcr: 'retrieve_configuration/valid' do
403
+ it 'retrieves the afterpay configuration' do
404
+ expect(response).to include('minimumAmount', 'maximumAmount')
405
+ end
406
+ end
407
+
408
+ context 'with invalid response' do
409
+ before do
410
+ allow(::Afterpay::API::Configuration::Retrieve).to receive(:call).and_raise(::Afterpay::BaseError.new(nil))
411
+ end
412
+
413
+ it 'retrieves the afterpay configuration' do
414
+ expect(response).to be_nil
415
+ end
416
+ end
417
+ end
418
+ end