spook_and_pay 0.2.0.alpha → 0.2.1.alpha
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 +4 -4
- data/lib/spook_and_pay/providers/base.rb +13 -1
- data/lib/spook_and_pay/providers/braintree.rb +11 -11
- data/lib/spook_and_pay/providers/spreedly.rb +237 -0
- data/lib/spook_and_pay/providers.rb +1 -0
- data/lib/spook_and_pay/result.rb +10 -0
- data/lib/spook_and_pay/submission_error.rb +9 -7
- data/lib/spook_and_pay.rb +2 -0
- metadata +27 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 34eb9c68d6931c91d5d0be37818b988f83d395e4
|
4
|
+
data.tar.gz: a68f338050ffb6aa11c063c2d1b787361b9f6410
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4fb560546d25e2ea6a2922a242465ba2b4cdcc38e57f6c34d6b577f4563c8a2a355ba57a4b605ba5ad6fc02f6e9d9a63197038148b6f50f8c57a048e2e139d6
|
7
|
+
data.tar.gz: 862c7cf15b3399fd43feda4618c99de2bd7dabb25c6cadcbf4c1c36837eb0c604c142ff722ba3bf8e01663add6e0512d80abd3677cbfc596359fd68961102a03
|
@@ -151,7 +151,7 @@ module SpookAndPay
|
|
151
151
|
# provided by a CreditCard instance.
|
152
152
|
#
|
153
153
|
# @param [SpookAndPay::CreditCard, String] id
|
154
|
-
# @param [String, Numeric] amount
|
154
|
+
# @param [String, Numeric] amount in dollars
|
155
155
|
# @return SpookAndPay::Result
|
156
156
|
# @api private
|
157
157
|
# @abstract Subclass to implement
|
@@ -188,6 +188,18 @@ module SpookAndPay
|
|
188
188
|
|
189
189
|
private
|
190
190
|
|
191
|
+
# Extracts the credit card id from it's argument. This is is to help with
|
192
|
+
# methods that accept either a card instance of an id.
|
193
|
+
#
|
194
|
+
# @param [SpookAndPay::CreditCard, String]
|
195
|
+
# @return String
|
196
|
+
def credit_card_id(id)
|
197
|
+
case id
|
198
|
+
when SpookAndPay::CreditCard then id.id
|
199
|
+
else id
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
191
203
|
# Extracts a transaction ID from it's target.
|
192
204
|
#
|
193
205
|
# @param [SpookAndPay::Transaction, String]
|
@@ -33,15 +33,15 @@ module SpookAndPay
|
|
33
33
|
# Because Braintree accepts payment details and processes payment in a
|
34
34
|
# single step, this method must also be provided with an amount.
|
35
35
|
#
|
36
|
-
# @param [:purchase, :authorize] type
|
37
36
|
# @param String redirect_url
|
38
|
-
# @param [String, Numeric] amount
|
39
37
|
# @param Hash opts
|
40
38
|
# @option opts [true, false] :vault
|
39
|
+
# @option opts [:purchase, :authorize] :type
|
40
|
+
# @option opts [String, Numeric] :amount
|
41
41
|
# @return Hash
|
42
|
-
def prepare_payment_submission(
|
42
|
+
def prepare_payment_submission(redirect_url, opts = {})
|
43
43
|
payload = {
|
44
|
-
:transaction => {:type => 'sale', :amount => amount},
|
44
|
+
:transaction => {:type => 'sale', :amount => opts[:amount]},
|
45
45
|
:redirect_url => redirect_url
|
46
46
|
}
|
47
47
|
|
@@ -49,7 +49,7 @@ module SpookAndPay
|
|
49
49
|
(payload[:transaction][:options] ||= {})[:store_in_vault] = true
|
50
50
|
end
|
51
51
|
|
52
|
-
if type == :purchase
|
52
|
+
if opts[:type] == :purchase
|
53
53
|
(payload[:transaction][:options] ||= {})[:submit_for_settlement] = true
|
54
54
|
end
|
55
55
|
|
@@ -128,13 +128,13 @@ module SpookAndPay
|
|
128
128
|
# second is the type of error and the third is the field — if any — it
|
129
129
|
# applies to.
|
130
130
|
ERROR_CODE_MAPPING = {
|
131
|
-
"81715" => [:credit_card, :
|
132
|
-
"81725" => [:credit_card, :
|
131
|
+
"81715" => [:credit_card, :invalid, :number],
|
132
|
+
"81725" => [:credit_card, :required, :number],
|
133
133
|
"81703" => [:credit_card, :type_not_accepted, :card_type],
|
134
|
-
"81716" => [:credit_card, :
|
135
|
-
"81712" => [:credit_card, :
|
136
|
-
"81713" => [:credit_card, :
|
137
|
-
"81707" => [:credit_card, :
|
134
|
+
"81716" => [:credit_card, :too_short, :number],
|
135
|
+
"81712" => [:credit_card, :invalid, :expiration_month],
|
136
|
+
"81713" => [:credit_card, :invalid, :expiration_year],
|
137
|
+
"81707" => [:credit_card, :invalid, :cvv],
|
138
138
|
"91507" => [:transaction, :cannot_capture, :status],
|
139
139
|
"91506" => [:transaction, :cannot_refund, :status],
|
140
140
|
"91504" => [:transaction, :cannot_void, :status]
|
@@ -0,0 +1,237 @@
|
|
1
|
+
module SpookAndPay
|
2
|
+
module Providers
|
3
|
+
class Spreedly < Base
|
4
|
+
# The map of generic field names to what is specifically required by
|
5
|
+
# Spreedly.
|
6
|
+
FORM_FIELD_NAMES = {
|
7
|
+
:name => "credit_card[full_name]",
|
8
|
+
:number => "credit_card[number]",
|
9
|
+
:expiration_month => "credit_card[month]",
|
10
|
+
:expiration_year => "credit_card[year]",
|
11
|
+
:cvv => "credit_card[verification_value]"
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
# The which refers to a specific gateway.
|
15
|
+
#
|
16
|
+
# @attr_reader String
|
17
|
+
attr_reader :gateway_token
|
18
|
+
|
19
|
+
# An instance of the spreedly spreedly.
|
20
|
+
#
|
21
|
+
# @attr_reader Spreedly::Environment
|
22
|
+
attr_reader :spreedly
|
23
|
+
|
24
|
+
# Generate a new instance of the Spreedly provider.
|
25
|
+
#
|
26
|
+
# @param Hash config
|
27
|
+
# @option config String :environment_key
|
28
|
+
# @option config String :access_secret
|
29
|
+
# @option config String :gateway_token
|
30
|
+
def initialize(env, config)
|
31
|
+
@gateway_token = config[:gateway_token]
|
32
|
+
@spreedly = ::Spreedly::Environment.new(config[:environment_key], config[:access_secret])
|
33
|
+
|
34
|
+
super(env, config)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param String redirect_url
|
38
|
+
# @param Hash opts
|
39
|
+
# @return Hash
|
40
|
+
def prepare_payment_submission(redirect_url, opts = {})
|
41
|
+
{
|
42
|
+
:url => spreedly.transparent_redirect_form_action,
|
43
|
+
:field_names => self.class::FORM_FIELD_NAMES,
|
44
|
+
:hidden_fields => {:redirect_url => redirect_url, :environment_key => spreedly.key}
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
# Confirms the submission of payment details to Spreedly Core.
|
49
|
+
#
|
50
|
+
# @param String query_string
|
51
|
+
# @return SpookAndPay::Result
|
52
|
+
def confirm_payment_submission(query_string)
|
53
|
+
token = Rack::Utils.parse_nested_query(query_string)["token"]
|
54
|
+
credit_card = spreedly.find_payment_method(token)
|
55
|
+
|
56
|
+
if credit_card.valid?
|
57
|
+
SpookAndPay::Result.new(
|
58
|
+
true,
|
59
|
+
credit_card,
|
60
|
+
:credit_card => coerce_credit_card(credit_card)
|
61
|
+
)
|
62
|
+
else
|
63
|
+
SpookAndPay::Result.new(
|
64
|
+
false,
|
65
|
+
credit_card,
|
66
|
+
:credit_card => coerce_credit_card(credit_card),
|
67
|
+
:errors => extract_card_errors(credit_card)
|
68
|
+
)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def credit_card(id)
|
73
|
+
result = spreedly.find_payment_method(id)
|
74
|
+
coerce_credit_card(result)
|
75
|
+
end
|
76
|
+
|
77
|
+
def credit_card_from_transaction(id)
|
78
|
+
result = spreedly.find_transaction(transaction_id(id))
|
79
|
+
coerce_credit_card(result.payment_method)
|
80
|
+
end
|
81
|
+
|
82
|
+
def transaction(id)
|
83
|
+
result = spreedly.find_transaction(id)
|
84
|
+
coerce_transaction(result)
|
85
|
+
end
|
86
|
+
|
87
|
+
def capture_transaction(id)
|
88
|
+
result = spreedly.capture_transaction(transaction_id(id))
|
89
|
+
coerce_result(result)
|
90
|
+
end
|
91
|
+
|
92
|
+
def refund_transaction(id)
|
93
|
+
result = spreedly.refund_transaction(transaction_id(id))
|
94
|
+
coerce_result(result)
|
95
|
+
end
|
96
|
+
|
97
|
+
def void_transaction(id)
|
98
|
+
result = spreedly.void_transaction(transaction_id(id))
|
99
|
+
coerce_result(result)
|
100
|
+
end
|
101
|
+
|
102
|
+
def authorize_via_credit_card(id, amount)
|
103
|
+
result = spreedly.authorize_on_gateway(gateway_token, credit_card_id(id), amount.to_f * 100)
|
104
|
+
coerce_result(result)
|
105
|
+
end
|
106
|
+
|
107
|
+
def purchase_via_credit_card(id, amount)
|
108
|
+
result = spreedly.purchase_on_gateway(gateway_token, credit_card_id(id), amount.to_f * 100)
|
109
|
+
coerce_result(result)
|
110
|
+
end
|
111
|
+
|
112
|
+
def delete_credit_card(id)
|
113
|
+
result = spreedly.redact_payment_method(credit_card_id(id))
|
114
|
+
coerce_result(result)
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
# Takes the result of running a transaction against a Spreedly gateway
|
120
|
+
# and coerces it into a SpookAndPay::Result
|
121
|
+
#
|
122
|
+
# @param Spreedly::Transaction result
|
123
|
+
# @return SpookAndPay::Result
|
124
|
+
def coerce_result(result)
|
125
|
+
opts = {
|
126
|
+
:transaction => coerce_transaction(result),
|
127
|
+
:errors => extract_transaction_errors(result)
|
128
|
+
}
|
129
|
+
|
130
|
+
if result.respond_to?(:payment_method)
|
131
|
+
opts[:card] = coerce_credit_card(result.payment_method)
|
132
|
+
end
|
133
|
+
|
134
|
+
SpookAndPay::Result.new(result.succeeded, result, opts)
|
135
|
+
end
|
136
|
+
|
137
|
+
# A mapping from the names used by Spreedly to the names SpookAndPay uses
|
138
|
+
# internally.
|
139
|
+
CARD_FIELDS = {
|
140
|
+
"full_name" => :name,
|
141
|
+
"number" => :number,
|
142
|
+
"year" => :expiration_year,
|
143
|
+
"month" => :expiration_month,
|
144
|
+
"verification_value" => :cvv
|
145
|
+
}.freeze
|
146
|
+
|
147
|
+
# Maps the error types from Spreedly's to the names used internally.
|
148
|
+
ERRORS = {
|
149
|
+
"errors.invalid" => :invalid,
|
150
|
+
"errors.blank" => :blank,
|
151
|
+
"errors.expired" => :expired
|
152
|
+
}.freeze
|
153
|
+
|
154
|
+
# Extracts/coerces errors from a Spreedly response into SubmissionError
|
155
|
+
# instances.
|
156
|
+
#
|
157
|
+
# @param Spreedly::CreditCard result
|
158
|
+
# @return Array<SpookAndPay::SubmissionError>
|
159
|
+
# @todo If the Spreedly API behaves later, the check for first/last
|
160
|
+
# name might not be needed anymore.
|
161
|
+
def extract_card_errors(result)
|
162
|
+
# This gnarly bit of code transforms errors on the first_name or
|
163
|
+
# last_name attributes into an error on full_name. This is because
|
164
|
+
# Spreedly accepts input for full_name, but propogates errors to the
|
165
|
+
# separate attributes.
|
166
|
+
errors = result.errors.map do |e|
|
167
|
+
case e[:attribute]
|
168
|
+
when 'first_name' then e.merge(:attribute => "full_name")
|
169
|
+
when 'last_name' then nil
|
170
|
+
else e
|
171
|
+
end
|
172
|
+
end.compact
|
173
|
+
|
174
|
+
errors.map do |e|
|
175
|
+
name = CARD_FIELDS[e[:attribute]]
|
176
|
+
error = ERRORS[e[:key]]
|
177
|
+
|
178
|
+
if name and error
|
179
|
+
SubmissionError.new(:credit_card, error, name, e)
|
180
|
+
else
|
181
|
+
SubmissionError.new(:unknown, :unknown, :unknown, e)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Extracts/coerces errors from a Spreedly transaction into
|
187
|
+
# SubmissionError instances.
|
188
|
+
#
|
189
|
+
# @param Spreedly::Transaction result
|
190
|
+
# @return Array<SpookAndPay::SubmissionError>
|
191
|
+
def extract_transaction_errors(result)
|
192
|
+
[]
|
193
|
+
end
|
194
|
+
|
195
|
+
# Takes the response generated by the Spreedly lib and coerces it into a
|
196
|
+
# SpookAndPay::CreditCard
|
197
|
+
#
|
198
|
+
# @param Spreedly::CreditCard
|
199
|
+
# @return SpookAndPay::CreditCard
|
200
|
+
def coerce_credit_card(card)
|
201
|
+
fields = {
|
202
|
+
:card_type => card.card_type,
|
203
|
+
:number => card.number,
|
204
|
+
:name => card.full_name,
|
205
|
+
:expiration_month => card.month,
|
206
|
+
:expiration_year => card.year
|
207
|
+
}
|
208
|
+
|
209
|
+
SpookAndPay::CreditCard.new(self, card.token, fields)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Takes a transaction generated by the Spreedly lib and coerces it into a
|
213
|
+
# SpookAndPay::Transaction
|
214
|
+
#
|
215
|
+
# @param Spreedly::Transaction transaction
|
216
|
+
# @return SpookAndPay::Transaction
|
217
|
+
# @todo extract created_at and status from the transaction.response
|
218
|
+
def coerce_transaction(transaction)
|
219
|
+
fields = {}
|
220
|
+
|
221
|
+
fields[:type], status = case transaction
|
222
|
+
when ::Spreedly::Authorization then [:authorize, :authorized]
|
223
|
+
when ::Spreedly::Purchase then [:purchase, :settled]
|
224
|
+
when ::Spreedly::Capture then [:capture, :settled]
|
225
|
+
when ::Spreedly::Refund then [:credit, :refunded]
|
226
|
+
when ::Spreedly::Void then [:void, :voided]
|
227
|
+
end
|
228
|
+
|
229
|
+
if transaction.respond_to?(:amount)
|
230
|
+
fields[:amount] = transaction.amount
|
231
|
+
end
|
232
|
+
|
233
|
+
SpookAndPay::Transaction.new(self, transaction.token, status, transaction, fields)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
data/lib/spook_and_pay/result.rb
CHANGED
@@ -43,6 +43,7 @@ module SpookAndPay
|
|
43
43
|
#
|
44
44
|
# @param Symbol target
|
45
45
|
# @return Hash
|
46
|
+
# @return Hash<Symbol, Array<SpookAndPay::SubmissionError>>
|
46
47
|
def errors_for(target)
|
47
48
|
errors.select{|e| e.target == target}.reduce({}) do |h, e|
|
48
49
|
h[e.field] ||= []
|
@@ -51,6 +52,15 @@ module SpookAndPay
|
|
51
52
|
end
|
52
53
|
end
|
53
54
|
|
55
|
+
# Returns the errors for a specific target and field.
|
56
|
+
#
|
57
|
+
# @param Symbol target
|
58
|
+
# @param Symbol field
|
59
|
+
# @return Array<SpookAndPay::SubmissionError>
|
60
|
+
def errors_for_field(target, field)
|
61
|
+
errors.select {|e| e.target == target and e.field == field}
|
62
|
+
end
|
63
|
+
|
54
64
|
# A nice alias for checking for success.
|
55
65
|
#
|
56
66
|
# @return [true, false]
|
@@ -14,12 +14,14 @@ module SpookAndPay
|
|
14
14
|
ERROR_MESSAGES = {
|
15
15
|
:credit_card => {
|
16
16
|
:number_required => "number is required",
|
17
|
-
:
|
17
|
+
:number_invalid => "number is invalid",
|
18
|
+
:number_too_short => "number must be between 12 and 19 digits",
|
18
19
|
:type_not_accepted => "card type is not accepted by this merchant",
|
19
|
-
:
|
20
|
-
:
|
21
|
-
:
|
22
|
-
:
|
20
|
+
:expiration_month_invalid => "expiration month is invalid",
|
21
|
+
:expiration_month_expired => "expiration month has expired",
|
22
|
+
:expiration_year_invalid => "expiration year is invalid",
|
23
|
+
:expiration_year_expired => "expiration year has expired",
|
24
|
+
:cvv_invalid => "CVV must be three digits"
|
23
25
|
},
|
24
26
|
:transaction => {
|
25
27
|
:cannot_capture => "must be authorized in order to capture funds",
|
@@ -37,8 +39,8 @@ module SpookAndPay
|
|
37
39
|
# Generates a new error. Based on the target and error type, it can
|
38
40
|
# generate the appropriate error messages or otherwise fall back.
|
39
41
|
#
|
40
|
-
# @param Symbol error_type
|
41
42
|
# @param Symbol target
|
43
|
+
# @param Symbol error_type
|
42
44
|
# @param [Symbol, nil] field
|
43
45
|
# @param Class raw
|
44
46
|
def initialize(target, error_type, field, raw)
|
@@ -60,7 +62,7 @@ module SpookAndPay
|
|
60
62
|
#
|
61
63
|
# @return String
|
62
64
|
def message
|
63
|
-
ERROR_MESSAGES[target][error_type]
|
65
|
+
ERROR_MESSAGES[target][error_type] || ERROR_MESSAGES[target][:"#{field}_#{error_type}"]
|
64
66
|
end
|
65
67
|
end
|
66
68
|
end
|
data/lib/spook_and_pay.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spook_and_pay
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1.alpha
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luke Sutton
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-11-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: braintree
|
@@ -26,47 +26,61 @@ dependencies:
|
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: 2.25.0
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
|
-
name:
|
29
|
+
name: spreedly
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
32
|
- - '='
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: 2.
|
35
|
-
type: :
|
34
|
+
version: 2.0.6
|
35
|
+
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - '='
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: 2.
|
41
|
+
version: 2.0.6
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
|
-
name:
|
43
|
+
name: rack
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
46
|
- - '='
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version:
|
48
|
+
version: 1.5.2
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - '='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 1.5.2
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rspec
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - '='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 2.14.1
|
49
63
|
type: :development
|
50
64
|
prerelease: false
|
51
65
|
version_requirements: !ruby/object:Gem::Requirement
|
52
66
|
requirements:
|
53
67
|
- - '='
|
54
68
|
- !ruby/object:Gem::Version
|
55
|
-
version:
|
69
|
+
version: 2.14.1
|
56
70
|
- !ruby/object:Gem::Dependency
|
57
|
-
name:
|
71
|
+
name: httparty
|
58
72
|
requirement: !ruby/object:Gem::Requirement
|
59
73
|
requirements:
|
60
74
|
- - '='
|
61
75
|
- !ruby/object:Gem::Version
|
62
|
-
version:
|
76
|
+
version: 0.11.0
|
63
77
|
type: :development
|
64
78
|
prerelease: false
|
65
79
|
version_requirements: !ruby/object:Gem::Requirement
|
66
80
|
requirements:
|
67
81
|
- - '='
|
68
82
|
- !ruby/object:Gem::Version
|
69
|
-
version:
|
83
|
+
version: 0.11.0
|
70
84
|
- !ruby/object:Gem::Dependency
|
71
85
|
name: debugger
|
72
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,6 +109,7 @@ files:
|
|
95
109
|
- lib/spook_and_pay/missing_value_error.rb
|
96
110
|
- lib/spook_and_pay/providers/base.rb
|
97
111
|
- lib/spook_and_pay/providers/braintree.rb
|
112
|
+
- lib/spook_and_pay/providers/spreedly.rb
|
98
113
|
- lib/spook_and_pay/providers.rb
|
99
114
|
- lib/spook_and_pay/result.rb
|
100
115
|
- lib/spook_and_pay/submission_error.rb
|