Cdiscount API产品上架
开发前仔细阅读此文档!!!
API 链接地址 https://dev.cdiscount.com/marketplace/?page_id=238
cdiscount 是一个法国小众电商平台 本公司主做跨境电商,所以需要一个ERP上架功能…
这个上架总的来说要分两步 1:先上传产品基本信息 2:再上传产品库存、价格、物流等其他信息…
step1:获取token
public function getToken($login, $password)
{
//相关请求
$req_url = "https://sts.cdiscount.com/users/httpIssue.svc/?realm=https://wsvc.cdiscount.com/MarketplaceAPIService.svc";
$api_check = base64_encode($login.":".$password);
$headers = array("Authorization: Basic ".$api_check,"Content-Type: application/json; charset=utf-8");
$fetch_token = $this->sendCurlGetRequest($req_url,$headers);
$token_xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
$token_xml.= $fetch_token;
$token_output = json_decode(json_encode(simplexml_load_string($token_xml)),true);
if($token_output){
return $token_output[0];
}else{
return false;
}
}
//模拟get请求
public function sendCurlGetRequest($url,$headers){
$ch = curl_init($url);
curl_setopt($ch,CURLOPT_URL,$url);
if($headers){
curl_setopt($ch, CURLOPT_HTTPHEADER,$headers);
}
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
// https请求 不验证证书和hosts
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
step2:生成 zip 文件 ( 经验太少 第一次遇到 传数据要包成zip文件做传输的 ) 并上传
Please find hereafter a sample of the product package :APIMPCdiscount_Sample_Products
zip 文件详解
https://dev.cdiscount.com/marketplace/?pagename=productintegration
整个zip文件的目录结构是这样的
主要文件 Products.xml (官方详情在这)
https://dev.cdiscount.com/marketplace/?pagename=products-xml
生成文件踩过的坑…
- Product element 中空值字段不要写进文件
- ProductImage Element 图片地址 不可写国内地址 接口响应图片超过5秒就会报错 所以要把图片放到外网
- zip文件生成目录层不可多 不要把这两个文件夹和文件放入另一个文件夹中!!! ( 这个亏吃大了,总是报错… )
// 创建临时文件 准备打包压缩 $xml:拼接成的xml文件内容
$this->createCdiscountDir('Products', $xml, $in_id);
// 创建ZIP文件
$zip = new ZipArchive();
try {
$zip_dir = "/cidscount/".date('Y-m-d');
!is_dir($zip_dir) && @mkdir($zip_dir, 0777, true);
$zip_name = "/cidscount/".date('Y-m-d')."/cidscount".$in_id.".zip";
$tmp_dir = array('_rels', 'Content', '[Content_Types].xml');
$this->createZip($tmp_dir, $zip_name);
$this->load->library('CdiscountApi');
$zip_name = $web_out.':80/'.$zip_name;
// 调用接口
$res = $this->SubmitProductPackage($zip_name);
// 删除临时文件
foreach ($tmp_dir as $tdk => $tdv) {
$this->delDirAndFile($tdv);
}
die;
} catch (\Exception $exception) {
local_log("============= CdiscountSubmit ERROR S ===========");
local_log($exception->getMessage());
local_log("============= CdiscountSubmit ERROR E ===========");
return false;
}
// 创建cdiscount目录
public function createCdiscountDir($type='Products', $xml, $in_id)
{
$dir = "Content/";
$path_arr = array();
!is_dir($dir) && @mkdir($dir, 0777, true);
// .rels file Structure
$relsPath = "_rels";
!is_dir($relsPath) && @mkdir($relsPath, 0777, true);
$rels_xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>
<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">
<Relationship Type=\"http://www.cdiscount.com/uri/document\" Target=\"/Content/$type.xml\" Id=\"cidscountRelsFm$in_id\" />
</Relationships>";
file_put_contents($relsPath.'/.rels', $rels_xml);
// [Content_Types].xml
$ctype_xml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>
<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">
<Default Extension=\"xml\" ContentType=\"text/xml\" />
<Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\" />
</Types>";
file_put_contents("[Content_Types].xml", $ctype_xml);
$Path = $dir. "/$type.xml";
file_put_contents($Path, $xml);
}
/**
*
* createZip - 创建压缩包,for文件夹/文件
*
* @param type string-or-array $from
* => 字符串 '/path/to/source/file/or/folder/'
* => 或者 包含字符串的数组 array('fileA','FolderA','fileB')
* @param type string $to
* => 字符串 '/path/to/output.zip'
*
*/
function createZip($from, $to) {
/* Check zip class */
if (!class_exists('ZipArchive')) {
$return = 'Missing ZipArchive module in server.';
return $return;
}
/* Check right of write for target zip file */
$zip = new ZipArchive();
if (!is_dir(dirname($to))) {
mkdir(dirname($to), 0755, TRUE);
}
if (is_file($to)) {
if ($zip->open($to, ZIPARCHIVE::OVERWRITE) !== TRUE) {
$return = "Cannot overwrite: {$to}";
return $return;
}
} else {
if ($zip->open($to, ZIPARCHIVE::CREATE) !== TRUE) {
$return = "Could not create archive: {$to}";
return $return;
}
}
/* Check path of source files or folder */
$source_path_including_dir = array();
$prefix_relative_path_for_source = '';
if (is_array($from)) {
foreach ($from as $path) {
if (file_exists($path)) {
if ($prefix_relative_path_for_source == '') {
$prefix_relative_path_for_source = (is_dir($path)) ? realpath($path) : realpath(dirname($path));
}
$source_path_including_dir[] = $path;
} else {
$return = 'No such file or folder: ' . $path;
return $return;
}
}
} elseif (file_exists($from)) {
$prefix_relative_path_for_source = (is_dir($from)) ? realpath($from) : realpath(dirname($from));
$source_path_including_dir[] = $from;
} else {
$return = 'No such file or folder: ' . $from;
return $return;
}
$prefix_relative_path_for_source = rtrim($prefix_relative_path_for_source, '/') . '/';
/* Get final list of files, no folder */
$final_list_of_files = array();
foreach ($source_path_including_dir as $path) {
if (is_file($path)) {
/* File */
$final_list_of_files[] = $path;
} else {
/* Folder */
$list_of_files = $this->recursive_get_files_by_path_of_folder($path);
foreach ($list_of_files as $one) {
$final_list_of_files[] = $one;
}
}
}
if (!count($final_list_of_files)) {
$return = 'No valid file or folder used to zip';
return $return;
}
/* Begin to add to zip file */
foreach ($final_list_of_files as $one_file) {
$zip->addFile($one_file, str_replace($prefix_relative_path_for_source, '', $one_file));
}
$zip->close();
return $to;
}
/**
* 获取文件夹下的文件列表,遍历模式
*
* @param type $dir
* @param type $is_tree
* @return string
*/
function recursive_get_files_by_path_of_folder($dir, $is_tree = false) {
$files = array();
$dir = preg_replace('/[\/]{1}$/i', '', $dir);
if (is_dir($dir)) {
if ($handle = opendir($dir)) {
while (($file = readdir($handle)) !== false) {
if ($file != "." && $file != "..") {
if (is_dir($dir . "/" . $file)) {
$sub_list = $this->recursive_get_files_by_path_of_folder($dir . "/" . $file, $is_tree);
if ($is_tree) {
$files[$file] = $sub_list;
} else {
foreach ($sub_list as $one_sub_file) {
$files[] = $one_sub_file;
}
}
} else {
$files[] = $dir . "/" . $file;
}
}
}
closedir($handle);
return $files;
}
} else {
$files[] = $dir;
return $files;
}
}
// 删除目录
function delDirAndFile($dirName)
{
if ( $dirName == '[Content_Types].xml' ){
unlink("[Content_Types].xml");
return;
}
if ($handle = opendir("$dirName")) {
while (false !== ($item = readdir($handle))) {
if ($item != "." && $item != "..") {
if (is_dir("$dirName/$item")) {
$this->delDirAndFile("$dirName/$item");
} else {
unlink("$dirName/$item") ;
}
}
}
closedir($handle);
if (rmdir($dirName)) return true ;
}
}
// 产品上架 shopid 是为了获取token 上面有获取token方法 自己结合实际情况对此段代码进行修改
public function SubmitProductPackage($shopid, $zip_name, $in_id)
{
$sql = "SELECT * FROM shop WHERE id = $shopid ";
$rs = $this->db->query($sql)->row_array();
$time = time();
if($time>$rs['access_timeout']){
$token = $this->getToken($rs['appkey'], $rs['appsecret'], $rs['id']);
}else{
$token = $rs['access_token'];
}
$xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
$xml.= '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<SubmitProductPackage xmlns="http://www.cdiscount.com">
'.$this->returnXMLHead($token).'
<productPackageRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<ZipFileFullPath>'.$zip_name.'</ZipFileFullPath>
</productPackageRequest>
</SubmitProductPackage>
</s:Body>
</s:Envelope>';
$output = $this->sendCurlPostRequest($xml, 'SubmitProductPackage');
$PackageId = $output['Body']['SubmitProductPackageResponse']['SubmitProductPackageResult']['PackageId'];
if ( $PackageId && $PackageId != '-1' ){
$this->db->update('cdiscount.db_submit_product_list', array('ProductPackageId'=>$PackageId, 'status'=>1), 'id='.$in_id);
return true;
}else{
$this->db->update('cdiscount.db_submit_product_list', array('ProductPackageId'=>-1, 'ProductStatus'=>$output['Body']['SubmitProductPackageResponse']['SubmitProductPackageResult']['ErrorMessage']), 'id='.$in_id);
return $output['Body']['SubmitProductPackageResponse']['SubmitProductPackageResult']['ErrorMessage'];
}
}
public function returnXMLHead($token)
{
$head = "<headerMessage xmlns:a=\"http://schemas.datacontract.org/2004/07/Cdiscount.Framework.Core.Communication.Messages\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
<a:Context>
<a:CatalogID>1</a:CatalogID >
<a:CustomerPoolID>1</a:CustomerPoolID >
<a:SiteID>100</a:SiteID >
</a:Context >
<a:Localization>
<a:Country>CN</a:Country >
<a:Currency>Eur</a:Currency >
<a:DecimalPosition>2</a:DecimalPosition >
<a:Language>En</a:Language >
</a:Localization>
<a:Security>
<a:DomainRightsList i:nil = \"true\" />
<a:IssuerID i:nil = \"true\" />
<a:SessionID i:nil = \"true\" />
<a:SubjectLocality i:nil = \"true\" />
<a:TokenId >$token</a:TokenId >
<a:UserName i:nil = \"true\" />
</a:Security>
<a:Version>1.0</a:Version >
</headerMessage>";
return $head;
}
//模拟post请求
public function sendCurlPostRequest($curlData, $api)
{
$url='https://wsvc.cdiscount.com/MarketplaceAPIService.svc';
$curl = curl_init();
curl_setopt ($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl,CURLOPT_TIMEOUT,120);
curl_setopt($curl,CURLOPT_ENCODING,'gzip');
curl_setopt($curl,CURLOPT_HTTPHEADER,array (
'Accept-Encoding: gzip,deflate',
'Content-Type: text/xml;charset=UTF-8',
'SOAPAction:"http://www.cdiscount.com/IMarketplaceAPIService/'.$api.'"'
));
curl_setopt ($curl, CURLOPT_POST, 1);
curl_setopt ($curl, CURLOPT_POSTFIELDS, $curlData);
// https请求 不验证证书和hosts
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
$result = curl_exec($curl);
//$http_error = curl_error($curl);
//$http_status = curl_getinfo($curl);
//print_r($http_status);exit;
if ( $result == false ){
local_log("============== sendCurlPostRequest ERROR S===============");
local_log(curl_error($curl));
local_log("============== sendCurlPostRequest ERROR E===============");
die('CURL ERROR');
}
curl_close ($curl);
//echo $result;exit;
//print_r($result);exit;
//解析xml文件 并作相关处理
$soap_xml = "";
$soap_xml.= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
$soap_xml.= $result;
$response = str_replace('</s:','</',$soap_xml);
$response = str_replace('<s:','<',$response);
//$response = strtr($soap_xml, ['</s:' => '</', '<s:' => '<']);
//$response = strtr($soap_xml, ['</s:' => '</', '<s:' => '<']);
$output = json_decode(json_encode(simplexml_load_string($response)),true);
return $output;
}
保存接口返回 PackageId
step3:获取产品导入的进度状态 GetProductPackageSubmissionResult
cdiscount 后台可查看上传进度
接口文档 https://dev.cdiscount.com/marketplace/?page_id=240
当返回信息状态为 Integrated 时 你就已经成功一半了!
step4:上传产品价格、库存、物流等其他信息… SubmitOfferPackage ( 奇葩公司 这都分两步走)
接口地址 https://dev.cdiscount.com/marketplace/?page_id=91
Detailed Description of the Offers.xml files
具体可参考文档及step2-step3
这里注意的是 上架的时候看这个
报错信息
// Not enough quota available (1 requested and 0 available)
// 一般这个只有在调用 SubmitProductPackage 方法时才会出现 返回这个的时候一般要间隔1-2小时才能再次调用此方法 不然会一直返回这个
{"Body":{"SubmitProductPackageResponse":{"SubmitProductPackageResult":{"ErrorMessage":"Not enough quota available (1 requested and 0 available).","OperationSuccess":"false","ErrorList":{"Error":{"ErrorType":"Quota","Message":"Not enough quota available (1 requested and 0 available)."}},"SellerLogin":"apinewcos16-api","TokenId":"98345a53105d45123123123213213219bd","NumberOfErrors":"0","PackageId":"0","PackageIntegrationStatus":[],"ProductLogList":[]}}}}
// Le fichier d'int\u00e9gration des produits soumis ('http:\\w81kukm95gd.xicp.io:55\fuman\cidscount\2020-05-13\cidscount44.zip') a \u00e9t\u00e9 int\u00e9gr\u00e9 dans un pr\u00e9c\u00e9dent appel ! Il n'est pas possible de soumettre plusieurs fois le m\u00eame fichier.
// 像这个 如果你确定不是上传了同一个文件 那指定是你的ZIP文件写错了 我的就是这个卡了好久 就是zip目录层的问题
{"Body":{"SubmitProductPackageResponse":{"SubmitProductPackageResult":{"ErrorMessage":"Le fichier d'int\u00e9gration des produits soumis ('http:\/\/w81kukm95gd.xicp.io:55\/fuman\/cidscount\/2020-05-13\/cidscount44.zip') a \u00e9t\u00e9 int\u00e9gr\u00e9 dans un pr\u00e9c\u00e9dent appel ! Il n'est pas possible de soumettre plusieurs fois le m\u00eame fichier.","OperationSuccess":"false","ErrorList":{"Error":{"ErrorType":"FileAlreadySubmitted","Message":"Le fichier d'int\u00e9gration des produits soumis ('http:\/\/w81kukm95gd.xicp.io:55\/fuman\/cidscount\/2020-05-13\/cidscount44.zip') a \u00e9t\u00e9 int\u00e9gr\u00e9 dans un pr\u00e9c\u00e9dent appel ! Il n'est pas possible de soumettre plusieurs fois le m\u00eame fichier."}},"SellerLogin":"apinewcos16-api","TokenId":"98345a53105d45d681559078e86219bd","NumberOfErrors":"0","PackageId":"-1","PackageIntegrationStatus":[],"ProductLogList":[]}}}}
总结
歪果仁的思维到底是和国内有点差别… 这个接口对接过程中会返回各种报错信息(大神玩家请忽略这句话) 总归一点一点解决了…
有其他问题欢迎留言 。。。 看到必回…
2021-01-30 新增
报错小结
Not enough quota available (1 requested and 0 available).
接口配额限制 等待一小时左右基本就好了 或者等待上一条处理信息结束(有一次我被卡了一天 发工单过去他们说是缓存问题…)
Produit : Les informations renseignées dans vos libellés et descriptifs ne sont pas assez claires pour déterminer la catégorie à associer. Si vous ne détectez pas d’erreur, vous pouvez nous contacter via la rubrique « Aide » de votre espace vendeur
通过在产品的标题(ShortLabel)和/或说明(Description)中添加产品的主类别,可以纠正错误。
https://beezup.zendesk.com/hc/fr/articles/215434843-Cdiscount-questions-fr%C3%A9quentes
这个连接是我谷歌到的一个网址 里面有很多报错信息的解释 可以看看