activemerchant 1.63.0 → 1.64.0

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: a16e6e0f311ac3b77069a4f05f5f257b6a69fc60
4
- data.tar.gz: 666b86bb155b6ff98a608bf8c38714a54af46627
3
+ metadata.gz: 1974da9f685774b95695ff91430141a164281f64
4
+ data.tar.gz: 5a547f1ff49dcf3d3ef6089d92636f0c92ac47f3
5
5
  SHA512:
6
- metadata.gz: bd479af93c39120f8a447058524d56de326189f0f3fc26ace5af2681166761fc81a967a66368f9d50e4d7a5a0b64c7a4867c7f61dc2b37e368b0aa19141c87c2
7
- data.tar.gz: b9f8ea0507a585f672f9abce5cf4c2359dbeeb781fe8cdda5e05413aa24fa638fc25a0f83279c4143e72278f8cf4e82f12a1987190390640a7c21ff52f488389
6
+ metadata.gz: 2beea9543a20c61f6549a94d71a6c6d73962cdc0ca21548d378a4ffbf26ade9df8b0cb0d7cb14b7f1355d4999625e9cae82fe3a87572070a18fcbcb81168f83b
7
+ data.tar.gz: 1624eb5f2942edcff808a3ac70fb693c0c7f617c229e384d279f2e8b517558243bd2943a808198b59f378d7f8752cb2807d84717a3b72fa9119caf2e809656a0
data/CHANGELOG CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  == HEAD
4
4
 
5
+ == Version 1.64.0 (March 6, 2017)
6
+ * Authorize.net: Allow settings to be passed for CIM purchases [fwilkins] #2300
7
+ * Authorize.net: Use new `unsupported_feature` standard error code [jasonwebster] #2322
8
+ * Base Gateway: Add new `unsupported_feature` standard error code [jasonwebster] #2322
9
+ * Braintree Blue: Pass cardholder_name with card [curiousepic] #2324
10
+ * Braintree: Add Android Pay meta data fields [jknipp] #2347
11
+ * CardStream: Add additional of currencies [shasum] #2337
12
+ * Credorax: Return failure response reason [shasum] #2341
13
+ * Digitzs: Add gateway [davidsantoso]
14
+ * Digitzs: Remove merchant_id from gateway credentials [davidsantoso]
15
+ * GlobalCollect: Pass options to Refund [curiousepic] #2330
16
+ * Kushki: Add new gateway [shasum] #2326
17
+ * Kushki: Remove body from void call [shasum] #2348
18
+ * Linkpoint: Raise ArgumentError when trying to instantiate without `:pem` [jasonwebster] #2329
19
+ * Omise: Enable Japan, JPY and JCB support [zdk] #2284
20
+ * PayU LATAM: Count pending refunds as succeeded [curiousepic] #2336
21
+ * PayU LATAM: Let Refund take amount value [curiousepic] #2334
22
+ * Paymill: Send new required fields on tokenization requests [tschelabaumann] #2279
23
+ * Revert "Authorize.net: Allow settings to be passed for CIM purchases" [curiousepic] #2339
24
+ * Sage: Default billing state when outside US [shasum] #2340
25
+ * Stripe: Remove idempotency key from verify [shasum] #2335
26
+ * TransFirst Transaction Express: Don't send order_id with refunds [curiousepic] #2350
27
+ * TransFirst Transaction Express: Fix improper AVS and CVV response code mapping [shasum] #2342
28
+ * WePay: Update API version [shasum] #2349
29
+ * USA ePay Advanced: Add quick_update_customer action [joshreeves] #2229
30
+
5
31
  == Version 1.63.0 (February 2, 2017)
6
32
  * Authorize.net: Add #unstore support [jimryan] #2293
7
33
  * AuthorizeNet: Fix line items quirk [shasum]
data/README.md CHANGED
@@ -162,7 +162,7 @@ The [ActiveMerchant Wiki](http://github.com/activemerchant/active_merchant/wikis
162
162
  * [NETPAY Gateway](http://www.netpay.com.mx) - MX
163
163
  * [NMI](http://nmi.com/) - US
164
164
  * [Ogone](http://www.ogone.com/) - BE, DE, FR, NL, AT, CH
165
- * [Omise](https://www.omise.co/) - TH
165
+ * [Omise](https://www.omise.co/) - TH, JP
166
166
  * [Openpay](Openpay) - MX
167
167
  * [Optimal Payments](http://www.optimalpayments.com/) - CA, US, GB
168
168
  * [Orbital Paymentech](http://chasepaymentech.com/) - US, CA
@@ -77,6 +77,9 @@ module ActiveMerchant #:nodoc:
77
77
  # :call_issuer - Transaction requires voice authentication, call issuer
78
78
  # :pickup_card - Issuer requests that you pickup the card from merchant
79
79
  # :test_mode_live_card - Card was declined. Request was in test mode, but used a non test card.
80
+ # :unsupported_feature - Transaction failed due to gateway or merchant
81
+ # configuration not supporting a feature used, such
82
+ # as network tokenization.
80
83
 
81
84
  STANDARD_ERROR_CODE = {
82
85
  :incorrect_number => 'incorrect_number',
@@ -93,7 +96,8 @@ module ActiveMerchant #:nodoc:
93
96
  :call_issuer => 'call_issuer',
94
97
  :pickup_card => 'pick_up_card',
95
98
  :config_error => 'config_error',
96
- :test_mode_live_card => 'test_mode_live_card'
99
+ :test_mode_live_card => 'test_mode_live_card',
100
+ :unsupported_feature => 'unsupported_feature',
97
101
  }
98
102
 
99
103
  cattr_reader :implementations
@@ -36,24 +36,25 @@ module ActiveMerchant
36
36
  }
37
37
 
38
38
  STANDARD_ERROR_CODE_MAPPING = {
39
- '36' => STANDARD_ERROR_CODE[:incorrect_number],
40
- '237' => STANDARD_ERROR_CODE[:invalid_number],
41
- '2315' => STANDARD_ERROR_CODE[:invalid_number],
42
- '37' => STANDARD_ERROR_CODE[:invalid_expiry_date],
43
- '2316' => STANDARD_ERROR_CODE[:invalid_expiry_date],
44
- '378' => STANDARD_ERROR_CODE[:invalid_cvc],
45
- '38' => STANDARD_ERROR_CODE[:expired_card],
46
- '2317' => STANDARD_ERROR_CODE[:expired_card],
47
- '244' => STANDARD_ERROR_CODE[:incorrect_cvc],
48
- '227' => STANDARD_ERROR_CODE[:incorrect_address],
49
39
  '2127' => STANDARD_ERROR_CODE[:incorrect_address],
50
40
  '22' => STANDARD_ERROR_CODE[:card_declined],
41
+ '227' => STANDARD_ERROR_CODE[:incorrect_address],
51
42
  '23' => STANDARD_ERROR_CODE[:card_declined],
52
- '3153' => STANDARD_ERROR_CODE[:processing_error],
43
+ '2315' => STANDARD_ERROR_CODE[:invalid_number],
44
+ '2316' => STANDARD_ERROR_CODE[:invalid_expiry_date],
45
+ '2317' => STANDARD_ERROR_CODE[:expired_card],
53
46
  '235' => STANDARD_ERROR_CODE[:processing_error],
47
+ '237' => STANDARD_ERROR_CODE[:invalid_number],
54
48
  '24' => STANDARD_ERROR_CODE[:pickup_card],
49
+ '244' => STANDARD_ERROR_CODE[:incorrect_cvc],
55
50
  '300' => STANDARD_ERROR_CODE[:config_error],
56
- '384' => STANDARD_ERROR_CODE[:config_error]
51
+ '3153' => STANDARD_ERROR_CODE[:processing_error],
52
+ '3155' => STANDARD_ERROR_CODE[:unsupported_feature],
53
+ '36' => STANDARD_ERROR_CODE[:incorrect_number],
54
+ '37' => STANDARD_ERROR_CODE[:invalid_expiry_date],
55
+ '378' => STANDARD_ERROR_CODE[:invalid_cvc],
56
+ '38' => STANDARD_ERROR_CODE[:expired_card],
57
+ '384' => STANDARD_ERROR_CODE[:config_error],
57
58
  }
58
59
 
59
60
  MARKET_TYPE = {
@@ -573,7 +573,7 @@ module ActiveMerchant #:nodoc:
573
573
  :number => credit_card_or_vault_id.number,
574
574
  :expiration_month => credit_card_or_vault_id.month.to_s.rjust(2, "0"),
575
575
  :expiration_year => credit_card_or_vault_id.year.to_s,
576
- :cardholder_name => "#{credit_card_or_vault_id.first_name} #{credit_card_or_vault_id.last_name}",
576
+ :cardholder_name => credit_card_or_vault_id.name,
577
577
  :cryptogram => credit_card_or_vault_id.payment_cryptogram
578
578
  }
579
579
  elsif credit_card_or_vault_id.source == :android_pay
@@ -582,7 +582,9 @@ module ActiveMerchant #:nodoc:
582
582
  :cryptogram => credit_card_or_vault_id.payment_cryptogram,
583
583
  :expiration_month => credit_card_or_vault_id.month.to_s.rjust(2, "0"),
584
584
  :expiration_year => credit_card_or_vault_id.year.to_s,
585
- :google_transaction_id => credit_card_or_vault_id.transaction_id
585
+ :google_transaction_id => credit_card_or_vault_id.transaction_id,
586
+ :source_card_type => credit_card_or_vault_id.brand,
587
+ :source_card_last_four => credit_card_or_vault_id.last_digits
586
588
  }
587
589
  end
588
590
  else
@@ -590,7 +592,8 @@ module ActiveMerchant #:nodoc:
590
592
  :number => credit_card_or_vault_id.number,
591
593
  :cvv => credit_card_or_vault_id.verification_value,
592
594
  :expiration_month => credit_card_or_vault_id.month.to_s.rjust(2, "0"),
593
- :expiration_year => credit_card_or_vault_id.year.to_s
595
+ :expiration_year => credit_card_or_vault_id.year.to_s,
596
+ :cardholder_name => credit_card_or_vault_id.name
594
597
  }
595
598
  end
596
599
  end
@@ -14,24 +14,95 @@ module ActiveMerchant #:nodoc:
14
14
 
15
15
  CURRENCY_CODES = {
16
16
  "AED" => "784",
17
+ "ALL" => "008",
18
+ "AMD" => "051",
19
+ "ANG" => "532",
20
+ "ARS" => "032",
17
21
  "AUD" => "036",
22
+ "AWG" => "533",
23
+ "BAM" => "977",
24
+ "BBD" => "052",
25
+ "BGN" => "975",
26
+ "BMD" => "060",
27
+ "BOB" => "068",
18
28
  "BRL" => "986",
29
+ "BSD" => "044",
30
+ "BWP" => "072",
31
+ "BZD" => "084",
19
32
  "CAD" => "124",
20
33
  "CHF" => "756",
34
+ "CLP" => "152",
35
+ "CNY" => "156",
36
+ "COP" => "170",
37
+ "CRC" => "188",
21
38
  "CZK" => "203",
22
39
  "DKK" => "208",
40
+ "DOP" => "214",
41
+ "EGP" => "818",
23
42
  "EUR" => "978",
24
43
  "GBP" => "826",
44
+ "GEL" => "981",
45
+ "GIP" => "292",
46
+ "GTQ" => "320",
47
+ "GYD" => "328",
25
48
  "HKD" => "344",
26
- "ICK" => "352",
49
+ "HNL" => "340",
50
+ "HRK" => "191",
51
+ "HUF" => "348",
52
+ "ISK" => "352",
53
+ "IDR" => "360",
54
+ "ILS" => "376",
55
+ "INR" => "356",
27
56
  "JPY" => "392",
57
+ "JMD" => "388",
58
+ "KES" => "404",
59
+ "KRW" => "410",
60
+ "KYD" => "136",
61
+ "LBP" => "422",
62
+ "LKR" => "144",
63
+ "MAD" => "504",
64
+ "MVR" => "462",
65
+ "MWK" => "454",
28
66
  "MXN" => "484",
67
+ "MYR" => "458",
68
+ "NAD" => "516",
69
+ "NGN" => "566",
70
+ "NIO" => "558",
29
71
  "NOK" => "578",
72
+ "NPR" => "524",
30
73
  "NZD" => "554",
74
+ "PAB" => "590",
31
75
  "PEN" => "604",
76
+ "PGK" => "598",
77
+ "PHP" => "608",
78
+ "PKR" => "586",
79
+ "PLN" => "985",
80
+ "PYG" => "600",
81
+ "QAR" => "634",
82
+ "RON" => "946",
83
+ "RSD" => "941",
84
+ "RUB" => "643",
85
+ "RWF" => "646",
86
+ "SAR" => "682",
32
87
  "SEK" => "752",
33
88
  "SGD" => "702",
89
+ "SRD" => "968",
90
+ "THB" => "764",
91
+ "TND" => "788",
92
+ "TRY" => "949",
93
+ "TTD" => "780",
94
+ "TWD" => "901",
95
+ "TZS" => "834",
96
+ "UAH" => "980",
97
+ "UGX" => "800",
34
98
  "USD" => "840",
99
+ "UYU" => "858",
100
+ "VND" => "704",
101
+ "WST" => "882",
102
+ "XAF" => "950",
103
+ "XCD" => "951",
104
+ "XOF" => "952",
105
+ "ZAR" => "710"
35
106
  }
36
107
 
37
108
  CVV_CODE = {
@@ -20,6 +20,83 @@ module ActiveMerchant #:nodoc:
20
20
  self.money_format = :cents
21
21
  self.supported_cardtypes = [:visa, :master, :maestro]
22
22
 
23
+ RESPONSE_MESSAGES = {
24
+ "00" => "Approved or completed successfully",
25
+ "01" => "Refer to card issuer",
26
+ "02" => "Refer to card issuer special condition",
27
+ "03" => "Invalid merchant",
28
+ "04" => "Pick up card",
29
+ "05" => "Do not Honour",
30
+ "06" => "Invalid Transaction for Terminal",
31
+ "07" => "Pick up card special condition",
32
+ "08" => "Time-Out",
33
+ "09" => "No Original",
34
+ "10" => "Approved for partial amount",
35
+ "11" => "Partial Approval",
36
+ "12" => "Invalid transaction card / issuer / acquirer",
37
+ "13" => "Invalid amount",
38
+ "14" => "Invalid card number",
39
+ "17" => "Invalid Capture date (terminal business date)",
40
+ "19" => "System Error; Re-enter transaction",
41
+ "20" => "No From Account",
42
+ "21" => "No To Account",
43
+ "22" => "No Checking Account",
44
+ "23" => "No Saving Account",
45
+ "24" => "No Credit Account",
46
+ "30" => "Format error",
47
+ "34" => "Implausible card data",
48
+ "39" => "Transaction Not Allowed",
49
+ "41" => "Lost Card, Pickup",
50
+ "42" => "Special Pickup",
51
+ "43" => "Hot Card, Pickup (if possible)",
52
+ "44" => "Pickup Card",
53
+ "51" => "Not sufficient funds",
54
+ "52" => "No checking Account",
55
+ "53" => "No savings account",
56
+ "54" => "Expired card",
57
+ "55" => "Pin incorrect",
58
+ "57" => "Transaction not allowed for cardholder",
59
+ "58" => "Transaction not allowed for merchant",
60
+ "59" => "Suspected Fraud",
61
+ "61" => "Exceeds withdrawal amount limit",
62
+ "62" => "Restricted card",
63
+ "63" => "MAC Key Error",
64
+ "65" => "Activity count limit exceeded",
65
+ "66" => "Exceeds Acquirer Limit",
66
+ "67" => "Retain Card; no reason specified",
67
+ "68" => "Response received too late",
68
+ "75" => "Pin tries exceeded",
69
+ "76" => "Invalid Account",
70
+ "77" => "Issuer Does Not Participate In The Service",
71
+ "78" => "Function Not Available",
72
+ "79" => "Key Validation Error",
73
+ "80" => "Approval for Purchase Amount Only",
74
+ "81" => "Unable to Verify PIN",
75
+ "82" => "Time out at issuer system",
76
+ "83" => "Not declined (Valid for all zero amount transactions)",
77
+ "84" => "Invalid Life Cycle of transaction",
78
+ "85" => "Not declined",
79
+ "86" => "Cannot verify pin",
80
+ "87" => "Purchase amount only, no cashback allowed",
81
+ "88" => "MAC sync Error",
82
+ "89" => "Security Violation",
83
+ "91" => "Issuer not available",
84
+ "92" => "Unable to route at acquirer Module",
85
+ "93" => "Transaction cannot be completed",
86
+ "94" => "Duplicate transaction",
87
+ "95" => "Contact Acquirer",
88
+ "96" => "System malfunction",
89
+ "97" => "No Funds Transfer",
90
+ "98" => "Duplicate Reversal",
91
+ "99" => "Duplicate Transaction",
92
+ "N3" => "Cash Service Not Available",
93
+ "N4" => "Cash Back Request Exceeds Issuer Limit",
94
+ "N7" => "N7 (visa), Decline CVV2 failure",
95
+ "R0" => "Stop Payment Order",
96
+ "R1" => "Revocation of Authorisation Order",
97
+ "R3" => "Revocation of all Authorisations Order"
98
+ }
99
+
23
100
  def initialize(options={})
24
101
  requires!(options, :merchant_id, :cipher_key)
25
102
  super
@@ -225,7 +302,7 @@ module ActiveMerchant #:nodoc:
225
302
  if success_from(response)
226
303
  "Succeeded"
227
304
  else
228
- response["Z3"] || "Unable to read error message"
305
+ RESPONSE_MESSAGES[response["Z6"]] || response["Z3"] || "Unable to read error message"
229
306
  end
230
307
  end
231
308
  end
@@ -0,0 +1,292 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class DigitzsGateway < Gateway
4
+ include Empty
5
+
6
+ self.test_url = 'https://beta.digitzsapi.com/sandbox'
7
+ self.live_url = 'https://beta.digitzsapi.com/v3'
8
+
9
+ self.supported_countries = ['US']
10
+ self.default_currency = 'USD'
11
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
12
+ self.money_format = :cents
13
+
14
+ self.homepage_url = 'https://digitzs.com'
15
+ self.display_name = 'Digitzs'
16
+
17
+ def initialize(options={})
18
+ requires!(options, :app_key, :api_key)
19
+ super
20
+ end
21
+
22
+ def purchase(money, payment, options={})
23
+ MultiResponse.run do |r|
24
+ r.process { commit("auth/token", app_token_request(options)) }
25
+ r.process { commit('payments', purchase_request(money, payment, options), options.merge({ app_token: app_token_from(r) })) }
26
+ end
27
+ end
28
+
29
+ def refund(money, authorization, options={})
30
+ MultiResponse.run do |r|
31
+ r.process { commit("auth/token", app_token_request(options)) }
32
+ r.process { commit('payments', refund_request(money, authorization, options), options.merge({ app_token: app_token_from(r) })) }
33
+ end
34
+ end
35
+
36
+ def store(payment, options = {})
37
+ MultiResponse.run do |r|
38
+ r.process { commit("auth/token", app_token_request(options)) }
39
+ options.merge!({ app_token: app_token_from(r) })
40
+
41
+ if options[:customer_id].present?
42
+ customer_id = check_customer_exists(options)
43
+
44
+ if customer_id
45
+ r.process { add_credit_card_to_customer(payment, options) }
46
+ else
47
+ r.process { add_customer_with_credit_card(payment, options) }
48
+ end
49
+ else
50
+ r.process { add_customer_with_credit_card(payment, options) }
51
+ end
52
+ end
53
+ end
54
+
55
+ def supports_scrubbing?
56
+ true
57
+ end
58
+
59
+ def scrub(transcript)
60
+ transcript.
61
+ gsub(%r((Authorization: Bearer ).+), '\1[FILTERED]').
62
+ gsub(%r((X-Api-Key: )\w+), '\1[FILTERED]').
63
+ gsub(%r((\"id\\\":\\\").+), '\1[FILTERED]').
64
+ gsub(%r((\"appKey\\\":\\\").+), '\1[FILTERED]').
65
+ gsub(%r((\"appToken\\\":\\\").+), '\1[FILTERED]').
66
+ gsub(%r((\"code\\\":\\\")\d+), '\1[FILTERED]').
67
+ gsub(%r((\"number\\\":\\\")\d+), '\1[FILTERED]')
68
+ end
69
+
70
+ private
71
+
72
+ def new_post
73
+ {
74
+ data: {
75
+ attributes: {}
76
+ }
77
+ }
78
+ end
79
+
80
+ def add_split(post, options)
81
+ return unless options[:payment_type] == "card_split" || options[:payment_type] == "token_split"
82
+
83
+ post[:data][:attributes][:split] = {
84
+ merchantId: options[:split_merchant_id],
85
+ amount: amount(options[:split_amount])
86
+ }
87
+ end
88
+
89
+ def add_payment(post, payment, options)
90
+ if payment.is_a? String
91
+ customer_id, token = split_authorization(payment)
92
+ post[:data][:attributes][:token] = {
93
+ customerId: customer_id,
94
+ tokenId: token
95
+ }
96
+ else
97
+ post[:data][:attributes][:card] = {
98
+ type: payment.brand,
99
+ holder: payment.name,
100
+ number: payment.number,
101
+ expiry: expdate(payment),
102
+ code: payment.verification_value
103
+ }
104
+ end
105
+ end
106
+
107
+ def add_transaction(post, money, options)
108
+ post[:data][:attributes][:transaction] = {
109
+ amount: amount(money),
110
+ currency: (options[:currency] || currency(money)),
111
+ invoice: options[:order_id] || generate_unique_id
112
+ }
113
+ end
114
+
115
+ def add_address(post, options)
116
+ if address = options[:billing_address] || options[:address]
117
+ post[:data][:attributes][:billingAddress] = {
118
+ line1: address[:address1] || "",
119
+ line2: address[:address2] || "",
120
+ city: address[:city] || "",
121
+ state: address[:state] || "",
122
+ zip: address[:zip] || "",
123
+ country: address["country"] || "USA"
124
+ }
125
+ end
126
+ end
127
+
128
+ def app_token_request(options)
129
+ post = new_post
130
+ post[:data][:type] = "auth"
131
+ post[:data][:attributes] = { appKey: @options[:app_key] }
132
+
133
+ post
134
+ end
135
+
136
+ def purchase_request(money, payment, options)
137
+ post = new_post
138
+ post[:data][:type] = "payments"
139
+ post[:data][:attributes][:merchantId] = options[:merchant_id]
140
+ post[:data][:attributes][:paymentType] = determine_payment_type(payment, options)
141
+ add_split(post, options)
142
+ add_payment(post, payment, options)
143
+ add_transaction(post, money, options)
144
+ add_address(post, options)
145
+
146
+ post
147
+ end
148
+
149
+ def refund_request(money, authorization, options)
150
+ post = new_post
151
+ post[:data][:type] = "payments"
152
+ post[:data][:attributes][:merchantId] = options[:merchant_id]
153
+ post[:data][:attributes][:paymentType] = "cardRefund"
154
+ post[:data][:attributes][:originalTransaction] = {id: authorization}
155
+ add_transaction(post, money, options)
156
+
157
+ post
158
+ end
159
+
160
+ def create_customer_request(payment, options)
161
+ post = new_post
162
+ post[:data][:type] = "customers"
163
+ post[:data][:attributes] = {
164
+ merchantId: options[:merchant_id],
165
+ name: payment.name,
166
+ externalId: "#{SecureRandom.hex(16)}"
167
+ }
168
+
169
+ post
170
+ end
171
+
172
+ def create_token_request(payment, options)
173
+ post = new_post
174
+ post[:data][:type] = "tokens"
175
+ post[:data][:attributes] = {
176
+ tokenType: "card",
177
+ customerId: options[:customer_id],
178
+ label: "Credit Card",
179
+ }
180
+ add_payment(post, payment, options)
181
+ add_address(post, options)
182
+
183
+ post
184
+ end
185
+
186
+ def check_customer_exists(options = {})
187
+ url = (test? ? test_url : live_url)
188
+ response = parse(ssl_get(url + "/customers/#{options[:customer_id]}", headers(options)))
189
+
190
+ return response.try(:[], "data").try(:[], "customerId") if success_from(response)
191
+ return nil
192
+ end
193
+
194
+ def add_credit_card_to_customer(payment, options = {})
195
+ commit('tokens', create_token_request(payment, options), options)
196
+ end
197
+
198
+ def add_customer_with_credit_card(payment, options = {})
199
+ customer_response = commit('customers', create_customer_request(payment, options), options)
200
+ options.merge!({customer_id: customer_response.authorization})
201
+ commit('tokens', create_token_request(payment, options), options)
202
+ end
203
+
204
+ def parse(body)
205
+ JSON.parse(body)
206
+ end
207
+
208
+ def commit(action, parameters, options={})
209
+ url = (test? ? test_url : live_url)
210
+ response = parse(ssl_post(url + "/#{action}", parameters.to_json, headers(options)))
211
+
212
+ Response.new(
213
+ success_from(response),
214
+ message_from(response),
215
+ response,
216
+ authorization: authorization_from(response),
217
+ avs_result: AVSResult.new(code: avs_result_from(response)),
218
+ cvv_result: CVVResult.new(cvv_result_from(response)),
219
+ test: test?,
220
+ error_code: error_code_from(response)
221
+ )
222
+ end
223
+
224
+ def success_from(response)
225
+ response["errors"].nil? && response["message"].nil?
226
+ end
227
+
228
+ def message_from(response)
229
+ return response["message"] if response["message"]
230
+ return "Success" if success_from(response)
231
+ response["errors"].map {|error_hash| error_hash["detail"] }.join(", ")
232
+ end
233
+
234
+ def authorization_from(response)
235
+ if customer_id = response.try(:[], "data").try(:[], "attributes").try(:[], "customerId")
236
+ "#{customer_id}|#{response.try(:[], "data").try(:[], "id")}"
237
+ else
238
+ response.try(:[], "data").try(:[], "id")
239
+ end
240
+ end
241
+
242
+ def avs_result_from(response)
243
+ response.try(:[], "data").try(:[], "attributes").try(:[], "transaction").try(:[], "avsResult")
244
+ end
245
+
246
+ def cvv_result_from(response)
247
+ response.try(:[], "data").try(:[], "attributes").try(:[], "transaction").try(:[], "codeResult")
248
+ end
249
+
250
+ def app_token_from(response)
251
+ response.params.try(:[], "data").try(:[], "attributes").try(:[], "appToken")
252
+ end
253
+
254
+ def headers(options)
255
+ headers = {
256
+ 'Content-Type' => 'application/json',
257
+ 'x-api-key' => @options[:api_key]
258
+ }
259
+
260
+ headers.merge!({"Authorization" => "Bearer #{options[:app_token]}"}) if options[:app_token]
261
+ headers
262
+ end
263
+
264
+ def error_code_from(response)
265
+ unless success_from(response)
266
+ response["errors"].nil? ? response["message"] : response["errors"].map {|error_hash| error_hash["code"] }.join(", ")
267
+ end
268
+ end
269
+
270
+ def split_authorization(authorization)
271
+ customer_id, token = authorization.split("|")
272
+ [customer_id, token]
273
+ end
274
+
275
+ def determine_payment_type(payment, options)
276
+ return "cardSplit" if options[:payment_type] == "card_split"
277
+ return "tokenSplit" if options[:payment_type] == "token_split"
278
+ return "token" if payment.is_a? String
279
+ "card"
280
+ end
281
+
282
+ def handle_response(response)
283
+ case response.code.to_i
284
+ when 200..499
285
+ response.body
286
+ else
287
+ raise ResponseError.new(response)
288
+ end
289
+ end
290
+ end
291
+ end
292
+ end