diff --git a/lib/authorize_net.rb b/lib/authorize_net.rb index d87c243..e6f2932 100644 --- a/lib/authorize_net.rb +++ b/lib/authorize_net.rb @@ -52,6 +52,11 @@ # ARB +require "authorize_net/arb/paging" +require "authorize_net/arb/sorting" +require "authorize_net/arb/subscription_list_response" +require "authorize_net/arb/subscription_detail" +require "authorize_net/arb/fields" require "authorize_net/arb/subscription" require "authorize_net/arb/response" require "authorize_net/arb/transaction" diff --git a/lib/authorize_net/arb/fields.rb b/lib/authorize_net/arb/fields.rb new file mode 100644 index 0000000..a978e3a --- /dev/null +++ b/lib/authorize_net/arb/fields.rb @@ -0,0 +1,24 @@ +module AuthorizeNet::ARB + module Fields + EntityDescription = Struct.new(:node_structure, :entity_class, :arg_mapping) + + SUBSCRIPTION_DETAIL_ENTITY_DESCRIPTION = EntityDescription.new( + [ + {:id => :id}, + {:name => :name}, + {:status => :status}, + {:createTimeStampUTC => :create_time_stamp_utc}, + {:firstName => :first_name}, + {:lastName => :last_name}, + {:totalOccurrences => :total_occurrences}, + {:pastOccurrences => :past_occurrences}, + {:paymentMethod => :payment_method}, + {:accountNumber => :account_number}, + {:invoice => :invoice}, + {:amount => :amount}, + {:currencyId => :currency_id} + ], + AuthorizeNet::ARB::SubscriptionDetail + ) + end +end diff --git a/lib/authorize_net/arb/paging.rb b/lib/authorize_net/arb/paging.rb new file mode 100644 index 0000000..ff0407f --- /dev/null +++ b/lib/authorize_net/arb/paging.rb @@ -0,0 +1,33 @@ +module AuthorizeNet::ARB + + class Paging + + # Models Paging + include AuthorizeNet::Model + + attr_accessor :offset,:limit + + # Initializes Paging object. + # + # Typical usage: + # paging = AuthorizeNet::ARB::Paging.new(1,1000) + # + # Valid values for offset: 1 to 100000 + # Valid values for limit: 1 to 1000 + # + def initialize(offset,limit) + @offset = offset + @limit = limit + end + + def to_hash + hash = { + :offset => @offset, + :limit => @limit + } + hash.delete_if {|k, v| v.nil?} + end + + end + +end diff --git a/lib/authorize_net/arb/sorting.rb b/lib/authorize_net/arb/sorting.rb new file mode 100644 index 0000000..399c0dc --- /dev/null +++ b/lib/authorize_net/arb/sorting.rb @@ -0,0 +1,43 @@ +module AuthorizeNet::ARB + + class Sorting + + # Models Sorting + include AuthorizeNet::Model + + attr_accessor :order_by, :order_descending + + # Initializes Sorting object. + # + # Typical usage: + # sorting = AuthorizeNet::ARB::Sorting.new('name',true) + # + # Valid values for order_by values of the AuthorizeNet::ARB::Sorting: + # id + # name + # status + # createTimeStampUTC + # lastName + # firstName + # accountNumber + # amount + # pastOccurrences + # + # Valid values for order_descending: true, false, 1, 0 + # + def initialize(order_by, order_descending) + @order_by = order_by + @order_descending = order_descending + end + + def to_hash + hash = { + :order_by => @order_by, + :order_descending => @order_descending + } + hash.delete_if {|k, v| v.nil?} + end + + end + +end diff --git a/lib/authorize_net/arb/subscription_detail.rb b/lib/authorize_net/arb/subscription_detail.rb new file mode 100644 index 0000000..2d31bbd --- /dev/null +++ b/lib/authorize_net/arb/subscription_detail.rb @@ -0,0 +1,14 @@ +module AuthorizeNet::ARB + + # Models Subscription Detail. + class SubscriptionDetail + + include AuthorizeNet::Model + + attr_accessor :id, :name, :status, :create_time_stamp_utc, :first_name, + :last_name, :total_occurrences, :past_occurrences, + :payment_method, :account_number, :invoice, :amount, :currency_id + + end + +end diff --git a/lib/authorize_net/arb/subscription_list_response.rb b/lib/authorize_net/arb/subscription_list_response.rb new file mode 100644 index 0000000..feea4a4 --- /dev/null +++ b/lib/authorize_net/arb/subscription_list_response.rb @@ -0,0 +1,43 @@ +module AuthorizeNet::ARB + + class SubscriptionListResponse < AuthorizeNet::XmlResponse + # Constructs a new response object from a +raw_response. You don't typically + # construct this object yourself, as AuthorizeNet::ARB::Transaction will + # build one for you when it makes the request to the gateway. + def initialize(raw_response, transaction) + super + unless connection_failure? + begin + @subscription_details = @root.at_css('subscriptionDetails') + @subscription_detail = @root.at_css('subscriptionDetail') + @total_num_in_resultset = node_content_unless_nil(@root.at_css('totalNumInResultSet')) + + rescue + @raw_response = $! + end + end + end + + # Returns total number of subscriptions matching the search criteria + def total_num_in_resultset + @total_num_in_resultset + end + + # Returns an Array of SubscriptionDetail objects built from the entities returned in the response. Returns nil if no subscriptions were returned. + def subscription_details + unless @subscription_details.nil? + subscription_details = [] + @subscription_details.element_children.each do |child| + unless child.nil? + subscription_detail = build_entity(child, Fields::SUBSCRIPTION_DETAIL_ENTITY_DESCRIPTION) + + subscription_details <<= subscription_detail + end + end + return subscription_details unless subscription_details.length == 0 + end + end + + end + +end diff --git a/lib/authorize_net/arb/transaction.rb b/lib/authorize_net/arb/transaction.rb index 019d2d1..7ad525f 100644 --- a/lib/authorize_net/arb/transaction.rb +++ b/lib/authorize_net/arb/transaction.rb @@ -22,8 +22,7 @@ class Transaction < AuthorizeNet::XmlTransaction @@date_fields = [:subscription_start_date] # The class to wrap our response in. - @response_class = AuthorizeNet::ARB::Response - + @response_class = AuthorizeNet::ARB::Response # Constructs an ARB transaction. You can use the new ARB transaction object # to issue a request to the payment gateway and parse the response into a new @@ -106,7 +105,39 @@ def get_status(subscription_id) handle_subscription_id(subscription_id) run end - + + # Sets up and submits a subscription list query (ARBGetSubscriptionListRequest). Returns a response object + # which contains the list of subscription details and the total number of subscriptions matching the search + # criteria. Sorting and Paging are optional parameters. + # + # Valid values for search type parameter: + # cardExpiringThisMonth + # subscriptionActive + # subscriptionExpiringThisMonth + # subscriptionInactive + # + # Typical usage: + # + # sorting = AuthorizeNet::ARB::Sorting.new('name',true) + # paging = AuthorizeNet::ARB::Paging.new(1,1000) + # response = transaction.get_subscription_list('subscriptionActive',sorting,paging) + # + def get_subscription_list(search_type, sorting = nil, paging = nil) + unless search_type.nil? + self.class.instance_variable_set(:@response_class,SubscriptionListResponse) + @type = Type::ARB_GET_SUBSCRIPTION_LIST + set_fields(:search_type => search_type.to_s) + unless sorting.nil? + set_fields(sorting.to_hash) + end + unless paging.nil? + set_fields(paging.to_hash) + end + run + end + return response + end + # Sets up and submits a subscription cancelation (ARBCancelSubscriptionRequest) transaction. Returns a response object. If the transaction # has already been run, it will return nil. # @@ -143,4 +174,4 @@ def make_request end end -end \ No newline at end of file +end diff --git a/lib/authorize_net/fields.rb b/lib/authorize_net/fields.rb index 55f280e..ecda7f3 100644 --- a/lib/authorize_net/fields.rb +++ b/lib/authorize_net/fields.rb @@ -165,7 +165,7 @@ module Fields module ARB # Contains the various lists of fields needed by the ARB API. module Fields - + # Describes the order and nesting of fields in the ARB Subscription XML. SUBSCRIPTION_FIELDS = {:subscription => [ {:name => :subscription_name}, @@ -251,13 +251,31 @@ module Fields {:refId => :reference_id}, {:subscriptionId => :subscription_id} ] + + # Describes the order and nesting of fields in the ARB ARBGetSubscriptionListRequest XML. + GET_SUBSCRIPTION_LIST_FIELDS = [ + {:refId => :reference_id}, + {:searchType => :search_type}, + {:sorting => [ + {:orderBy => :order_by}, + {:orderDescending => :order_descending} + ] + }, + {:paging => [ + {:limit => :limit}, + {:offset => :offset} + ] + } + ] FIELDS = { AuthorizeNet::XmlTransaction::Type::ARB_CREATE => CREATE_FIELDS, AuthorizeNet::XmlTransaction::Type::ARB_UPDATE => UPDATE_FIELDS, AuthorizeNet::XmlTransaction::Type::ARB_GET_STATUS => GET_STATUS_FIELDS, - AuthorizeNet::XmlTransaction::Type::ARB_CANCEL => CANCEL_FIELDS + AuthorizeNet::XmlTransaction::Type::ARB_CANCEL => CANCEL_FIELDS, + AuthorizeNet::XmlTransaction::Type::ARB_GET_SUBSCRIPTION_LIST => GET_SUBSCRIPTION_LIST_FIELDS } + end end @@ -746,4 +764,4 @@ module Fields } end end -end \ No newline at end of file +end diff --git a/lib/authorize_net/xml_transaction.rb b/lib/authorize_net/xml_transaction.rb index 4499df5..72d96e5 100644 --- a/lib/authorize_net/xml_transaction.rb +++ b/lib/authorize_net/xml_transaction.rb @@ -18,6 +18,7 @@ module Type ARB_UPDATE = "ARBUpdateSubscriptionRequest" ARB_GET_STATUS = "ARBGetSubscriptionStatusRequest" ARB_CANCEL = "ARBCancelSubscriptionRequest" + ARB_GET_SUBSCRIPTION_LIST = "ARBGetSubscriptionListRequest" CIM_CREATE_PROFILE = "createCustomerProfileRequest" CIM_CREATE_PAYMENT = "createCustomerPaymentProfileRequest" CIM_CREATE_ADDRESS = "createCustomerShippingAddressRequest" @@ -157,6 +158,7 @@ def build_nodes(builder, nodeList, data) multivalue = node[:_multivalue] conditional = node[:_conditional] value = node[nodeName] + unless conditional.nil? value = self.send(conditional, nodeName) end @@ -238,7 +240,7 @@ def make_request end fields = @fields - + builder = Nokogiri::XML::Builder.new(:encoding => 'utf-8') do |x| x.send(@type.to_sym, :xmlns => XML_NAMESPACE) { x.merchantAuthentication { @@ -248,8 +250,7 @@ def make_request build_nodes(x, self.class.const_get(:FIELDS)[@type], fields) } end - @xml = builder.to_xml - + @xml = builder.to_xml url = URI.parse(@gateway) request = Net::HTTP::Post.new(url.path) @@ -272,4 +273,4 @@ def make_request end end -end \ No newline at end of file +end diff --git a/spec/arb_spec.rb b/spec/arb_spec.rb index 9e8e33a..e5bf97b 100644 --- a/spec/arb_spec.rb +++ b/spec/arb_spec.rb @@ -13,7 +13,7 @@ warn "WARNING: Running w/o valid AuthorizeNet sandbox credentials. Create spec/credentials.yml." end end - + before do @gateway = :sandbox @subscription = AuthorizeNet::ARB::Subscription.new( @@ -96,6 +96,82 @@ response = update.update(subscription) response.success?.should be_truthy end + + it "should be able to retrieve list of subscriptions" do + transaction = AuthorizeNet::ARB::Transaction.new(@api_login, @api_key, :gateway => :sandbox) + sorting = AuthorizeNet::ARB::Sorting.new('name',true) + paging = AuthorizeNet::ARB::Paging.new(1,1000) + response = transaction.get_subscription_list('subscriptionActive',sorting,paging) + response.success?.should be_truthy + + unless response.subscription_details.nil? + response.subscription_details.length.should > 0 + else + warn "no subscriptons found - please create some." + end + end + + it "should return successful response for valid subscription list search types and sortBy fields" do + transaction = AuthorizeNet::ARB::Transaction.new(@api_login, @api_key, :gateway => :sandbox) + paging = AuthorizeNet::ARB::Paging.new(1,1000) + + # iterate over valid search types + [:cardExpiringThisMonth,:subscriptionActive,:subscriptionExpiringThisMonth, + :subscriptionInactive].each{ |searchType| + # iterate over valid sortBy fields + [:id,:name,:status,:createTimeStampUTC,:lastName,:firstName,:accountNumber,:amount, + :pastOccurrences].each{ |sortBy| + sorting = AuthorizeNet::ARB::Sorting.new(sortBy,false) + response = transaction.get_subscription_list(searchType,sorting,paging) + response.success?.should be_truthy + } + } + end + + it "should return error when invalid search type specified" do + transaction = AuthorizeNet::ARB::Transaction.new(@api_login, @api_key, :gateway => :sandbox) + paging = AuthorizeNet::ARB::Paging.new(1,1000) + sorting = AuthorizeNet::ARB::Sorting.new(:name,true) + + response = transaction.get_subscription_list(:bogusSearchType,sorting,paging) + response.success?.should be_falsey + end + + it "should return error when invalid sortBy field specified" do + transaction = AuthorizeNet::ARB::Transaction.new(@api_login, @api_key, :gateway => :sandbox) + paging = AuthorizeNet::ARB::Paging.new(1,1000) + sorting = AuthorizeNet::ARB::Sorting.new(:bogusSortField,true) + + response = transaction.get_subscription_list(:subscriptionActive,sorting,paging) + response.success?.should be_falsey + end + + it "should return error when invalid order descending parameter is specified" do + transaction = AuthorizeNet::ARB::Transaction.new(@api_login, @api_key, :gateway => :sandbox) + paging = AuthorizeNet::ARB::Paging.new(1,1000) + sorting = AuthorizeNet::ARB::Sorting.new(:name,2) + + response = transaction.get_subscription_list(:subscriptionActive,sorting,paging) + response.success?.should be_falsey + end + + it "should return error when invalid paging offset is specified" do + transaction = AuthorizeNet::ARB::Transaction.new(@api_login, @api_key, :gateway => :sandbox) + paging = AuthorizeNet::ARB::Paging.new(0,1000) + sorting = AuthorizeNet::ARB::Sorting.new(:name,false) + + response = transaction.get_subscription_list(:subscriptionActive,sorting,paging) + response.success?.should be_falsey + end + + it "should return error when invalid paging limit is specified" do + transaction = AuthorizeNet::ARB::Transaction.new(@api_login, @api_key, :gateway => :sandbox) + paging = AuthorizeNet::ARB::Paging.new(1,-1) + sorting = AuthorizeNet::ARB::Sorting.new(:name,false) + + response = transaction.get_subscription_list(:subscriptionActive,sorting,paging) + response.success?.should be_falsey + end end describe AuthorizeNet::ARB::Response do @@ -186,6 +262,6 @@ subscription.unit = :months subscription.unit.should == AuthorizeNet::ARB::Subscription::IntervalUnits::MONTH end -end +end