activemerchant 1.20.4 → 1.21.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.
Files changed (36) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +29 -0
  3. data/CONTRIBUTORS +13 -0
  4. data/lib/active_merchant/billing/credit_card_methods.rb +1 -1
  5. data/lib/active_merchant/billing/gateway.rb +1 -1
  6. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +104 -18
  7. data/lib/active_merchant/billing/gateways/beanstream.rb +29 -1
  8. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +105 -3
  9. data/lib/active_merchant/billing/gateways/braintree_orange.rb +4 -0
  10. data/lib/active_merchant/billing/gateways/certo_direct.rb +279 -0
  11. data/lib/active_merchant/billing/gateways/epay.rb +2 -2
  12. data/lib/active_merchant/billing/gateways/eway_managed.rb +1 -0
  13. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +1 -0
  14. data/lib/active_merchant/billing/gateways/nab_transact.rb +244 -0
  15. data/lib/active_merchant/billing/gateways/payflow.rb +10 -2
  16. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
  17. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +1 -1
  18. data/lib/active_merchant/billing/gateways/paypal_digital_goods.rb +43 -0
  19. data/lib/active_merchant/billing/gateways/paypal_express.rb +36 -1
  20. data/lib/active_merchant/billing/gateways/paypal_express_common.rb +8 -3
  21. data/lib/active_merchant/billing/gateways/quickpay.rb +1 -0
  22. data/lib/active_merchant/billing/gateways/samurai.rb +1 -0
  23. data/lib/active_merchant/billing/gateways/secure_pay_au.rb +136 -49
  24. data/lib/active_merchant/billing/gateways/secure_pay_tech.rb +1 -1
  25. data/lib/active_merchant/billing/gateways/stripe.rb +23 -11
  26. data/lib/active_merchant/billing/gateways/verifi.rb +2 -2
  27. data/lib/active_merchant/billing/gateways/viaklix.rb +1 -1
  28. data/lib/active_merchant/billing/integrations/action_view_helper.rb +5 -1
  29. data/lib/active_merchant/billing/integrations/authorize_net_sim.rb +38 -0
  30. data/lib/active_merchant/billing/integrations/authorize_net_sim/helper.rb +228 -0
  31. data/lib/active_merchant/billing/integrations/authorize_net_sim/notification.rb +340 -0
  32. data/lib/active_merchant/billing/integrations/helper.rb +13 -1
  33. data/lib/active_merchant/billing/integrations/payflow_link/helper.rb +1 -1
  34. data/lib/active_merchant/version.rb +1 -1
  35. metadata +37 -31
  36. metadata.gz.sig +0 -0
data.tar.gz.sig CHANGED
Binary file
data/CHANGELOG CHANGED
@@ -1,5 +1,34 @@
1
1
  = ActiveMerchant CHANGELOG
2
2
 
3
+ == Version 1.21.0 (March 7, 2012)
4
+
5
+ * Stripe: Add support for passing IP [collision]
6
+ * Merchant e-Solutions: pass expiration date when purchasing with a stored credit card [chrisyoung]
7
+ * Braintree: Fix passing custom processor ids to old accounts [maxsilver]
8
+ * Authorize.net CIM: Add validation mode option to create_customer_profile_request [jwood]
9
+ * eWay Managed: Include transaction number in response params [jamsi]
10
+ * Fix various hash ordering issues exposed by Ruby 1.8 [ntalbott]
11
+ * Authorize.Net CIM: Add WEB echeck type [deathbob]
12
+ * Move Braintree from the gemspec to the Gemfile [ntalbott]
13
+ * Add CertoDirect gateway [hron]
14
+ * Authorize.Net CIM: Add option for setting a custom delimiter [bmorton]
15
+ * Authorize.Net CIM: Add 3.1 response fields [bmorton]
16
+ * Authorize.Net CIM: Misc fixes and doc improvements [bmorton]
17
+ * Authorize.Net CIM: Fix error when order is blank [KeeperPat]
18
+ * Beanstream: Add recurring payments support [castiglione]
19
+ * Make ePay password optional [ePay]
20
+ * Quickpay: skip testmode if transaction provided [brentmc79]
21
+ * Payflow: add additional fields [thorstadt]
22
+ * Authorize.Net CIM: Add get_customer_profile_ids [howaboutwe]
23
+ * PayPal Express: Add support for BrandName and Custom fields [exviva]
24
+ * Payflow: Handle dates with leading zeros [jcoleman]
25
+ * Authorize.Net CIM: Add CCV code support & improve tests [tgarnett]
26
+ * Add Authorize.Net SIM integration [courtland & rdp]
27
+ * Secure Pay AU: Handle periodic payments [tommeier]
28
+ * Viaklix: Add discover as a supported card type [waelchatila]
29
+ * Improvements to testing infrastructure for integrations [jduff]
30
+ * Add NAB Transact (AU) Gateway [tommeier]
31
+
3
32
  == Version 1.20.4 (February 22, 2012)
4
33
 
5
34
  * Fix json dependency
data/CONTRIBUTORS CHANGED
@@ -259,3 +259,16 @@ Dwolla (September, 2011)
259
259
  Samurai (November, 2011)
260
260
 
261
261
  * Joshua Krall (jkrall)
262
+
263
+ CertoDirect Gateway (February, 2012)
264
+
265
+ * Aleksei Gusev (hron)
266
+
267
+ Authorize.Net SIM Integration (February, 2012)
268
+
269
+ * Roger Pack (rdp)
270
+ * Nick Rogers (courtland)
271
+
272
+ NAB Transact (AU) Gateway (February, 2012)
273
+
274
+ * Tom Meier (tommeier)
@@ -14,7 +14,7 @@ module ActiveMerchant #:nodoc:
14
14
  'dankort' => /^5019\d{12}$/,
15
15
  'maestro' => /^(5[06-8]|6\d)\d{10,17}$/,
16
16
  'forbrugsforeningen' => /^600722\d{10}$/,
17
- 'laser' => /^(6304|6706|6771|6709)\d{8}(\d{4}|\d{6,7})?$/
17
+ 'laser' => /^(6304|6706|6709|6771(?!89))\d{8}(\d{4}|\d{6,7})?$/
18
18
  }
19
19
 
20
20
  def self.included(base)
@@ -53,7 +53,7 @@ module ActiveMerchant #:nodoc:
53
53
  #
54
54
  # == Implmenting new gateways
55
55
  #
56
- # See the {ActiveMerchant Guide to Contributing}[http://code.google.com/p/activemerchant/wiki/Contributing]
56
+ # See the {ActiveMerchant Guide to Contributing}[https://github.com/Shopify/active_merchant/wiki/Contributing]
57
57
  #
58
58
  class Gateway
59
59
  include PostsData
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  module ActiveMerchant #:nodoc:
2
3
  module Billing #:nodoc:
3
4
  # ==== Customer Information Manager (CIM)
@@ -39,6 +40,7 @@ module ActiveMerchant #:nodoc:
39
40
  :create_customer_payment_profile => 'createCustomerPaymentProfile',
40
41
  :create_customer_shipping_address => 'createCustomerShippingAddress',
41
42
  :get_customer_profile => 'getCustomerProfile',
43
+ :get_customer_profile_ids => 'getCustomerProfileIds',
42
44
  :get_customer_payment_profile => 'getCustomerPaymentProfile',
43
45
  :get_customer_shipping_address => 'getCustomerShippingAddress',
44
46
  :delete_customer_profile => 'deleteCustomerProfile',
@@ -75,7 +77,8 @@ module ActiveMerchant #:nodoc:
75
77
 
76
78
  ECHECK_TYPES = {
77
79
  :ccd => 'CCD',
78
- :ppd => 'PPD'
80
+ :ppd => 'PPD',
81
+ :web => 'WEB'
79
82
  }
80
83
 
81
84
  self.homepage_url = 'http://www.authorize.net/'
@@ -93,6 +96,7 @@ module ActiveMerchant #:nodoc:
93
96
  # * <tt>:login</tt> -- The Authorize.Net API Login ID (REQUIRED)
94
97
  # * <tt>:password</tt> -- The Authorize.Net Transaction Key. (REQUIRED)
95
98
  # * <tt>:test</tt> -- +true+ or +false+. If true, perform transactions against the test server.
99
+ # * <tt>:delimiter</tt> -- The delimiter used in the direct response. Default is ',' (comma).
96
100
  # Otherwise, perform transactions against the production server.
97
101
  def initialize(options = {})
98
102
  requires!(options, :login, :password)
@@ -107,11 +111,28 @@ module ActiveMerchant #:nodoc:
107
111
  # It is *CRITICAL* that you save this ID. There is no way to retrieve this through the API. You will not
108
112
  # be able to create another Customer Profile with the same information.
109
113
  #
114
+ #
115
+ #
110
116
  # ==== Options
117
+ #
118
+ # * <tt>:profile</tt> -- A hash containing at least one of the CONDITIONAL profile options below (REQUIRED)
119
+ #
120
+ # ==== Profile
111
121
  #
112
- # TODO
122
+ # * <tt>:email</tt> -- Email address associated with the customer profile (CONDITIONAL)
123
+ # * <tt>:description</tt> -- Description of the customer or customer profile (CONDITIONAL)
124
+ # * <tt>:merchant_customer_id</tt> -- Merchant assigned ID for the customer (CONDITIONAL)
125
+ # * <tt>:payment_profile</tt> -- A hash containing the elements of the new payment profile (optional)
126
+ #
127
+ # ==== Payment Profile
128
+ #
129
+ # * <tt>:payment</tt> -- A hash containing information on payment. Either :credit_card or :bank_account (optional)
113
130
  def create_customer_profile(options)
114
- # TODO Add requires
131
+ requires!(options, :profile)
132
+ requires!(options[:profile], :email) unless options[:profile][:merchant_customer_id] || options[:profile][:description]
133
+ requires!(options[:profile], :description) unless options[:profile][:email] || options[:profile][:merchant_customer_id]
134
+ requires!(options[:profile], :merchant_customer_id) unless options[:profile][:description] || options[:profile][:email]
135
+
115
136
  request = build_request(:create_customer_profile, options)
116
137
  commit(:create_customer_profile, request)
117
138
  end
@@ -203,6 +224,11 @@ module ActiveMerchant #:nodoc:
203
224
  commit(:get_customer_profile, request)
204
225
  end
205
226
 
227
+ def get_customer_profile_ids(options = {})
228
+ request = build_request(:get_customer_profile_ids, options)
229
+ commit(:get_customer_profile_ids, request)
230
+ end
231
+
206
232
  # Retrieve a customer payment profile for an existing customer profile.
207
233
  #
208
234
  # Returns a Response whose params hash contains all the payment profile information. Sensitive information such as credit card
@@ -332,7 +358,11 @@ module ActiveMerchant #:nodoc:
332
358
  # - :type = (:void, :refund, :prior_auth_capture) (REQUIRED)
333
359
  # - :type = (:auth_only, :capture_only, :auth_capture) (NOT USED)
334
360
  #
335
- # * <tt>customer_shipping_address_id</tt> -- Payment gateway assigned ID associated with the customer shipping address (CONDITIONAL)
361
+ # * <tt>:card_code</tt> -- CVV/CCV code (OPTIONAL)
362
+ # - :type = (:void, :refund, :prior_auth_capture) (NOT USED)
363
+ # - :type = (:auth_only, :capture_only, :auth_capture) (OPTIONAL)
364
+ #
365
+ # * <tt>:customer_shipping_address_id</tt> -- Payment gateway assigned ID associated with the customer shipping address (CONDITIONAL)
336
366
  # - :type = (:void, :refund) (OPTIONAL)
337
367
  # - :type = (:auth_only, :capture_only, :auth_capture) (NOT USED)
338
368
  # - :type = (:prior_auth_capture) (OPTIONAL)
@@ -383,6 +413,10 @@ module ActiveMerchant #:nodoc:
383
413
  #
384
414
  # * <tt>:bank_routing_number_masked</tt> -- The last four gidits of the routing number to be refunded (CONDITIONAL - must be used with :bank_account_number_masked)
385
415
  # * <tt>:bank_account_number_masked</tt> -- The last four digis of the bank account number to be refunded, Ex. XXXX1234 (CONDITIONAL - must be used with :bank_routing_number_masked)
416
+ #
417
+ # * <tt>:tax</tt> - A hash containing tax information for the refund (OPTIONAL - <tt>:amount</tt>, <tt>:name</tt> (31 characters), <tt>:description</tt> (255 characters))
418
+ # * <tt>:duty</tt> - A hash containting duty information for the refund (OPTIONAL - <tt>:amount</tt>, <tt>:name</tt> (31 characters), <tt>:description</tt> (255 characters))
419
+ # * <tt>:shipping</tt> - A hash containing shipping information for the refund (OPTIONAL - <tt>:amount</tt>, <tt>:name</tt> (31 characters), <tt>:description</tt> (255 characters))
386
420
  def create_customer_profile_transaction_for_refund(options)
387
421
  requires!(options, :transaction)
388
422
  options[:transaction][:type] = :refund
@@ -424,8 +458,9 @@ module ActiveMerchant #:nodoc:
424
458
  #
425
459
  # * <tt>:customer_profile_id</tt> -- The Customer Profile ID of the customer to use in this transaction. (REQUIRED)
426
460
  # * <tt>:customer_payment_profile_id</tt> -- The Customer Payment Profile ID of the Customer Payment Profile to be verified. (REQUIRED)
427
- # * <tt>:customer_address_id</tt> -- The Customer Address ID of the Customer Shipping Address to be verified.
428
- # * <tt>:validation_mode</tt> -- <tt>:live</tt> or <tt>:test</tt> In Test Mode, only field validation is performed.
461
+ # * <tt>:customer_address_id</tt> -- The Customer Address ID of the Customer Shipping Address to be verified. (OPTIONAL)
462
+ # * <tt>:card_code</tt> -- If the payment profile is a credit card, the CCV/CVV code to validate with (OPTIONAL)
463
+ # * <tt>:validation_mode</tt> -- <tt>:live</tt> or <tt>:test</tt> In Test Mode, only field validation is performed. (REQUIRED
429
464
  # In Live Mode, a transaction is generated and submitted to the processor with the amount of $0.01. If successful, the transaction is immediately voided. (REQUIRED)
430
465
  def validate_customer_payment_profile(options)
431
466
  requires!(options, :customer_profile_id, :customer_payment_profile_id, :validation_mode)
@@ -466,6 +501,14 @@ module ActiveMerchant #:nodoc:
466
501
  def build_create_customer_profile_request(xml, options)
467
502
  add_profile(xml, options[:profile])
468
503
 
504
+ xml.tag!('validationMode', CIM_VALIDATION_MODES[options[:validation_mode]]) if options[:validation_mode]
505
+
506
+ if options.has_key?(:payment_profile)
507
+ xml.tag!('paymentProfile') do
508
+ add_payment_profile(xml, options[:payment_profile])
509
+ end
510
+ end
511
+
469
512
  xml.target!
470
513
  end
471
514
 
@@ -513,6 +556,10 @@ module ActiveMerchant #:nodoc:
513
556
  xml.target!
514
557
  end
515
558
 
559
+ def build_get_customer_profile_ids_request(xml, options)
560
+ xml.target!
561
+ end
562
+
516
563
  def build_get_customer_payment_profile_request(xml, options)
517
564
  xml.tag!('customerProfileId', options[:customer_profile_id])
518
565
  xml.tag!('customerPaymentProfileId', options[:customer_payment_profile_id])
@@ -564,6 +611,7 @@ module ActiveMerchant #:nodoc:
564
611
  xml.tag!('customerProfileId', options[:customer_profile_id])
565
612
  xml.tag!('customerPaymentProfileId', options[:customer_payment_profile_id])
566
613
  xml.tag!('customerShippingAddressId', options[:customer_address_id]) if options[:customer_address_id]
614
+ tag_unless_blank(xml, 'cardCode', options[:card_code])
567
615
  xml.tag!('validationMode', CIM_VALIDATION_MODES[options[:validation_mode]]) if options[:validation_mode]
568
616
 
569
617
  xml.target!
@@ -606,7 +654,7 @@ module ActiveMerchant #:nodoc:
606
654
  tag_unless_blank(xml,'customerShippingAddressId', transaction[:customer_shipping_address_id])
607
655
  xml.tag!('transId', transaction[:trans_id])
608
656
  when :refund
609
- #TODO - add support for all the other options fields
657
+ #TODO - add lineItems and extraOptions fields
610
658
  xml.tag!('amount', transaction[:amount])
611
659
  tag_unless_blank(xml, 'customerProfileId', transaction[:customer_profile_id])
612
660
  tag_unless_blank(xml, 'customerPaymentProfileId', transaction[:customer_payment_profile_id])
@@ -615,6 +663,9 @@ module ActiveMerchant #:nodoc:
615
663
  tag_unless_blank(xml, 'bankRoutingNumberMasked', transaction[:bank_routing_number_masked])
616
664
  tag_unless_blank(xml, 'bankAccountNumberMasked', transaction[:bank_account_number_masked])
617
665
  xml.tag!('transId', transaction[:trans_id])
666
+ add_tax(xml, transaction[:tax]) if transaction[:tax]
667
+ add_duty(xml, transaction[:duty]) if transaction[:duty]
668
+ add_shipping(xml, transaction[:shipping]) if transaction[:shipping]
618
669
  when :prior_auth_capture
619
670
  xml.tag!('amount', transaction[:amount])
620
671
  xml.tag!('transId', transaction[:trans_id])
@@ -623,12 +674,37 @@ module ActiveMerchant #:nodoc:
623
674
  xml.tag!('customerProfileId', transaction[:customer_profile_id])
624
675
  xml.tag!('customerPaymentProfileId', transaction[:customer_payment_profile_id])
625
676
  xml.tag!('approvalCode', transaction[:approval_code]) if transaction[:type] == :capture_only
677
+ tag_unless_blank(xml, 'cardCode', transaction[:card_code])
626
678
  end
627
- add_order(xml, transaction[:order]) if transaction[:order]
679
+ add_order(xml, transaction[:order]) if transaction[:order].present?
628
680
  end
629
681
  end
630
682
  end
683
+
684
+ def add_tax(xml, tax)
685
+ xml.tag!('tax') do
686
+ xml.tag!('amount', tax[:amount]) if tax[:amount]
687
+ xml.tag!('name', tax[:name]) if tax[:name]
688
+ xml.tag!('description', tax[:description]) if tax[:description]
689
+ end
690
+ end
691
+
692
+ def add_duty(xml, duty)
693
+ xml.tag!('duty') do
694
+ xml.tag!('amount', duty[:amount]) if duty[:amount]
695
+ xml.tag!('name', duty[:name]) if duty[:name]
696
+ xml.tag!('description', duty[:description]) if duty[:description]
697
+ end
698
+ end
631
699
 
700
+ def add_shipping(xml, shipping)
701
+ xml.tag!('shipping') do
702
+ xml.tag!('amount', shipping[:amount]) if shipping[:amount]
703
+ xml.tag!('name', shipping[:name]) if shipping[:name]
704
+ xml.tag!('description', shipping[:description]) if shipping[:description]
705
+ end
706
+ end
707
+
632
708
  def add_order(xml, order)
633
709
  xml.tag!('order') do
634
710
  xml.tag!('invoiceNumber', order[:invoice_number]) if order[:invoice_number]
@@ -702,6 +778,10 @@ module ActiveMerchant #:nodoc:
702
778
  xml.tag!('cardNumber', credit_card.number)
703
779
  # The expiration date of the credit card used for the subscription
704
780
  xml.tag!('expirationDate', expdate(credit_card))
781
+ # Note that Authorize.net does not save CVV codes as part of the
782
+ # payment profile. Any transactions/validations after the payment
783
+ # profile is created that wish to use CVV verification must pass
784
+ # the CVV code to authorize.net again.
705
785
  xml.tag!('cardCode', credit_card.verification_value) if credit_card.verification_value?
706
786
  end
707
787
  end
@@ -756,24 +836,23 @@ module ActiveMerchant #:nodoc:
756
836
  message = response_params['messages']['message']['text']
757
837
  test_mode = test? || message =~ /Test Mode/
758
838
  success = response_params['messages']['result_code'] == 'Ok'
839
+ response_params['direct_response'] = parse_direct_response(response_params['direct_response']) if response_params['direct_response']
840
+ transaction_id = response_params['direct_response']['transaction_id'] if response_params['direct_response']
759
841
 
760
- response = Response.new(success, message, response_params,
842
+ Response.new(success, message, response_params,
761
843
  :test => test_mode,
762
- :authorization => response_params['customer_profile_id'] || (response_params['profile'] ? response_params['profile']['customer_profile_id'] : nil)
844
+ :authorization => transaction_id || response_params['customer_profile_id'] || (response_params['profile'] ? response_params['profile']['customer_profile_id'] : nil)
763
845
  )
764
-
765
- response.params['direct_response'] = parse_direct_response(response) if response.params['direct_response']
766
- response
767
846
  end
768
847
 
769
848
  def tag_unless_blank(xml, tag_name, data)
770
849
  xml.tag!(tag_name, data) unless data.blank? || data.nil?
771
850
  end
772
851
 
773
- def parse_direct_response(response)
774
- direct_response = {'raw' => response.params['direct_response']}
775
- direct_response_fields = response.params['direct_response'].split(',')
776
-
852
+ def parse_direct_response(params)
853
+ delimiter = @options[:delimiter] || ','
854
+ direct_response = {'raw' => params}
855
+ direct_response_fields = params.split(delimiter)
777
856
  direct_response.merge(
778
857
  {
779
858
  'response_code' => direct_response_fields[0],
@@ -815,7 +894,14 @@ module ActiveMerchant #:nodoc:
815
894
  'purchase_order_number' => direct_response_fields[36],
816
895
  'md5_hash' => direct_response_fields[37],
817
896
  'card_code' => direct_response_fields[38],
818
- 'cardholder_authentication_verification_response' => direct_response_fields[39]
897
+ 'cardholder_authentication_verification_response' => direct_response_fields[39],
898
+ # The following direct response fields are only available in version 3.1 of the
899
+ # transaction response. Check your merchant account settings for details.
900
+ 'account_number' => direct_response_fields[50] || '',
901
+ 'card_type' => direct_response_fields[51] || '',
902
+ 'split_tender_id' => direct_response_fields[52] || '',
903
+ 'requested_amount' => direct_response_fields[53] || '',
904
+ 'balance_on_card' => direct_response_fields[54] || '',
819
905
  }
820
906
  )
821
907
  end
@@ -19,7 +19,6 @@ module ActiveMerchant #:nodoc:
19
19
  # To learn more about storing credit cards with the Beanstream gateway, please read the BEAN_Payment_Profiles.pdf (I had to phone BeanStream to request it.)
20
20
  #
21
21
  # == Notes
22
- # * Recurring billing is not yet implemented.
23
22
  # * Adding of order products information is not implemented.
24
23
  # * Ensure that country and province data is provided as a code such as "CA", "US", "QC".
25
24
  # * login is the Beanstream merchant ID, username and password should be enabled in your Beanstream account and passed in using the <tt>:user</tt> and <tt>:password</tt> options.
@@ -95,6 +94,35 @@ module ActiveMerchant #:nodoc:
95
94
  commit(post)
96
95
  end
97
96
 
97
+ def recurring(money, source, options = {})
98
+ post = {}
99
+ add_amount(post, money)
100
+ add_invoice(post, options)
101
+ add_credit_card(post, source)
102
+ add_address(post, options)
103
+ add_transaction_type(post, purchase_action(source))
104
+ add_recurring_type(post, options)
105
+ commit(post)
106
+ end
107
+
108
+ def update_recurring(amount, source, options = {})
109
+ post = {}
110
+ add_recurring_amount(post, amount)
111
+ add_recurring_invoice(post, options)
112
+ add_credit_card(post, source)
113
+ add_address(post, options)
114
+ add_recurring_operation_type(post, :update)
115
+ add_recurring_service(post, options)
116
+ recurring_commit(post)
117
+ end
118
+
119
+ def cancel_recurring(options = {})
120
+ post = {}
121
+ add_recurring_operation_type(post, :cancel)
122
+ add_recurring_service(post, options)
123
+ recurring_commit(post)
124
+ end
125
+
98
126
  def interac
99
127
  @interac ||= BeanstreamInteracGateway.new(@options)
100
128
  end
@@ -2,6 +2,7 @@ module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  module BeanstreamCore
4
4
  URL = 'https://www.beanstream.com/scripts/process_transaction.asp'
5
+ RECURRING_URL = 'https://www.beanstream.com/scripts/recurring_billing.asp'
5
6
  SECURE_PROFILE_URL = 'https://www.beanstream.com/scripts/payment_profile.asp'
6
7
  SP_SERVICE_VERSION = '1.1'
7
8
 
@@ -37,6 +38,27 @@ module ActiveMerchant #:nodoc:
37
38
  '9' => 'I'
38
39
  }
39
40
 
41
+ PERIODS = {
42
+ :days => 'D',
43
+ :weeks => 'W',
44
+ :months => 'M',
45
+ :years => 'Y'
46
+ }
47
+
48
+ PERIODICITIES = {
49
+ :daily => [:days, 1],
50
+ :weekly => [:weeks, 1],
51
+ :biweekly => [:weeks, 2],
52
+ :monthly => [:months, 1],
53
+ :bimonthly => [:months, 2],
54
+ :yearly => [:years, 1]
55
+ }
56
+
57
+ RECURRING_OPERATION = {
58
+ :update => 'M',
59
+ :cancel => 'C'
60
+ }
61
+
40
62
  def self.included(base)
41
63
  base.default_currency = 'CAD'
42
64
 
@@ -207,11 +229,66 @@ module ActiveMerchant #:nodoc:
207
229
  post[:status] = options[:status]
208
230
  end
209
231
 
232
+ def add_recurring_amount(post, money)
233
+ post[:amount] = amount(money)
234
+ end
235
+
236
+ def add_recurring_invoice(post, options)
237
+ post[:rbApplyTax1] = options[:apply_tax1]
238
+ end
239
+
240
+ def add_recurring_operation_type(post, operation)
241
+ post[:operationType] = RECURRING_OPERATION[operation]
242
+ end
243
+
244
+ def add_recurring_service(post, options)
245
+ post[:serviceVersion] = '1.0'
246
+ post[:merchantId] = @options[:login]
247
+ post[:passCode] = @options[:recurring_api_key]
248
+ post[:rbAccountId] = options[:account_id]
249
+ end
250
+
251
+ def add_recurring_type(post, options)
252
+ # XXX requires!
253
+ post[:trnRecurring] = 1
254
+ period, increment = interval(options)
255
+ post[:rbBillingPeriod] = PERIODS[period]
256
+ post[:rbBillingIncrement] = increment
257
+
258
+ if options.include? :start_date
259
+ post[:rbCharge] = 0
260
+ post[:rbFirstBilling] = options[:start_date].strftime('%m%d%Y')
261
+ end
262
+
263
+ if count = options[:occurrences] || options[:payments]
264
+ post[:rbExpiry] = (options[:start_date] || Date.current).advance(period => count).strftime('%m%d%Y')
265
+ end
266
+ end
267
+
268
+ def interval(options)
269
+ if options.include? :periodicity
270
+ requires!(options, [:periodicity, *PERIODICITIES.keys])
271
+ PERIODICITIES[options[:periodicity]]
272
+ elsif options.include? :interval
273
+ interval = options[:interval]
274
+ if interval.respond_to? :parts
275
+ parts = interval.parts
276
+ raise ArgumentError.new("Cannot recur with mixed interval (#{interval}). Use only one of: days, weeks, months or years") if parts.length > 1
277
+ parts.first
278
+ elsif interval.kind_of? Hash
279
+ requires!(interval, :unit)
280
+ unit, length = interval.values_at(:unit, :length)
281
+ length ||= 1
282
+ [unit, length]
283
+ end
284
+ end
285
+ end
286
+
210
287
  def parse(body)
211
288
  results = {}
212
289
  if !body.nil?
213
290
  body.split(/&/).each do |pair|
214
- key,val = pair.split(/=/)
291
+ key, val = pair.split(/=/)
215
292
  results[key.to_sym] = val.nil? ? nil : CGI.unescape(val)
216
293
  end
217
294
  end
@@ -225,11 +302,22 @@ module ActiveMerchant #:nodoc:
225
302
 
226
303
  results
227
304
  end
228
-
305
+
306
+ def recurring_parse(data)
307
+ REXML::Document.new(data).root.elements.to_a.inject({}) do |response, element|
308
+ response[element.name.to_sym] = element.text
309
+ response
310
+ end
311
+ end
312
+
229
313
  def commit(params, use_profile_api = false)
230
314
  post(post_data(params,use_profile_api),use_profile_api)
231
315
  end
232
316
 
317
+ def recurring_commit(params)
318
+ recurring_post(post_data(params, false))
319
+ end
320
+
233
321
  def post(data, use_profile_api=nil)
234
322
  response = parse(ssl_post((use_profile_api ? SECURE_PROFILE_URL : URL), data))
235
323
  response[:customer_vault_id] = response[:customerCode] if response[:customerCode]
@@ -240,7 +328,12 @@ module ActiveMerchant #:nodoc:
240
328
  :avs_result => { :code => (AVS_CODES.include? response[:avsId]) ? AVS_CODES[response[:avsId]] : response[:avsId] }
241
329
  )
242
330
  end
243
-
331
+
332
+ def recurring_post(data)
333
+ response = recurring_parse(ssl_post(RECURRING_URL, data))
334
+ build_response(recurring_success?(response), recurring_message_from(response), response)
335
+ end
336
+
244
337
  def authorization_from(response)
245
338
  "#{response[:trnId]};#{response[:trnAmount]};#{response[:trnType]}"
246
339
  end
@@ -249,10 +342,18 @@ module ActiveMerchant #:nodoc:
249
342
  response[:messageText] || response[:responseMessage]
250
343
  end
251
344
 
345
+ def recurring_message_from(response)
346
+ response[:message]
347
+ end
348
+
252
349
  def success?(response)
253
350
  response[:responseType] == 'R' || response[:trnApproved] == '1' || response[:responseCode] == '1'
254
351
  end
255
352
 
353
+ def recurring_success?(response)
354
+ response[:code] == '1'
355
+ end
356
+
256
357
  def add_source(post, source)
257
358
  if source.is_a?(String) or source.is_a?(Integer)
258
359
  post[:customerCode] = source
@@ -280,6 +381,7 @@ module ActiveMerchant #:nodoc:
280
381
 
281
382
  params.reject{|k, v| v.blank?}.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
282
383
  end
384
+
283
385
  end
284
386
  end
285
387
  end