active_merchant_square 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6f62b79c7b1d1942c4d3b36f1a8b0ec9a1bdd44f
4
+ data.tar.gz: b68fb0c81d9de4086e26e37703dd91babd361b58
5
+ SHA512:
6
+ metadata.gz: d9115e25a97d496b84fcd9bbd00b15911f8c01c445ec26384fabf1d3350fddef4c4e8c81bfe32ca26f7c454ff6069a1751a850a0c622f3e9915eac46f88ce7b0
7
+ data.tar.gz: 42d3a01093a27786c58a3887cb867f4c970e0c8ccdf4eb274fc078c3a85c77b64964f03bae007ac4c73f565e316fb9c65fb11aed99f15b3606f552ddf33fd7f8
@@ -0,0 +1,50 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ # Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
@@ -0,0 +1,9 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.1
5
+ - 2.2.7
6
+ - 2.3.4
7
+ - 2.4.1
8
+
9
+ before_install: gem install bundler -v 1.14.6
@@ -0,0 +1,15 @@
1
+ Contributing
2
+ ============
3
+
4
+ If you would like to contribute code to this project you can do so through GitHub by
5
+ forking the repository and sending a pull request.
6
+
7
+ When submitting code, please make every effort to follow existing conventions
8
+ and style in order to keep the code as readable as possible. Please also make
9
+ sure your tests passes by running `bundle exec rake test`.
10
+
11
+ Before your code can be accepted into the project you must also sign the
12
+ [Individual Contributor License Agreement (CLA)][1].
13
+
14
+
15
+ [1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in active_merchant_square.gemspec
4
+ gemspec
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2017 Square, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,47 @@
1
+ # Active Merchant Square
2
+
3
+ ## Installation
4
+
5
+ Add this line to your application's Gemfile:
6
+
7
+ ```ruby
8
+ gem 'active_merchant_square'
9
+ ```
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install active_merchant_square
18
+
19
+ ## Usage
20
+
21
+ This simple example demonstrates how a purchase can be made after getting [a card nonce](https://docs.connect.squareup.com/articles/processing-payment-rest#chargingcardnonce)
22
+
23
+ ```ruby
24
+ require 'active_merchant_square'
25
+
26
+ # Get your login and password by going to: https://connect.squareup.com/apps
27
+ credentials = {
28
+ login: 'sandbox-sq0idp-APPLICATION_ID',
29
+ password: 'sandbox-sq0atb-APPLICATION_SECRET',
30
+ # How to get your location ID, see: https://docs.connect.squareup.com/articles/faq-lookup-my-location-id
31
+ location_id: 'LOCATION_ID',
32
+ }
33
+
34
+ amount = 1000 # $10.00
35
+
36
+ gateway = ActiveMerchant::Billing::SquareGateway.new(credentials)
37
+ response = gateway.purchase(amount, card_nonce)
38
+
39
+ if response.success?
40
+ puts "Successfully charged $#{sprintf("%.2f", amount / 100)}"
41
+ else
42
+ raise StandardError, response.message
43
+ end
44
+
45
+ ```
46
+
47
+ For more in-depth documentation and tutorials, see [Square Documentation site](https://docs.connect.squareup.com/)
@@ -0,0 +1,33 @@
1
+ require "bundler/gem_tasks"
2
+ $:.unshift File.expand_path('../lib', __FILE__)
3
+
4
+ begin
5
+ require 'bundler'
6
+ Bundler.setup
7
+ rescue LoadError => e
8
+ puts "Error loading bundler (#{e.message}): \"gem install bundler\" for bundler support."
9
+ require 'rubygems'
10
+ end
11
+
12
+ require 'rake'
13
+ require 'rake/testtask'
14
+
15
+ desc "Run the unit test suite"
16
+ task :default => 'test:units'
17
+ task :test => 'test:units'
18
+
19
+ namespace :test do
20
+ Rake::TestTask.new(:units) do |t|
21
+ t.pattern = 'test/unit/**/*_test.rb'
22
+ t.ruby_opts << '-rubygems -w'
23
+ t.libs << 'test'
24
+ t.verbose = true
25
+ end
26
+
27
+ Rake::TestTask.new(:remote) do |t|
28
+ t.pattern = 'test/remote/**/*_test.rb'
29
+ t.ruby_opts << '-rubygems -w'
30
+ t.libs << 'test'
31
+ t.verbose = true
32
+ end
33
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'active_merchant_square/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "active_merchant_square"
8
+ spec.version = ActiveMerchantSquare::VERSION
9
+ spec.authors = ["Francisco Rojas"]
10
+ spec.email = ["frojas@squareup.com"]
11
+
12
+ spec.summary = %q{Active Merchant Square is a Square enhancement of the payment abstraction library active_merchant to support for Square's e-commerce API.}
13
+ spec.description = %q{Use this gem instead of ActiveMerchant gem if you want to use Square's e-commerce APIs (https://squareup.com/developers) with ActiveMerchant. The official ActiveMerchant gem does not support Square because Square shields raw credit card numbers from developers to make PCI compliance easier. This has all ActiveMerchant functionality, plus support of Square's card nonces in the iframe API.}
14
+ spec.homepage = "https://github.com/square/active_merchant_square"
15
+
16
+ spec.license = "MIT"
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.bindir = "exe"
22
+
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.14"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "test-unit", "~> 3"
28
+ spec.add_development_dependency 'mocha', '~> 1'
29
+
30
+ spec.add_dependency 'activemerchant', "~> 1.66"
31
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "active_merchant_square"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,396 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class SquareGateway < Gateway
4
+
5
+
6
+ # test uses a 'sandbox' access token, same URL
7
+ self.live_url = 'https://connect.squareup.com/v2/'
8
+
9
+ self.money_format = :cents
10
+ self.supported_countries = ['US', 'CA', 'JP', 'GB', 'AU']
11
+ self.default_currency = 'USD'
12
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro]
13
+
14
+ self.homepage_url = 'https://squareup.com/developers'
15
+ self.display_name = 'Square'
16
+
17
+ # Map to Square's error codes:
18
+ # https://docs.connect.squareup.com/api/connect/v2/#handlingerrors
19
+ STANDARD_ERROR_CODE_MAPPING = {
20
+ 'INVALID_CARD' => STANDARD_ERROR_CODE[:invalid_number],
21
+ 'INVALID_EXPIRATION' => STANDARD_ERROR_CODE[:invalid_expiry_date],
22
+ 'INVALID_EXPIRATION_DATE' => STANDARD_ERROR_CODE[:invalid_expiry_date],
23
+ 'INVALID_EXPIRATION_YEAR' => STANDARD_ERROR_CODE[:invalid_expiry_date],
24
+
25
+ # Something invalid in the card, e.g. verify declined when linking card to customer.
26
+ 'INVALID_CARD_DATA' => STANDARD_ERROR_CODE[:processing_error],
27
+ 'CARD_EXPIRED' => STANDARD_ERROR_CODE[:expired_card],
28
+ 'VERIFY_CVV_FAILURE' => STANDARD_ERROR_CODE[:incorrect_cvc],
29
+ 'VERIFY_AVS_FAILURE' => STANDARD_ERROR_CODE[:incorrect_zip],
30
+ 'CARD_DECLINED' => STANDARD_ERROR_CODE[:card_declined],
31
+ 'UNAUTHORIZED' => STANDARD_ERROR_CODE[:config_error]
32
+ }
33
+
34
+ # The `login` key is the client_id (also known as application id)
35
+ # in the dev portal. Get it after you create a new app:
36
+ # https://connect.squareup.com/apps/
37
+ # The `password` is the access token (personal or OAuth)
38
+ # The `location_id` must be fetched initially
39
+ # https://docs.connect.squareup.com/articles/processing-payment-rest/
40
+ # The `test` indicates if these credentials are for sandbox or
41
+ # production (money moving) access
42
+ def initialize(options={})
43
+ requires!(options, :login, :password, :location_id)
44
+ @client_id = options[:login].strip
45
+ @bearer_token = options[:password].strip
46
+ @location_id = options[:location_id].strip
47
+
48
+ super
49
+ end
50
+
51
+ # To create a charge on a card using a card nonce:
52
+ # purchase(money, card_nonce, { ...create transaction options... })
53
+ #
54
+ # To create a customer and save a card (via card_nonce) to the customer:
55
+ # purchase(money, card_nonce, {customer: {...params hash same as in store() method...}, ...})
56
+ # Note for US and CA, you must have {customer: {billing_address: {zip: 12345}}} which passes AVS to store a card.
57
+ # Note this always creates a new customer, so it may make a duplicate
58
+ # customer if this card was associated to another customer previously.
59
+ #
60
+ # To use a customer's card on file:
61
+ # purchase(money, nil, {customer: {id: 'customer-id', card_id: 'card-id'}})
62
+ # Note this does not update any fields on the customer.
63
+ #
64
+ # To use a customer, and link a new card to the customer:
65
+ # purchase(money, card_nonce, {customer: {id: 'customer-id', billing_address: {zip: 12345}})
66
+ # Note the zip is required to store the new nonce, and it must pass AVS.
67
+ # Note this does not update any other fields on the customer.
68
+ #
69
+ # As this may make multiple requests, it returns a MultiResponse.
70
+ def purchase(money, card_nonce, options={})
71
+ raise ArgumentError('money required') if money.nil?
72
+ if card_nonce.nil?
73
+ requires!(options, :customer)
74
+ requires!(options[:customer], :card_id, :id)
75
+ end
76
+ if card_nonce && options[:customer] && options[:customer][:card_id]
77
+ raise ArgumentError('Cannot call with both card_nonce and' +
78
+ ' options[:customer][:card_id], choose one.')
79
+ end
80
+
81
+ post = options.slice(:buyer_email_address, :delay_capture, :note,
82
+ :reference_id)
83
+ add_idempotency_key(post, options)
84
+ add_amount(post, money, options)
85
+ add_address(post, options)
86
+ post[:reference_id] = options[:order_id] if options[:order_id]
87
+ post[:note] = options[:description] if options[:description]
88
+
89
+ MultiResponse.run do |r|
90
+ if options[:customer] && card_nonce
91
+ # Since customer was passed in, create customer (if needed) and
92
+ # store card (always in here).
93
+ options[:customer][:customer_id] = options[:customer][:id] if options[:customer][:id] # To make store() happy.
94
+ r.process { store(card_nonce, options[:customer]) }
95
+
96
+ # If we just created a customer.
97
+ if options[:customer][:id].nil?
98
+ options[:customer][:id] =
99
+ r.responses.first.params['customer']['id']
100
+ end
101
+
102
+ # We always stored a card, so grab it.
103
+ options[:customer][:card_id] =
104
+ r.responses.last.params['card']['id']
105
+
106
+ # Empty the card_nonce, since we now have the card on file.
107
+ card_nonce = nil
108
+
109
+ # Invariant: we have a customer and a linked card, and our options
110
+ # hash is correct.
111
+ end
112
+
113
+ add_payment(post, card_nonce, options)
114
+ r.process { commit(:post, "locations/#{@location_id}/transactions", post) }
115
+ end
116
+ end
117
+
118
+ # Authorize for Square uses the Charge with delay_capture = true option.
119
+ # https://docs.connect.squareup.com/api/connect/v2/#endpoint-charge
120
+ # Same as with `purchase`, pass nil for `card_nonce` if using a customer's
121
+ # stored card on file.
122
+ #
123
+ # See purchase for more details for calling this.
124
+ def authorize(money, card_nonce, options={})
125
+ options[:delay_capture] = true
126
+ purchase(money, card_nonce, options)
127
+ end
128
+
129
+ # Capture is only used if you did an Authorize, (creating a delayed capture).
130
+ # https://docs.connect.squareup.com/api/connect/v2/#endpoint-capturetransaction
131
+ # Both `money` and `options` are unused. Only a full capture is supported.
132
+ def capture(ignored_money, txn_id, ignored_options={})
133
+ raise ArgumentError('txn_id required') if txn_id.nil?
134
+ commit(:post, "locations/#{CGI.escape(@location_id)}/transactions/#{CGI.escape(txn_id)}/capture")
135
+ end
136
+
137
+ # Refund refunds a previously Charged transaction.
138
+ # https://docs.connect.squareup.com/api/connect/v2/#endpoint-createrefund
139
+ # Options require: `tender_id`, and permit `idempotency_key`, `reason`.
140
+ def refund(money, txn_id, options={})
141
+ raise ArgumentError('txn_id required') if txn_id.nil?
142
+ raise ArgumentError('money required') if money.nil?
143
+ requires!(options, :tender_id)
144
+
145
+ post = options.slice(:tender_id, :reason)
146
+ add_idempotency_key(post, options)
147
+ add_amount(post, money, options)
148
+ commit(:post, "locations/#{CGI.escape(@location_id)}/transactions/#{CGI.escape(txn_id)}/refund", post)
149
+ end
150
+
151
+ # Void cancels a delayed capture (not-yet-captured) transaction.
152
+ # https://docs.connect.squareup.com/api/connect/v2/#endpoint-voidtransaction
153
+ def void(txn_id, options={})
154
+ raise ArgumentError('txn_id required') if txn_id.nil?
155
+ commit(:post, "locations/#{CGI.escape(@location_id)}/transactions/#{CGI.escape(txn_id)}/void")
156
+ end
157
+
158
+ # Do an Authorize (Charge with delayed capture) and then Void.
159
+ # Storing a card with a customer will do a verify, however a direct
160
+ # verification only endpoint is not exposed today (Oct '16).
161
+ def verify(card_nonce, options={})
162
+ raise ArgumentError('card_nonce required') if card_nonce.nil?
163
+ MultiResponse.run(:use_first_response) do |r|
164
+ r.process { authorize(100, card_nonce, options) }
165
+ r.process(:ignore_result) { void(r.authorization, options) }
166
+ end
167
+ end
168
+
169
+ # Required in options hash one of:
170
+ # a) :customer_id from the Square CreateCustomer endpoint of customer to link to.
171
+ # Required in the US and CA: options[:billing_address][:zip] (AVS must pass to link)
172
+ # https://docs.connect.squareup.com/api/connect/v2/#endpoint-createcustomercard
173
+ # b) :email, :family_name, :given_name, :company_name, :phone_number to create a new customer.
174
+ #
175
+ # Optional: :cardholder_name, :address (to store on customer)
176
+ # Return values (e.g. the card id) are available on the response.params['card']['id']
177
+ def store(card_nonce, options = {})
178
+ raise ArgumentError('card_nonce required') if card_nonce.nil?
179
+ raise ArgumentError.new('card_nonce nil but is a required field.') if card_nonce.nil?
180
+
181
+ MultiResponse.run do |r|
182
+ if !(options[:customer_id])
183
+ r.process { create_customer(options) }
184
+ options[:customer_id] = r.responses.last.params['customer']['id']
185
+ end
186
+ post = options.slice(:cardholder_name, :billing_address)
187
+ if post[:billing_address].present?
188
+ post[:billing_address][:postal_code] = post[:billing_address].delete(:zip)
189
+ end
190
+ post[:card_nonce] = card_nonce
191
+ r.process { commit(:post, "customers/#{CGI.escape(options[:customer_id])}/cards", post) }
192
+ end
193
+ end
194
+
195
+ def update(customer_id, card_id, options = {})
196
+ raise Exception.new('Square API does not currently support updating' +
197
+ ' a given card_id, instead create a new one and delete the old one.')
198
+ end
199
+
200
+ # https://docs.connect.squareup.com/api/connect/v2/#endpoint-updatecustomer
201
+ def update_customer(customer_id, options = {})
202
+ raise ArgumentError.new('customer_id nil but is a required field.') if customer_id.nil?
203
+ options[:email_address] = options[:email] if options[:email]
204
+ options[:note] = options[:description] if options[:description]
205
+ commit(:put, "customers/#{CGI.escape(customer_id)}", options)
206
+ end
207
+
208
+ # https://docs.connect.squareup.com/api/connect/v2/#endpoint-deletecustomercard
209
+ # Required options[:customer][:id] and 'card_id' params.
210
+ def unstore(card_id, options = {}, deprecated_options = {})
211
+ raise ArgumentError.new('card_id nil but is a required field.') if card_id.nil?
212
+ requires!(options, :customer)
213
+ requires!(options[:customer], :id)
214
+ commit(:delete, "customers/#{CGI.escape(options[:customer][:id])}/cards/#{CGI.escape(card_id)}", nil)
215
+ end
216
+
217
+ # See also store().
218
+ # Options hash takes the keys as defined here:
219
+ # https://docs.connect.squareup.com/api/connect/v2/#endpoint-createcustomer
220
+ def create_customer(options)
221
+ required_one_of = [:email, :email_address, :family_name, :given_name,
222
+ :company_name, :phone_number]
223
+ if required_one_of.none?{|k| options.key?(k)}
224
+ raise ArgumentError.new("one of these options keys required:" +
225
+ " #{required_one_of} but none included.")
226
+ end
227
+
228
+ MultiResponse.run do |r|
229
+ post = options.slice(*required_one_of - [:email] +
230
+ [:phone_number, :reference_id, :note, :nickname])
231
+ post[:email_address] = options[:email] if options[:email]
232
+ post[:note] = options[:description] if options[:description]
233
+ add_address(post, options, :address)
234
+ r.process{ commit(:post, 'customers', post) }
235
+ end
236
+ end
237
+
238
+ # Scrubbing removes the access token from the header and the card_nonce.
239
+ # Square does not let the merchant ever see PCI data. All payment card
240
+ # data is directly handled on Square's servers via iframes as described
241
+ # here: https://docs.connect.squareup.com/articles/adding-payment-form/
242
+ def supports_scrubbing?
243
+ true
244
+ end
245
+
246
+ def scrub(transcript)
247
+ transcript.
248
+ gsub(%r((Authorization: Bearer )[^\r\n]+), '\1[FILTERED]').
249
+ # Extra [\\]* for test. We do an extra escape in the regex of [\\]*
250
+ # b/c the remote_square_test.rb seems to double escape the
251
+ # backslashes before the quote. This ensures tests pass.
252
+ gsub(%r((\"card_nonce[\\]*\":[\\]*")[^"]+), '\1[FILTERED]')
253
+ end
254
+
255
+ private
256
+
257
+ def add_address(post, options, non_billing_addr_key = :shipping_address)
258
+ if address = options[:billing_address] || options[:address]
259
+ add_address_for(post, address, :billing_address)
260
+ end
261
+ non_billing_addr_key = non_billing_addr_key.to_sym
262
+ if address = options[non_billing_addr_key] || options[:address]
263
+ add_address_for(post, address, non_billing_addr_key)
264
+ end
265
+ end
266
+
267
+ def add_address_for(post, address, addr_key)
268
+ addr_key = addr_key.to_sym
269
+ post[addr_key] ||= {} # Or-Equals in case they passed in using Square's key format
270
+ post[addr_key][:address_line_1] = address[:address1] if address[:address1]
271
+ post[addr_key][:address_line_2] = address[:address2] if address[:address2]
272
+ post[addr_key][:address_line_3] = address[:address3] if address[:address3]
273
+
274
+ post[addr_key][:locality] = address[:city] if address[:city]
275
+ post[addr_key][:sublocality] = address[:sublocality] if address[:sublocality]
276
+ post[addr_key][:sublocality_2] = address[:sublocality_2] if address[:sublocality_2]
277
+ post[addr_key][:sublocality_3] = address[:sublocality_3] if address[:sublocality_3]
278
+
279
+ post[addr_key][:administrative_district_level_1] = address[:state] if address[:state]
280
+ post[addr_key][:administrative_district_level_2] = address[:administrative_district_level_2] if address[:administrative_district_level_2] # In the US, this is the county.
281
+ post[addr_key][:administrative_district_level_3] = address[:administrative_district_level_3] if address[:administrative_district_level_3] # Used in JP not the US
282
+ post[addr_key][:postal_code] = address[:zip] if address[:zip]
283
+ post[addr_key][:country] = address[:country] if address[:country]
284
+ end
285
+
286
+ def add_amount(post, money, options)
287
+ post[:amount_money] = {}
288
+ post[:amount_money][:amount] = Integer(amount(money))
289
+ post[:amount_money][:currency] =
290
+ (options[:currency] || currency(money))
291
+ end
292
+
293
+ def add_payment(post, card_nonce, options)
294
+ if card_nonce.nil?
295
+ # use card on file
296
+ requires!(options, :customer)
297
+ requires!(options[:customer], :id, :card_id)
298
+ post[:customer_id] = options[:customer][:id]
299
+ post[:customer_card_id] = options[:customer][:card_id]
300
+ else
301
+ # use nonce
302
+ post[:card_nonce] = card_nonce
303
+ end
304
+ end
305
+
306
+ def commit(method, endpoint, parameters=nil)
307
+ response = api_request(method, endpoint, parameters)
308
+ success = !response.key?("errors")
309
+ Response.new(success,
310
+ message_from(success, response),
311
+ response,
312
+ authorization: authorization_from(response),
313
+ # Neither avs nor cvv match are not exposed in the api.
314
+ avs_result: nil,
315
+ cvv_result: nil,
316
+ test: test?,
317
+ error_code: success ? nil : error_code_from(response)
318
+ )
319
+ end
320
+
321
+ def headers
322
+ {
323
+ 'Authorization' => "Bearer " + @bearer_token,
324
+ 'User-Agent' =>
325
+ "Square/v2 ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
326
+ 'X-Square-Client-User-Agent' => user_agent,
327
+ 'Accept' => 'application/json',
328
+ 'Content-Type' => 'application/json',
329
+ # Uncomment below to generate request/response json for unit tests.
330
+ # 'Accept-Encoding' => ''
331
+ }
332
+ end
333
+
334
+ def add_idempotency_key(post, options)
335
+ post[:idempotency_key] =
336
+ (options[:idempotency_key] || generate_unique_id).to_s
337
+ end
338
+
339
+ def message_from(success, response)
340
+ # e.g. {"errors":[{"category":"INVALID_REQUEST_ERROR","code":"VALUE_TOO_LOW","detail":"`amount_money.amount` must be greater than 100.","field":"amount_money.amount"}]}
341
+ success ? "Success" : response['errors'].first['detail']
342
+ end
343
+
344
+ def authorization_from(response)
345
+ if response['transaction']
346
+ # This will return the transaction level identifier, of which there
347
+ # is >= 1 nested tender id which you may need to look up depending
348
+ # on your use case (e.g. refunding). That is available in the
349
+ # response.transaction.tenders array.
350
+ return response['transaction']['id']
351
+ end
352
+ end
353
+
354
+ def api_request(method, endpoint, parameters)
355
+ json_payload = JSON.generate(parameters) if parameters
356
+ begin
357
+ raw_response = ssl_request(
358
+ method, self.live_url + endpoint, json_payload, headers)
359
+ response = JSON.parse(raw_response)
360
+ rescue ResponseError => e
361
+ raw_response = e.response.body
362
+ response = response_error(raw_response)
363
+ rescue JSON::ParserError
364
+ response = json_error(raw_response)
365
+ end
366
+ response
367
+ end
368
+
369
+ def response_error(raw_response)
370
+ JSON.parse(raw_response)
371
+ rescue JSON::ParserError
372
+ json_error(raw_response)
373
+ end
374
+
375
+ def json_error(raw_response)
376
+ msg = 'Invalid non-parsable json data response from the Square API.' +
377
+ ' Please contact' +
378
+ ' squareup.com/help/us/en/contact?prefill=developer_api' +
379
+ ' if you continue to receive this message.' +
380
+ " (The raw API response returned was #{raw_response.inspect})"
381
+ {
382
+ "errors" => [{
383
+ "category" => "API_ERROR",
384
+ "detail" => msg
385
+ }]
386
+ }
387
+ end
388
+
389
+ def error_code_from(response)
390
+ code = response['errors'].first['code']
391
+ error_code = STANDARD_ERROR_CODE_MAPPING[code]
392
+ error_code
393
+ end
394
+ end
395
+ end
396
+ end
@@ -0,0 +1,7 @@
1
+ require "active_merchant_square/version"
2
+ require "active_merchant"
3
+ require "active_merchant/billing/gateways/square"
4
+ require "json"
5
+
6
+ module ActiveMerchantSquare
7
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveMerchantSquare
2
+ VERSION = "1.0.0"
3
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_merchant_square
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Francisco Rojas
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-05-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: test-unit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: mocha
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activemerchant
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.66'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.66'
83
+ description: Use this gem instead of ActiveMerchant gem if you want to use Square's
84
+ e-commerce APIs (https://squareup.com/developers) with ActiveMerchant. The official
85
+ ActiveMerchant gem does not support Square because Square shields raw credit card
86
+ numbers from developers to make PCI compliance easier. This has all ActiveMerchant
87
+ functionality, plus support of Square's card nonces in the iframe API.
88
+ email:
89
+ - frojas@squareup.com
90
+ executables: []
91
+ extensions: []
92
+ extra_rdoc_files: []
93
+ files:
94
+ - ".gitignore"
95
+ - ".travis.yml"
96
+ - CONTRIBUTING.txt
97
+ - Gemfile
98
+ - LICENSE.txt
99
+ - README.md
100
+ - Rakefile
101
+ - active_merchant_square.gemspec
102
+ - bin/console
103
+ - bin/setup
104
+ - lib/active_merchant/billing/gateways/square.rb
105
+ - lib/active_merchant_square.rb
106
+ - lib/active_merchant_square/version.rb
107
+ homepage: https://github.com/square/active_merchant_square
108
+ licenses:
109
+ - MIT
110
+ metadata: {}
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 2.6.11
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: Active Merchant Square is a Square enhancement of the payment abstraction
131
+ library active_merchant to support for Square's e-commerce API.
132
+ test_files: []