yetanothernguyen-activemerchant 1.16.0 → 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 (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
+