Reputation: 391
I'm trying to create a category through the Magento SOAP API using Savon. Before anyone suggests it, I can't use the Magento SOAP v2 or REST APIs.
This code sets up my client and logs in:
@client = Savon.client(wsdl: "#{EnvConfig.base_url}/api/soap/?wsdl", log_level: :debug, raise_errors: true, pretty_print_xml: true)
response = @client.call(:login, message: {
username: EnvConfig.magento_api_user,
apiKey: EnvConfig.magento_api_key
})
@token = response.to_hash[:login_response][:login_return]
I'm then able to call magentos various methods. To list all of the products, I can call:
@response = @client.call(:call, message: {session: @token, method: 'catalog_product.list'})
This all works properly, so there doesn't seem to be a problem with any of the code above.
When I call catalog_category.create, I'm getting an error. If I call the method with only the parentID parameter:
@response = @client.call(:call, message: {session: @token, method: 'catalog_category.create', parent_id: 90})
then the following XML is sent by Savon:
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:typens="urn:Magento" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Body>
<typens:call>
<session>6939ace91ba26b1da9a21334d7ef2c13</session>
<method>catalog_category.create</method>
<parentId>90</parentId>
</typens:call>
</env:Body>
</env:Envelope>
This returns the response Savon::SOAPFault: (103) Attribute "name" is required
:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>103</faultcode>
<faultstring>Attribute "name" is required.</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I'd expect this, as the API documentation makes it clear that the following attributes are not optional:
So If I make a call containing name
:
@response = @client.call(:call, message: {session: @token, method: 'catalog_category.create', parent_id: 90, category_data: {name: 'Fooooo'}})
This XML is sent:
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:typens="urn:Magento" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Body>
<typens:call>
<session>6939ace91ba26b1da9a21334d7ef2c13</session>
<method>catalog_category.create</method>
<parentId>90</parentId>
<categoryData>
<name>Fooooo</name>
</categoryData>
</typens:call>
</env:Body>
</env:Envelope>
But I get a Savon::SOAPFault: (SOAP-ENV:Client) Error cannot find parameter
error:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Client</faultcode>
<faultstring>Error cannot find parameter</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I've spent quite a long time trying to work this out, and have tried sending the name
parameter without wrapping it in categoryData, as well as other parameters. All return the same error message.
At the very least, it would be good to know which parameter can't be found!
Any help would really be appreciated :)
Upvotes: 3
Views: 2692
Reputation: 391
I could't really find a decent solution to this. In the end it was solved by building out the XML myself with this extremely messy code:
# Builds an XML Request for use with the Magento API
#-------------------------------------------------------------------------------------------------------------
# Usage:
#
# @m = MageAPI.new(token, call, params) Creates a new MageAPIRequest object with @call, @token, @data and
# @xml_data instance variables.
#
# Expects a hash of params like:
#
# { parentId: '90',
# categoryData: {
# name: 'OMG, does this work?',
# is_active: 1,
# available_sort_by: 'position',
# default_sort_by: 'position',
# description: 'Foo',
# url_key: 'url_key',
# include_in_menu: 1}
# }
#
#-------------------------------------------------------------------------------------------------------------
class MageAPIRequest
attr_reader :call, :token, :data, :xml_data
def initialize(token, call, params = {})
@call = call
@token = token
@data = params
@xml_data = build_xml
end
def to_s
return @xml_data
end
# Build out the xml string to send to the server, excluding the Envelope, body and call tags, which are added by Savon.
def build_xml
token = LibXML::XML::Node.new('sessionId', @token)
token['xsi:type']='xsd:string'
resourcePath = LibXML::XML::Node.new('resourcePath', @call)
resourcePath['xsi:type']='xsd:string'
unless @data.empty?
args = LibXML::XML::Node.new 'args'
args['SOAP-ENC:arrayType'] = "xsd:ur-type[#{@data.length}]"
args['xsi:type'] = 'SOAP-ENC:Array'
# Build the structure insude 'args', based on the Hash.
# TODO: Make this recursive, so it can work deeper than one level.
@data.each do |k,v|
if v.is_a? Hash
details = LibXML::XML::Node.new(k.to_s)
details["xsi:type"] = 'ns2:Map'
args << details
v.each do |key,value|
item_node = LibXML::XML::Node.new("item")
key_node = LibXML::XML::Node.new("key", key.to_s)
key_node['xsi:type'] = 'xsd:string'
if value.is_a? Fixnum
value_node = LibXML::XML::Node.new("value", value.to_s)
value_node['xsi:type'] = 'xsd:int'
elsif value.is_a? Float
value_node = LibXML::XML::Node.new("value", sprintf("%.2f", value))
value_node['xsi:type'] = 'xsd:string'
elsif value.is_a? Array
value_node = LibXML::XML::Node.new("value")
value_node['SOAP-ENC:arrayType'] = "xsd:int[#{value.length}]"
value_node['xsi:type'] = 'SOAP-ENC:Array'
value.each do |v|
v_node = LibXML::XML::Node.new("item", v.to_s)
v_node['xsi:type'] = 'xsd:string'
v_node['xsi:type'] = 'xsd:int' if v.is_a? Fixnum
value_node << v_node
end
else
value_node = LibXML::XML::Node.new("value", value.to_s)
value_node['xsi:type'] = 'xsd:string'
end
item_node << key_node
item_node << value_node
details << item_node
end
else
pair = LibXML::XML::Node.new(k.to_s, v.to_s)
# if v.is_a? Fixnum
# pair['xsi:type']='xsd:int'
# else
pair['xsi:type']='xsd:string'
# end
args << pair
end
end
end
xml_string = token.to_s
xml_string << resourcePath.to_s
xml_string << args.to_s
end
end
I then used that when creating my request:
request = MageAPIRequest.new(@token, call, params)
Far from ideal, but it works. It's worth noting that I also had to build a parser to turn the responses which look like this:
{:balance=>"10",
:exchange_rate=>"1.000000",
:response_type=>"ACCEPT",
:customer_data=>
{:item=>
[
{:key=>"iredeem_member_id", :value=>"110"},
{:key=>"prefix", :value=>"Ms."},
{:key=>"lastname", :value=>"Last name"},
{:key=>"firstname", :value=>"First name"},
{:key=>"iredeem_points_balance", :value=>"10"},
]
}
}
into something useable like this:
{:balance=>"10",
:exchange_rate=>"1.000000",
:response_type=>"ACCEPT",
:customer_data=>
{:iredeem_member_id=>"110",
:prefix=>"Ms.",
:lastname=>"Last name",
:firstname=>"First name",
:iredeem_points_balance=>"10"
}
}
Upvotes: 0