对接方案
- Walmart API授权采用基于OAuth Token 的授权模式,并不是OAuth2.0的授权模式;
- 这种授权模式是使用凭证授权的方式,需要卖家在 Walmart开发者后台生成一套凭证,凭证信息包括了Client ID 和 Client Secret。
一、US站点
如果是US站点,就需要在开发者后台获取以下信息。
请求头参数
- WM_SVC.NAME Walmart Service Name 必填 例如:Walmart Marketplace
- WM_QOS.CORRELATION_ID 每个请求的唯一Id 必填 例如: UUID
- Authorization 用Client ID and Client Secret组合生成的Base64 encodes 必填 例如:Basic YzcyOTFjNmItNzI5MC0
- WM_CONSUMER.CHANNEL.TYPE 如果凭证是卖家自己用的,这个值为空。如果是第三方服务商调用API,这个值需要第三方服务商从Walmart那里通过商务合作获得,这个参数是用于Walmart识别是哪个第三方在调用API的
- WM_SEC.ACCESS_TOKEN 必填,通过用Client ID and Client Secret 请求Token API 获取的access_token,有效期为15分钟
1.获取授权
/**
* 对密钥进行编码
*
* @return
*/
private String getAuthorization(String clientSecret, String clientId) {
String str = clientSecret+ ":" + clientId;
return "Basic " + Base64.encodeBase64String(str.getBytes());
}
2.获取 US站点的token
**
* 获取美国店铺的token
*
* @return
*/
public String getUSAAccessToken(String authorization, ShopSettings shopSettings) {
//先从redis中获取
String redisKey = String.format(WalmartConstant.ACCESS_TOKEN, shopSettings.getAccountName());
String accessToken = redisCache.getCacheObject(redisKey);
if (StringUtils.isNotEmpty(accessToken)) {
log.debug("从redis中获取token:{}", accessToken);
return accessToken;
}
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", authorization);
headers.put("WM_SVC.NAME", shopSettings.getAccountName());
headers.put("WM_QOS.CORRELATION_ID", UUID.randomUUID().toString());
try {
HttpResponse response = HttpRequest.post(WalmartConstant.USA_TOKEN_URL)
.addHeaders(headers)
.body("grant_type=client_credentials", "application/x-www-form-urlencoded")
.execute();
JSONObject jsonObject = JSONObject.parseObject(response.body());
accessToken = jsonObject.getString("access_token");
if (accessToken == null) {
throw new CustomException("获取沃尔玛接口调用凭证失败", 500);
}
}catch (Exception e){
log.error("【获取美国站点token值异常】,{}",e.getMessage());
return null;
}
redisCache.setCacheObject(redisKey, accessToken, 899, TimeUnit.SECONDS);
log.debug("生成的token为:{}", accessToken);
return accessToken;
}
3.获取访问US站点API的请求头
/**
* 获取访问Us站点API的请求头
*
* @return
*/
public Map<String, String> getUsHeaders(Long accountId) {
ShopSettings shopSettings = shopSettingsService.getShopSettingCache(accountId, "walmart");
String authorization = getAuthorization(shopSettings.getClientSecret(), shopSettings.getClientId());
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", authorization);
headers.put("WM_SVC.NAME", shopSettings.getAccountName());
headers.put("WM_QOS.CORRELATION_ID", UUID.randomUUID().toString());
headers.put("WM_SEC.ACCESS_TOKEN", getUSAccessToken(authorization, shopSettings));
return headers;
}
4.调用API测试
/**
* 获取节点上商品的库存
* @param sku
* @return
*/
@GetMapping("/inventories/{sku}")
public AjaxResult getInventory(@PathVariable(value = "sku") String sku,
@RequestParam(required = false, value = "shipNode")String shipNode){
if (StringUtils.isBlank(sku)){
return AjaxResult.error(BaseError.CHECK_ERROR);
}
String url = "https://marketplace.walmartapis.com/v3/inventories/"+ sku;
if (StringUtils.isNotBlank(shipNode)){
url = url+"?shipNode=" + shipNode;
}
Map<String, String> headers = walmartUtil.getUsHeaders(60L);
//执行请求,获取数据
HttpResponse response = HttpRequest.get(url)
.addHeaders(headers)
.contentType("application/json")//一定要加这一行代码,否则可能会报错
.execute();
if (200 == response.getStatus()) {
return AjaxResult.success(JSONObject.parseObject(response.body()));
}
return AjaxResult.error();
}
二、CA站点
CA站点和US站点稍有不同,CA站点是在卖家中心的General Settings那里点击API跳转到页面去获取Consumer ID、Channel Type、Private Key。Consumer ID等同于US站点的Client ID,Private Key等同于Client Secret。Channel Type就是请求头参数中的WM_CONSUMER.CHANNEL.TYPE
1.获取CA站点的API的数字签名和时间戳
/**
* 获取访问加拿大店铺API的数字签名和时间戳参数
*
* @param requestUrl
* @param requestMethod
* @return
*/
public Map<String, String> getSignatureAndTimestamp(String requestUrl, String requestMethod, Long accountId) {
if (StringUtils.isEmpty(requestMethod) || StringUtils.isEmpty(requestUrl)) {
throw new CustomException(BaseError.CHECK_ERROR);
}
Map<String, String> result = new HashMap<>();
String timestamp = String.valueOf(System.currentTimeMillis());
String signature = getWalmartAuthSignature(requestUrl, requestMethod, timestamp, accountId);
result.put("authSignature", signature);
result.put("timestamp", timestamp);
return result;
}
/**
* 获取沃尔玛认证数字签名
*
* @param requestUrl 要调用的完整 URL,包括路径和查询参数
* @param requestMethod GET or POST
* @param timestamp 时间戳
* @return
*/
private String getWalmartAuthSignature(String requestUrl, String requestMethod, String timestamp, Long accountId) {
ShopSettings shopSettings = shopSettingsService.getShopSettingCache(accountId, "walmart");
//登录后从开发者中心获取的Consumer ID
String consumerId = shopSettings.getConsumerId();
//私钥
String privateKey = shopSettings.getPrivateKey();
String stringToSign = consumerId + "\n" + requestUrl + "\n" + requestMethod + "\n" + timestamp + "\n";
return signData(stringToSign, privateKey);
}
/**
* 签名算法
*
* @param stringToBeSigned 签名结构 消费者ID(Consumer ID) + "\n" + 完整路径(url) + "\n" + 时间戳(timestamp) + "\n";
* @param encodedPrivateKey 私密秘钥Private Key
* @return
*/
private static String signData(String stringToBeSigned, String encodedPrivateKey) {
String signatureString = null;
try {
byte[] encodedKeyBytes = Base64.decodeBase64(encodedPrivateKey);
PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(encodedKeyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey myPrivateKey = kf.generatePrivate(privSpec);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(myPrivateKey);
byte[] data = stringToBeSigned.getBytes("UTF-8");
signature.update(data);
byte[] signedBytes = signature.sign();
signatureString = Base64.encodeBase64String(signedBytes);
} catch (Exception e) {
e.printStackTrace();
}
return signatureString;
}
2.获取访问CA站点API的请求头
/**
* 获取访问CA站点API的请求头
*
* @return
*/
public Map<String, String> getCaHeaders(String requestUrl, String requestMethod, Long accountId) {
ShopSettings shopSettings = shopSettingsService.getShopSettingCache(accountId, "walmart");
Map<String, String> map = getSignatureAndTimestamp(requestUrl, requestMethod, accountId);
String authSignature = map.get("authSignature");
String timestamp = map.get("timestamp");
log.debug(authSignature);
log.debug(timestamp);
Map<String, String> headers = new HashMap<>();
headers.put("WM_CONSUMER.CHANNEL.TYPE", shopSettings.getChannelType());
headers.put("WM_SVC.NAME", shopSettings.getAccountName());
headers.put("WM_QOS.CORRELATION_ID", UUID.randomUUID().toString());
headers.put("WM_SEC.TIMESTAMP", timestamp);
headers.put("WM_SEC.AUTH_SIGNATURE", authSignature);
headers.put("WM_CONSUMER.ID", shopSettings.getConsumerId());
return headers;
}
3.调用API测试
public JSONObject listItemsCa(ItemParams itemParams, Long accountId) {
String nextCursor = Optional.ofNullable(itemParams.getNextCursor()).orElse("*");
String limit = Optional.ofNullable(itemParams.getLimit()).orElse("50");
String url = "https://marketplace.walmartapis.com/v3/ca/items?nextCursor=" + nextCursor + "&limit=" + limit;
Map<String, String> headers = walmartUtil.getCaHeaders(url, "GET", accountId);
try {
HttpResponse response = HttpRequest.get(url)
.addHeaders(headers)
.execute();
if (200 == response.getStatus()){
return JSONObject.parseObject(response.body());
}
}catch (Exception e){
log.error("【walmart CA 查询商品错误】:{}", e.getMessage());
}
return new JSONObject();
}
更新:2023年6月15日
如果在调CA站点的API时可能会出现下面这种几种情况,
- WM_CONSUMER.CHANNEL.TYPE填错后的异常
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:errors xmlns:ns2="http://walmart.com/">
<ns2:error>
<ns2:code>INVALID_REQUEST_HEADER.GMP_GATEWAY_API</ns2:code>
<ns2:field>WM_CONSUMER.CHANNEL.TYPE</ns2:field>
<ns2:description>WM_CONSUMER.CHANNEL.TYPE set null or invalid</ns2:description>
<ns2:info>One or more request headers are invalid.</ns2:info>
<ns2:severity>ERROR</ns2:severity>
<ns2:category>DATA</ns2:category>
<ns2:causes/>
<ns2:errorIdentifiers/>
</ns2:error>
</ns2:errors>
- WM_CONSUMER.ID填错后的异常
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:errors xmlns:ns2="http://walmart.com/">
<ns2:error>
<ns2:code>UNAUTHORIZED.GMP_GATEWAY_API</ns2:code>
<ns2:field>UNAUTHORIZED</ns2:field>
<ns2:description>Unauthorized</ns2:description>
<ns2:info>Unauthorized token or incorrect authorization header. Please verify correct format: "Authorization: Basic Base64Encode(clientId:clientSecret)" For more information, see https://developer.walmart.com/#/apicenter/marketPlace/latest#apiAuthentication.</ns2:info>
<ns2:severity>ERROR</ns2:severity>
<ns2:category>DATA</ns2:category>
<ns2:causes/>
<ns2:errorIdentifiers/>
</ns2:error>
</ns2:errors>
- privateKey填错后的异常
```java
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:errors xmlns:ns2="http://walmart.com/">
<ns2:error>
<ns2:code>UNAUTHORIZED.GMP_GATEWAY_API</ns2:code>
<ns2:field>UNAUTHORIZED</ns2:field>
<ns2:description>Unauthorized</ns2:description>
<ns2:info>Unauthorized token or incorrect authorization header. Please verify correct format: "Authorization: Basic Base64Encode(clientId:clientSecret)" For more information, see https://developer.walmart.com/#/apicenter/marketPlace/latest#apiAuthentication.</ns2:info>
<ns2:severity>ERROR</ns2:severity>
<ns2:category>DATA</ns2:category>
<ns2:causes/>
<ns2:errorIdentifiers/>
</ns2:error>
</ns2:errors>
- timestamp填错后的异常【检查代码运行机器的系统时间是否和正常时间有偏差】
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:errors xmlns:ns2="http://walmart.com/">
<ns2:error>
<ns2:code>UNAUTHORIZED.GMP_GATEWAY_API</ns2:code>
<ns2:field>UNAUTHORIZED</ns2:field>
<ns2:description>Unauthorized</ns2:description>
<ns2:info>Unauthorized token or incorrect authorization header. Please verify correct format: "Authorization: Basic Base64Encode(clientId:clientSecret)" For more information, see https://developer.walmart.com/#/apicenter/marketPlace/latest#apiAuthentication.</ns2:info>
<ns2:severity>ERROR</ns2:severity>
<ns2:category>DATA</ns2:category>
<ns2:causes/>
<ns2:errorIdentifiers/>
</ns2:error>
</ns2:errors>