arrow_payments 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +25 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +3 -0
  5. data/README.md +272 -0
  6. data/Rakefile +10 -0
  7. data/arrow_payments.gemspec +25 -0
  8. data/lib/arrow_payments.rb +25 -0
  9. data/lib/arrow_payments/address.rb +11 -0
  10. data/lib/arrow_payments/client.rb +57 -0
  11. data/lib/arrow_payments/client/customers.rb +45 -0
  12. data/lib/arrow_payments/client/payment_methods.rb +90 -0
  13. data/lib/arrow_payments/client/transactions.rb +64 -0
  14. data/lib/arrow_payments/configuration.rb +7 -0
  15. data/lib/arrow_payments/connection.rb +78 -0
  16. data/lib/arrow_payments/customer.rb +38 -0
  17. data/lib/arrow_payments/entity.rb +39 -0
  18. data/lib/arrow_payments/errors.rb +13 -0
  19. data/lib/arrow_payments/line_item.rb +10 -0
  20. data/lib/arrow_payments/payment_method.rb +19 -0
  21. data/lib/arrow_payments/recurring_billing.rb +30 -0
  22. data/lib/arrow_payments/transaction.rb +74 -0
  23. data/lib/arrow_payments/version.rb +3 -0
  24. data/spec/arrow_payments_spec.rb +40 -0
  25. data/spec/client_spec.rb +42 -0
  26. data/spec/configuration_spec.rb +24 -0
  27. data/spec/customer_spec.rb +29 -0
  28. data/spec/customers_spec.rb +160 -0
  29. data/spec/entity_spec.rb +55 -0
  30. data/spec/fixtures/complete_payment_method.json +37 -0
  31. data/spec/fixtures/customer.json +25 -0
  32. data/spec/fixtures/customers.json +133 -0
  33. data/spec/fixtures/headers/payment_method_complete.yml +9 -0
  34. data/spec/fixtures/headers/payment_method_start.yml +9 -0
  35. data/spec/fixtures/line_item.json +8 -0
  36. data/spec/fixtures/start_payment_method.json +6 -0
  37. data/spec/fixtures/transaction.json +29 -0
  38. data/spec/fixtures/transaction_capture.json +5 -0
  39. data/spec/fixtures/transactions.json +68 -0
  40. data/spec/line_item_spec.rb +17 -0
  41. data/spec/payment_method_spec.rb +16 -0
  42. data/spec/payment_methods_spec.rb +181 -0
  43. data/spec/recurring_billing_spec.rb +28 -0
  44. data/spec/spec_helper.rb +19 -0
  45. data/spec/transactions_spec.rb +101 -0
  46. metadata +225 -0
@@ -0,0 +1,45 @@
1
+ module ArrowPayments
2
+ module Customers
3
+ # Get all existing customers
4
+ # @return [Array<Customer>]
5
+ def customers
6
+ get('/customers').map { |c| Customer.new(c) }
7
+ end
8
+
9
+ # Get an existing customer
10
+ # @param [Integer] customer ID
11
+ # @return [Customer]
12
+ def customer(id)
13
+ Customer.new(get("/customer/#{id}"))
14
+ rescue NotFound
15
+ nil
16
+ end
17
+
18
+ # Create a new customer
19
+ # @param [Hash] customer attributes
20
+ # @return [Customer]
21
+ def create_customer(options={})
22
+ customer = options.kind_of?(Hash) ? Customer.new(options) : options
23
+ Customer.new(post("/customer/add", customer.to_source_hash))
24
+ end
25
+
26
+ # Update an existing customer attributes
27
+ # @param [Customer] customer instance
28
+ # @return [Boolean] update result
29
+ def update_customer(customer)
30
+ params = customer.to_source_hash
31
+ params['CustomerID'] = customer.id
32
+
33
+ resp = post('/customer/update', params)
34
+ resp['Success'] == true
35
+ end
36
+
37
+ # Delete an existing customer
38
+ # @param [Integer] customer ID
39
+ # @return [Boolean]
40
+ def delete_customer(id)
41
+ resp = post('/customer/delete', 'CustomerID' => id)
42
+ resp['Success'] == true
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,90 @@
1
+ module ArrowPayments
2
+ module PaymentMethods
3
+ # Get a single payment method
4
+ # @param [Integer] customer ID
5
+ # @param [Integer] payment method ID
6
+ # @return [PaymentMethod] payment method instance
7
+ def payment_method(customer_id, id)
8
+ customer(customer_id).payment_methods.select { |cc| cc.id == id }.first
9
+ end
10
+
11
+ # Start a new payment method
12
+ # @param [Integer] customer ID
13
+ # @param [Address] billing address instance
14
+ # @param [String] return url
15
+ # @return [String] payment method form url
16
+ def start_payment_method(customer_id, billing_address, return_url=nil)
17
+ if billing_address.kind_of?(Hash)
18
+ billing_address = ArrowPayments::Address.new(billing_address)
19
+ end
20
+
21
+ params = {
22
+ 'CustomerId' => customer_id,
23
+ 'BillingAddress' => billing_address.to_source_hash
24
+ }
25
+
26
+ # If return url is blank means that its not browser-less payment method
27
+ # creation. Reponse should include token ID for the Step 3.
28
+ if return_url
29
+ params['ReturnUrl'] = return_url
30
+ end
31
+
32
+ post("/paymentmethod/start", params)['FormPostUrl']
33
+ end
34
+
35
+ # Setup a new payment method
36
+ # @param [String] payment method form url
37
+ # @param [PaymentMethod] payment method instance or hash
38
+ # @return [String] confirmation token
39
+ def setup_payment_method(form_url, payment_method)
40
+ cc = payment_method
41
+
42
+ if payment_method.kind_of?(Hash)
43
+ cc = ArrowPayments::PaymentMethod.new(payment_method)
44
+ end
45
+
46
+ resp = post_to_url(form_url, payment_method_form(cc))
47
+ resp.headers['location'].scan(/token-id=(.*)/).flatten.first
48
+ end
49
+
50
+ # Complete payment method creation
51
+ # @param [String] token ID
52
+ # @return [PaymentMethod]
53
+ def complete_payment_method(token_id)
54
+ resp = post('/paymentmethod/complete', 'TokenID' => token_id)
55
+ ArrowPayments::PaymentMethod.new(resp)
56
+ end
57
+
58
+ # Create a new payment method. This is a wrapper on top of 3 step process
59
+ # @param [Integer] customer ID
60
+ # @param [Address] credit card address
61
+ # @param [PaymentMethod] credit card
62
+ # @return [PaymentMethod]
63
+ def create_payment_method(customer_id, address, card)
64
+ url = start_payment_method(customer_id, address)
65
+ token = setup_payment_method(url, card)
66
+
67
+ complete_payment_method(token)
68
+ end
69
+
70
+ # Delete an existing payment method
71
+ # @param [Integer] payment method ID
72
+ # @return [Boolean]
73
+ def delete_payment_method(id)
74
+ resp = post('/paymentmethod/delete', 'PaymentMethodId' => id)
75
+ resp['Success'] == true
76
+ end
77
+
78
+ private
79
+
80
+ def payment_method_form(cc)
81
+ {
82
+ 'billing-cc-number' => cc.number,
83
+ 'billing-cc-exp' => [cc.expiration_month, cc.expiration_year].join,
84
+ 'billing-cvv' => cc.security_code,
85
+ 'billing-first-name' => cc.first_name,
86
+ 'billing-last-name' => cc.last_name
87
+ }
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,64 @@
1
+ module ArrowPayments
2
+ module Transactions
3
+ # Get transaction details
4
+ # @param [String] transaction ID
5
+ # @return [Transaction] transactions instance
6
+ def transaction(id)
7
+ Transaction.new(get("/transaction/#{id}"))
8
+ rescue NotFound
9
+ nil
10
+ end
11
+
12
+ # Get customer transactions by status
13
+ # @param [String] customer ID
14
+ # @param [String] transaction status
15
+ # @return [Array<Transaction>]
16
+ def transactions(customer_id, status='NotSettled')
17
+ unless Transaction::STATUSES.include?(status)
18
+ raise ArgumentError, "Invalid status: #{status}"
19
+ end
20
+
21
+ resp = get("/customer/#{customer_id}/Transactions/#{status}")
22
+ resp['Transactions'].map { |t| Transaction.new(t) }
23
+ end
24
+
25
+ # Create a new transaction
26
+ # @return [Transaction]
27
+ def create_transaction(transaction)
28
+ if transaction.kind_of?(Hash)
29
+ transaction = ArrowPayments::Transaction.new(transaction)
30
+ end
31
+
32
+ # Set default transaction attributes
33
+ transaction.transaction_source = 'API'
34
+
35
+ params = transaction.to_source_hash
36
+ params['Amount'] = params['TotalAmount']
37
+
38
+ resp = post('/transaction/add', params)
39
+
40
+ if resp['Success'] == true
41
+ ArrowPayments::Transaction.new(resp)
42
+ else
43
+ raise ArrowPayments::Error, resp['Message']
44
+ end
45
+ end
46
+
47
+ # Capture an unsettled transaction
48
+ # @param [String] transaction ID
49
+ # @param [Integer] amount, less than or equal to original amount
50
+ # @return [Boolean]
51
+ def capture_transaction(id, amount)
52
+ resp = post('/transaction/capture', 'TransactionId' => id, 'Amount' => amount)
53
+ resp['Success'] == true
54
+ end
55
+
56
+ # Void a previously submitted transaction that have not yet settled
57
+ # @param [String] transaction ID
58
+ # @return [Boolean]
59
+ def void_transaction(id)
60
+ resp = post('/transaction/void', 'TransactionId' => id)
61
+ resp['Success'] == true
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,7 @@
1
+ module ArrowPayments
2
+ class Configuration
3
+ class << self
4
+ attr_accessor :api_key, :mode, :merchant_id
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,78 @@
1
+ require 'faraday'
2
+ require 'json'
3
+
4
+ module ArrowPayments
5
+ module Connection
6
+ API_PRODUCTION = 'https://gateway.arrowpayments.com'
7
+ API_SANDBOX = 'http://demo.arrowpayments.com'
8
+
9
+ def get(path, params={}, raw=false)
10
+ request(:get, path, params, raw)
11
+ end
12
+
13
+ def post(path, params={}, raw=false)
14
+ request(:post, path, params, raw)
15
+ end
16
+
17
+ def post_to_url(url, params)
18
+ Faraday.post(url, params)
19
+ end
20
+
21
+ protected
22
+
23
+ def request(method, path, params={}, raw=false)
24
+ if method == :post
25
+ path = "/api#{path}"
26
+
27
+ params['ApiKey'] = api_key
28
+ params['MID'] = merchant_id
29
+ else
30
+ path = "/api/#{api_key}#{path}"
31
+ end
32
+
33
+ headers = {
34
+ 'Accept' => 'application/json',
35
+ 'Content-Type' => 'application/json'
36
+ }
37
+
38
+ api_url = production? ? API_PRODUCTION : API_SANDBOX
39
+
40
+ response = connection(api_url).send(method, path, params) do |request|
41
+ request.headers = headers
42
+
43
+ case method
44
+ when :get, :delete
45
+ request.url(path, params)
46
+ when :post, :put
47
+ request.path = path
48
+ request.body = params.to_json
49
+ end
50
+ end
51
+
52
+ unless response.success?
53
+ handle_failed_response(response)
54
+ end
55
+
56
+ raw ? response : JSON.parse(response.body)
57
+ end
58
+
59
+ def connection(url)
60
+ connection = Faraday.new(url) do |c|
61
+ c.use(Faraday::Request::UrlEncoded)
62
+ c.use(Faraday::Response::Logger) if debug?
63
+ c.adapter(Faraday.default_adapter)
64
+ end
65
+ end
66
+
67
+ def handle_failed_response(response)
68
+ case response.status
69
+ when 400
70
+ raise ArrowPayments::BadRequest, response.headers['error']
71
+ when 404
72
+ raise ArrowPayments::NotFound, response.headers['error']
73
+ when 500
74
+ raise ArrowPayments::Error, response.headers['error']
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,38 @@
1
+ module ArrowPayments
2
+ class Customer < Entity
3
+ property :id, :from => 'ID'
4
+ property :name, :from => 'Name'
5
+ property :code, :from => 'Code'
6
+ property :contact, :from => 'PrimaryContact'
7
+ property :phone, :from => 'PrimaryContactPhone'
8
+ property :email, :from => 'PrimaryContactEmailAddress'
9
+ property :recurring_billings, :from => 'RecurrentBilling'
10
+ property :payment_methods, :from => 'PaymentMethods'
11
+
12
+ def PaymentMethods=(data)
13
+ if data.kind_of?(Array)
14
+ self.payment_methods = data.map { |d| PaymentMethod.new(d) }
15
+ else
16
+ self.payment_methods = []
17
+ end
18
+ end
19
+
20
+ def PaymentMethods
21
+ (payment_methods || []).map(&:to_source_hash)
22
+ end
23
+
24
+ def RecurrentBillings=(data)
25
+ if data.kind_of?(Array)
26
+ self.recurring_billings = data.map { |d| RecurringBilling.new(d) }
27
+ else
28
+ self.recurring_billings = []
29
+ end
30
+ end
31
+
32
+ def to_source_hash(options={})
33
+ hash = super(options)
34
+ hash.merge!('PaymentMethods' => (payment_methods || []).map(&:to_source_hash))
35
+ hash
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,39 @@
1
+ require 'hashie'
2
+
3
+ module ArrowPayments
4
+ class Entity < Hashie::Trash
5
+ include Hashie::Extensions::Coercion
6
+
7
+ class << self
8
+ attr_reader :properties_map
9
+ end
10
+
11
+ instance_variable_set('@properties_map', {})
12
+
13
+ def to_source_hash(options={})
14
+ hash = {}
15
+
16
+ self.class.properties_map.each_pair do |k, v|
17
+ val = send(k)
18
+ hash[v] = val unless val.nil?
19
+ end
20
+
21
+ hash
22
+ end
23
+
24
+ def self.property(property_name, options = {})
25
+ super(property_name, options)
26
+
27
+ if options[:from]
28
+ @properties_map ||= {}
29
+ @properties_map[property_name.to_sym] = options[:from]
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def property_exists?(property)
36
+ self.class.property?(property.to_sym)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,13 @@
1
+ module ArrowPayments
2
+ # Generic API error
3
+ class Error < StandardError ; end
4
+
5
+ # Error when API entity is not found
6
+ class NotFound < Error ; end
7
+
8
+ # Error when API endpoint or method is not implemented
9
+ class NotImplemented < Error ; end
10
+
11
+ # Bad request error, does not contain a message
12
+ class BadRequest < Error ; end
13
+ end
@@ -0,0 +1,10 @@
1
+ module ArrowPayments
2
+ class LineItem < Entity
3
+ property :id, :from => 'ID'
4
+ property :commodity_code, :from => 'CommodityCode'
5
+ property :description, :from => 'Description'
6
+ property :price, :from => 'Price'
7
+ property :product_code, :from => 'ProductCode'
8
+ property :unit_of_measure, :from => 'UnitOfMeasure'
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ module ArrowPayments
2
+ class PaymentMethod < Entity
3
+ property :id, :from => 'ID'
4
+ property :card_type, :from => 'CardType'
5
+ property :last_digits, :from => 'Last4'
6
+ property :first_name, :from => 'CardholderFirstName'
7
+ property :last_name, :from => 'CardholderLastName'
8
+ property :expiration_month, :from => 'ExpirationMonth'
9
+ property :expiration_year, :from => 'ExpirationYear'
10
+ property :address, :from => 'BillingStreet1'
11
+ property :address2, :from => 'BillingStreet2'
12
+ property :city, :from => 'BillingCity'
13
+ property :state, :from => 'BillingState'
14
+ property :zip, :from => 'BillingZip'
15
+
16
+ property :number
17
+ property :security_code
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ # From documentation:
2
+ # * Frequency -> W, M, Q, Y (weekly, monthly, quarterly, yearly)
3
+ # * TransactionDay -> Weekly: [1-7], Monthly: [1-28], Quarterly:[1,2,3], Yearly: [1-12]
4
+
5
+ module ArrowPayments
6
+ class RecurringBilling < Entity
7
+ FREQUENCIES = %w(W M Q Y)
8
+ FREQUENCY_NAMES = {
9
+ 'W' => 'Weekly',
10
+ 'M' => 'Monthly',
11
+ 'Q' => 'Quarterly',
12
+ 'Y' => 'Yearly'
13
+ }
14
+
15
+ property :id, :from => 'ID'
16
+ property :payment_method_id, :from => 'PaymentMethodId'
17
+ property :frequency, :from => 'Frequency'
18
+ property :total_amount, :from => 'TotalAmount'
19
+ property :shipping_amount, :from => 'ShippingAmount'
20
+ property :description, :from => 'Description'
21
+ property :transaction_day, :from => 'TransactionDay'
22
+ property :date_created, :from => 'DateCreated'
23
+
24
+ # Get billing frequency name
25
+ # @return [String]
26
+ def frequency_name
27
+ FREQUENCY_NAMES[frequency]
28
+ end
29
+ end
30
+ end