activemerchant 1.29.1 → 1.31.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.
- data/CHANGELOG +58 -0
- data/CONTRIBUTORS +27 -0
- data/README.md +45 -41
- data/lib/active_merchant/billing/check.rb +11 -11
- data/lib/active_merchant/billing/credit_card.rb +1 -1
- data/lib/active_merchant/billing/credit_card_formatting.rb +8 -8
- data/lib/active_merchant/billing/gateway.rb +2 -2
- data/lib/active_merchant/billing/gateways/authorize_net.rb +9 -1
- data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +15 -4
- data/lib/active_merchant/billing/gateways/balanced.rb +9 -3
- data/lib/active_merchant/billing/gateways/banwire.rb +15 -1
- data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +6 -2
- data/lib/active_merchant/billing/gateways/beanstream.rb +26 -24
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +5 -2
- data/lib/active_merchant/billing/gateways/cyber_source.rb +55 -22
- data/lib/active_merchant/billing/gateways/evo_ca.rb +308 -0
- data/lib/active_merchant/billing/gateways/eway.rb +114 -171
- data/lib/active_merchant/billing/gateways/eway_managed.rb +52 -22
- data/lib/active_merchant/billing/gateways/firstdata_e4.rb +232 -0
- data/lib/active_merchant/billing/gateways/ideal_rabobank.rb +13 -2
- data/lib/active_merchant/billing/gateways/litle.rb +50 -19
- data/lib/active_merchant/billing/gateways/merchant_ware.rb +44 -9
- data/lib/active_merchant/billing/gateways/merchant_warrior.rb +190 -0
- data/lib/active_merchant/billing/gateways/moneris.rb +3 -5
- data/lib/active_merchant/billing/gateways/moneris_us.rb +1 -1
- data/lib/active_merchant/billing/gateways/nab_transact.rb +20 -3
- data/lib/active_merchant/billing/gateways/netbilling.rb +1 -0
- data/lib/active_merchant/billing/gateways/netpay.rb +223 -0
- data/lib/active_merchant/billing/gateways/ogone.rb +6 -4
- data/lib/active_merchant/billing/gateways/optimal_payment.rb +18 -3
- data/lib/active_merchant/billing/gateways/orbital.rb +9 -5
- data/lib/active_merchant/billing/gateways/payment_express.rb +62 -1
- data/lib/active_merchant/billing/gateways/paymill.rb +161 -0
- data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +1 -1
- data/lib/active_merchant/billing/gateways/paypal_express.rb +17 -11
- data/lib/active_merchant/billing/gateways/pin.rb +157 -0
- data/lib/active_merchant/billing/gateways/qbms.rb +3 -2
- data/lib/active_merchant/billing/gateways/quickpay.rb +66 -28
- data/lib/active_merchant/billing/gateways/sage_pay.rb +6 -0
- data/lib/active_merchant/billing/gateways/smart_ps.rb +1 -1
- data/lib/active_merchant/billing/gateways/spreedly_core.rb +235 -0
- data/lib/active_merchant/billing/gateways/stripe.rb +1 -0
- data/lib/active_merchant/billing/gateways/wirecard.rb +15 -9
- data/lib/active_merchant/billing/gateways/worldpay.rb +19 -5
- data/lib/active_merchant/billing/integrations/payflow_link/helper.rb +4 -1
- data/lib/active_merchant/billing/integrations/paypal/notification.rb +39 -31
- data/lib/active_merchant/billing/integrations/quickpay/helper.rb +13 -10
- data/lib/active_merchant/billing/integrations/quickpay/notification.rb +14 -14
- data/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb +2 -2
- data/lib/active_merchant/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +32 -24
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
require 'nokogiri'
|
|
2
|
+
|
|
3
|
+
module ActiveMerchant #:nodoc:
|
|
4
|
+
module Billing #:nodoc:
|
|
5
|
+
# Public: This gateway allows you to interact with any gateway you've
|
|
6
|
+
# created in Spreedly Core (https://spreedlycore.com). It's an adapter
|
|
7
|
+
# which can be particularly useful if you already have code interacting with
|
|
8
|
+
# ActiveMerchant and want to easily take advantage of Core's vault.
|
|
9
|
+
class SpreedlyCoreGateway < Gateway
|
|
10
|
+
self.live_url = 'https://spreedlycore.com/v1'
|
|
11
|
+
|
|
12
|
+
self.supported_countries = %w(AD AE AT AU BD BE BG BN CA CH CY CZ DE DK EE EG ES FI FR GB
|
|
13
|
+
GI GR HK HU ID IE IL IM IN IS IT JO KW LB LI LK LT LU LV MC
|
|
14
|
+
MT MU MV MX MY NL NO NZ OM PH PL PT QA RO SA SE SG SI SK SM
|
|
15
|
+
TR TT UM US VA VN ZA)
|
|
16
|
+
|
|
17
|
+
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
|
|
18
|
+
self.homepage_url = 'https://spreedlycore.com'
|
|
19
|
+
self.display_name = 'Spreedly Core'
|
|
20
|
+
self.money_format = :cents
|
|
21
|
+
self.default_currency = 'USD'
|
|
22
|
+
|
|
23
|
+
# Public: Create a new Spreedly Core Gateway.
|
|
24
|
+
#
|
|
25
|
+
# options - A hash of options:
|
|
26
|
+
# :login - Your Spreedly Core API login.
|
|
27
|
+
# :password - Your Spreedly Core API secret.
|
|
28
|
+
# :gateway_token - The token of the gateway you've created in
|
|
29
|
+
# Spreedly Core.
|
|
30
|
+
def initialize(options = {})
|
|
31
|
+
requires!(options, :login, :password, :gateway_token)
|
|
32
|
+
super
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Public: Run a purchase transaction.
|
|
36
|
+
#
|
|
37
|
+
# money - The monetary amount of the transaction in cents.
|
|
38
|
+
# payment_method - The CreditCard or the Spreedly Core payment method
|
|
39
|
+
# token.
|
|
40
|
+
# options - A standard ActiveMerchant options hash
|
|
41
|
+
def purchase(money, payment_method, options = {})
|
|
42
|
+
if payment_method.is_a?(String)
|
|
43
|
+
purchase_with_token(money, payment_method, options)
|
|
44
|
+
else
|
|
45
|
+
MultiResponse.run do |r|
|
|
46
|
+
r.process { save_card(false, payment_method, options) }
|
|
47
|
+
r.process { purchase_with_token(money, r.authorization, options) }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Public: Run an authorize transaction.
|
|
53
|
+
#
|
|
54
|
+
# money - The monetary amount of the transaction in cents.
|
|
55
|
+
# payment_method - The CreditCard or the Spreedly Core payment method
|
|
56
|
+
# token.
|
|
57
|
+
# options - A standard ActiveMerchant options hash
|
|
58
|
+
def authorize(money, payment_method, options = {})
|
|
59
|
+
if payment_method.is_a?(String)
|
|
60
|
+
authorize_with_token(money, payment_method, options)
|
|
61
|
+
else
|
|
62
|
+
MultiResponse.run do |r|
|
|
63
|
+
r.process { save_card(false, payment_method, options) }
|
|
64
|
+
r.process { authorize_with_token(money, r.authorization, options) }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def capture(money, authorization, options={})
|
|
70
|
+
request = build_xml_request('transaction') do |doc|
|
|
71
|
+
add_invoice(doc, money, options)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
commit("transactions/#{authorization}/capture.xml", request)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def refund(money, authorization, options={})
|
|
78
|
+
request = build_xml_request('transaction') do |doc|
|
|
79
|
+
add_invoice(doc, money, options)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
commit("transactions/#{authorization}/credit.xml", request)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def void(authorization, options={})
|
|
86
|
+
commit("transactions/#{authorization}/void.xml", '')
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Public: Store a credit card in the Spreedly Core vault and retain it.
|
|
90
|
+
#
|
|
91
|
+
# credit_card - The CreditCard to store
|
|
92
|
+
# options - A standard ActiveMerchant options hash
|
|
93
|
+
def store(credit_card, options={})
|
|
94
|
+
save_card(true, credit_card, options)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Public: Redact the CreditCard in Spreedly Core. This wipes the
|
|
98
|
+
# sensitive payment information from the card.
|
|
99
|
+
#
|
|
100
|
+
# credit_card - The CreditCard to store
|
|
101
|
+
# options - A standard ActiveMerchant options hash
|
|
102
|
+
def unstore(authorization, options={})
|
|
103
|
+
commit("payment_methods/#{authorization}/redact.xml", '', :put)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
def save_card(retain, credit_card, options)
|
|
108
|
+
request = build_xml_request('payment_method') do |doc|
|
|
109
|
+
add_credit_card(doc, credit_card, options)
|
|
110
|
+
add_data(doc, options)
|
|
111
|
+
doc.retained(true) if retain
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
commit("payment_methods.xml", request, :post, :payment_method_token)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def purchase_with_token(money, payment_method_token, options)
|
|
118
|
+
request = auth_purchase_request(money, payment_method_token, options)
|
|
119
|
+
commit("gateways/#{@options[:gateway_token]}/purchase.xml", request)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def authorize_with_token(money, payment_method_token, options)
|
|
123
|
+
request = auth_purchase_request(money, payment_method_token, options)
|
|
124
|
+
commit("gateways/#{@options[:gateway_token]}/authorize.xml", request)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def auth_purchase_request(money, payment_method_token, options)
|
|
128
|
+
build_xml_request('transaction') do |doc|
|
|
129
|
+
add_invoice(doc, money, options)
|
|
130
|
+
doc.payment_method_token(payment_method_token)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def add_invoice(doc, money, options)
|
|
135
|
+
doc.amount amount(money)
|
|
136
|
+
doc.currency_code(options[:currency] || currency(money) || default_currency)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def add_credit_card(doc, credit_card, options)
|
|
140
|
+
doc.credit_card do
|
|
141
|
+
doc.number(credit_card.number)
|
|
142
|
+
doc.first_name(credit_card.first_name)
|
|
143
|
+
doc.last_name(credit_card.last_name)
|
|
144
|
+
doc.month(credit_card.month)
|
|
145
|
+
doc.year(credit_card.year)
|
|
146
|
+
doc.email(options[:email])
|
|
147
|
+
doc.address1(options[:billing_address].try(:[], :address1))
|
|
148
|
+
doc.address2(options[:billing_address].try(:[], :address2))
|
|
149
|
+
doc.city(options[:billing_address].try(:[], :city))
|
|
150
|
+
doc.state(options[:billing_address].try(:[], :state))
|
|
151
|
+
doc.zip(options[:billing_address].try(:[], :zip))
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def add_data(doc, options)
|
|
156
|
+
doc.data do
|
|
157
|
+
data_to_doc(doc, options[:data])
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def data_to_doc(doc, value)
|
|
162
|
+
return doc.text value unless value.kind_of? Hash
|
|
163
|
+
value.each do |k, v|
|
|
164
|
+
doc.send(k) do
|
|
165
|
+
data_to_doc(doc, v)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def parse(xml)
|
|
171
|
+
response = {}
|
|
172
|
+
|
|
173
|
+
doc = Nokogiri::XML(xml)
|
|
174
|
+
doc.root.xpath('*').each do |node|
|
|
175
|
+
if (node.elements.empty?)
|
|
176
|
+
response[node.name.downcase.to_sym] = node.text
|
|
177
|
+
else
|
|
178
|
+
node.elements.each do |childnode|
|
|
179
|
+
childnode_to_response(response, node, childnode)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
response
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def childnode_to_response(response, node, childnode)
|
|
188
|
+
name = "#{node.name.downcase}_#{childnode.name.downcase}"
|
|
189
|
+
if name == 'payment_method_data' && !childnode.elements.empty?
|
|
190
|
+
response[name.to_sym] = Hash.from_xml(childnode.to_s).values.first
|
|
191
|
+
else
|
|
192
|
+
response[name.to_sym] = childnode.text
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def build_xml_request(root)
|
|
197
|
+
builder = Nokogiri::XML::Builder.new
|
|
198
|
+
builder.__send__(root) do |doc|
|
|
199
|
+
yield(doc)
|
|
200
|
+
end
|
|
201
|
+
builder.to_xml
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def commit(relative_url, request, method = :post, authorization_field = :token)
|
|
205
|
+
begin
|
|
206
|
+
raw_response = ssl_request(method, "#{live_url}/#{relative_url}", request, headers)
|
|
207
|
+
rescue ResponseError => e
|
|
208
|
+
raw_response = e.response.body
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
response_from(raw_response, authorization_field)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def response_from(raw_response, authorization_field)
|
|
215
|
+
parsed = parse(raw_response)
|
|
216
|
+
options = {
|
|
217
|
+
:authorization => parsed[authorization_field],
|
|
218
|
+
:test => (parsed[:on_test_gateway] == 'true'),
|
|
219
|
+
:avs_result => { :code => parsed[:response_avs_code] },
|
|
220
|
+
:cvv_result => parsed[:response_cvv_code]
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
Response.new(parsed[:succeeded] == 'true', parsed[:message] || parsed[:error], parsed, options)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def headers
|
|
227
|
+
{
|
|
228
|
+
'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:login]}:#{@options[:password]}").chomp),
|
|
229
|
+
'Content-Type' => 'text/xml'
|
|
230
|
+
}
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
@@ -50,6 +50,7 @@ module ActiveMerchant #:nodoc:
|
|
|
50
50
|
add_customer(post, options)
|
|
51
51
|
add_customer_data(post,options)
|
|
52
52
|
post[:description] = options[:description] || options[:email]
|
|
53
|
+
post[:application_fee] = options[:application_fee] if options[:application_fee]
|
|
53
54
|
add_flags(post, options)
|
|
54
55
|
|
|
55
56
|
meta = generate_meta(options)
|
|
@@ -16,7 +16,7 @@ module ActiveMerchant #:nodoc:
|
|
|
16
16
|
'xsi:noNamespaceSchemaLocation' => 'wirecard.xsd'
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
PERMITTED_TRANSACTIONS = %w[
|
|
19
|
+
PERMITTED_TRANSACTIONS = %w[ PREAUTHORIZATION CAPTURE PURCHASE ]
|
|
20
20
|
|
|
21
21
|
RETURN_CODES = %w[ ACK NOK ]
|
|
22
22
|
|
|
@@ -63,13 +63,13 @@ module ActiveMerchant #:nodoc:
|
|
|
63
63
|
# Authorization
|
|
64
64
|
def authorize(money, creditcard, options = {})
|
|
65
65
|
options[:credit_card] = creditcard
|
|
66
|
-
commit(:
|
|
66
|
+
commit(:preauthorization, money, options)
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
# Capture Authorization
|
|
70
70
|
def capture(money, authorization, options = {})
|
|
71
|
-
options[:
|
|
72
|
-
commit(:
|
|
71
|
+
options[:preauthorization] = authorization
|
|
72
|
+
commit(:capture, money, options)
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
# Purchase
|
|
@@ -148,16 +148,17 @@ module ActiveMerchant #:nodoc:
|
|
|
148
148
|
options[:order_id] ||= generate_unique_id
|
|
149
149
|
|
|
150
150
|
xml.tag! "FNC_CC_#{options[:action].to_s.upcase}" do
|
|
151
|
-
xml.tag! 'FunctionID', options[:description]
|
|
151
|
+
xml.tag! 'FunctionID', options[:description].to_s.slice(0,32)
|
|
152
152
|
xml.tag! 'CC_TRANSACTION' do
|
|
153
153
|
xml.tag! 'TransactionID', options[:order_id]
|
|
154
154
|
case options[:action]
|
|
155
|
-
when :
|
|
155
|
+
when :preauthorization, :purchase
|
|
156
156
|
add_invoice(xml, money, options)
|
|
157
157
|
add_creditcard(xml, options[:credit_card])
|
|
158
158
|
add_address(xml, options[:billing_address])
|
|
159
|
-
when :
|
|
160
|
-
xml.tag! 'GuWID', options[:
|
|
159
|
+
when :capture
|
|
160
|
+
xml.tag! 'GuWID', options[:preauthorization]
|
|
161
|
+
add_amount(xml, money)
|
|
161
162
|
end
|
|
162
163
|
end
|
|
163
164
|
end
|
|
@@ -165,7 +166,7 @@ module ActiveMerchant #:nodoc:
|
|
|
165
166
|
|
|
166
167
|
# Includes the payment (amount, currency, country) to the transaction-xml
|
|
167
168
|
def add_invoice(xml, money, options)
|
|
168
|
-
xml
|
|
169
|
+
add_amount(xml, money)
|
|
169
170
|
xml.tag! 'Currency', options[:currency] || currency(money)
|
|
170
171
|
xml.tag! 'CountryCode', options[:billing_address][:country]
|
|
171
172
|
xml.tag! 'RECURRING_TRANSACTION' do
|
|
@@ -173,6 +174,11 @@ module ActiveMerchant #:nodoc:
|
|
|
173
174
|
end
|
|
174
175
|
end
|
|
175
176
|
|
|
177
|
+
# Include the amount in the transaction-xml
|
|
178
|
+
def add_amount(xml, money)
|
|
179
|
+
xml.tag! 'Amount', amount(money)
|
|
180
|
+
end
|
|
181
|
+
|
|
176
182
|
# Includes the credit-card data to the transaction-xml
|
|
177
183
|
def add_creditcard(xml, creditcard)
|
|
178
184
|
raise "Creditcard must be supplied!" if creditcard.nil?
|
|
@@ -16,6 +16,9 @@ module ActiveMerchant #:nodoc:
|
|
|
16
16
|
'master' => 'ECMC-SSL',
|
|
17
17
|
'discover' => 'DISCOVER-SSL',
|
|
18
18
|
'american_express' => 'AMEX-SSL',
|
|
19
|
+
'jcb' => 'JCB-SSL',
|
|
20
|
+
'maestro' => 'MAESTRO-SSL',
|
|
21
|
+
'laser' => 'LASER-SSL'
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
def initialize(options = {})
|
|
@@ -38,6 +41,10 @@ module ActiveMerchant #:nodoc:
|
|
|
38
41
|
def capture(money, authorization, options = {})
|
|
39
42
|
MultiResponse.run do |r|
|
|
40
43
|
r.process{inquire_request(authorization, options, "AUTHORISED")} unless options[:authorization_validated]
|
|
44
|
+
if r.params
|
|
45
|
+
authorization_currency = r.params['amount_currency_code']
|
|
46
|
+
options = options.merge(:currency => authorization_currency) if authorization_currency.present?
|
|
47
|
+
end
|
|
41
48
|
r.process{capture_request(money, authorization, options)}
|
|
42
49
|
end
|
|
43
50
|
end
|
|
@@ -80,7 +87,7 @@ module ActiveMerchant #:nodoc:
|
|
|
80
87
|
|
|
81
88
|
def build_request
|
|
82
89
|
xml = Builder::XmlMarkup.new :indent => 2
|
|
83
|
-
xml.instruct!
|
|
90
|
+
xml.instruct! :xml, :encoding => 'ISO-8859-1'
|
|
84
91
|
xml.declare! :DOCTYPE, :paymentService, :PUBLIC, "-//WorldPay//DTD WorldPay PaymentService v1//EN", "http://dtd.wp3.rbsworldpay.com/paymentService_v1.dtd"
|
|
85
92
|
xml.tag! 'paymentService', 'version' => "1.4", 'merchantCode' => @options[:login] do
|
|
86
93
|
yield xml
|
|
@@ -148,10 +155,10 @@ module ActiveMerchant #:nodoc:
|
|
|
148
155
|
end
|
|
149
156
|
|
|
150
157
|
def add_amount(xml, money, options)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
158
|
+
currency = options[:currency] || currency(money)
|
|
159
|
+
amount = localized_amount(money, currency)
|
|
160
|
+
|
|
161
|
+
xml.tag! 'amount', :value => amount, 'currencyCode' => currency, 'exponent' => 2
|
|
155
162
|
end
|
|
156
163
|
|
|
157
164
|
def add_payment_method(xml, amount, payment_method, options)
|
|
@@ -261,6 +268,13 @@ module ActiveMerchant #:nodoc:
|
|
|
261
268
|
credentials = "#{@options[:login]}:#{@options[:password]}"
|
|
262
269
|
"Basic #{[credentials].pack('m').strip}"
|
|
263
270
|
end
|
|
271
|
+
|
|
272
|
+
def localized_amount(money, currency)
|
|
273
|
+
amount = amount(money)
|
|
274
|
+
return amount unless CURRENCIES_WITHOUT_FRACTIONS.include?(currency.to_s)
|
|
275
|
+
|
|
276
|
+
amount.to_i / 100 * 100
|
|
277
|
+
end
|
|
264
278
|
end
|
|
265
279
|
end
|
|
266
280
|
end
|
|
@@ -20,7 +20,6 @@ module ActiveMerchant #:nodoc:
|
|
|
20
20
|
mapping :credential2, 'pwd'
|
|
21
21
|
mapping :credential3, 'partner'
|
|
22
22
|
mapping :order, 'user1'
|
|
23
|
-
mapping :description, 'description'
|
|
24
23
|
|
|
25
24
|
mapping :amount, 'amt'
|
|
26
25
|
|
|
@@ -35,6 +34,10 @@ module ActiveMerchant #:nodoc:
|
|
|
35
34
|
|
|
36
35
|
mapping :customer, :name => 'name'
|
|
37
36
|
|
|
37
|
+
def description(value)
|
|
38
|
+
add_field('description', "#{value}".delete("#"))
|
|
39
|
+
end
|
|
40
|
+
|
|
38
41
|
def customer(params = {})
|
|
39
42
|
add_field(mappings[:customer][:name], [params.delete(:first_name), params.delete(:last_name)].compact.join(' '))
|
|
40
43
|
end
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
require 'net/http'
|
|
2
|
+
require 'time'
|
|
2
3
|
|
|
3
4
|
module ActiveMerchant #:nodoc:
|
|
4
5
|
module Billing #:nodoc:
|
|
5
6
|
module Integrations #:nodoc:
|
|
6
7
|
module Paypal
|
|
7
|
-
# Parser and handler for incoming Instant payment notifications from paypal.
|
|
8
|
+
# Parser and handler for incoming Instant payment notifications from paypal.
|
|
8
9
|
# The Example shows a typical handler in a rails application. Note that this
|
|
9
10
|
# is an example, please read the Paypal API documentation for all the details
|
|
10
11
|
# on creating a safe payment controller.
|
|
11
12
|
#
|
|
12
13
|
# Example
|
|
13
|
-
#
|
|
14
|
+
#
|
|
14
15
|
# class BackendController < ApplicationController
|
|
15
16
|
# include ActiveMerchant::Billing::Integrations
|
|
16
17
|
#
|
|
@@ -20,28 +21,28 @@ module ActiveMerchant #:nodoc:
|
|
|
20
21
|
# if notify.masspay?
|
|
21
22
|
# masspay_items = notify.items
|
|
22
23
|
# end
|
|
23
|
-
#
|
|
24
|
+
#
|
|
24
25
|
# order = Order.find(notify.item_id)
|
|
25
|
-
#
|
|
26
|
-
# if notify.acknowledge
|
|
26
|
+
#
|
|
27
|
+
# if notify.acknowledge
|
|
27
28
|
# begin
|
|
28
|
-
#
|
|
29
|
+
#
|
|
29
30
|
# if notify.complete? and order.total == notify.amount
|
|
30
|
-
# order.status = 'success'
|
|
31
|
-
#
|
|
31
|
+
# order.status = 'success'
|
|
32
|
+
#
|
|
32
33
|
# shop.ship(order)
|
|
33
34
|
# else
|
|
34
35
|
# logger.error("Failed to verify Paypal's notification, please investigate")
|
|
35
36
|
# end
|
|
36
|
-
#
|
|
37
|
+
#
|
|
37
38
|
# rescue => e
|
|
38
|
-
# order.status = 'failed'
|
|
39
|
+
# order.status = 'failed'
|
|
39
40
|
# raise
|
|
40
41
|
# ensure
|
|
41
42
|
# order.save
|
|
42
43
|
# end
|
|
43
44
|
# end
|
|
44
|
-
#
|
|
45
|
+
#
|
|
45
46
|
# render :nothing
|
|
46
47
|
# end
|
|
47
48
|
# end
|
|
@@ -52,7 +53,7 @@ module ActiveMerchant #:nodoc:
|
|
|
52
53
|
super
|
|
53
54
|
extend MassPayNotification if masspay?
|
|
54
55
|
end
|
|
55
|
-
|
|
56
|
+
|
|
56
57
|
# Was the transaction complete?
|
|
57
58
|
def complete?
|
|
58
59
|
status == "Completed"
|
|
@@ -63,13 +64,20 @@ module ActiveMerchant #:nodoc:
|
|
|
63
64
|
type == "masspay"
|
|
64
65
|
end
|
|
65
66
|
|
|
66
|
-
# When was this payment received by the client.
|
|
67
|
-
# sometimes it can happen that we get the notification much later.
|
|
68
|
-
# One possible scenario is that our web application was down. In this case paypal tries several
|
|
67
|
+
# When was this payment received by the client.
|
|
68
|
+
# sometimes it can happen that we get the notification much later.
|
|
69
|
+
# One possible scenario is that our web application was down. In this case paypal tries several
|
|
69
70
|
# times an hour to inform us about the notification
|
|
70
71
|
def received_at
|
|
71
|
-
parsed_time_fields = DateTime._strptime(params['payment_date'], "%H:%M:%S %b %d, %Y %
|
|
72
|
-
Time.
|
|
72
|
+
parsed_time_fields = DateTime._strptime(params['payment_date'], "%H:%M:%S %b %d, %Y %Z")
|
|
73
|
+
Time.gm(
|
|
74
|
+
parsed_time_fields[:year],
|
|
75
|
+
parsed_time_fields[:mon],
|
|
76
|
+
parsed_time_fields[:mday],
|
|
77
|
+
parsed_time_fields[:hour],
|
|
78
|
+
parsed_time_fields[:min],
|
|
79
|
+
parsed_time_fields[:sec]
|
|
80
|
+
) + Time.zone_offset(parsed_time_fields[:zone])
|
|
73
81
|
end
|
|
74
82
|
|
|
75
83
|
# Status of transaction. List of possible values:
|
|
@@ -94,8 +102,8 @@ module ActiveMerchant #:nodoc:
|
|
|
94
102
|
params['txn_id']
|
|
95
103
|
end
|
|
96
104
|
|
|
97
|
-
# What type of transaction are we dealing with?
|
|
98
|
-
# "cart" "send_money" "web_accept" are possible here.
|
|
105
|
+
# What type of transaction are we dealing with?
|
|
106
|
+
# "cart" "send_money" "web_accept" are possible here.
|
|
99
107
|
def type
|
|
100
108
|
params['txn_type']
|
|
101
109
|
end
|
|
@@ -115,37 +123,37 @@ module ActiveMerchant #:nodoc:
|
|
|
115
123
|
params['mc_currency']
|
|
116
124
|
end
|
|
117
125
|
|
|
118
|
-
# This is the item number which we submitted to paypal
|
|
126
|
+
# This is the item number which we submitted to paypal
|
|
119
127
|
# The custom field is also mapped to item_id because PayPal
|
|
120
128
|
# doesn't return item_number in dispute notifications
|
|
121
129
|
def item_id
|
|
122
130
|
params['item_number'] || params['custom']
|
|
123
131
|
end
|
|
124
132
|
|
|
125
|
-
# This is the invoice which you passed to paypal
|
|
133
|
+
# This is the invoice which you passed to paypal
|
|
126
134
|
def invoice
|
|
127
135
|
params['invoice']
|
|
128
|
-
end
|
|
136
|
+
end
|
|
129
137
|
|
|
130
138
|
# Was this a test transaction?
|
|
131
139
|
def test?
|
|
132
140
|
params['test_ipn'] == '1'
|
|
133
141
|
end
|
|
134
|
-
|
|
142
|
+
|
|
135
143
|
def account
|
|
136
144
|
params['business'] || params['receiver_email']
|
|
137
145
|
end
|
|
138
146
|
|
|
139
|
-
# Acknowledge the transaction to paypal. This method has to be called after a new
|
|
140
|
-
# ipn arrives. Paypal will verify that all the information we received are correct and will return a
|
|
141
|
-
# ok or a fail.
|
|
142
|
-
#
|
|
147
|
+
# Acknowledge the transaction to paypal. This method has to be called after a new
|
|
148
|
+
# ipn arrives. Paypal will verify that all the information we received are correct and will return a
|
|
149
|
+
# ok or a fail.
|
|
150
|
+
#
|
|
143
151
|
# Example:
|
|
144
|
-
#
|
|
152
|
+
#
|
|
145
153
|
# def paypal_ipn
|
|
146
154
|
# notify = PaypalNotification.new(request.raw_post)
|
|
147
155
|
#
|
|
148
|
-
# if notify.acknowledge
|
|
156
|
+
# if notify.acknowledge
|
|
149
157
|
# ... process order ... if notify.complete?
|
|
150
158
|
# else
|
|
151
159
|
# ... log possible hacking attempt ...
|
|
@@ -153,11 +161,11 @@ module ActiveMerchant #:nodoc:
|
|
|
153
161
|
def acknowledge
|
|
154
162
|
payload = raw
|
|
155
163
|
|
|
156
|
-
response = ssl_post(Paypal.service_url + '?cmd=_notify-validate', payload,
|
|
164
|
+
response = ssl_post(Paypal.service_url + '?cmd=_notify-validate', payload,
|
|
157
165
|
'Content-Length' => "#{payload.size}",
|
|
158
166
|
'User-Agent' => "Active Merchant -- http://activemerchant.org"
|
|
159
167
|
)
|
|
160
|
-
|
|
168
|
+
|
|
161
169
|
raise StandardError.new("Faulty paypal result: #{response}") unless ["VERIFIED", "INVALID"].include?(response)
|
|
162
170
|
|
|
163
171
|
response == "VERIFIED"
|
|
@@ -7,39 +7,40 @@ module ActiveMerchant #:nodoc:
|
|
|
7
7
|
def initialize(order, account, options = {})
|
|
8
8
|
md5secret options.delete(:credential2)
|
|
9
9
|
super
|
|
10
|
-
add_field('protocol', '
|
|
10
|
+
add_field('protocol', '6')
|
|
11
11
|
add_field('msgtype', 'authorize')
|
|
12
12
|
add_field('language', 'da')
|
|
13
13
|
add_field('autocapture', 0)
|
|
14
14
|
add_field('testmode', test? ? 1 : 0)
|
|
15
15
|
add_field('ordernumber', format_order_number(order))
|
|
16
16
|
end
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
def md5secret(value)
|
|
19
19
|
@md5secret = value
|
|
20
20
|
end
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
def form_fields
|
|
23
23
|
@fields.merge('md5check' => generate_md5check)
|
|
24
24
|
end
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
def generate_md5string
|
|
27
27
|
MD5_CHECK_FIELDS.map {|key| @fields[key.to_s]} * "" + @md5secret
|
|
28
28
|
end
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
def generate_md5check
|
|
31
31
|
Digest::MD5.hexdigest(generate_md5string)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# Limited to 20 digits max
|
|
35
35
|
def format_order_number(number)
|
|
36
|
-
number.to_s.gsub(/[^\
|
|
36
|
+
number.to_s.gsub(/[^\w]/, '').rjust(4, "0")[0...20]
|
|
37
37
|
end
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
MD5_CHECK_FIELDS = [
|
|
40
|
-
:protocol, :msgtype, :merchant, :language, :ordernumber,
|
|
40
|
+
:protocol, :msgtype, :merchant, :language, :ordernumber,
|
|
41
41
|
:amount, :currency, :continueurl, :cancelurl, :callbackurl,
|
|
42
|
-
:autocapture, :cardtypelock, :description, :ipaddress, :testmode
|
|
42
|
+
:autocapture, :cardtypelock, :description, :ipaddress, :testmode,
|
|
43
|
+
:deadline, :cardhash
|
|
43
44
|
]
|
|
44
45
|
|
|
45
46
|
mapping :protocol, 'protocol'
|
|
@@ -48,7 +49,7 @@ module ActiveMerchant #:nodoc:
|
|
|
48
49
|
mapping :language, 'language'
|
|
49
50
|
mapping :amount, 'amount'
|
|
50
51
|
mapping :currency, 'currency'
|
|
51
|
-
|
|
52
|
+
|
|
52
53
|
mapping :return_url, 'continueurl'
|
|
53
54
|
mapping :cancel_return_url, 'cancelurl'
|
|
54
55
|
mapping :notify_url, 'callbackurl'
|
|
@@ -59,6 +60,8 @@ module ActiveMerchant #:nodoc:
|
|
|
59
60
|
mapping :description, 'description'
|
|
60
61
|
mapping :ipaddress, 'ipaddress'
|
|
61
62
|
mapping :testmode, 'testmode'
|
|
63
|
+
mapping :deadline, 'deadline'
|
|
64
|
+
mapping :cardhash, 'cardhash'
|
|
62
65
|
|
|
63
66
|
mapping :customer, ''
|
|
64
67
|
mapping :billing_address, {}
|