aktivemerchant 2.0.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 +7 -0
- data/CHANGELOG +1596 -0
- data/CONTRIBUTORS +511 -0
- data/MIT-LICENSE +20 -0
- data/README.md +18 -0
- data/lib/active_merchant.rb +108 -0
- data/lib/active_merchant/billing.rb +13 -0
- data/lib/active_merchant/billing/apple_pay_payment_token.rb +22 -0
- data/lib/active_merchant/billing/avs_result.rb +98 -0
- data/lib/active_merchant/billing/base.rb +72 -0
- data/lib/active_merchant/billing/check.rb +76 -0
- data/lib/active_merchant/billing/compatibility.rb +120 -0
- data/lib/active_merchant/billing/credit_card.rb +352 -0
- data/lib/active_merchant/billing/credit_card_formatting.rb +24 -0
- data/lib/active_merchant/billing/credit_card_methods.rb +160 -0
- data/lib/active_merchant/billing/cvv_result.rb +38 -0
- data/lib/active_merchant/billing/gateway.rb +268 -0
- data/lib/active_merchant/billing/gateways.rb +17 -0
- data/lib/active_merchant/billing/gateways/adyen.rb +209 -0
- data/lib/active_merchant/billing/gateways/alfabank.rb +117 -0
- data/lib/active_merchant/billing/gateways/app55.rb +176 -0
- data/lib/active_merchant/billing/gateways/authorize_net.rb +419 -0
- data/lib/active_merchant/billing/gateways/authorize_net_arb.rb +417 -0
- data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +976 -0
- data/lib/active_merchant/billing/gateways/balanced.rb +256 -0
- data/lib/active_merchant/billing/gateways/bank_frick.rb +225 -0
- data/lib/active_merchant/billing/gateways/banwire.rb +105 -0
- data/lib/active_merchant/billing/gateways/barclays_epdq.rb +314 -0
- data/lib/active_merchant/billing/gateways/barclays_epdq_extra_plus.rb +15 -0
- data/lib/active_merchant/billing/gateways/be2bill.rb +131 -0
- data/lib/active_merchant/billing/gateways/beanstream.rb +188 -0
- data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +393 -0
- data/lib/active_merchant/billing/gateways/beanstream_interac.rb +54 -0
- data/lib/active_merchant/billing/gateways/blue_pay.rb +506 -0
- data/lib/active_merchant/billing/gateways/bogus.rb +140 -0
- data/lib/active_merchant/billing/gateways/borgun.rb +210 -0
- data/lib/active_merchant/billing/gateways/braintree.rb +19 -0
- data/lib/active_merchant/billing/gateways/braintree/braintree_common.rb +9 -0
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +515 -0
- data/lib/active_merchant/billing/gateways/braintree_orange.rb +20 -0
- data/lib/active_merchant/billing/gateways/bridge_pay.rb +189 -0
- data/lib/active_merchant/billing/gateways/card_save.rb +23 -0
- data/lib/active_merchant/billing/gateways/card_stream.rb +220 -0
- data/lib/active_merchant/billing/gateways/cashnet.rb +191 -0
- data/lib/active_merchant/billing/gateways/cc5.rb +201 -0
- data/lib/active_merchant/billing/gateways/cecabank.rb +229 -0
- data/lib/active_merchant/billing/gateways/certo_direct.rb +278 -0
- data/lib/active_merchant/billing/gateways/checkout.rb +213 -0
- data/lib/active_merchant/billing/gateways/commercegate.rb +143 -0
- data/lib/active_merchant/billing/gateways/conekta.rb +209 -0
- data/lib/active_merchant/billing/gateways/cyber_source.rb +709 -0
- data/lib/active_merchant/billing/gateways/data_cash.rb +600 -0
- data/lib/active_merchant/billing/gateways/efsnet.rb +219 -0
- data/lib/active_merchant/billing/gateways/elavon.rb +348 -0
- data/lib/active_merchant/billing/gateways/epay.rb +275 -0
- data/lib/active_merchant/billing/gateways/evo_ca.rb +308 -0
- data/lib/active_merchant/billing/gateways/eway.rb +214 -0
- data/lib/active_merchant/billing/gateways/eway_managed.rb +291 -0
- data/lib/active_merchant/billing/gateways/eway_rapid.rb +524 -0
- data/lib/active_merchant/billing/gateways/exact.rb +218 -0
- data/lib/active_merchant/billing/gateways/fat_zebra.rb +173 -0
- data/lib/active_merchant/billing/gateways/federated_canada.rb +160 -0
- data/lib/active_merchant/billing/gateways/finansbank.rb +23 -0
- data/lib/active_merchant/billing/gateways/first_giving.rb +143 -0
- data/lib/active_merchant/billing/gateways/first_pay.rb +160 -0
- data/lib/active_merchant/billing/gateways/firstdata_e4.rb +355 -0
- data/lib/active_merchant/billing/gateways/garanti.rb +257 -0
- data/lib/active_merchant/billing/gateways/global_transport.rb +183 -0
- data/lib/active_merchant/billing/gateways/hdfc.rb +207 -0
- data/lib/active_merchant/billing/gateways/hps.rb +288 -0
- data/lib/active_merchant/billing/gateways/iats_payments.rb +251 -0
- data/lib/active_merchant/billing/gateways/ideal/ideal_base.rb +246 -0
- data/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem +13 -0
- data/lib/active_merchant/billing/gateways/ideal/ideal_response.rb +29 -0
- data/lib/active_merchant/billing/gateways/ideal_rabobank.rb +66 -0
- data/lib/active_merchant/billing/gateways/inspire.rb +213 -0
- data/lib/active_merchant/billing/gateways/instapay.rb +163 -0
- data/lib/active_merchant/billing/gateways/iridium.rb +457 -0
- data/lib/active_merchant/billing/gateways/itransact.rb +448 -0
- data/lib/active_merchant/billing/gateways/jetpay.rb +275 -0
- data/lib/active_merchant/billing/gateways/linkpoint.rb +438 -0
- data/lib/active_merchant/billing/gateways/litle.rb +346 -0
- data/lib/active_merchant/billing/gateways/maxipago.rb +197 -0
- data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +170 -0
- data/lib/active_merchant/billing/gateways/merchant_one.rb +114 -0
- data/lib/active_merchant/billing/gateways/merchant_ware.rb +319 -0
- data/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb +268 -0
- data/lib/active_merchant/billing/gateways/merchant_warrior.rb +195 -0
- data/lib/active_merchant/billing/gateways/mercury.rb +333 -0
- data/lib/active_merchant/billing/gateways/metrics_global.rb +303 -0
- data/lib/active_merchant/billing/gateways/migs.rb +265 -0
- data/lib/active_merchant/billing/gateways/migs/migs_codes.rb +100 -0
- data/lib/active_merchant/billing/gateways/modern_payments.rb +37 -0
- data/lib/active_merchant/billing/gateways/modern_payments_cim.rb +219 -0
- data/lib/active_merchant/billing/gateways/moneris.rb +309 -0
- data/lib/active_merchant/billing/gateways/moneris_us.rb +291 -0
- data/lib/active_merchant/billing/gateways/money_movers.rb +152 -0
- data/lib/active_merchant/billing/gateways/nab_transact.rb +280 -0
- data/lib/active_merchant/billing/gateways/net_registry.rb +198 -0
- data/lib/active_merchant/billing/gateways/netaxept.rb +181 -0
- data/lib/active_merchant/billing/gateways/netbilling.rb +190 -0
- data/lib/active_merchant/billing/gateways/netpay.rb +223 -0
- data/lib/active_merchant/billing/gateways/network_merchants.rb +242 -0
- data/lib/active_merchant/billing/gateways/nmi.rb +256 -0
- data/lib/active_merchant/billing/gateways/ogone.rb +435 -0
- data/lib/active_merchant/billing/gateways/openpay.rb +194 -0
- data/lib/active_merchant/billing/gateways/optimal_payment.rb +313 -0
- data/lib/active_merchant/billing/gateways/orbital.rb +803 -0
- data/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb +47 -0
- data/lib/active_merchant/billing/gateways/pac_net_raven.rb +207 -0
- data/lib/active_merchant/billing/gateways/pago_facil.rb +122 -0
- data/lib/active_merchant/billing/gateways/pay_gate_xml.rb +261 -0
- data/lib/active_merchant/billing/gateways/pay_junction.rb +390 -0
- data/lib/active_merchant/billing/gateways/pay_secure.rb +112 -0
- data/lib/active_merchant/billing/gateways/pay_u_latam.rb +462 -0
- data/lib/active_merchant/billing/gateways/paybox_direct.rb +188 -0
- data/lib/active_merchant/billing/gateways/payex.rb +412 -0
- data/lib/active_merchant/billing/gateways/payflow.rb +304 -0
- data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +209 -0
- data/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb +39 -0
- data/lib/active_merchant/billing/gateways/payflow/payflow_response.rb +13 -0
- data/lib/active_merchant/billing/gateways/payflow_express.rb +224 -0
- data/lib/active_merchant/billing/gateways/payflow_express_uk.rb +15 -0
- data/lib/active_merchant/billing/gateways/payflow_uk.rb +21 -0
- data/lib/active_merchant/billing/gateways/payment_express.rb +353 -0
- data/lib/active_merchant/billing/gateways/paymill.rb +281 -0
- data/lib/active_merchant/billing/gateways/paypal.rb +117 -0
- data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +670 -0
- data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +65 -0
- data/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb +262 -0
- data/lib/active_merchant/billing/gateways/paypal_ca.rb +13 -0
- data/lib/active_merchant/billing/gateways/paypal_digital_goods.rb +44 -0
- data/lib/active_merchant/billing/gateways/paypal_express.rb +264 -0
- data/lib/active_merchant/billing/gateways/paypal_express_common.rb +30 -0
- data/lib/active_merchant/billing/gateways/payscout.rb +162 -0
- data/lib/active_merchant/billing/gateways/paystation.rb +199 -0
- data/lib/active_merchant/billing/gateways/payway.rb +207 -0
- data/lib/active_merchant/billing/gateways/pin.rb +197 -0
- data/lib/active_merchant/billing/gateways/plugnpay.rb +283 -0
- data/lib/active_merchant/billing/gateways/psigate.rb +216 -0
- data/lib/active_merchant/billing/gateways/psl_card.rb +303 -0
- data/lib/active_merchant/billing/gateways/qbms.rb +292 -0
- data/lib/active_merchant/billing/gateways/quantum.rb +276 -0
- data/lib/active_merchant/billing/gateways/quickpay.rb +367 -0
- data/lib/active_merchant/billing/gateways/realex.rb +298 -0
- data/lib/active_merchant/billing/gateways/redsys.rb +391 -0
- data/lib/active_merchant/billing/gateways/sage.rb +175 -0
- data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +87 -0
- data/lib/active_merchant/billing/gateways/sage/sage_core.rb +114 -0
- data/lib/active_merchant/billing/gateways/sage/sage_vault.rb +149 -0
- data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +102 -0
- data/lib/active_merchant/billing/gateways/sage_pay.rb +398 -0
- data/lib/active_merchant/billing/gateways/sallie_mae.rb +143 -0
- data/lib/active_merchant/billing/gateways/secure_net.rb +252 -0
- data/lib/active_merchant/billing/gateways/secure_pay.rb +201 -0
- data/lib/active_merchant/billing/gateways/secure_pay_au.rb +281 -0
- data/lib/active_merchant/billing/gateways/secure_pay_tech.rb +105 -0
- data/lib/active_merchant/billing/gateways/skip_jack.rb +452 -0
- data/lib/active_merchant/billing/gateways/smart_ps.rb +283 -0
- data/lib/active_merchant/billing/gateways/so_easy_pay.rb +194 -0
- data/lib/active_merchant/billing/gateways/spreedly_core.rb +247 -0
- data/lib/active_merchant/billing/gateways/stripe.rb +411 -0
- data/lib/active_merchant/billing/gateways/swipe_checkout.rb +157 -0
- data/lib/active_merchant/billing/gateways/tns.rb +227 -0
- data/lib/active_merchant/billing/gateways/trans_first.rb +126 -0
- data/lib/active_merchant/billing/gateways/transax.rb +23 -0
- data/lib/active_merchant/billing/gateways/transnational.rb +10 -0
- data/lib/active_merchant/billing/gateways/trust_commerce.rb +416 -0
- data/lib/active_merchant/billing/gateways/usa_epay.rb +25 -0
- data/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +1516 -0
- data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +254 -0
- data/lib/active_merchant/billing/gateways/verifi.rb +225 -0
- data/lib/active_merchant/billing/gateways/viaklix.rb +183 -0
- data/lib/active_merchant/billing/gateways/vindicia.rb +385 -0
- data/lib/active_merchant/billing/gateways/webpay.rb +97 -0
- data/lib/active_merchant/billing/gateways/wepay.rb +189 -0
- data/lib/active_merchant/billing/gateways/wirecard.rb +421 -0
- data/lib/active_merchant/billing/gateways/worldpay.rb +331 -0
- data/lib/active_merchant/billing/gateways/worldpay_us.rb +181 -0
- data/lib/active_merchant/billing/model.rb +30 -0
- data/lib/active_merchant/billing/payment_token.rb +21 -0
- data/lib/active_merchant/billing/rails.rb +3 -0
- data/lib/active_merchant/billing/response.rb +91 -0
- data/lib/active_merchant/country.rb +332 -0
- data/lib/active_merchant/empty.rb +20 -0
- data/lib/active_merchant/errors.rb +29 -0
- data/lib/active_merchant/offsite_payments_shim.rb +19 -0
- data/lib/active_merchant/version.rb +3 -0
- data/lib/activemerchant.rb +1 -0
- data/lib/support/gateway_support.rb +71 -0
- data/lib/support/outbound_hosts.rb +25 -0
- data/lib/support/ssl_verify.rb +93 -0
- metadata +400 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
module ActiveMerchant
|
2
|
+
module Billing
|
3
|
+
module MigsCodes
|
4
|
+
TXN_RESPONSE_CODES = {
|
5
|
+
'?' => 'Response Unknown',
|
6
|
+
'0' => 'Transaction Successful',
|
7
|
+
'1' => 'Transaction Declined - Bank Error',
|
8
|
+
'2' => 'Bank Declined Transaction',
|
9
|
+
'3' => 'Transaction Declined - No Reply from Bank',
|
10
|
+
'4' => 'Transaction Declined - Expired Card',
|
11
|
+
'5' => 'Transaction Declined - Insufficient funds',
|
12
|
+
'6' => 'Transaction Declined - Error Communicating with Bank',
|
13
|
+
'7' => 'Payment Server Processing Error - Typically caused by invalid input data such as an invalid credit card number. Processing errors can also occur',
|
14
|
+
'8' => 'Transaction Declined - Transaction Type Not Supported',
|
15
|
+
'9' => 'Bank Declined Transaction (Do not contact Bank)',
|
16
|
+
'A' => 'Transaction Aborted',
|
17
|
+
'C' => 'Transaction Cancelled',
|
18
|
+
'D' => 'Deferred Transaction',
|
19
|
+
'E' => 'Issuer Returned a Referral Response',
|
20
|
+
'F' => '3D Secure Authentication Failed',
|
21
|
+
'I' => 'Card Security Code Failed',
|
22
|
+
'L' => 'Shopping Transaction Locked (This indicates that there is another transaction taking place using the same shopping transaction number)',
|
23
|
+
'N' => 'Cardholder is not enrolled in 3D Secure (Authentication Only)',
|
24
|
+
'P' => 'Transaction is Pending',
|
25
|
+
'R' => 'Retry Limits Exceeded, Transaction Not Processed',
|
26
|
+
'S' => 'Duplicate OrderInfo used. (This is only relevant for Payment Servers that enforce the uniqueness of this field)',
|
27
|
+
'U' => 'Card Security Code Failed'
|
28
|
+
}
|
29
|
+
|
30
|
+
ISSUER_RESPONSE_CODES = {
|
31
|
+
'00' => 'Approved',
|
32
|
+
'01' => 'Refer to Card Issuer',
|
33
|
+
'02' => 'Refer to Card Issuer',
|
34
|
+
'03' => 'Invalid Merchant',
|
35
|
+
'04' => 'Pick Up Card',
|
36
|
+
'05' => 'Do Not Honor',
|
37
|
+
'07' => 'Pick Up Card',
|
38
|
+
'12' => 'Invalid Transaction',
|
39
|
+
'14' => 'Invalid Card Number (No such Number)',
|
40
|
+
'15' => 'No Such Issuer',
|
41
|
+
'33' => 'Expired Card',
|
42
|
+
'34' => 'Suspected Fraud',
|
43
|
+
'36' => 'Restricted Card',
|
44
|
+
'39' => 'No Credit Account',
|
45
|
+
'41' => 'Card Reported Lost',
|
46
|
+
'43' => 'Stolen Card',
|
47
|
+
'51' => 'Insufficient Funds',
|
48
|
+
'54' => 'Expired Card',
|
49
|
+
'57' => 'Transaction Not Permitted',
|
50
|
+
'59' => 'Suspected Fraud',
|
51
|
+
'62' => 'Restricted Card',
|
52
|
+
'65' => 'Exceeds withdrawal frequency limit',
|
53
|
+
'91' => 'Cannot Contact Issuer'
|
54
|
+
}
|
55
|
+
|
56
|
+
VERIFIED_3D_CODES = {
|
57
|
+
'Y' => 'The cardholder was successfully authenticated.',
|
58
|
+
'E' => 'The cardholder is not enrolled.',
|
59
|
+
'N' => 'The cardholder was not verified.',
|
60
|
+
'U' => 'The cardholder\'s Issuer was unable to authenticate due to a system error at the Issuer.',
|
61
|
+
'F' => 'An error exists in the format of the request from the merchant. For example, the request did not contain all required fields, or the format of some fields was invalid.',
|
62
|
+
'A' => 'Authentication of your Merchant ID and Password to the Directory Server Failed (see "What does a Payment Authentication Status of "A" mean?" on page 85).',
|
63
|
+
'D' => 'Error communicating with the Directory Server, for example, the Payment Server could not connect to the directory server or there was a versioning mismatch.',
|
64
|
+
'C' => 'The card type is not supported for authentication.',
|
65
|
+
'M' => 'This indicates that attempts processing was used. Verification is marked with status M - ACS attempts processing used. Payment is performed with authentication. Attempts is when a cardholder has successfully passed the directory server but decides not to continue with the authentication process and cancels.',
|
66
|
+
'S' => 'The signature on the response received from the Issuer could not be validated. This should be considered a failure.',
|
67
|
+
'T' => 'ACS timed out. The Issuer\'s ACS did not respond to the Authentication request within the time out period.',
|
68
|
+
'P' => 'Error parsing input from Issuer.',
|
69
|
+
'I' => 'Internal Payment Server system error. This could be caused by a temporary DB failure or an error in the security module or by some error in an internal system.'
|
70
|
+
}
|
71
|
+
|
72
|
+
class CreditCardType
|
73
|
+
attr_accessor :am_code, :migs_code, :migs_long_code, :name
|
74
|
+
def initialize(am_code, migs_code, migs_long_code, name)
|
75
|
+
@am_code = am_code
|
76
|
+
@migs_code = migs_code
|
77
|
+
@migs_long_code = migs_long_code
|
78
|
+
@name = name
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
CARD_TYPES = [
|
83
|
+
# The following are 4 different representations of credit card types
|
84
|
+
# am_code: The active merchant code
|
85
|
+
# migs_code: Used in response for purchase/authorize/status
|
86
|
+
# migs_long_code: Used to pre-select card for server_purchase_url
|
87
|
+
# name: The nice display name
|
88
|
+
%w(american_express AE Amex American\ Express),
|
89
|
+
%w(diners_club DC Dinersclub Diners\ Club),
|
90
|
+
%w(jcb JC JCB JCB\ Card),
|
91
|
+
%w(maestro MS Maestro Maestro\ Card),
|
92
|
+
%w(master MC Mastercard MasterCard),
|
93
|
+
%w(na PL PrivateLabelCard Private\ Label\ Card),
|
94
|
+
%w(visa VC Visa Visa\ Card')
|
95
|
+
].map do |am_code, migs_code, migs_long_code, name|
|
96
|
+
CreditCardType.new(am_code, migs_code, migs_long_code, name)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/modern_payments_cim'
|
2
|
+
|
3
|
+
module ActiveMerchant #:nodoc:
|
4
|
+
module Billing #:nodoc:
|
5
|
+
class ModernPaymentsGateway < Gateway
|
6
|
+
self.supported_countries = ModernPaymentsCimGateway.supported_countries
|
7
|
+
self.supported_cardtypes = ModernPaymentsCimGateway.supported_cardtypes
|
8
|
+
self.homepage_url = ModernPaymentsCimGateway.homepage_url
|
9
|
+
self.display_name = ModernPaymentsCimGateway.display_name
|
10
|
+
|
11
|
+
self.abstract_class = true
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
requires!(options, :login, :password)
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def purchase(money, credit_card, options = {})
|
19
|
+
customer_response = cim.create_customer(options)
|
20
|
+
return customer_response unless customer_response.success?
|
21
|
+
|
22
|
+
customer_id = customer_response.params["create_customer_result"]
|
23
|
+
|
24
|
+
card_response = cim.modify_customer_credit_card(customer_id, credit_card)
|
25
|
+
return card_response unless card_response.success?
|
26
|
+
|
27
|
+
cim.authorize_credit_card_payment(customer_id, money)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def cim
|
32
|
+
@cim ||= ModernPaymentsCimGateway.new(@options)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,219 @@
|
|
1
|
+
module ActiveMerchant #:nodoc:
|
2
|
+
module Billing #:nodoc:
|
3
|
+
class ModernPaymentsCimGateway < Gateway #:nodoc:
|
4
|
+
self.test_url = "https://secure.modpay.com/netservices/test/ModpayTest.asmx"
|
5
|
+
self.live_url = 'https://secure.modpay.com/ws/modpay.asmx'
|
6
|
+
|
7
|
+
LIVE_XMLNS = "https://secure.modpay.com/ws/"
|
8
|
+
TEST_XMLNS = "https://secure.modpay.com/netservices/test/"
|
9
|
+
|
10
|
+
self.supported_countries = ['US']
|
11
|
+
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
|
12
|
+
self.homepage_url = 'http://www.modpay.com'
|
13
|
+
self.display_name = 'Modern Payments'
|
14
|
+
|
15
|
+
SUCCESS_MESSAGE = "Transaction accepted"
|
16
|
+
FAILURE_MESSAGE = "Transaction failed"
|
17
|
+
ERROR_MESSAGE = "Transaction error"
|
18
|
+
|
19
|
+
PAYMENT_METHOD = {
|
20
|
+
:check => 1,
|
21
|
+
:credit_card => 2
|
22
|
+
}
|
23
|
+
|
24
|
+
def initialize(options = {})
|
25
|
+
requires!(options, :login, :password)
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_customer(options = {})
|
30
|
+
post = {}
|
31
|
+
add_customer_data(post, options)
|
32
|
+
add_address(post, options)
|
33
|
+
|
34
|
+
commit('CreateCustomer', post)
|
35
|
+
end
|
36
|
+
|
37
|
+
def modify_customer_credit_card(customer_id, credit_card)
|
38
|
+
raise ArgumentError, "The customer_id cannot be blank" if customer_id.blank?
|
39
|
+
|
40
|
+
post = {}
|
41
|
+
add_customer_id(post, customer_id)
|
42
|
+
add_credit_card(post, credit_card)
|
43
|
+
|
44
|
+
commit('ModifyCustomerCreditCard', post)
|
45
|
+
end
|
46
|
+
|
47
|
+
def authorize_credit_card_payment(customer_id, amount)
|
48
|
+
raise ArgumentError, "The customer_id cannot be blank" if customer_id.blank?
|
49
|
+
|
50
|
+
post = {}
|
51
|
+
add_customer_id(post, customer_id)
|
52
|
+
add_amount(post, amount)
|
53
|
+
|
54
|
+
commit('AuthorizeCreditCardPayment', post)
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_payment(customer_id, amount, options = {})
|
58
|
+
raise ArgumentError, "The customer_id cannot be blank" if customer_id.blank?
|
59
|
+
|
60
|
+
post = {}
|
61
|
+
add_customer_id(post, customer_id)
|
62
|
+
add_amount(post, amount)
|
63
|
+
add_payment_details(post, options)
|
64
|
+
|
65
|
+
commit('CreatePayment', post)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def add_payment_details(post, options)
|
70
|
+
post[:pmtDate] = (options[:payment_date] || Time.now.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
71
|
+
post[:pmtType] = PAYMENT_METHOD[options[:payment_method] || :credit_card]
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_amount(post, money)
|
75
|
+
post[:pmtAmount] = amount(money)
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_customer_id(post, customer_id)
|
79
|
+
post[:custId] = customer_id
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_customer_data(post, options)
|
83
|
+
post[:acctNum] = options[:customer]
|
84
|
+
end
|
85
|
+
|
86
|
+
def add_address(post, options)
|
87
|
+
address = options[:billing_address] || options[:address] || {}
|
88
|
+
|
89
|
+
if name = address[:name]
|
90
|
+
segments = name.split(' ')
|
91
|
+
post[:lastName] = segments.pop
|
92
|
+
post[:firstName] = segments.join(' ')
|
93
|
+
else
|
94
|
+
post[:firstName] = address[:first_name]
|
95
|
+
post[:lastName] = address[:last_name]
|
96
|
+
end
|
97
|
+
|
98
|
+
post[:address] = address[:address1]
|
99
|
+
post[:city] = address[:city]
|
100
|
+
post[:state] = address[:state]
|
101
|
+
post[:zip] = address[:zip]
|
102
|
+
post[:phone] = address[:phone]
|
103
|
+
post[:fax] = address[:fax]
|
104
|
+
post[:email] = options[:email]
|
105
|
+
end
|
106
|
+
|
107
|
+
def add_credit_card(post, credit_card)
|
108
|
+
post[:ccName] = credit_card.name
|
109
|
+
post[:ccNum] = credit_card.number
|
110
|
+
post[:expMonth] = credit_card.month
|
111
|
+
post[:expYear] = credit_card.year
|
112
|
+
end
|
113
|
+
|
114
|
+
def build_request(action, params)
|
115
|
+
xml = Builder::XmlMarkup.new :indent => 2
|
116
|
+
xml.instruct!
|
117
|
+
xml.tag! 'env:Envelope',
|
118
|
+
{ 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
|
119
|
+
'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/',
|
120
|
+
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance' } do
|
121
|
+
|
122
|
+
xml.tag! 'env:Body' do
|
123
|
+
xml.tag! action, { "xmlns" => xmlns(action) } do
|
124
|
+
xml.tag! "clientId", @options[:login]
|
125
|
+
xml.tag! "clientCode", @options[:password]
|
126
|
+
params.each {|key, value| xml.tag! key, value }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
xml.target!
|
131
|
+
end
|
132
|
+
|
133
|
+
def xmlns(action)
|
134
|
+
if test? && action == 'AuthorizeCreditCardPayment'
|
135
|
+
TEST_XMLNS
|
136
|
+
else
|
137
|
+
LIVE_XMLNS
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def url(action)
|
142
|
+
if test? && action == 'AuthorizeCreditCardPayment'
|
143
|
+
self.test_url
|
144
|
+
else
|
145
|
+
self.live_url
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def commit(action, params)
|
150
|
+
data = ssl_post(url(action), build_request(action, params),
|
151
|
+
{ 'Content-Type' =>'text/xml; charset=utf-8',
|
152
|
+
'SOAPAction' => "#{xmlns(action)}#{action}" }
|
153
|
+
)
|
154
|
+
|
155
|
+
response = parse(action, data)
|
156
|
+
Response.new(successful?(action, response), message_from(action, response), response,
|
157
|
+
:test => test?,
|
158
|
+
:authorization => authorization_from(action, response),
|
159
|
+
:avs_result => { :code => response[:avs_code] }
|
160
|
+
)
|
161
|
+
end
|
162
|
+
|
163
|
+
def authorization_from(action, response)
|
164
|
+
response[authorization_key(action)]
|
165
|
+
end
|
166
|
+
|
167
|
+
def authorization_key(action)
|
168
|
+
action == "AuthorizeCreditCardPayment" ? :trans_id : "#{action.underscore}_result".to_sym
|
169
|
+
end
|
170
|
+
|
171
|
+
def successful?(action, response)
|
172
|
+
key = authorization_key(action)
|
173
|
+
|
174
|
+
if key == :trans_id
|
175
|
+
response[:approved] == "true"
|
176
|
+
else
|
177
|
+
response[key].to_i > 0
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def message_from(action, response)
|
182
|
+
if response[:faultcode]
|
183
|
+
ERROR_MESSAGE
|
184
|
+
elsif successful?(action, response)
|
185
|
+
SUCCESS_MESSAGE
|
186
|
+
else
|
187
|
+
FAILURE_MESSAGE
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def parse(action, xml)
|
192
|
+
response = {}
|
193
|
+
response[:action] = action
|
194
|
+
|
195
|
+
xml = REXML::Document.new(xml)
|
196
|
+
if root = REXML::XPath.first(xml, "//#{action}Response")
|
197
|
+
root.elements.to_a.each do |node|
|
198
|
+
parse_element(response, node)
|
199
|
+
end
|
200
|
+
elsif root = REXML::XPath.first(xml, "//soap:Fault")
|
201
|
+
root.elements.to_a.each do |node|
|
202
|
+
response[node.name.underscore.to_sym] = node.text
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
response
|
207
|
+
end
|
208
|
+
|
209
|
+
def parse_element(response, node)
|
210
|
+
if node.has_elements?
|
211
|
+
node.elements.each{|e| parse_element(response, e) }
|
212
|
+
else
|
213
|
+
response[node.name.underscore.to_sym] = node.text.to_s.strip
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
@@ -0,0 +1,309 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
|
3
|
+
module ActiveMerchant #:nodoc:
|
4
|
+
module Billing #:nodoc:
|
5
|
+
|
6
|
+
# To learn more about the Moneris gateway, please contact
|
7
|
+
# eselectplus@moneris.com for a copy of their integration guide. For
|
8
|
+
# information on remote testing, please see "Test Environment Penny Value
|
9
|
+
# Response Table", and "Test Environment eFraud (AVS and CVD) Penny
|
10
|
+
# Response Values", available at Moneris' {eSelect Plus Documentation
|
11
|
+
# Centre}[https://www3.moneris.com/connect/en/documents/index.html].
|
12
|
+
class MonerisGateway < Gateway
|
13
|
+
self.test_url = 'https://esqa.moneris.com/gateway2/servlet/MpgRequest'
|
14
|
+
self.live_url = 'https://www3.moneris.com/gateway2/servlet/MpgRequest'
|
15
|
+
|
16
|
+
self.supported_countries = ['CA']
|
17
|
+
self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover]
|
18
|
+
self.homepage_url = 'http://www.moneris.com/'
|
19
|
+
self.display_name = 'Moneris'
|
20
|
+
|
21
|
+
# Initialize the Gateway
|
22
|
+
#
|
23
|
+
# The gateway requires that a valid login and password be passed
|
24
|
+
# in the +options+ hash.
|
25
|
+
#
|
26
|
+
# ==== Options
|
27
|
+
#
|
28
|
+
# * <tt>:login</tt> -- Your Store ID
|
29
|
+
# * <tt>:password</tt> -- Your API Token
|
30
|
+
# * <tt>:cvv_enabled</tt> -- Specify that you would like the CVV passed to the gateway.
|
31
|
+
# Only particular account types at Moneris will allow this.
|
32
|
+
# Defaults to false. (optional)
|
33
|
+
def initialize(options = {})
|
34
|
+
requires!(options, :login, :password)
|
35
|
+
@cvv_enabled = options[:cvv_enabled]
|
36
|
+
@avs_enabled = options[:avs_enabled]
|
37
|
+
options = { :crypt_type => 7 }.merge(options)
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
# Referred to as "PreAuth" in the Moneris integration guide, this action
|
42
|
+
# verifies and locks funds on a customer's card, which then must be
|
43
|
+
# captured at a later date.
|
44
|
+
#
|
45
|
+
# Pass in +order_id+ and optionally a +customer+ parameter.
|
46
|
+
def authorize(money, creditcard_or_datakey, options = {})
|
47
|
+
requires!(options, :order_id)
|
48
|
+
post = {}
|
49
|
+
add_payment_source(post, creditcard_or_datakey, options)
|
50
|
+
post[:amount] = amount(money)
|
51
|
+
post[:order_id] = options[:order_id]
|
52
|
+
post[:address] = options[:billing_address] || options[:address]
|
53
|
+
post[:crypt_type] = options[:crypt_type] || @options[:crypt_type]
|
54
|
+
action = (post[:data_key].blank?) ? 'preauth' : 'res_preauth_cc'
|
55
|
+
commit(action, post)
|
56
|
+
end
|
57
|
+
|
58
|
+
# This action verifies funding on a customer's card and readies them for
|
59
|
+
# deposit in a merchant's account.
|
60
|
+
#
|
61
|
+
# Pass in <tt>order_id</tt> and optionally a <tt>customer</tt> parameter
|
62
|
+
def purchase(money, creditcard_or_datakey, options = {})
|
63
|
+
requires!(options, :order_id)
|
64
|
+
post = {}
|
65
|
+
add_payment_source(post, creditcard_or_datakey, options)
|
66
|
+
post[:amount] = amount(money)
|
67
|
+
post[:order_id] = options[:order_id]
|
68
|
+
post[:address] = options[:billing_address] || options[:address]
|
69
|
+
post[:crypt_type] = options[:crypt_type] || @options[:crypt_type]
|
70
|
+
action = (post[:data_key].blank?) ? 'purchase' : 'res_purchase_cc'
|
71
|
+
commit(action, post)
|
72
|
+
end
|
73
|
+
|
74
|
+
# This method retrieves locked funds from a customer's account (from a
|
75
|
+
# PreAuth) and prepares them for deposit in a merchant's account.
|
76
|
+
#
|
77
|
+
# Note: Moneris requires both the order_id and the transaction number of
|
78
|
+
# the original authorization. To maintain the same interface as the other
|
79
|
+
# gateways the two numbers are concatenated together with a ; separator as
|
80
|
+
# the authorization number returned by authorization
|
81
|
+
def capture(money, authorization, options = {})
|
82
|
+
commit 'completion', crediting_params(authorization, :comp_amount => amount(money))
|
83
|
+
end
|
84
|
+
|
85
|
+
# Voiding requires the original transaction ID and order ID of some open
|
86
|
+
# transaction. Closed transactions must be refunded.
|
87
|
+
#
|
88
|
+
# Moneris supports the voiding of an unsettled capture or purchase via
|
89
|
+
# its <tt>purchasecorrection</tt> command. This action can only occur
|
90
|
+
# on the same day as the capture/purchase prior to 22:00-23:00 EST. If
|
91
|
+
# you want to do this, pass <tt>:purchasecorrection => true</tt> as
|
92
|
+
# an option.
|
93
|
+
#
|
94
|
+
# Fun, Historical Trivia:
|
95
|
+
# Voiding an authorization in Moneris is a relatively new feature
|
96
|
+
# (September, 2011). It is actually done by doing a $0 capture.
|
97
|
+
#
|
98
|
+
# Concatenate your transaction number and order_id by using a semicolon
|
99
|
+
# (';'). This is to keep the Moneris interface consistent with other
|
100
|
+
# gateways. (See +capture+ for details.)
|
101
|
+
def void(authorization, options = {})
|
102
|
+
if options[:purchasecorrection]
|
103
|
+
commit 'purchasecorrection', crediting_params(authorization)
|
104
|
+
else
|
105
|
+
capture(0, authorization, options)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Performs a refund. This method requires that the original transaction
|
110
|
+
# number and order number be included. Concatenate your transaction
|
111
|
+
# number and order_id by using a semicolon (';'). This is to keep the
|
112
|
+
# Moneris interface consistent with other gateways. (See +capture+ for
|
113
|
+
# details.)
|
114
|
+
def credit(money, authorization, options = {})
|
115
|
+
ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
|
116
|
+
refund(money, authorization, options)
|
117
|
+
end
|
118
|
+
|
119
|
+
def refund(money, authorization, options = {})
|
120
|
+
commit 'refund', crediting_params(authorization, :amount => amount(money))
|
121
|
+
end
|
122
|
+
|
123
|
+
def store(credit_card, options = {})
|
124
|
+
post = {}
|
125
|
+
post[:pan] = credit_card.number
|
126
|
+
post[:expdate] = expdate(credit_card)
|
127
|
+
post[:crypt_type] = options[:crypt_type] || @options[:crypt_type]
|
128
|
+
commit('res_add_cc', post)
|
129
|
+
end
|
130
|
+
|
131
|
+
def unstore(data_key, options = {})
|
132
|
+
post = {}
|
133
|
+
post[:data_key] = data_key
|
134
|
+
commit('res_delete', post)
|
135
|
+
end
|
136
|
+
|
137
|
+
def update(data_key, credit_card, options = {})
|
138
|
+
post = {}
|
139
|
+
post[:pan] = credit_card.number
|
140
|
+
post[:expdate] = expdate(credit_card)
|
141
|
+
post[:data_key] = data_key
|
142
|
+
post[:crypt_type] = options[:crypt_type] || @options[:crypt_type]
|
143
|
+
commit('res_update_cc', post)
|
144
|
+
end
|
145
|
+
|
146
|
+
private # :nodoc: all
|
147
|
+
|
148
|
+
def expdate(creditcard)
|
149
|
+
sprintf("%.4i", creditcard.year)[-2..-1] + sprintf("%.2i", creditcard.month)
|
150
|
+
end
|
151
|
+
|
152
|
+
def add_payment_source(post, source, options)
|
153
|
+
if source.is_a?(String)
|
154
|
+
post[:data_key] = source
|
155
|
+
post[:cust_id] = options[:customer]
|
156
|
+
else
|
157
|
+
if source.respond_to?(:track_data) && source.track_data.present?
|
158
|
+
post[:pos_code] = '00'
|
159
|
+
post[:track2] = source.track_data
|
160
|
+
else
|
161
|
+
post[:pan] = source.number
|
162
|
+
post[:expdate] = expdate(source)
|
163
|
+
post[:cvd_value] = source.verification_value if source.verification_value?
|
164
|
+
end
|
165
|
+
post[:cust_id] = options[:customer] || source.name
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Common params used amongst the +credit+, +void+ and +capture+ methods
|
170
|
+
def crediting_params(authorization, options = {})
|
171
|
+
{
|
172
|
+
:txn_number => split_authorization(authorization).first,
|
173
|
+
:order_id => split_authorization(authorization).last,
|
174
|
+
:crypt_type => options[:crypt_type] || @options[:crypt_type]
|
175
|
+
}.merge(options)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Splits an +authorization+ param and retrieves the order id and
|
179
|
+
# transaction number in that order.
|
180
|
+
def split_authorization(authorization)
|
181
|
+
if authorization.nil? || authorization.empty? || authorization !~ /;/
|
182
|
+
raise ArgumentError, 'You must include a valid authorization code (e.g. "1234;567")'
|
183
|
+
else
|
184
|
+
authorization.split(';')
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def commit(action, parameters = {})
|
189
|
+
data = post_data(action, parameters)
|
190
|
+
url = test? ? self.test_url : self.live_url
|
191
|
+
raw = ssl_post(url, data)
|
192
|
+
response = parse(raw)
|
193
|
+
|
194
|
+
Response.new(successful?(response), message_from(response[:message]), response,
|
195
|
+
:test => test?,
|
196
|
+
:avs_result => { :code => response[:avs_result_code] },
|
197
|
+
:cvv_result => response[:cvd_result_code] && response[:cvd_result_code][-1,1],
|
198
|
+
:authorization => authorization_from(response)
|
199
|
+
)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Generates a Moneris authorization string of the form 'trans_id;receipt_id'.
|
203
|
+
def authorization_from(response = {})
|
204
|
+
if response[:trans_id] && response[:receipt_id]
|
205
|
+
"#{response[:trans_id]};#{response[:receipt_id]}"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Tests for a successful response from Moneris' servers
|
210
|
+
def successful?(response)
|
211
|
+
response[:response_code] &&
|
212
|
+
response[:complete] &&
|
213
|
+
(0..49).include?(response[:response_code].to_i)
|
214
|
+
end
|
215
|
+
|
216
|
+
def parse(xml)
|
217
|
+
response = { :message => "Global Error Receipt", :complete => false }
|
218
|
+
hashify_xml!(xml, response)
|
219
|
+
response
|
220
|
+
end
|
221
|
+
|
222
|
+
def hashify_xml!(xml, response)
|
223
|
+
xml = REXML::Document.new(xml)
|
224
|
+
return if xml.root.nil?
|
225
|
+
xml.elements.each('//receipt/*') do |node|
|
226
|
+
response[node.name.underscore.to_sym] = normalize(node.text)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def post_data(action, parameters = {})
|
231
|
+
xml = REXML::Document.new
|
232
|
+
root = xml.add_element("request")
|
233
|
+
root.add_element("store_id").text = options[:login]
|
234
|
+
root.add_element("api_token").text = options[:password]
|
235
|
+
root.add_element(transaction_element(action, parameters))
|
236
|
+
|
237
|
+
xml.to_s
|
238
|
+
end
|
239
|
+
|
240
|
+
def transaction_element(action, parameters)
|
241
|
+
transaction = REXML::Element.new(action)
|
242
|
+
|
243
|
+
# Must add the elements in the correct order
|
244
|
+
actions[action].each do |key|
|
245
|
+
case key
|
246
|
+
when :avs_info
|
247
|
+
transaction.add_element(avs_element(parameters[:address])) if @avs_enabled && parameters[:address]
|
248
|
+
when :cvd_info
|
249
|
+
transaction.add_element(cvd_element(parameters[:cvd_value])) if @cvv_enabled
|
250
|
+
else
|
251
|
+
transaction.add_element(key.to_s).text = parameters[key] unless parameters[key].blank?
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
transaction
|
256
|
+
end
|
257
|
+
|
258
|
+
def avs_element(address)
|
259
|
+
full_address = "#{address[:address1]} #{address[:address2]}"
|
260
|
+
tokens = full_address.split(/\s+/)
|
261
|
+
|
262
|
+
element = REXML::Element.new('avs_info')
|
263
|
+
element.add_element('avs_street_number').text = tokens.select{|x| x =~ /\d/}.join(' ')
|
264
|
+
element.add_element('avs_street_name').text = tokens.reject{|x| x =~ /\d/}.join(' ')
|
265
|
+
element.add_element('avs_zipcode').text = address[:zip]
|
266
|
+
element
|
267
|
+
end
|
268
|
+
|
269
|
+
def cvd_element(cvd_value)
|
270
|
+
element = REXML::Element.new('cvd_info')
|
271
|
+
if cvd_value
|
272
|
+
element.add_element('cvd_indicator').text = "1"
|
273
|
+
element.add_element('cvd_value').text = cvd_value
|
274
|
+
else
|
275
|
+
element.add_element('cvd_indicator').text = "0"
|
276
|
+
end
|
277
|
+
element
|
278
|
+
end
|
279
|
+
|
280
|
+
def message_from(message)
|
281
|
+
return 'Unspecified error' if message.blank?
|
282
|
+
message.gsub(/[^\w]/, ' ').split.join(" ").capitalize
|
283
|
+
end
|
284
|
+
|
285
|
+
def actions
|
286
|
+
{
|
287
|
+
"purchase" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :avs_info, :cvd_info, :track2, :pos_code],
|
288
|
+
"preauth" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :avs_info, :cvd_info, :track2, :pos_code],
|
289
|
+
"command" => [:order_id],
|
290
|
+
"refund" => [:order_id, :amount, :txn_number, :crypt_type],
|
291
|
+
"indrefund" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
|
292
|
+
"completion" => [:order_id, :comp_amount, :txn_number, :crypt_type],
|
293
|
+
"purchasecorrection" => [:order_id, :txn_number, :crypt_type],
|
294
|
+
"cavvpurcha" => [:order_id, :cust_id, :amount, :pan, :expdate, :cav],
|
295
|
+
"cavvpreaut" => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv],
|
296
|
+
"transact" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
|
297
|
+
"Batchcloseall" => [],
|
298
|
+
"opentotals" => [:ecr_number],
|
299
|
+
"batchclose" => [:ecr_number],
|
300
|
+
"res_add_cc" => [:pan, :expdate, :crypt_type],
|
301
|
+
"res_delete" => [:data_key],
|
302
|
+
"res_update_cc" => [:data_key, :pan, :expdate, :crypt_type],
|
303
|
+
"res_purchase_cc" => [:data_key, :order_id, :cust_id, :amount, :crypt_type],
|
304
|
+
"res_preauth_cc" => [:data_key, :order_id, :cust_id, :amount, :crypt_type]
|
305
|
+
}
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|