solidus_gateway_checkout_v2 1.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/CONTRIBUTING.md +82 -0
- data/Gemfile +9 -0
- data/LICENSE.md +26 -0
- data/README.md +35 -0
- data/Rakefile +21 -0
- data/app/models/spree/gateway/checkout_v2_gateway.rb +33 -0
- data/bin/rails +7 -0
- data/config/locales/en.yml +4 -0
- data/config/routes.rb +3 -0
- data/lib/active_merchant/billing/checkout_v2.rb +218 -0
- data/lib/solidus_gateway_checkout_v2.rb +4 -0
- data/lib/spree_gateway_checkout_v2/engine.rb +30 -0
- data/lib/views/frontend/spree/checkout/payment/_checkout_v2.html.erb +83 -0
- data/solidus_gateway_checkout_v2.gemspec +32 -0
- data/spec/spec_helper.rb +47 -0
- metadata +202 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b4553466121a1142278574daa84f17c3b9e2b35c
|
4
|
+
data.tar.gz: d843f8152afe2d8fe4de2a5fbd7c95ccb91ad122
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4322ff8f704df7d0d735331aa68c511013d97875f5e8be9778630bbf7ff40e3b4d7990900dd0eaea929ecf684550840066722099f18a81b1398b0c60316a8be2
|
7
|
+
data.tar.gz: d738d9ce3b724bfcb71dc3403661e82ffdf2a70b4cf967c588fd86eab5b5cffe8fe1f2d7ff7dbb16215bce098d214d59b3d81d7ebeebf3f1807e690d9f5d9488
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
*(Originally copied from [Spree Static Content's Contribution guidelines](https://github.com/spree-contrib/spree_static_content/blob/master/CONTRIBUTING.md)*
|
4
|
+
|
5
|
+
Solidus Gateway Checkout V2 is an open source project and we encourage contributions.
|
6
|
+
|
7
|
+
In the spirit of [free software][1], **everyone** is encouraged to help improve this project.
|
8
|
+
|
9
|
+
Here are some ways *you* can contribute:
|
10
|
+
|
11
|
+
* by using prerelease versions
|
12
|
+
* by reporting [bugs][2]
|
13
|
+
* by suggesting new features
|
14
|
+
* by writing [translations][3]
|
15
|
+
* by writing or editing documentation
|
16
|
+
* by writing specifications
|
17
|
+
* by writing code (*no patch is too small*: fix typos, add comments, clean up inconsistent whitespace)
|
18
|
+
* by refactoring code
|
19
|
+
* by resolving [issues][2]
|
20
|
+
* by reviewing patches
|
21
|
+
|
22
|
+
---
|
23
|
+
|
24
|
+
## Filing an issue
|
25
|
+
|
26
|
+
When filing an issue on this extension, please first do these things:
|
27
|
+
|
28
|
+
* Verify you can reproduce this issue in a brand new application.
|
29
|
+
* Run through the steps to reproduce the issue again.
|
30
|
+
|
31
|
+
In the issue itself please provide:
|
32
|
+
|
33
|
+
* A comprehensive list of steps to reproduce the issue.
|
34
|
+
* What you're *expecting* to happen compared with what's *actually* happening.
|
35
|
+
* The version of Spree *and* the version of Rails.
|
36
|
+
* A list of all extensions.
|
37
|
+
* Any relevant stack traces ("Full trace" preferred)
|
38
|
+
* Your `Gemfile`
|
39
|
+
|
40
|
+
In 99% of cases, this information is enough to determine the cause and solution to the problem that is being described.
|
41
|
+
|
42
|
+
---
|
43
|
+
|
44
|
+
## Pull requests
|
45
|
+
|
46
|
+
We gladly accept pull requests to fix bugs and, in some circumstances, add new features to this extension.
|
47
|
+
|
48
|
+
Here's a quick guide:
|
49
|
+
|
50
|
+
1. Fork the repo.
|
51
|
+
|
52
|
+
2. Run the tests. We only take pull requests with passing tests, and it's great to know that you have a clean slate.
|
53
|
+
|
54
|
+
3. Create new branch then make changes and add tests for your changes. Only refactoring and documentation changes require no new tests. If you are adding functionality or fixing a bug, we need tests!
|
55
|
+
|
56
|
+
4. Push to your fork and submit a pull request. If the changes will apply cleanly to the latest stable branches and master branch, you will only need to submit one pull request.
|
57
|
+
|
58
|
+
At this point you're waiting on us. We may suggest some changes or improvements or alternatives.
|
59
|
+
|
60
|
+
Some things that will increase the chance that your pull request is accepted, taken straight from the Ruby on Rails guide:
|
61
|
+
|
62
|
+
* Use Rails idioms and helpers.
|
63
|
+
* Include tests that fail without your code, and pass with it.
|
64
|
+
* Update the documentation, the surrounding one, examples elsewhere, guides, whatever is affected by your contribution.
|
65
|
+
|
66
|
+
---
|
67
|
+
|
68
|
+
## TL;DR
|
69
|
+
|
70
|
+
* Fork the repo
|
71
|
+
* Clone your repo
|
72
|
+
* Run `bundle install`
|
73
|
+
* Run `bundle exec rake test_app` to create the test application in `spec/dummy`
|
74
|
+
* Make your changes
|
75
|
+
* Ensure specs pass by running `bundle exec rspec spec`
|
76
|
+
* Submit your pull request
|
77
|
+
|
78
|
+
And in case we didn't emphasize it enough: **we love tests!**
|
79
|
+
|
80
|
+
[1]: http://www.fsf.org/licensing/essays/free-sw.html
|
81
|
+
[2]: https://github.com/whelton/solidus_gateway_checkout_v2/issues
|
82
|
+
[3]: https://github.com/whelton/solidus_gateway_checkout_v2/tree/master/config/locales
|
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Copyright (c) 2016 James Whelton and contributors.
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
* Neither the name Solidus nor the names of its contributors may be used to
|
13
|
+
endorse or promote products derived from this software without specific
|
14
|
+
prior written permission.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
17
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
18
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
19
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
20
|
+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
21
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
22
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
23
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
24
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
25
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
26
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Solidus Gateway Checkout V2
|
2
|
+
|
3
|
+
Adds [Checkout.com V2](checkout.com) Gateway to Soldius, supporting both server and card tokenisation payments (via the JS library.)
|
4
|
+
|
5
|
+
---
|
6
|
+
## Basic Installation
|
7
|
+
|
8
|
+
Add to your `Gemfile`:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'solidus_gateway_checkout_v2'
|
12
|
+
```
|
13
|
+
|
14
|
+
Make sure to **restart your app**. Navigate to *Configuration >
|
15
|
+
Payment Methods > New Payment Method* in the admin panel and you should see
|
16
|
+
that Checkout V2 has been added to the list.
|
17
|
+
|
18
|
+
|
19
|
+
That's all!
|
20
|
+
|
21
|
+
---
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
See corresponding [guidelines][1] and check out the [issues][2].
|
26
|
+
|
27
|
+
---
|
28
|
+
|
29
|
+
Copyright (c) 2015 [James Whelton][3] and [contributors][4], released under the [New BSD License][5]
|
30
|
+
|
31
|
+
[1]: https://github.com/whelton/solidus_gateway_checkout_v2/blob/master/CONTRIBUTING.md
|
32
|
+
[2]: https://github.com/whelton/solidus_gateway_checkout_v2/issues
|
33
|
+
[3]: https://github.com/whelton
|
34
|
+
[4]: https://github.com/whelton/solidus_gateway_checkout_v2/graphs/contributors
|
35
|
+
[5]: https://github.com/whelton/solidus_gateway_checkout_v2/blob/master/LICENSE.md
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'spree/testing_support/common_rake'
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new
|
8
|
+
|
9
|
+
task :default do
|
10
|
+
if Dir["spec/dummy"].empty?
|
11
|
+
Rake::Task[:test_app].invoke
|
12
|
+
Dir.chdir("../../")
|
13
|
+
end
|
14
|
+
Rake::Task[:spec].invoke
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Generates a dummy app for testing"
|
18
|
+
task :test_app do
|
19
|
+
ENV['LIB_NAME'] = 'solidus_gateway_checkout_v2'
|
20
|
+
Rake::Task['common:test_app'].invoke
|
21
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
class Spree::Gateway::CheckoutV2Gateway < Spree::Gateway
|
3
|
+
|
4
|
+
preference :secret_key, :string
|
5
|
+
preference :publishable_key, :string
|
6
|
+
preference :use_card_tokenisation, :boolean, default: true
|
7
|
+
preference :test, :boolean, default: true
|
8
|
+
|
9
|
+
def method_type
|
10
|
+
'checkout_v2'
|
11
|
+
end
|
12
|
+
|
13
|
+
def provider_class
|
14
|
+
ActiveMerchant::Billing::CheckoutV2
|
15
|
+
end
|
16
|
+
|
17
|
+
def authorize(money, creditcard, gateway_options)
|
18
|
+
if creditcard.number.blank? && creditcard.gateway_payment_profile_id.present?
|
19
|
+
provider.authorize(money, creditcard.gateway_payment_profile_id, gateway_options)
|
20
|
+
else
|
21
|
+
provider.authorize(money, creditcard, gateway_options)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def purchase(money, creditcard, gateway_options)
|
26
|
+
if creditcard.number.blank? && creditcard.gateway_payment_profile_id.present?
|
27
|
+
provider.purchase(money, creditcard.gateway_payment_profile_id, gateway_options)
|
28
|
+
else
|
29
|
+
provider.purchase(money, creditcard, gateway_options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/bin/rails
ADDED
data/config/routes.rb
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
# From https://github.com/activemerchant/active_merchant/blob/8f8c46e65969896c869dfb7dbef3a3b47921e1c4/lib/active_merchant/billing/gateways/checkout_v2.rb
|
2
|
+
#
|
3
|
+
# Solidus currently (version '1.1.1' at time of writing) uses Active Merchant at '~> 1.48.0'
|
4
|
+
# CheckoutV2 Gateway was added in Active Merchant at '1.51.0'
|
5
|
+
# so Solidus does not currently have access to it
|
6
|
+
#
|
7
|
+
# This has been modified to authorize with card token if provided as payment method
|
8
|
+
|
9
|
+
module ActiveMerchant #:nodoc:
|
10
|
+
module Billing #:nodoc:
|
11
|
+
class CheckoutV2 < Gateway
|
12
|
+
self.display_name = "Checkout.com V2 Gateway"
|
13
|
+
self.homepage_url = "https://www.checkout.com/"
|
14
|
+
self.live_url = "https://api2.checkout.com/v2"
|
15
|
+
self.test_url = "http://sandbox.checkout.com/api2/v2"
|
16
|
+
|
17
|
+
self.supported_countries = ['AD', 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FO', 'FI', 'FR', 'GB', 'GI', 'GL', 'GR', 'HR', 'HU', 'IE', 'IS', 'IL', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SM', 'SK', 'SJ', 'TR', 'VA']
|
18
|
+
self.default_currency = "USD"
|
19
|
+
self.money_format = :cents
|
20
|
+
self.supported_cardtypes = [:visa, :master, :american_express, :diners_club]
|
21
|
+
|
22
|
+
def initialize(options={})
|
23
|
+
requires!(options, :secret_key)
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def purchase(amount, payment_method, options={})
|
28
|
+
MultiResponse.run do |r|
|
29
|
+
r.process { authorize(amount, payment_method, options) }
|
30
|
+
r.process { capture(amount, r.authorization, options) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def authorize(amount, payment_method, options={})
|
35
|
+
post = {}
|
36
|
+
post[:autoCapture] = "n"
|
37
|
+
add_invoice(post, amount, options)
|
38
|
+
|
39
|
+
if payment_method.is_a?(String) && payment_method.include?('card_tok_')
|
40
|
+
post[:cardToken] = payment_method
|
41
|
+
post[:email] = options[:email]
|
42
|
+
|
43
|
+
commit(:authorize_token, post)
|
44
|
+
else
|
45
|
+
add_payment_method(post, payment_method)
|
46
|
+
add_customer_data(post, options)
|
47
|
+
|
48
|
+
commit(:authorize, post)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def capture(amount, authorization, options={})
|
53
|
+
post = {}
|
54
|
+
add_invoice(post, amount, options)
|
55
|
+
add_customer_data(post, options)
|
56
|
+
|
57
|
+
commit(:capture, post, authorization)
|
58
|
+
end
|
59
|
+
|
60
|
+
def void(authorization, options={})
|
61
|
+
post = {}
|
62
|
+
commit(:void, post, authorization)
|
63
|
+
end
|
64
|
+
|
65
|
+
def refund(amount, authorization, options={})
|
66
|
+
post = {}
|
67
|
+
add_invoice(post, amount, options)
|
68
|
+
add_customer_data(post, options)
|
69
|
+
|
70
|
+
commit(:refund, post, authorization)
|
71
|
+
end
|
72
|
+
|
73
|
+
def verify(credit_card, options={})
|
74
|
+
MultiResponse.run(:use_first_response) do |r|
|
75
|
+
r.process { authorize(100, credit_card, options) }
|
76
|
+
r.process(:ignore_result) { void(r.authorization, options) }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def supports_scrubbing?
|
81
|
+
true
|
82
|
+
end
|
83
|
+
|
84
|
+
def scrub(transcript)
|
85
|
+
transcript.
|
86
|
+
gsub(%r((Authorization: )[^\\]*)i, '\1[FILTERED]').
|
87
|
+
gsub(%r(("number\\":\\")\d+), '\1[FILTERED]').
|
88
|
+
gsub(%r(("cvv\\":\\")\d+), '\1[FILTERED]')
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def add_invoice(post, money, options)
|
94
|
+
post[:value] = amount(money)
|
95
|
+
post[:trackId] = options[:order_id]
|
96
|
+
post[:currency] = options[:currency] || currency(money)
|
97
|
+
end
|
98
|
+
|
99
|
+
def add_payment_method(post, payment_method)
|
100
|
+
post[:card] = {}
|
101
|
+
post[:card][:name] = payment_method.name
|
102
|
+
post[:card][:number] = payment_method.number
|
103
|
+
post[:card][:cvv] = payment_method.verification_value
|
104
|
+
post[:card][:expiryYear] = format(payment_method.year, :four_digits)
|
105
|
+
post[:card][:expiryMonth] = format(payment_method.month, :two_digits)
|
106
|
+
end
|
107
|
+
|
108
|
+
def add_customer_data(post, options)
|
109
|
+
post[:email] = options[:email] || "unspecified@example.com"
|
110
|
+
address = options[:billing_address]
|
111
|
+
if(address && post[:card])
|
112
|
+
post[:card][:billingDetails] = {}
|
113
|
+
post[:card][:billingDetails][:address1] = address[:address1]
|
114
|
+
post[:card][:billingDetails][:address2] = address[:address2]
|
115
|
+
post[:card][:billingDetails][:city] = address[:city]
|
116
|
+
post[:card][:billingDetails][:state] = address[:state]
|
117
|
+
post[:card][:billingDetails][:country] = address[:country]
|
118
|
+
post[:card][:billingDetails][:postcode] = address[:zip]
|
119
|
+
post[:card][:billingDetails][:phone] = { number: address[:phone] } unless address[:phone].blank?
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def commit(action, post, authorization = nil)
|
124
|
+
begin
|
125
|
+
raw_response = ssl_post(url(post, action, authorization), post.to_json, headers)
|
126
|
+
response = parse(raw_response)
|
127
|
+
rescue ResponseError => e
|
128
|
+
raise unless(e.response.code.to_s =~ /4\d\d/)
|
129
|
+
response = parse(e.response.body)
|
130
|
+
end
|
131
|
+
|
132
|
+
succeeded = success_from(response)
|
133
|
+
Response.new(
|
134
|
+
succeeded,
|
135
|
+
message_from(succeeded, response),
|
136
|
+
response,
|
137
|
+
authorization: authorization_from(response),
|
138
|
+
error_code: error_code_from(succeeded, response),
|
139
|
+
test: test?,
|
140
|
+
avs_result: avs_result(action, response),
|
141
|
+
cvv_result: cvv_result(action, response))
|
142
|
+
end
|
143
|
+
|
144
|
+
def headers
|
145
|
+
{
|
146
|
+
"Authorization" => @options[:secret_key],
|
147
|
+
"Content-Type" => "application/json;charset=UTF-8"
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
151
|
+
def url(post, action, authorization)
|
152
|
+
if action == :authorize
|
153
|
+
"#{base_url}/charges/card"
|
154
|
+
elsif action == :authorize_token
|
155
|
+
"#{base_url}/charges/token"
|
156
|
+
else
|
157
|
+
"#{base_url}/charges/#{authorization}/#{action}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def base_url
|
162
|
+
test? ? test_url : live_url
|
163
|
+
end
|
164
|
+
|
165
|
+
def avs_result(action, response)
|
166
|
+
action == :purchase ? AVSResult.new(code: response["card"]["avsCheck"]) : nil
|
167
|
+
end
|
168
|
+
|
169
|
+
def cvv_result(action, response)
|
170
|
+
action == :purchase ? CVVResult.new(response["card"]["cvvCheck"]) : nil
|
171
|
+
end
|
172
|
+
|
173
|
+
def parse(body)
|
174
|
+
JSON.parse(body)
|
175
|
+
rescue JSON::ParserError
|
176
|
+
{
|
177
|
+
"message" => "Invalid JSON response received from CheckoutV2Gateway. Please contact CheckoutV2Gateway if you continue to receive this message.",
|
178
|
+
"raw_response" => scrub(body)
|
179
|
+
}
|
180
|
+
end
|
181
|
+
|
182
|
+
def success_from(response)
|
183
|
+
response["responseCode"] == ("10000" || "10100")
|
184
|
+
end
|
185
|
+
|
186
|
+
def message_from(succeeded, response)
|
187
|
+
if succeeded
|
188
|
+
"Succeeded"
|
189
|
+
elsif response["errors"]
|
190
|
+
response["message"] + ": " + response["errors"].first
|
191
|
+
else
|
192
|
+
response["responseMessage"] || response["message"] || "Unable to read error message"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
STANDARD_ERROR_CODE_MAPPING = {
|
197
|
+
"20014" => STANDARD_ERROR_CODE[:invalid_number],
|
198
|
+
"20100" => STANDARD_ERROR_CODE[:invalid_expiry_date],
|
199
|
+
"20054" => STANDARD_ERROR_CODE[:expired_card],
|
200
|
+
"40104" => STANDARD_ERROR_CODE[:incorrect_cvc],
|
201
|
+
"40108" => STANDARD_ERROR_CODE[:incorrect_zip],
|
202
|
+
"40111" => STANDARD_ERROR_CODE[:incorrect_address],
|
203
|
+
"20005" => STANDARD_ERROR_CODE[:card_declined],
|
204
|
+
"20088" => STANDARD_ERROR_CODE[:processing_error],
|
205
|
+
"20001" => STANDARD_ERROR_CODE[:call_issuer],
|
206
|
+
"30004" => STANDARD_ERROR_CODE[:pickup_card]
|
207
|
+
}
|
208
|
+
|
209
|
+
def authorization_from(raw)
|
210
|
+
raw["id"]
|
211
|
+
end
|
212
|
+
|
213
|
+
def error_code_from(succeeded, response)
|
214
|
+
succeeded ? nil : STANDARD_ERROR_CODE_MAPPING[response["responseCode"]]
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module SpreeGatewayCheckoutV2
|
2
|
+
class Engine < Rails::Engine
|
3
|
+
engine_name 'solidus_gateway_checkout_v2'
|
4
|
+
|
5
|
+
config.autoload_paths += %W(#{config.root}/lib)
|
6
|
+
|
7
|
+
initializer "spree.gateway.payment_methods", :after => "spree.register.payment_methods" do |app|
|
8
|
+
app.config.spree.payment_methods << Spree::Gateway::CheckoutV2Gateway
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.activate
|
12
|
+
if SpreeGatewayCheckoutV2::Engine.frontend_available?
|
13
|
+
Dir.glob(File.join(File.dirname(__FILE__), "../../controllers/frontend/*/*_decorator*.rb")) do |c|
|
14
|
+
Rails.configuration.cache_classes ? require(c) : load(c)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def self.frontend_available?
|
21
|
+
@@frontend_available ||= ::Rails::Engine.subclasses.map(&:instance).map{ |e| e.class.to_s }.include?('Spree::Frontend::Engine')
|
22
|
+
end
|
23
|
+
|
24
|
+
if self.frontend_available?
|
25
|
+
paths["app/views"] << "lib/views/frontend"
|
26
|
+
end
|
27
|
+
|
28
|
+
config.to_prepare &method(:activate).to_proc
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
<%= render "spree/checkout/payment/gateway", payment_method: payment_method %>
|
2
|
+
|
3
|
+
<% if payment_method.preferences[:use_card_tokenisation] %>
|
4
|
+
<!-- Using Card Tokenisation For Checkout V2 Gateway -->
|
5
|
+
<script type="text/javascript" src="https://<%= payment_method.preferences[:test_mode] ? 'sandbox.checkout.com/js/v1/checkoutkit.js' : 'cdn.checkout.com/js/checkoutkit.js' %>"></script>
|
6
|
+
<script type="text/javascript">
|
7
|
+
// Payment Method Element & Id
|
8
|
+
Spree.checkoutV2PaymentMethod = $('#payment_method_' + <%= payment_method.id %>);
|
9
|
+
Spree.checkoutV2PaymentMethodId = Spree.checkoutV2PaymentMethod.prop('id').split("_")[2];
|
10
|
+
// CheckoutKit Credentials
|
11
|
+
CheckoutKit.setPublicKey('<%= payment_method.preferred_publishable_key %>');
|
12
|
+
CheckoutKit.setCustomerEmail('<%= current_order.email %>');
|
13
|
+
// CheckoutKit API Error Event Handler
|
14
|
+
CheckoutKit.addEventHandler(CheckoutKit.Events.API_ERROR, function(response){
|
15
|
+
// Create Error Messages
|
16
|
+
var messages = '<b>'+response.data.message+'</b>';
|
17
|
+
messages += '<ul>';
|
18
|
+
$.each(response.data.errors, function(index, value){
|
19
|
+
messages += '<li>'+value+'</li>';
|
20
|
+
});
|
21
|
+
messages += '</ul>';
|
22
|
+
// Show Errors
|
23
|
+
$('#checkoutV2Error').html(messages);
|
24
|
+
$('#checkoutV2Error').show();
|
25
|
+
// Enable Inputs
|
26
|
+
Spree.checkoutV2PaymentMethod.find('#name_on_card_'+Spree.checkoutV2PaymentMethodId+', #card_number, #card_expiry, #card_code').prop("disabled", false);
|
27
|
+
$('#checkout_form_payment input[type="submit"]').removeClass('disabled').prop('disabled', false);
|
28
|
+
});
|
29
|
+
// Response Handler
|
30
|
+
var checkoutV2ResponseHandler = function(response){
|
31
|
+
// Disable Inputs (Meaning they won't be submitted with form)
|
32
|
+
Spree.checkoutV2PaymentMethod.find('#card_number, #card_expiry, #card_code').prop("disabled", true);
|
33
|
+
// Enable Name Input
|
34
|
+
Spree.checkoutV2PaymentMethod.find('#name_on_card_'+Spree.checkoutV2PaymentMethodId).prop("disabled", false);
|
35
|
+
// Create Hidden Inputs With Data From API
|
36
|
+
Spree.checkoutV2PaymentMethod.append("<input type='hidden' id='checkout_v2_token' name='payment_source[" + Spree.checkoutV2PaymentMethodId + "][gateway_payment_profile_id]' value='" + response.id + "'/>");
|
37
|
+
Spree.checkoutV2PaymentMethod.append("<input type='hidden' name='payment_source[" + Spree.checkoutV2PaymentMethodId + "][paymentMethod]' value='" + response.card.paymentMethod + "'/>");
|
38
|
+
Spree.checkoutV2PaymentMethod.append("<input type='hidden' name='payment_source[" + Spree.checkoutV2PaymentMethodId + "][last_digits]' value='" + response.card.last4 + "'/>");
|
39
|
+
Spree.checkoutV2PaymentMethod.append("<input type='hidden' name='payment_source[" + Spree.checkoutV2PaymentMethodId + "][month]' value='" + response.card.expiryMonth + "'/>");
|
40
|
+
Spree.checkoutV2PaymentMethod.append("<input type='hidden' name='payment_source[" + Spree.checkoutV2PaymentMethodId + "][year]' value='" + response.card.expiryYear + "'/>");
|
41
|
+
// Submit
|
42
|
+
return Spree.checkoutV2PaymentMethod.parents("form").trigger('submit');
|
43
|
+
};
|
44
|
+
// On Page Ready
|
45
|
+
$(document).ready(function() {
|
46
|
+
// Prepend div to Payment Method Element to display any errors
|
47
|
+
Spree.checkoutV2PaymentMethod.prepend("<div id='checkoutV2Error' class='errorExplanation' style='display:none'></div>");
|
48
|
+
// Form Submission Handler
|
49
|
+
$('#checkout_form_payment').submit(function(e) {
|
50
|
+
var expiration, card;
|
51
|
+
// Hide any existing errors
|
52
|
+
$('#checkoutV2Error').hide();
|
53
|
+
// Check if the token input exists
|
54
|
+
// Meaning we already gotten a token and can do a normal submission
|
55
|
+
if ( $( "#checkout_v2_token" ).length ){
|
56
|
+
return true;
|
57
|
+
}
|
58
|
+
// If CheckoutV2 Payment Method Element is visible, stop form submission and create card token
|
59
|
+
if (Spree.checkoutV2PaymentMethod.is(':visible')) {
|
60
|
+
e.preventDefault(); // Stop form submission
|
61
|
+
// Disable Inputs
|
62
|
+
Spree.checkoutV2PaymentMethod.find('#name_on_card_'+Spree.checkoutV2PaymentMethodId+', #card_number, #card_expiry, #card_code').prop("disabled", true);
|
63
|
+
// Create Card Token
|
64
|
+
expiration = $('.cardExpiry:visible').payment('cardExpiryVal');
|
65
|
+
card = {
|
66
|
+
number: $('.cardNumber:visible').val(),
|
67
|
+
name: $('#name_on_card_'+Spree.checkoutV2PaymentMethodId+':visible').val(),
|
68
|
+
expiryMonth: expiration.month || 0,
|
69
|
+
expiryYear: expiration.year || 0,
|
70
|
+
cvv: $('.cardCode:visible').val()
|
71
|
+
};
|
72
|
+
CheckoutKit.createCardToken(card, function(response){
|
73
|
+
// If response has a card and id and not of type error, call the response handler.
|
74
|
+
// If it is an error, the added error handler above will pick it up
|
75
|
+
if (('id' in response) && ('card' in response) && !(response.type == 'error')) {
|
76
|
+
checkoutV2ResponseHandler(response);
|
77
|
+
}
|
78
|
+
});
|
79
|
+
}
|
80
|
+
});
|
81
|
+
});
|
82
|
+
</script>
|
83
|
+
<% end %>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.platform = Gem::Platform::RUBY
|
5
|
+
s.name = "solidus_gateway_checkout_v2"
|
6
|
+
s.version = "1.0.1"
|
7
|
+
s.summary = "Checkout V2 Payment Gateway for Solidus"
|
8
|
+
s.description = s.summary
|
9
|
+
s.required_ruby_version = ">= 2.1"
|
10
|
+
|
11
|
+
s.author = "James Whelton"
|
12
|
+
s.email = "james@whelton.io"
|
13
|
+
s.homepage = "https://github.com/whelton/solidus_gateway_checkout_v2"
|
14
|
+
s.license = %q{BSD-3}
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
18
|
+
s.require_path = "lib"
|
19
|
+
s.requirements << "none"
|
20
|
+
|
21
|
+
s.add_dependency "solidus_core", "~> 1.1"
|
22
|
+
|
23
|
+
s.add_development_dependency "rspec-rails", "~> 3.2"
|
24
|
+
s.add_development_dependency "simplecov"
|
25
|
+
s.add_development_dependency "sqlite3"
|
26
|
+
s.add_development_dependency "sass-rails"
|
27
|
+
s.add_development_dependency "coffee-rails"
|
28
|
+
s.add_development_dependency "factory_girl", "~> 4.4"
|
29
|
+
s.add_development_dependency "capybara"
|
30
|
+
s.add_development_dependency "poltergeist", "~> 1.5.0"
|
31
|
+
s.add_development_dependency "database_cleaner", "1.2.0"
|
32
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require "simplecov"
|
2
|
+
SimpleCov.start "rails"
|
3
|
+
|
4
|
+
ENV["RAILS_ENV"] ||= "test"
|
5
|
+
|
6
|
+
require File.expand_path("../dummy/config/environment.rb", __FILE__)
|
7
|
+
|
8
|
+
require "rspec/rails"
|
9
|
+
|
10
|
+
require "capybara/rspec"
|
11
|
+
require 'capybara/poltergeist'
|
12
|
+
|
13
|
+
require "database_cleaner"
|
14
|
+
require "braintree"
|
15
|
+
require "ffaker"
|
16
|
+
|
17
|
+
require "spree/testing_support/factories"
|
18
|
+
require "spree/testing_support/order_walkthrough"
|
19
|
+
|
20
|
+
Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")].each { |f| require f }
|
21
|
+
|
22
|
+
RSpec.configure do |config|
|
23
|
+
config.infer_spec_type_from_file_location!
|
24
|
+
config.mock_with :rspec
|
25
|
+
|
26
|
+
config.filter_run focus: true
|
27
|
+
config.run_all_when_everything_filtered = true
|
28
|
+
config.use_transactional_fixtures = false
|
29
|
+
|
30
|
+
config.include FactoryGirl::Syntax::Methods
|
31
|
+
|
32
|
+
config.before :suite do
|
33
|
+
DatabaseCleaner.clean_with :truncation
|
34
|
+
end
|
35
|
+
|
36
|
+
config.before do
|
37
|
+
DatabaseCleaner.strategy = RSpec.current_example.metadata[:js] ? :truncation : :transaction
|
38
|
+
DatabaseCleaner.start
|
39
|
+
end
|
40
|
+
|
41
|
+
config.after do
|
42
|
+
DatabaseCleaner.clean
|
43
|
+
end
|
44
|
+
|
45
|
+
Capybara.javascript_driver = :poltergeist
|
46
|
+
FactoryGirl.find_definitions
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: solidus_gateway_checkout_v2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James Whelton
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: solidus_core
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec-rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.2'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: simplecov
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sqlite3
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sass-rails
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: coffee-rails
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: factory_girl
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '4.4'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '4.4'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: capybara
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: poltergeist
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 1.5.0
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 1.5.0
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: database_cleaner
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - '='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 1.2.0
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - '='
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 1.2.0
|
153
|
+
description: Checkout V2 Payment Gateway for Solidus
|
154
|
+
email: james@whelton.io
|
155
|
+
executables: []
|
156
|
+
extensions: []
|
157
|
+
extra_rdoc_files: []
|
158
|
+
files:
|
159
|
+
- ".gitignore"
|
160
|
+
- ".rspec"
|
161
|
+
- CONTRIBUTING.md
|
162
|
+
- Gemfile
|
163
|
+
- LICENSE.md
|
164
|
+
- README.md
|
165
|
+
- Rakefile
|
166
|
+
- app/models/spree/gateway/checkout_v2_gateway.rb
|
167
|
+
- bin/rails
|
168
|
+
- config/locales/en.yml
|
169
|
+
- config/routes.rb
|
170
|
+
- lib/active_merchant/billing/checkout_v2.rb
|
171
|
+
- lib/solidus_gateway_checkout_v2.rb
|
172
|
+
- lib/spree_gateway_checkout_v2/engine.rb
|
173
|
+
- lib/views/frontend/spree/checkout/payment/_checkout_v2.html.erb
|
174
|
+
- solidus_gateway_checkout_v2.gemspec
|
175
|
+
- spec/spec_helper.rb
|
176
|
+
homepage: https://github.com/whelton/solidus_gateway_checkout_v2
|
177
|
+
licenses:
|
178
|
+
- BSD-3
|
179
|
+
metadata: {}
|
180
|
+
post_install_message:
|
181
|
+
rdoc_options: []
|
182
|
+
require_paths:
|
183
|
+
- lib
|
184
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
185
|
+
requirements:
|
186
|
+
- - ">="
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
version: '2.1'
|
189
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
190
|
+
requirements:
|
191
|
+
- - ">="
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
version: '0'
|
194
|
+
requirements:
|
195
|
+
- none
|
196
|
+
rubyforge_project:
|
197
|
+
rubygems_version: 2.4.5
|
198
|
+
signing_key:
|
199
|
+
specification_version: 4
|
200
|
+
summary: Checkout V2 Payment Gateway for Solidus
|
201
|
+
test_files:
|
202
|
+
- spec/spec_helper.rb
|