solidus_afterpay 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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