一、调用介绍
O2O作为京东的数据出口,用户只需要按照O2O的规范拼装一个正确的URL,通过https请求到O2O,即能获取到所需的数据。
O2O作为京东的数据出口,用户只需要按照O2O的规范拼装一个正确的URL,通过https请求到O2O,即能获取到所需的数据。
• 所有的API请求和响应数据编码皆为UTF-8格式(切勿转成unicode编码),如采用Get方式请求,URL里的所有参数值在发送请求时请做UrlEncoder处理。
• API请求的应用级参数需要转换成json格式,例如:jd_param_json={"return_id":"20032","trade_no":"20032"}
• json格式应为标准的json
• 目前只支持返回结果是json格式
• 所有API请求和响应内的日期格式都为yyyy-MM-dd HH:mm:ss,注意小时格式是24小时制,例如:2011-07-21 20:23:30
• API接口的错误信息在http response body内
• 签名方式为 md5(appsecret + key+ value .... key + value+appsecret)然后转大写字母,其中key、value对是除签名所有请求参数按key做的升序排列,value无需编码。
•关于订单查询,推荐 每次返回少量结果,然后多次按页查询。 在获取订单查询结果后,建议优先进行订单排重处理。
•查询开放平台接口触发限频场景,建议开发者自行实现重试补偿,以免因触发限流影响业务流程。
调用API时,必须传入系统级参数和对应的应用级参数。各个API的输入参数和返回结果详见API文档。
①系统级参数
字段 |
类型 |
是否必须 |
描述 |
token |
String |
是 |
采用OAuth授权方式为必填参数。 |
app_key |
String |
是 |
应用的app_key |
sign |
String |
是 |
签名 |
timestamp |
String |
是 |
时间戳,格式为yyyy-MM-dd HH:mm:ss,例如:2011-06-16 13:23:30。每次请求时间戳要求秒级不同 |
format |
String |
是 |
暂时只支持json |
v |
String |
是 |
API协议版本,可选值:1.0. |
②应用级参数(以通过京东SKUID修改门店价格 API为例)
字段 | 类型 | 是否必须 | 示例值 | 描述 |
skuId | Long | 是 | 京东SKU编号。 | |
stationNo | String | 是 | 京东门店编号。 | |
price | Long | 是 | 价格(单位:分)。 | |
marketPrice | Long | 否 | 市场价(单位:分)。 |
③如下为O2O应用级参数拼装(拼装示例 ,仅供参考)
字段 |
描述 |
|
示例值 |
jd_param_json |
String |
标准json 类型 |
{ |
④关于uuid字段的说明
在常规接口的调用中,返回的msg字段中会带有uuid的值,这个值唯一标识一次请求,可以帮助到家侧快速定位请求和解决问题
调用API时需要对请求参数进行签名,O2O服务器端会验证请求参数是否合法。
加密规则
①
所有请求参数按照字母先后顺序排列
例如:将token,app_key,timestamp,format,v,jd_param_json{}
排序为app_key,format,jd_param_json{},timestamp,token,v
② 把所有参数名和参数值进行拼装
例如:app_keyxxxformatxxxxjd_param_json{xxxx}timestampxxxxxxtokenxxxvx
③
把appSecret夹在字符串的两端
例如:appSecret+XXXX+appSecret
④ 使用MD5进行加密,再转化成大写
参考示例代码
示例(以下示例只体现逻辑)
调用https://openapi.jddj.com/djapi/
,假设app_key=7fd1c34598924181b3ba295b41c63507、appSecret=a7182e7f06274e4ebcbb0c64213fcfa7、token=2f3da4db-a0d4-40a8-bf4e-22007b5603d5
1) 输入参数
token=2f3da4db-a0d4-40a8-bf4e-22007b5603d5
app_key=7fd1c34598924181b3ba295b41c63507
timestamp=2016-08-08
12:00:00
format=json
v=1.0
jd_param_json=
{
"skuId":"123456789",
"stationNo":"135792468",
"price":
"20",
"marketPrice":"20"
}
2)
把参数按照字母顺序排列
app_key=7fd1c34598924181b3ba295b41c63507
format=json
jd_param_json=
{
"marketPrice":"20"
"price":
"20",
"skuId":"123456789",
"stationNo":"135792468"
}
timestamp=2016-08-08 12:00:00
token=2f3da4db-a0d4-40a8-bf4e-22007b5603d5
v=1.0
3) 连接参数名与参数值,并在首尾加上appSecret
授权接口:(示例)
a7182e7f06274e4ebcbb0c64213fcfa7app_key7fd1c34598924181b3ba295b41c63507formatjsonjd_param_json{"marketPrice":"20","price":"20","skuId":"123456789","stationNo":"135792468"
}timestamp2016-08-08 12:00:00token2f3da4db-a0d4-40a8-bf4e-22007b5603d5v1.0a7182e7f06274e4ebcbb0c64213fcfa7
注:
①以上字符串不允许有跨行,粘贴时请特别注意,除日期和时间中间的空格之外,不允许在其它位置出现空格。
②时间戳
timestamp最好就填写当前时间的前几分钟(必须在6分钟之内)
4) MD5加密后转成大写:08D99B718B35A0A98B07B2271ABB87F1
调用API时,将所有的参数转换成UTF-8编码,然后进行拼装,通过浏览器访问该地址即可成功调取一次接口。
[使用Get方式请求]
授权API 沙箱调用URL示例(仅为示例):
https://openapi.jddj.com/mockapi/order/finish?jd_param_json={"marketPrice":"20","price":"20","skuId":"123456789","stationNo":"135792468"}&token=12345678-b0e1-4d0c-9d10-a998d9597d75
&app_key=58830cc993ce426d9896b23670810653×tamp=2016-08-08 12:00:00&v=1.0&format=json&sign=08D99B718B35A0A98B07B2271ABB87F1
授权API 正式调用URL示例(仅为示例):
https://openapi.jddj.com/djapi/order/finish?jd_param_json={"marketPrice":"20","price":"20","skuId":"123456789","stationNo":"135792468"}&token=12345678-b0e1-4d0c-9d10-a998d9597d75
&app_key=58830cc993ce426d9896b23670810653×tamp=2016-08-08 12:00:00&v=1.0&format=json&sign=08D99B718B35A0A98B07B2271ABB87F1
若拼装后的请求URL长度小于1024个字符,可以使用Get方式请求。否则,必须用Post方式请求,平台所有API均支持Post方式请求。
[使用Post方式请求]
根据不同的开发语言实现Post表单方式请求(Content-Type应该是application/x-www-form-urlencoded),请求时参数须以名值对(name/value)形式传递,具体代码实现可参考“SDK下载”链接中的SDK源码。
1、加密算法采取AES(AES/CBC/NoPadding,即AES-128-CBC加密的NoPadding模式)算法。返回结果增加新字段encryptData,此字段是对原字段data的整体加密,加密的接口在API文档列表上会打上密标密
2、数据解析规则:
1)、对于无需加密的接口,数据内容不变
2)、对于需要加密的接口,明文密文并行期,两个字段data(明文),encryptData(密文)均有值
3)、并行期过后,仅encryptData(密文)有值
4)、具体处理逻辑(参考):
所有接口,优先判断密文字段encryptData内容是否为空,
如果为空,仍然解析明文字段data中的业务数据
如果不为空的,获取密文字段encryptData内容并解密,解密后获取业务数据(解密后的数据等效于明文字段data中的数据)
一定不要将需要解密的接口限制到一个固定范围,只针对现有加密接口进行解密。请参考以上逻辑,一旦有非加密接口变成加密接口时,仍然能够正常解析
5)、AES算法需要的key和偏移量采用appSecret的前16位和后16位
以java版SDK为例,使用方法
String key = appSecret.substring(0, 16);
String iv = appSecret.substring(16, 32);
String json = AESUtils.decryptAES(encryptData, key, iv);
3、参考示例(可用于验证算法):
密文:8FvHJcQmVojAIU61SNaS1ermHN2UVWknueRHFSNf2q5EbxNNmznoTYpRu7ySc/8CuU+QGZ9UIBMCyTuFafY3PuszEokEKc8M1Qfv/+o15h5bIU8LXfwRKOCm3JYzZtTOvJVU0hk/USvtDgraToszFl2hQZjZN5gGH1af0X8vopo=
key:0bcbe9d6e6124cf2
iv:aef2856a540f1326
对应明文:{"billId":"232219501234567","outBillId":"12345678901","statusId":"150","storeId":"11912345","timestamp":"2022-08-14
17:24:44"}
4、具体示例代码(JAVA):
import org.apache.commons.codec.binary.Base64; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; /** * @Classname AESUtils */ public class AESUtils { /** * @Description AES算法加密明文,加密用的Key 可以用26个字母和数字组成 使用AES-128-CBC加密模式,key需要为16位。 * key 密钥,长度16 * iv 偏移量,长度16 * @param data 明文 * @return 密文 */ public static String encryptAES(String data,String key,String iv) throws Exception { try { Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); int blockSize = cipher.getBlockSize(); byte[] dataBytes = data.getBytes(); int plaintextLength = dataBytes.length; if (plaintextLength % blockSize != 0) { plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize)); } byte[] plaintext = new byte[plaintextLength]; System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length); SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES"); IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes()); // CBC模式,需要一个向量iv,可增加加密算法的强度 cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec); byte[] encrypted = cipher.doFinal(plaintext); return AESUtils.encode(encrypted).trim(); // BASE64做转码。 } catch (Exception e) { e.printStackTrace(); return null; } } /** * @Description AES算法解密密文 * @param data 密文 * @return 明文 */ public static String decryptAES(String data,String key,String iv) throws Exception { try { byte[] encrypted1 = AESUtils.decode(data);//先用base64解密 Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES"); IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes()); cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec); byte[] original = cipher.doFinal(encrypted1); String originalString = new String(original); return originalString.trim(); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 编码 * @param byteArray * @return */ public static String encode(byte[] byteArray) { return new String(new Base64().encode(byteArray)); } /** * 解码 * @param base64EncodedString * @return */ public static byte[] decode(String base64EncodedString) { return new Base64().decode(base64EncodedString); } }
函数 | 作用 |
concatParams | 将输入参数排序并拼接 |
getSign | 通过给定的param,appSecret计算签名 |
getMD5String | 对字符串MD5加密 |
import java.util.Arrays; import java.util.Map; public class JdHelper { private static String concatParams(Map<String, String> params2) { Object[] key_arr = params2.keySet().toArray(); Arrays.sort(key_arr); StringBuilder str = new StringBuilder(); for (Object key : key_arr) { String val = params2.get(key); str.append(key).append(val); } return str.toString(); } public static String getSign(Map<String,String> param,String appSecret ) throws Exception{ String sysStr=concatParams(param); StringBuilder resultStr=new StringBuilder(); resultStr.append(appSecret).append(sysStr).append(appSecret); return MD5Util.getMD5String(resultStr.toString()).toUpperCase(); } } import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class MD5Util { private static final char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9','a', 'b', 'c', 'd', 'e', 'f' }; public static String getMD5String(String s) { try { return getMD5String(s.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e.getLocalizedMessage()); } } public static String getMD5String(byte[] bytes) { try { MessageDigest messagedigest = MessageDigest.getInstance("MD5"); messagedigest.update(bytes); return bufferToHex(messagedigest.digest()); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e.getLocalizedMessage()); } return null; } private static String bufferToHex(byte bytes[]) { return bufferToHex(bytes, 0, bytes.length); } private static String bufferToHex(byte bytes[], int m, int n) { StringBuffer stringbuffer = new StringBuffer(2 * n); int k = m + n; for (int l = m; l < k; l++) { appendHexPair(bytes[l], stringbuffer); } return stringbuffer.toString(); } private static void appendHexPair(byte bt, StringBuffer stringbuffer) { char c0 = hexDigits[(bt & 0xf0) >> 4]; char c1 = hexDigits[bt & 0xf]; stringbuffer.append(c0); stringbuffer.append(c1); } }