Cdiscount API产品上架
开发前仔细阅读此文档!!!
API 链接地址 https://dev.cdiscount.com/marketplace/?page_id=238
https://portal-marketplaceapi.cdiscount.com/get-started
最新API接口地址:https://marketplace.cdiscount.com/zh/api/
https://blog.youkuaiyun.com/ts3211/article/details/106186472
https://download.youkuaiyun.com/download/highfiresun/7344067?utm_medium=distribute.pc_relevant_t0.none-task-download-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-download-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control
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
这个连接是我谷歌到的一个网址 里面有很多报错信息的解释 可以看看