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 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