spook_and_pay 0.2.0.alpha → 0.2.1.alpha

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 91f5461e5cc5382b312c2e0d9c7a1ddf576a14e2
4
- data.tar.gz: 78f8ef0364586a4f8d22283b8ccbf79925068d66
3
+ metadata.gz: 34eb9c68d6931c91d5d0be37818b988f83d395e4
4
+ data.tar.gz: a68f338050ffb6aa11c063c2d1b787361b9f6410
5
5
  SHA512:
6
- metadata.gz: 6061677abfdec2995ad03ef6de15d48308f2685f2cd91cbfff58f4a7fc19ef04a2a1adbf626efa3df1af88d951855661af4f49a24d634d21102965376ea479ea
7
- data.tar.gz: 4b34cc7037809402145583ee019751230ebd3b4bbe04903d9794486c92530236a80a9d2e95c2318c44cdbaad565dd4b5b7e7da1ff56633c2ae8cc2e22d961fb9
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(type, redirect_url, amount, opts = {})
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, :invalid_number, :number],
132
- "81725" => [:credit_card, :number_required, :number],
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, :wrong_length, :number],
135
- "81712" => [:credit_card, :invalid_expiration_month, :expiration_month],
136
- "81713" => [:credit_card, :invalid_expiration_year, :expiration_year],
137
- "81707" => [:credit_card, :invalid_cvv, :cvv],
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
@@ -1,2 +1,3 @@
1
1
  require 'spook_and_pay/providers/base'
2
2
  require 'spook_and_pay/providers/braintree'
3
+ require 'spook_and_pay/providers/spreedly'
@@ -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
- :invalid_number => "number is invalid",
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
- :wrong_length => "number must be between 12 and 19 digits",
20
- :invalid_expiration_month => "expiration month is invalid",
21
- :invalid_expiration_year => "expiration year is invalid",
22
- :invalid_cvv => "CVV must be three digits"
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
@@ -8,6 +8,8 @@ require 'openssl'
8
8
  require 'net/http'
9
9
 
10
10
  require 'braintree'
11
+ require 'spreedly'
12
+ require 'rack/utils'
11
13
 
12
14
  require 'spook_and_pay/submission_error'
13
15
  require 'spook_and_pay/missing_value_error'
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.0.alpha
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-08-26 00:00:00.000000000 Z
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: rspec
29
+ name: spreedly
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
32
  - - '='
33
33
  - !ruby/object:Gem::Version
34
- version: 2.14.1
35
- type: :development
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.14.1
41
+ version: 2.0.6
42
42
  - !ruby/object:Gem::Dependency
43
- name: httparty
43
+ name: rack
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - '='
47
47
  - !ruby/object:Gem::Version
48
- version: 0.11.0
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: 0.11.0
69
+ version: 2.14.1
56
70
  - !ruby/object:Gem::Dependency
57
- name: rack
71
+ name: httparty
58
72
  requirement: !ruby/object:Gem::Requirement
59
73
  requirements:
60
74
  - - '='
61
75
  - !ruby/object:Gem::Version
62
- version: 1.5.2
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: 1.5.2
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