forked from spree/spree
-
Notifications
You must be signed in to change notification settings - Fork 1
/
card_methods.rb
207 lines (175 loc) · 8.01 KB
/
card_methods.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
module SpreePaymentGateway
module CardMethods
#RAILS3 TODO
# def self.included(base)
# base.scope :with_payment_profile, where("gateway_customer_profile_id IS NOT NULL")
# end
def authorize(amount, payment)
# ActiveMerchant is configured to use cents so we need to multiply order total by 100
response = payment_gateway.authorize((amount * 100).round, self, gateway_options(payment))
gateway_error(response) unless response.success?
# create a transaction to reflect the authorization
save
payment.txns << CreditcardTxn.create(
:amount => amount,
:response_code => response.authorization,
:txn_type => CreditcardTxn::TxnType::AUTHORIZE,
:avs_response => response.avs_result['code']
)
payment.authorize!
rescue ActiveMerchant::ConnectionError => e
payment.fail!
gateway_error I18n.t(:unable_to_connect_to_gateway)
end
def capture(payment)
return unless transaction = authorization(payment)
if payment_gateway.payment_profiles_supported?
# Gateways supporting payment profiles will need access to creditcard object because this stores the payment profile information
# so supply the authorization itself as well as the creditcard, rather than just the authorization code
response = payment_gateway.capture(transaction, self, minimal_gateway_options(payment))
else
# Standard ActiveMerchant capture usage
response = payment_gateway.capture((transaction.amount * 100).round, transaction.response_code, minimal_gateway_options(payment))
end
gateway_error(response) unless response.success?
# create a transaction to reflect the capture
save
payment.txns << CreditcardTxn.create(
:amount => transaction.amount,
:response_code => response.authorization,
:txn_type => CreditcardTxn::TxnType::CAPTURE
)
payment.finalize!
rescue ActiveMerchant::ConnectionError => e
gateway_error I18n.t(:unable_to_connect_to_gateway)
end
def purchase(amount, payment)
#combined Authorize and Capture that gets processed by the ActiveMerchant gateway as one single transaction.
response = payment_gateway.purchase((amount * 100).round, self, gateway_options(payment))
gateway_error(response) unless response.success?
# create a transaction to reflect the purchase
save
payment.txns << CreditcardTxn.create(
:amount => amount,
:response_code => response.authorization,
:txn_type => CreditcardTxn::TxnType::PURCHASE,
:avs_response => response.avs_result['code']
)
rescue ActiveMerchant::ConnectionError => e
payment.fail!
gateway_error t(:unable_to_connect_to_gateway)
end
def void(payment)
return unless transaction = purchase_or_authorize_transaction_for_payment(payment)
response = payment_gateway.void(transaction.response_code, self, minimal_gateway_options(payment))
gateway_error(response) unless response.success?
# create a transaction to reflect the void
save
payment.txns << CreditcardTxn.create(
:amount => -transaction.amount,
:response_code => response.authorization,
:txn_type => CreditcardTxn::TxnType::VOID
)
payment.update_attribute(:amount, 0.00)
payment.finalize!
end
def credit(payment, amount=nil)
return unless transaction = purchase_or_authorize_transaction_for_payment(payment)
amount ||= payment.order.outstanding_credit
if payment_gateway.payment_profiles_supported?
response = payment_gateway.credit((amount * 100).round, self, transaction.response_code, minimal_gateway_options(payment))
else
response = payment_gateway.credit((amount * 100).round, transaction.response_code, minimal_gateway_options(payment))
end
gateway_error(response) unless response.success?
# create a transaction to reflect the purchase
save
payment.txns << CreditcardTxn.create(
:amount => -amount,
:response_code => response.authorization,
:txn_type => CreditcardTxn::TxnType::CREDIT
)
payment.update_attribute(:amount, payment.amount - amount)
rescue ActiveMerchant::ConnectionError => e
gateway_error I18n.t(:unable_to_connect_to_gateway)
end
# find the transaction associated with the original authorization/capture
def authorization(payment)
payment.txns.find(:first,
:conditions => ["type = 'CreditcardTxn' AND txn_type = ? AND response_code IS NOT NULL", CreditcardTxn::TxnType::AUTHORIZE.to_s],
:order => 'created_at DESC')
end
# find a transaction that can be used to void or credit
def purchase_or_authorize_transaction_for_payment(payment)
payment.txns.detect {|txn| [CreditcardTxn::TxnType::AUTHORIZE, CreditcardTxn::TxnType::PURCHASE].include?(txn.txn_type) and txn.response_code.present?}
end
def actions
%w{capture void credit}
end
def can_capture?(payment)
authorization(payment).present? &&
has_no_transaction_of_types?(payment, CreditcardTxn::TxnType::PURCHASE, CreditcardTxn::TxnType::CAPTURE, CreditcardTxn::TxnType::VOID)
end
def can_void?(payment)
has_transaction_of_types?(payment, CreditcardTxn::TxnType::AUTHORIZE, CreditcardTxn::TxnType::PURCHASE, CreditcardTxn::TxnType::CAPTURE) &&
has_no_transaction_of_types?(payment, CreditcardTxn::TxnType::VOID)
end
# Can only refund a captured transaction but if transaction hasn't been cleared by merchant, refund may still fail
def can_credit?(payment)
has_transaction_of_types?(payment, CreditcardTxn::TxnType::PURCHASE, CreditcardTxn::TxnType::CAPTURE) &&
has_no_transaction_of_types?(payment, CreditcardTxn::TxnType::VOID) and payment.order.outstanding_credit?
end
def has_payment_profile?
gateway_customer_profile_id.present?
end
def gateway_error(error)
if error.is_a? ActiveMerchant::Billing::Response
text = error.params['message'] ||
error.params['response_reason_text'] ||
error.message
else
text = error.to_s
end
logger.error(I18n.t('gateway_error'))
logger.error(" #{error.to_yaml}")
raise Spree::GatewayError.new(text)
end
def gateway_options(payment)
options = {:billing_address => generate_address_hash(payment.order.bill_address),
:shipping_address => generate_address_hash(payment.order.shipment.address)}
options.merge minimal_gateway_options(payment)
end
# Generates an ActiveMerchant compatible address hash from one of Spree's address objects
def generate_address_hash(address)
return {} if address.nil?
{:name => address.full_name, :address1 => address.address1, :address2 => address.address2, :city => address.city,
:state => address.state_text, :zip => address.zipcode, :country => address.country.iso, :phone => address.phone}
end
# Generates a minimal set of gateway options. There appears to be some issues with passing in
# a billing address when authorizing/voiding a previously captured transaction. So omits these
# options in this case since they aren't necessary.
def minimal_gateway_options(payment)
{:email => payment.order.email,
:customer => payment.order.email,
:ip => payment.order.ip_address,
:order_id => payment.order.number,
:shipping => payment.order.ship_total * 100,
:tax => payment.order.tax_total * 100,
:subtotal => payment.order.item_total * 100}
end
def spree_cc_type
return "visa" if ENV['RAILS_ENV'] == "development"
self.class.type?(number)
end
def payment_gateway
@payment_gateway ||= Gateway.current
end
private
def has_transaction_of_types?(payment, *types)
(payment.txns.map(&:txn_type) & types).any?
end
def has_no_transaction_of_types?(payment, *types)
(payment.txns.map(&:txn_type) & types).none?
end
end
end