分类目录归档:速卖通开放平台

速卖通目录产品模型

速卖通类目产品模型
速卖通产品模型———————————-
1 目录(category)

{
	“id”:xxxxxxx,
	“level”:0,
	“names:{
		“zh”:xxx,
		“pt”:xxx,
		“fr”:xxx,
		“en”:xxx,
		“ru”:xxx,
		“in”:xxx,
		“es”:xxx
},
isleaf:0
}

注意,level并没有维护层次关系,所以在下载目录时需要根据调用关系把层次关系维护起来。

2 目录属性(category_attribute)

{
	“id”:xxx,			//属性编号
	“spec”:xxx,			//
	“visible”:1			//是否显示
	“customized_name”:0,		//是否可以定制属性名称
	“customized_pic”:0,		//是否可定制属性图片
	“key_attribute”:0,		//是否关键属性
	“sku”:1,			//是否SKU属性
	“name_zh”:xxx,			//
	“name_en”:xxx,			//
	“input_type”:STRING,		//数据类型
	“required”:0,			//是否必填
	“attribute_show_type_vale”:check_box, //实现标签类型
	“values”:[
		{“id”:1111, “name_zh”:xxx,”name_en”:xxxx}
        ]
}

3 目录属性值(category_attribute_value)就是目录属性里面的values字段(是个数组)

{
	“id”:xxx			//属性编号
	“names_zh”:xxx,			//
	“names_en”:xxx			//
}

4 产品(Product)

{
//来自产品查询列表
gmtModified,
gmtCreate,
productMinPrice,
productMaxPrice,

//来自产品详情
summary,				//摘要
lotNum,					//每包数量,大于1为打包销售
detail,					//产品信息
packageType,				//打包销售:大于1 非打包销售0
freightTemplateId,		        //运费模板ID
subject,				//产品标题
productMoreKeywords1,	                //关键字
productMoreKeywords2,	                //关键字
productUnit,				//商品单位(固定一个可选列表)
wsOfflineDate,			        //下架时间
packageLength,
wsDisplay,				//商品下架原因
isImageDynamic,			        //多图为1
packageHeight,
packageWidth,
isPackSell,				//是否自定义计重,true为自定义
isImageWatermark,			
ownerMemberSeq,			        //商品所属人Seq
categoryId,				//发布类目
keyword,				//关键字
imageURLs,
ownerMemberId,			        //商品所属人loginId
productStatusType,		        //商品业务状态,上架:onSelling ;下架:offline ;审核中:auditing ;审核不通过:editingRequired
grossWeight,
productId,
groupId,
deliveryTime,
wsValidNum,				//商品有效天数,取值范围1-30,单位天
promiseTemplateId,		        //服务模板ID
productPrice,				//商品一口价  上传多属性产品的时候,有好几个SKU和价格,productprice无需填写
aeopAeProductSKUs			//*******参考 产品SKU
aeopAeProductPropertys			//*******参考 产品属性


//*******以下数据条件返回***********
bulkOrder					//批发最小数量 。取值范围2-100000。批发最小数量和批发折扣需同时有值或无值。	
bulkDiscount					//批发折扣
sizechartId					//尺码表模版Id
reduceStrategy					//库存扣减策略
baseUnit						// isPackSell为true时,此项必填。购买几件以内不增加运费。
addUnit						//isPackSell为true时,此项必填。 每增加件数
addWeight					//isPackSell为true时,此项必填。 对应增加的重量
src

注意,产品详情部分的参数是跟发布产品时的参数一对一对应的,比如bulkDiscount在部分时设置了,那么返回也带有这个值。

5 产品属性(product_property)

"aeopAeProductPropertys": [ 
{ "attrNameId": 284, "attrName":xxx, "attrValueId": 494, "attrValue"}, //标准格式
{ "attrValueId": 494, "attrNameId": 284 }, 
{ "attrValueId": 200004213, "attrNameId": 200000309 }, 
{ "attrValueId": 1875, "attrNameId": 200000303 }, 
{ "attrName": "Modeling clothing", "attrValue": "slim" },
{ "attrName": "clothes design details", "attrValue": "wool collar" }
]

这些产品的属性(property),一部分是从类目属性中选择的(在发布产品时),这部分的属性,一般不需要给出属性名 和 值(只有属性的名 和 值的ID),因为它可以通过类目属性表通过定位对应的ID找到(属性名ID找到属性名,属性名ID和属性值ID定位到属性值),另外,有给出属性名ID,没有给出属性值的ID,但是对应了属性值,这个是值这个这个类名属性的类型是text,就是要自己输入名字的,这个名字就是这个值;另一部分是没有给出属性名ID 和 属性值ID的,只有属性名和值,这些就是自定义属性。

注意:这里属性列表中不能出现SKU属性。

5 产品SKU(SKU属性)

"aeopAeProductSKUs": [ 
{ 
"ipmSkuStock": 0,				//实际库存
"skuPrice": "105.00", 			//SKU售价
"skuStock": false, 				//是否有库存  废弃
“skuCode”:xxxx,				//商家编码
"aeopSKUProperty": [ 
{ 
"propertyValueId": 496, 
"skuImage": "", 
“propertyValueDefinitionName”:””,
"skuPropertyId": 14 
}, 
{ 
"propertyValueId": 100014064, 
"skuPropertyId": 5 
} 
], 
"skuCode": "abc"
} 
]

每个产品的aeopAeProductSKUs是一个对象数组(每个元素是一个AeopAeProductSKU类型对象),每个数组代表一个子SKU(如果对象数组只有一个,那么表示没有子SKU,那个这个唯一的数组元素的aeopSKUProperty往往是空的),元素中的aeopSKUProperty就是这个子SKU的属性组成(就是有多少个属性决定了这个SKU,文档说明最多三个),每个SKU属性实际引用类目属性表中具体的SKU属性, skuPropertyId就是显示的属性名称;propertyValueId就是属性的值,也可以通过propertyValueDefinitionName指定一个名称覆盖 或 指定skuImage为一个链接图片来覆盖它。
sku-property
举例:Color就是SKU属性名称(skuPropertyId),后面的颜色就是属性值,大概:
color: 黑色 SKU1
color: 蓝色 SKU2
按照给出的例子,aeopAeProductSKUs需要有4个对象,每个对象的aeopSKUProperty只包含一个元素(因为SKU只有一个属性决定),每个元素中都存在一个skuPropertyId指向“color”的SKU类目属性名,对应一个颜色值。

速卖通API接入之授权二

之前曾写过“速卖通API接入之授权”,这次是写点有关授权的官方文档中没有的内容。

一般,如果你自己想开发一个程序,这个程序需要用到速卖通的API,那么你首先需要开一个速卖通账户,用这个账户去申请接入的相关信息。

如果你不想开发,那么你可以用第三方的程序,你不需要自己去申请API接入的相关信息,但是你要授权这个第三方程序它通过API来抓取你的店铺的数据。

很简单,一个是要你自己去申请接入的Key和秘钥,然后调用速卖通的API抓取数据;一个是不需要自己去搞这些,委托第三方来通过API来抓取数据。实际上,这里描述的这两种方式,对应于“App授权协议”的自用 和 商用,简单来说就是,如果是自用的,那么你开发的程序就仅仅用来抓取你的店铺的数据,如果是商用的,你的程序可以被其它账户委托,用来抓取别人店铺的数据。

那么你申请了一个自用账户,开发了一个程序,完了之后去接受别人的委托,然后去抓取别人的信息,是否可以呢? 至少从理论上来说,应该是可以的。关键就在于速卖通到底有没有严格执行自用 与 商用的逻辑。比如,如果你是自用的,就限制不可以接受委托,怎么进行呢?

我们知道,为了获取授权,通常有如下:
速卖通授权
在验证用户和密码的同时验证过来的Appkey是否跟这个用户一致,就可以限制自用。

不过,到目前为止,这个逻辑并没有实施。那么也就是说,自用的程序,搞完之后,也可以接受别人的委托抓别人的数据。这样的好处在于,如果你有多个店铺,那么只要拿一个去申请自用的Appkey和秘钥,其它的店铺就不需要了,因为它们可以委托这个已经存在的App去抓取数据。还有一个原因是,如果一个新店铺,如果还没有交易,那么是无法申请自用App的(也就是无法使用API),但是通过委托的方式就可以,所以对于新店铺,就可以在没有交易的情况下,通过自己的程序去铺产品。

速卖通目前只能是自用就自用,说起来是让有资质的第三方公司来做这种专门的开发,实际是卡主这里,和第三方应用进行应用的利润分成。

永久链接:http://blog.ifeeline.com/1071.html
原创文章,装载务必保留出处。

速卖通API接入之授权

关于速卖通API的授权,官方文档有详细的相关说明。引用如下:


应用程序可通过调用阿里巴巴开放平台提供的API获取到阿里巴巴的会员、交易等数据,因为涉及隐私,在使用前必须获得阿里巴巴会员的授权,方可调用API(公开数据除外),而access_token则做为用户唯一的授权标识。

阿里巴巴开放平台采用OAuth 2.0作为授权协议,授权流程可以简单归纳为:
(1)获取临时令牌;
(2)用临时令牌换取长时令牌以及访问令牌;
(3)访问令牌过期后用长时令牌刷新访问令牌。

由于使用OAuth 2.0作为授权协议,所以它的过程就复杂一些,跟一般SOAP应用手动生成一个访问令牌不同。速卖通的授权简单来说就是首先获取一个临时令牌(一次性),然后使用临时令牌换取访问令牌和刷新令牌,访问令牌有效期为10个小时,刷新令牌为6个月,当过期后需要重新获取,这个时候获取访问令牌有两个方式可选,第一种方法还是先获取临时另外然后用临时令牌换访问令牌,第二种就是用刷新令牌换取访问令牌。一般,在有刷新令牌的情况下,直接使用刷新令牌换取访问令牌,这也同时意味着在第一次换取临时令牌后用临时令牌换取访问令牌和刷新临牌时,应该把它们保存下来,以便一下API调用时使用。

临时令牌仅仅用来换取访问令牌和刷新令牌,而刷新令牌也仅仅用来换取访问令牌,实际调用API时需要使用是访问令牌。

第一步,获取临时令牌
不管是什么方式,总是要解决如何得到服务端给出的临时令牌。实际上最有效的办法就是在向服务器发送请求时,同时把接收这个临时令牌的URL传递过去,而服务器生成临时令牌后,把临时令牌以请求参数的形式附加到这个URL后面,然后服务端发送一个重定向到这个URL的指令,这样,URL对应的程序就能获取到这个临时临牌(请求参数)。
速卖通认证过去

这个图描述了这个过程,APP就是自己的程序。这里的第一步和第二部实际是可以合并的,取决于你希望通过App发送请求,还是由客户端直接发起请求,在我的实现中,使用了第一步和第二部合并由客户端直接发起请求的方法。你可能会被卡这里这一步,因为这里不仅仅是发送请求那么简单。事实上,速卖通的官方文档在描述第一步时如此说“用户使用app,访问在速卖通的隐私数据”,如果首次阅读,感觉文绉绉的,实际意思就是你的程序调用速卖通的API时,在调用API时需要访问临牌,如果没有访问令牌,需要先获取访问令牌,那么这就是一个完整的授权过程,何必把调用API作为第一步,编写文档的人水平不行。

授权过程如下:
首先,用户端或App发起授权请求
http://gw.api.alibaba.com/auth/authorize.htm?client_id=xxx&site=aliexpress&redirect_uri=YOUR_REDIRECT_URL&state=YOUR_PARM&_aop_signature=SIGENATURE
a) client_id:app注册时,分配给app的唯一标示,又称appKey
b) site:site参数标识当前授权的站点,直接填写aliexpress
c) redirect_uri: app的入口地址,授权临时令牌会以queryString的形式跟在该url后返回
d) state:可选,app自定义参数,回跳到redirect_uri时,会原样返回
e) _aop_signature:签名
参数签名(_aop_signature)为所有参数key + value 字符串拼接后排序,把排序结果拼接为字符串后通过bytesToHexString(HAMC-RSA1(data, appSecret))计算签名。 验证签名的方式为对参数执行同样的签名,比较传入的签名结果和计算的结果是否一致,一致为验签通过。

程序代码参考:

//生成签名
$code_arr = array(
	'client_id' => $appKey,
	'redirect_uri' => $redirectUrl,
	'site' => 'aliexpress',
	'state' => $state
);
ksort($code_arr);
foreach ($code_arr as $key=>$val)
	$sign_str .= $key . $val;
$code_sign = strtoupper(bin2hex(hash_hmac("sha1", $sign_str, $appSecret, true)));
			  
$get_code_url = "http://gw.api.alibaba.com/auth/authorize.htm?client_id={$appKey}&site=aliexpress&redirect_uri={$redirectUrl}&state={$state}&_aop_signature={$code_sign}";

发出请求后,客户端将获取到如下需要授权的交互内容:
速卖通认证

然后,输入用户名和密码提交以后就是官方描述的第三步。服务器接收后,如果用户名密码匹配,就会产生临时临牌并且把临时令牌附加到redirect_uri并定位到这个redirect_uri。这个redirect_uri对应的程序需要一段接收这个临时临牌的代码,然后用它来换成访问令牌和刷新令牌,这个过程就是授权的第二步。

第二步,用临时令牌获取访问令牌和刷新令牌
https://gw.api.alibaba.com/openapi/http/1/system.oauth2/getToken/YOUR_APPKEY?grant_type=authorization_code&need_refresh_token=true&client_id= YOUR_APPKEY&client_secret= YOUR_APPSECRET&redirect_uri=YOUR_REDIRECT_URI&code=CODE
注:此接口必须使用POST方法提交;必须使用https
getToken接口参数说明:
a) grant_type为授权类型,使用authorization_code即可
b) need_refresh_token为是否需要返回refresh_token,如果返回了refresh_token,原来获取的refresh_token也不会失效,除非超过半年有效期
c) client_id为app唯一标识,即appKey
d) client_secret为app密钥
e) redirect_uri为app入口地址
f) code为授权完成后返回的一次性令牌
g) 调用getToken接口不需要签名
注:如果超过code有效期(2分钟)或者已经使用code获取了一次令牌,code都将失效,需要返回第二步重新获取code

这里会返回访问令牌和刷新令牌,需要把它们保存下来,由于有有效期,所以保存是需要把时间戳带上,当超时时就需要重新获取。

代码参考:

function getDataUseCurl($url='', $data='', $post=false){
	if('' == $url){ return false; }
	/*
	$urldata = parse_url($url); 
	$headers = array("Host: ".$urldata['host']); 
	curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
	*/
	
	$ch = curl_init();	
	curl_setopt($ch, CURLOPT_HEADER, '');
	curl_setopt($ch, CURLOPT_URL, trim($url));
	curl_setopt($ch, CURLOPT_HEADER, false);
	//curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 
	curl_setopt($ch, CURLOPT_TIMEOUT, 30);
	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
	if($post === false){
		curl_setopt($ch, CURLOPT_POST, false);
	}else{
		curl_setopt($ch, CURLOPT_POST, true);
		if('' != $data){
			curl_setopt($ch, CURLOPT_POSTFIELDS,$data);
		}
	}
	if ((int)preg_match('/^HTTPS/i', $url) > 0) { //应该测试有没有SSL支持
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 
	}
	
	curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
	curl_setopt($ch,CURLOPT_FOLLOWLOCATION,true);
	curl_setopt($ch,CURLOPT_MAXREDIRS,10);
	
	curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0");
	$result = curl_exec($ch);
	$errn = curl_errno($ch);
	curl_close($ch);

	if((int)$errn == 0){
		return $result;
	}
	
	return false;
}


$url = "https://gw.api.alibaba.com/openapi/http/1/system.oauth2/getToken/{$appKey}";
$data = "grant_type=authorization_code&need_refresh_token=true&client_id={$appKey}&client_secret={$appSecret}&redirect_uri={$rurl}&code={$code}";

$bdata = getDataUseCurl($url, $data, $post=true);

$r = json_decode($bdata);
//成功 返回的结果标示了属于哪个账户,所以实际不需要传递用户标识。这里采用显式传递的方法。
/*
{"aliId":"8888888888","resourceOwner":"xxx","expires_in":"36000","access_token":"8795258a-6c8f-43a5-b8d0-763631edb610","refresh_token":"8795258a-6c8f-43a5-b8d0-763631edb610"} 
*/
if(isset($r->access_token) && isset($r->refresh_token)){ 
	$nw = time();
	$anw = $nw + 3600 * 9; // 有效10小时,改为9小时
	$rnw = $nw + 3600*24*30*5.5; // 有效半年,改为5.5月
	$db->Execute("update ".TABLE_AI_ACCOUNT." set code = '".$code."', access_token='".trim($r->access_token)."', access_token_expire='".$anw."', refresh_token='".trim($r->refresh_token)."', refresh_token_expire='".$rnw."' where sid='".trim($sid)."' limit 1");
	return true;
}

这样访问令牌和刷新令牌就保存下来了。授权过程已经完成。不过为了通用性,还需要包装一下。比如调用API时需要需要获取访问令牌,如果访问令牌过期了,就需要使用刷新令牌换取一下访问令牌。所以有如下两个封装函数:

//根据refresh_token刷新access_token
function getAccessTokenByRefreshToken($sid=''){
	global $db;
	
	if('' == $sid){
		return false;
	}else{
		$d = $db->Execute("select * from ".TABLE_AI_ACCOUNT." where sid ='".$db->prepareInput(trim($sid))."' limit 1");
		if($d->RecordCount() < 1){
			return false;
		}
		$id = (int)$d->fields['id'];
		$appKey = trim($d->fields['app_key']);
		$appSecret = trim($d->fields['app_secret']);
    	$refreshToken = trim($d->fields['refresh_token']);
		$refreshToken_expire = trim($d->fields['refresh_token_expire']);
		if(($appKey == '') || ($appSecret == '') || ($refreshToken == '')){ 
			
			return false; 
		}
		
		// 检查$refreshToken是否过期
		if(($refreshToken_expire - time()) < 0){
			return false;
		}
		
		$url = "https://gw.api.alibaba.com/openapi/param2/1/system.oauth2/getToken/{$appKey}";
		$post_data = "grant_type=refresh_token&client_id={$appKey}&client_secret={$appSecret}&refresh_token={$refreshToken}";

		$result = getDataUseCurl($url, $post_data, true);
		
		if($result){
			//成功
			/* {"aliId":"1728303813","resource_owner":"cn1501350670","expires_in":"36000","access_token":"d24c4d16-af2e-46b9-97e5-66eccfc8a8a7"} */
			//错误
			/*{"error":"invalid_request","error_description":"wrong refreshToken"}*/
			$r = json_decode($result);

			//成功返回
			if(isset($r->access_token)){ 
				//刷新
				$db->Execute("update ".TABLE_AI_ACCOUNT." set access_token='".trim($r->access_token)."', access_token_expire='".(time()+3600*9)."' where id = {$id} limit 1");
				return trim($r->access_token);
			}
			//可能是refresh_token过期 或 代码错误
			if(isset($r->error)){ 
				//echo "Error, {$r->error_description}\n";
				return false;
			}
		}
		//CURL错误
		return false;
	}	
}

//获取access_token,在调用具体的API时调用获取这个token
function getAccessToken($sid=''){
	global $db;
	if('' == $sid){
		return false;
	}else{
		$d = $db->Execute("select * from ".TABLE_AI_ACCOUNT." where sid ='".trim($sid)."' limit 1");
		if($d->RecordCount() < 1){
			return false;
		}
		
		$appKey = trim($d->fields['app_key']);
		$appSecret = trim($d->fields['app_secret']);
		$accessToken = trim($d->fields['access_token']);
		$accessTokenExpire = trim($d->fields['access_token_expire']);
    	$refreshToken = trim($d->fields['refresh_token']);
		
		if(($appKey == '') || ($appSecret == '')){ return false; }
		
		if(($accessToken != '') && (($accessTokenExpire - time()) > 0)){
			return $accessToken;
		}
		
		if($refreshToken == ''){ return false; }

		return getAccessTokenByRefreshToken($sid);
	}
}

这里就是授权的全部内容。

原创文章,转载务必保留出处。
永久链接:http://blog.ifeeline.com/1045.html