yetanothernguyen-activemerchant 1.16.0 → 1.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/CHANGELOG +95 -0
  2. data/CONTRIBUTORS +29 -0
  3. data/lib/active_merchant/billing/credit_card.rb +105 -19
  4. data/lib/active_merchant/billing/credit_card_methods.rb +5 -1
  5. data/lib/active_merchant/billing/gateway.rb +1 -1
  6. data/lib/active_merchant/billing/gateways/authorize_net.rb +24 -2
  7. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +104 -18
  8. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +110 -4
  9. data/lib/active_merchant/billing/gateways/beanstream.rb +29 -1
  10. data/lib/active_merchant/billing/gateways/braintree_blue.rb +9 -4
  11. data/lib/active_merchant/billing/gateways/braintree_orange.rb +4 -0
  12. data/lib/active_merchant/billing/gateways/card_save.rb +23 -0
  13. data/lib/active_merchant/billing/gateways/certo_direct.rb +279 -0
  14. data/lib/active_merchant/billing/gateways/efsnet.rb +9 -9
  15. data/lib/active_merchant/billing/gateways/elavon.rb +2 -1
  16. data/lib/active_merchant/billing/gateways/epay.rb +12 -6
  17. data/lib/active_merchant/billing/gateways/eway_managed.rb +46 -12
  18. data/lib/active_merchant/billing/gateways/exact.rb +5 -0
  19. data/lib/active_merchant/billing/gateways/ideal/ideal_base.rb +3 -3
  20. data/lib/active_merchant/billing/gateways/ipay88.rb +157 -0
  21. data/lib/active_merchant/billing/gateways/iridium.rb +3 -3
  22. data/lib/active_merchant/billing/gateways/itransact.rb +450 -0
  23. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +1 -0
  24. data/lib/active_merchant/billing/gateways/moneris.rb +4 -0
  25. data/lib/active_merchant/billing/gateways/nab_transact.rb +244 -0
  26. data/lib/active_merchant/billing/gateways/ogone.rb +94 -56
  27. data/lib/active_merchant/billing/gateways/optimal_payment.rb +277 -0
  28. data/lib/active_merchant/billing/gateways/orbital.rb +57 -34
  29. data/lib/active_merchant/billing/gateways/pay_junction.rb +6 -1
  30. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
  31. data/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb +2 -2
  32. data/lib/active_merchant/billing/gateways/payflow.rb +10 -2
  33. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +8 -5
  34. data/lib/active_merchant/billing/gateways/paypal_digital_goods.rb +43 -0
  35. data/lib/active_merchant/billing/gateways/paypal_express.rb +93 -40
  36. data/lib/active_merchant/billing/gateways/paypal_express_common.rb +8 -3
  37. data/lib/active_merchant/billing/gateways/qbms.rb +4 -0
  38. data/lib/active_merchant/billing/gateways/quickpay.rb +97 -22
  39. data/lib/active_merchant/billing/gateways/realex.rb +5 -1
  40. data/lib/active_merchant/billing/gateways/samurai.rb +121 -0
  41. data/lib/active_merchant/billing/gateways/secure_pay_au.rb +136 -49
  42. data/lib/active_merchant/billing/gateways/secure_pay_tech.rb +1 -1
  43. data/lib/active_merchant/billing/gateways/skip_jack.rb +7 -2
  44. data/lib/active_merchant/billing/gateways/stripe.rb +51 -19
  45. data/lib/active_merchant/billing/gateways/usa_epay.rb +13 -184
  46. data/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +1496 -0
  47. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +206 -0
  48. data/lib/active_merchant/billing/gateways/verifi.rb +2 -2
  49. data/lib/active_merchant/billing/gateways/viaklix.rb +1 -1
  50. data/lib/active_merchant/billing/gateways/worldpay.rb +1 -1
  51. data/lib/active_merchant/billing/integrations/action_view_helper.rb +6 -2
  52. data/lib/active_merchant/billing/integrations/authorize_net_sim/helper.rb +228 -0
  53. data/lib/active_merchant/billing/integrations/authorize_net_sim/notification.rb +340 -0
  54. data/lib/active_merchant/billing/integrations/authorize_net_sim.rb +38 -0
  55. data/lib/active_merchant/billing/integrations/direc_pay/helper.rb +4 -4
  56. data/lib/active_merchant/billing/integrations/dwolla/helper.rb +31 -0
  57. data/lib/active_merchant/billing/integrations/dwolla/notification.rb +55 -0
  58. data/lib/active_merchant/billing/integrations/dwolla/return.rb +38 -0
  59. data/lib/active_merchant/billing/integrations/dwolla.rb +30 -0
  60. data/lib/active_merchant/billing/integrations/helper.rb +19 -2
  61. data/lib/active_merchant/billing/integrations/ipay88/helper.rb +120 -0
  62. data/lib/active_merchant/billing/integrations/ipay88/return.rb +121 -0
  63. data/lib/active_merchant/billing/integrations/ipay88.rb +40 -0
  64. data/lib/active_merchant/billing/integrations/nochex.rb +1 -1
  65. data/lib/active_merchant/billing/integrations/payflow_link/helper.rb +100 -0
  66. data/lib/active_merchant/billing/integrations/payflow_link/notification.rb +78 -0
  67. data/lib/active_merchant/billing/integrations/payflow_link.rb +21 -0
  68. data/lib/active_merchant/billing/integrations/sage_pay_form/encryption.rb +4 -4
  69. data/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb +18 -2
  70. data/lib/active_merchant/billing/integrations/two_checkout.rb +1 -2
  71. data/lib/active_merchant/railtie.rb +7 -7
  72. data/lib/active_merchant/railtie.rb.orig +19 -0
  73. data/lib/active_merchant/version.rb +1 -1
  74. data/lib/active_merchant.rb +23 -10
  75. data/lib/active_merchant.rb.orig +78 -0
  76. metadata +147 -63
  77. data/lib/active_merchant/common/connection.rb +0 -177
  78. data/lib/active_merchant/common/country.rb +0 -328
  79. data/lib/active_merchant/common/error.rb +0 -26
  80. data/lib/active_merchant/common/post_data.rb +0 -24
  81. data/lib/active_merchant/common/posts_data.rb +0 -63
  82. data/lib/active_merchant/common/requires_parameters.rb +0 -16
  83. data/lib/active_merchant/common/utils.rb +0 -22
  84. data/lib/active_merchant/common/validateable.rb +0 -81
  85. data/lib/active_merchant/common.rb +0 -14
  86. data/lib/certs/cacert.pem +0 -7815
@@ -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
@@ -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
 
@@ -90,7 +112,11 @@ module ActiveMerchant #:nodoc:
90
112
 
91
113
  private
92
114
  def purchase_action(source)
93
- (card_brand(source) == "check") ? :check_purchase : :purchase
115
+ if source.is_a?(Check)
116
+ :check_purchase
117
+ else
118
+ :purchase
119
+ end
94
120
  end
95
121
 
96
122
  def void_action(original_transaction_type)
@@ -203,11 +229,66 @@ module ActiveMerchant #:nodoc:
203
229
  post[:status] = options[:status]
204
230
  end
205
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
+
206
287
  def parse(body)
207
288
  results = {}
208
289
  if !body.nil?
209
290
  body.split(/&/).each do |pair|
210
- key,val = pair.split(/=/)
291
+ key, val = pair.split(/=/)
211
292
  results[key.to_sym] = val.nil? ? nil : CGI.unescape(val)
212
293
  end
213
294
  end
@@ -221,11 +302,22 @@ module ActiveMerchant #:nodoc:
221
302
 
222
303
  results
223
304
  end
224
-
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
+
225
313
  def commit(params, use_profile_api = false)
226
314
  post(post_data(params,use_profile_api),use_profile_api)
227
315
  end
228
316
 
317
+ def recurring_commit(params)
318
+ recurring_post(post_data(params, false))
319
+ end
320
+
229
321
  def post(data, use_profile_api=nil)
230
322
  response = parse(ssl_post((use_profile_api ? SECURE_PROFILE_URL : URL), data))
231
323
  response[:customer_vault_id] = response[:customerCode] if response[:customerCode]
@@ -236,7 +328,12 @@ module ActiveMerchant #:nodoc:
236
328
  :avs_result => { :code => (AVS_CODES.include? response[:avsId]) ? AVS_CODES[response[:avsId]] : response[:avsId] }
237
329
  )
238
330
  end
239
-
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
+
240
337
  def authorization_from(response)
241
338
  "#{response[:trnId]};#{response[:trnAmount]};#{response[:trnType]}"
242
339
  end
@@ -245,10 +342,18 @@ module ActiveMerchant #:nodoc:
245
342
  response[:messageText] || response[:responseMessage]
246
343
  end
247
344
 
345
+ def recurring_message_from(response)
346
+ response[:message]
347
+ end
348
+
248
349
  def success?(response)
249
350
  response[:responseType] == 'R' || response[:trnApproved] == '1' || response[:responseCode] == '1'
250
351
  end
251
352
 
353
+ def recurring_success?(response)
354
+ response[:code] == '1'
355
+ end
356
+
252
357
  def add_source(post, source)
253
358
  if source.is_a?(String) or source.is_a?(Integer)
254
359
  post[:customerCode] = source
@@ -276,6 +381,7 @@ module ActiveMerchant #:nodoc:
276
381
 
277
382
  params.reject{|k, v| v.blank?}.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
278
383
  end
384
+
279
385
  end
280
386
  end
281
387
  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
@@ -18,10 +18,11 @@ module ActiveMerchant #:nodoc:
18
18
  def initialize(options = {})
19
19
  requires!(options, :merchant_id, :public_key, :private_key)
20
20
  @options = options
21
+ @merchant_account_id = options[:merchant_account_id]
21
22
  Braintree::Configuration.merchant_id = options[:merchant_id]
22
23
  Braintree::Configuration.public_key = options[:public_key]
23
24
  Braintree::Configuration.private_key = options[:private_key]
24
- Braintree::Configuration.environment = test? ? :sandbox : :production
25
+ Braintree::Configuration.environment = (options[:environment] || (test? ? :sandbox : :production)).to_sym
25
26
  Braintree::Configuration.logger.level = Logger::ERROR if Braintree::Configuration.logger
26
27
  Braintree::Configuration.custom_user_agent = "ActiveMerchant #{ActiveMerchant::VERSION}"
27
28
  super
@@ -175,7 +176,11 @@ module ActiveMerchant #:nodoc:
175
176
  :postal_match => result.transaction.avs_postal_code_response_code
176
177
  }
177
178
  response_options[:cvv_result] = result.transaction.cvv_response_code
178
- message = "#{result.transaction.processor_response_code} #{result.transaction.processor_response_text}"
179
+ if result.transaction.status == "gateway_rejected"
180
+ message = "Transaction declined - gateway rejected"
181
+ else
182
+ message = "#{result.transaction.processor_response_code} #{result.transaction.processor_response_text}"
183
+ end
179
184
  else
180
185
  message = message_from_result(result)
181
186
  end
@@ -276,8 +281,8 @@ module ActiveMerchant #:nodoc:
276
281
  :submit_for_settlement => options[:submit_for_settlement]
277
282
  }
278
283
  }
279
- if options.has_key?(:merchant_account_id)
280
- parameters[:merchant_account_id] = options[:merchant_account_id]
284
+ if merchant_account_id = (options[:merchant_account_id] || @merchant_account_id)
285
+ parameters[:merchant_account_id] = merchant_account_id
281
286
  end
282
287
  if credit_card_or_vault_id.is_a?(String) || credit_card_or_vault_id.is_a?(Integer)
283
288
  parameters[:customer_id] = credit_card_or_vault_id
@@ -11,6 +11,10 @@ module ActiveMerchant #:nodoc:
11
11
  def api_url
12
12
  'https://secure.braintreepaymentgateway.com/api/transact.php'
13
13
  end
14
+
15
+ def add_processor(post, options)
16
+ post[:processor_id] = options[:processor] unless options[:processor].nil?
17
+ end
14
18
  end
15
19
  end
16
20
  end
@@ -0,0 +1,23 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class CardSaveGateway < IridiumGateway
4
+ #CardSave lets you handle failovers on payments by providing 3 gateways in case one happens to be down
5
+ #URLS = ['https://gw1.cardsaveonlinepayments.com:4430/','https://gw2.cardsaveonlinepayments.com:4430/','https://gw3.cardsaveonlinepayments.com:4430/']
6
+
7
+ self.money_format = :cents
8
+ self.default_currency = 'GBP'
9
+ self.supported_cardtypes = [ :visa, :switch, :maestro, :master, :solo, :american_express, :jcb ]
10
+ self.supported_countries = [ 'GB' ]
11
+ self.homepage_url = 'http://www.cardsave.net/'
12
+ self.display_name = 'CardSave'
13
+
14
+ def initialize(options={})
15
+ super
16
+ @test_url = 'https://gw1.cardsaveonlinepayments.com:4430/'
17
+ @live_url = 'https://gw1.cardsaveonlinepayments.com:4430/'
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+