Edit: Found a better way, see Solution 2 below
As mentioned in the comment, the SOAP API is the way to go.
Solution 1:
Suds worked for me with slight modification (Usage of Titanium.Network.HTTPClient
instead of XMLHttpRequest
), but it does not much more than creating a SOAP envelope for the call and returning the whole XML response.
Proof-of-Concept implementation, using jQuery Deferred for request chaining:
Service.MagentoClient = function()
{
var self = this;
var suds = new SudsClient({
endpoint : "http://the-magento-host/api/v2_soap/",
targetNamespace : "urn:Magento",
});
self.login = function() {
var deferred = new $.Deferred();
var args = {
username : 'the-username',
apiKey: 'the-api-key'
};
suds.invoke("login", args, function(xmlDoc) {
self.sessionId = $(xmlDoc).find("loginReturn").text();
deferred.resolve({});
//TODO reject if no sessionid returned
});
return deferred;
};
self.setStatus = function(orderId, status, comment, notify) {
var deferred = new $.Deferred();
if (!self.sessionId) {
deferred.reject({ error: 'Login not successful.' });
return;
}
var args = {
sessionId : self.sessionId,
orderIncrementId : orderId,
status : status,
comment : comment,
notify : notify
}
suds.invoke("salesOrderAddComment", args, function(xmlDoc) {
var success = $(xmlDoc).find("salesOrderAddCommentResponse").text();
if (success) {
deferred.resolve({});
} else {
deferred.reject({ error: 'Update not successful.' });
}
});
return deferred;
};
};
Usage example:
var magento = new Service.MagentoClient();
magento.login().then(function() {
magento.setStatus('100000029', 'complete', 'soap test');
}).then(function() {
alert('Update successful');
}, function(reject) {
alert('Update failed: ' + reject.error);
});
Solution 2:
Turned out that writing an own API adapter can be really easy. With the example of this core-hack (dead link) I was able to write a clean module for a JSON-RPC adapter based on Zend_Json_Server
. It uses the same Authentication and ACL as the SOAP and XML-RPC APIs.
To use the entry point /api/jsonrpc
, the new controller has to be added to the api
route:
<config>
<frontend>
<routers>
<api>
<args>
<modules>
<my_jsonrpc before="Mage_Api">My_JsonRpc_Api</my_jsonrpc>
</modules>
</args>
</api>
</routers>
</frontend>
</config>
Update 02/2015: The above link is dead now, so I open sourced my JSON-RPC adapter as a complete extension: https://github.com/sgh-it/jsonrpc
My JS client now looks like this (again with JQuery.Deferred, but no additional 3rd party libraries for the API):
/**
* Client for the Magento API
*/
Service.MagentoClient = function()
{
var self = this;
/**
* @param string method the remote procedure to call
* @param object params parameters for the RPC
* @param callback onSuccess callback for successful request. Expects one parameter (decoded response object)
* @param callback onError callback for failed request. Expects one parameter (error message)
*
* @return void
*/
self.jsonRpc = function(method, params, onSuccess, onError) {
var request = {
method : method,
params : params,
jsonrpc : "2.0",
id : 1
};
var options = {
entryPoint : config.magentoClient.entryPoint,
method: 'post',
timeout: config.magentoClient.timeout
};
var httpClient = Titanium.Network.createHTTPClient();
httpClient.onload = function(e) {
try {
var response = JSON.parse(this.responseText);
} catch (jsonError) {
return onError(jsonError);
}
if (response.error) {
if (response.error.code == 5) { // session expired
self.sessionId = null;
}
return onError(response.error.message);
}
onSuccess(response);
};
httpClient.onerror = function(e) {
onError(e.error + '; Response:' + this.responseText);
};
httpClient.setTimeout(options.timeout);
if (httpClient.open(options.method, options.entryPoint)) {
httpClient.setRequestHeader("Content-type", "application/json");
httpClient.send(JSON.stringify(request));
} else {
onError('cannot open connection');
}
}
/**
* Retrieve session id for API
*
* @return JQuery.Deferred deferred object for asynchronous chaining
*/
self.login = function() {
var deferred = new $.Deferred();
if (self.sessionId) {
deferred.resolve();
return deferred;
}
var loginParams = config.magentoClient.login;
try {
self.jsonRpc('login', loginParams, function(response) {
if (response && response.result) {
self.sessionId = response.result;
deferred.resolve();
} else {
deferred.reject('Login failed.');
}
}, function(error) {
deferred.reject(error);
});
} catch (rpcError) {
deferred.reject(rpcError);
}
return deferred;
};
/**
* Updates order states in Magento
*
* @param string method name of the remote method
* @param object args arguments for the remote method
*
* @return JQuery.Deferred deferred object for asynchronous chaining
*/
self.call = function(method, args) {
var deferred = new $.Deferred();
if (!self.sessionId) {
deferred.reject('No session.');
return;
}
var callParams = {
sessionId : self.sessionId,
apiPath : method,
args : args
};
try {
self.jsonRpc('call', callParams, function(response) {
deferred.resolve(response.result);
}, function(error) {
deferred.reject(error);
});
} catch (rpcError) {
deferred.reject(rpcError);
}
return deferred;
};
};
Note that all methods after login are routed through call
. The method
parameter is something like sales_order.list
, the args
parameter an array or object with the method arguments.
Usage example:
var filters = [];
var magento = new Service.MagentoClient();
magento.login().then(function() {
magento.call('sales_order.list', [filters]).then(
function(orders) {
// do something with the response
}, function(error) {
alert('Magento API error: ' + error);
}
);
});
first of all you should know working with APIs using PHP, sending requests and parsing responses. And every API has own structure, signature and methods, see your third party API documentation. Using PHP you can call api methods with curl or standard http requests. Here is some tips.
Here is scenario how to update your products data using standard Magento approach:
1. Create custom local module
2. Write your updater model, it uses curl or http request to call to third party api. In this model you should implement all methods which is required to interconnect the api. To optimal develop architecture of your updater you should know how to use your third party api, read its documentation. For example documentation:
a. api url: http://www.some.web.url/api
b. api returns JSON (or XML) compliant data
c. api secure key sends over http header **ApiKey**
c. method **products_list**: `<api_url>/products/list/`
result:
{
"count":2,
"items":[
{
"name":"Product Name",
"sku":"pn_123",
"price":10,
"qty":20,
},
{
"name":"Another Product",
"sku":"ap_1234",
"price":14,
"qty":60,
}
]
}
d. method **product_detail**: `<api_url>/product/detail/sku/<product_sku>/`
result:
{
"name":"Another Product",
"sku":"ap_1234",
"price":14,
"qty":60,
}
And so on. API documentation gives full scheme of using methods.
Methods of api given above are implemented in the your custom Model:
class Ssd_Interconnect_Model_Updater
{
const USP = '/';
private $_url = "http://www.some.web.url/api";
private $_apiKey = "Some secure api key/token here";
private $_command;
private $_subCommand;
private $_params;
public function getUrl()
{
$url = $this->_url . self::USP . $this->_command . self::USP . $this->_subCommand;
if (count($this->_params)) {
foreach ($this->_params as $key=> $value) {
$url = $url . self::USP . $key . self::USP . $value;
}
}
return $url;
}
protected function call()
{
if ($curl = curl_init()) {
$result = false;
$header = array('Content-Type: text/json', 'ApiKey: ' . $this->_apiKey, 'Accept: application/json');
curl_setopt($curl, CURLOPT_URL, $this->getUrl());
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
try {
$response = curl_exec($curl);
$result = $this->parseResult($response, $curl);
} catch (Exception $e) {
Mage::logException($e);
}
return $result;
}
return false;
}
protected function parseResult($response, &$curl)
{
try {
$info = curl_getinfo($curl);
$code = $info['http_code'];
switch ($code) {
case 401:
throw new Exception('The API Key was missing or invalid');
break;
case 500:
throw new Exception('An unhandled exception occured on the server');
break;
}
$data = Mage::helper('core')->jsonDecode($response);
if (isset($data['ErrorCode'])) {
throw new Exception($data['Message'], $data['ErrorCode']);
}
return $data;
} catch (Exception $e) {
$this->_errorCode = $e->getCode();
$this->_errorMessage = $e->getMessage();
}
curl_close($curl);
return false;
}
public function getProductList()
{
$this->_command = 'products';
$this->_subCommand = 'list';
return $this->call();
}
public function getProductDetail($product)
{
$this->_command = 'product';
$this->_subCommand = 'detail';
$this->_params = array('sku'=> $product->getSku());
return $this->call();
}
public function updateProducts()
{
$apiProducts = $this->getProductList();
if ($apiProducts && isset($apiProducts['items'])) {
foreach ($apiProducts['items'] as $p) {
if ($product = Mage::getModel('catalog/product')->loadBySku($p['sku'])) {
$product->setPrice($p['price'])->setName($p['name']);
$qty = $p['qty'];
$product->setStockData(array(
'is_in_stock' => $qty > 0,
'qty' => $qty
));
try {
$product->save();
} catch (Exception $e) {
//some error handler logic here
}
}
}
}
}
}
And finally you should schedule some cronjob to update products every x time in your config.xml:
<crontab>
<jobs>
<ssd_interconnect_customjobs_15m>
<schedule>
<cron_expr>*/15 * * * *</cron_expr>
</schedule>
<run>
<model>interconnect/updater::updateProducts</model>
</run>
</ssd_interconnect_customjobs_15m>
</jobs>
</crontab>
P.S I didn't test the code given above, I try to describe how to using API to update products. Thanks.
Best Answer
Just starting out with Magento2 I found this to be pretty painful to figure out. It took me about half a day! To be fair, Magento has the appropriate documentation you need here...
The problem is it's very minimal. With my working knowledge of OAuth1 taken together with a gist of how to get an access token from Magento1 REST services I was finally able to pull it off. Details below!
1. Create a dedicated vhost (for the consumer application)
On my test environment, my Magento installation is installed at magev2.local, and I created a consumer application for purposes of obtaining an access token with the domain mage-access.local
2. Write a PHP file to interact with Magento (or use mine)
Here's the script I used (adapted from the gist I mentioned above). It uses the native PHP OAuth class, so you'll need to install that extension or swap some of this code out with your OAuth client library of choice.
3. Get your Access Token!
When you create your integration in the Magento2 admin, (substitute the domains you're using of course) use oauth.php on your shiny new consumer application for both the Callback URL and the Identity link URL, here's what mine looks like for my test environment
I recommend using the Save & Activate option as shown in the image because it seems like Magento has a very short lifetime for the consumer token. I found if I didn't generate the access token immediately after provisioning the consumer, I'd get error messages about the consumer key being expired.
On the next screen ensure the permissions for your consumer are as you wish then click the Allow button. This loads a form from the oauth.php script asking for your authorization and simultaneously, Magento sends a POST request to oauth.php passing a temporary token from which you can procure request and access tokens.
All you have to do now is click the Authorize button and if everything works you'll have a shiny new access token! The list view of your integrations should now look like this
and when you drill into the details, you'll see the access token