SHOPLAZZA_TOKEN_REFRESH.md
8.93 KB
店匠 Token 刷新文档
✅ 正确的刷新 Token 端点
根据店匠官方文档,刷新 Token 的端点不是 partners.shoplazza.com,而是店铺域名下的端点:
POST https://{store_name}.myshoplaza.com/admin/oauth/token
📋 请求格式
curl 命令
curl --location --request POST 'https://47167113-1.myshoplaza.com/admin/oauth/token' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data-raw '{
"client_id": "m8F9PrPnxpyrlz4ONBWRoINsa5xyNT4Qd-Fh_h7o1es",
"client_secret": "m2cDNrBqAa8TKeridXd4eXnhi9E7pda2gKXet_72rjo",
"refresh_token": "NjYhBexdDZ0NE87Bg8Xerx1rFRnHvMQ9XDy_PITV1ME",
"grant_type": "refresh_token",
"redirect_uri": "https://saas-ai-api.essa.top/search/oauth_sdk/redirect_uri"
}'
请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
client_id |
String | ✅ | APP 的 Client ID |
client_secret |
String | ✅ | APP 的 Client Secret |
refresh_token |
String | ✅ | 之前获得的 Refresh Token |
grant_type |
String | ✅ | 固定值:"refresh_token" |
redirect_uri |
String | ✅ | OAuth 配置的 Redirect URI |
📥 响应格式
成功响应
{
"access_token": "R77yIJLfYy5mGWs_MGLsw7dT67iAIeHxwJga5psB5Yg",
"token_type": "Bearer",
"expires_in": 31556951,
"refresh_token": "R3bAhXUSAFZ7V5Kzk7YEJXdepNAQbefi85QyG4XiEkY",
"scope": "read_shop write_shop read_product read_order read_customer read_app_proxy",
"created_at": 1763037342,
"store_id": "2286274",
"store_name": "47167113-1",
"expires_at": 1794594294,
"locale": "zh-CN"
}
响应字段说明
| 字段 | 说明 |
|---|---|
access_token |
新的 Access Token(需要更新数据库) |
refresh_token |
新的 Refresh Token(需要更新数据库) |
expires_at |
新的过期时间戳 |
store_id |
店铺 ID |
store_name |
店铺名称 |
⚠️ 重要: 刷新后会返回新的 access_token 和 refresh_token,必须更新数据库!
💻 代码实现示例
Java 实现
@Service
public class ShoplazzaTokenService {
@Value("${shoplazza.oauth.client-id}")
private String clientId;
@Value("${shoplazza.oauth.client-secret}")
private String clientSecret;
@Value("${shoplazza.oauth.redirect-uri}")
private String redirectUri;
@Autowired
private RestTemplate restTemplate;
@Autowired
private ShopConfigMapper shopConfigMapper;
/**
* 刷新 Access Token
*/
public TokenResponse refreshToken(String storeDomain, String refreshToken) {
// 注意:端点是店铺域名,不是 partners.shoplazza.com
String url = String.format("https://%s/admin/oauth/token", storeDomain);
Map<String, String> requestBody = new HashMap<>();
requestBody.put("client_id", clientId);
requestBody.put("client_secret", clientSecret);
requestBody.put("refresh_token", refreshToken);
requestBody.put("grant_type", "refresh_token");
requestBody.put("redirect_uri", redirectUri); // ← 重要:必须包含
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, String>> request = new HttpEntity<>(requestBody, headers);
try {
ResponseEntity<TokenResponse> response = restTemplate.postForEntity(
url,
request,
TokenResponse.class
);
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
return response.getBody();
}
throw new BusinessException("Failed to refresh token: " + response.getStatusCode());
} catch (HttpClientErrorException e) {
log.error("Token refresh failed: {}", e.getResponseBodyAsString());
throw new BusinessException("Token refresh failed: " + e.getMessage());
}
}
/**
* 刷新指定店铺的 Token
*/
@Transactional
public void refreshShopToken(Long shopConfigId) {
ShopConfig shop = shopConfigMapper.selectById(shopConfigId);
if (shop == null) {
throw new BusinessException("Shop not found");
}
if (StringUtils.isEmpty(shop.getRefreshToken())) {
throw new BusinessException("Refresh token not found for shop: " + shop.getStoreName());
}
try {
// 调用刷新接口(使用店铺域名)
TokenResponse newToken = refreshToken(
shop.getStoreDomain(), // 如:47167113-1.myshoplaza.com
shop.getRefreshToken()
);
// 更新数据库
shop.setAccessToken(newToken.getAccessToken());
shop.setRefreshToken(newToken.getRefreshToken());
shop.setTokenExpiresAt(newToken.getExpiresAt());
shop.setUpdatedAt(new Date());
shopConfigMapper.updateById(shop);
log.info("Token refreshed successfully for shop: {}", shop.getStoreName());
} catch (Exception e) {
log.error("Failed to refresh token for shop: {}", shop.getStoreName(), e);
throw new BusinessException("Token refresh failed", e);
}
}
}
Python 实现
import requests
from datetime import datetime
class ShoplazzaTokenService:
def __init__(self, client_id, client_secret, redirect_uri):
self.client_id = client_id
self.client_secret = client_secret
self.redirect_uri = redirect_uri
def refresh_token(self, store_domain, refresh_token):
"""刷新 Access Token"""
# 注意:端点是店铺域名
url = f"https://{store_domain}/admin/oauth/token"
payload = {
"client_id": self.client_id,
"client_secret": self.client_secret,
"refresh_token": refresh_token,
"grant_type": "refresh_token",
"redirect_uri": self.redirect_uri # ← 重要:必须包含
}
headers = {
"Content-Type": "application/json",
"Accept": "application/json"
}
try:
response = requests.post(
url,
json=payload,
headers=headers,
timeout=10
)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Token refresh failed: {e}")
raise
⏰ 定时刷新策略
提前刷新(推荐)
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
public void refreshExpiringTokens() {
// 查询7天内过期的 Token
DateTime sevenDaysLater = DateTime.now().plusDays(7);
List<ShopConfig> shops = shopConfigMapper.selectExpiringTokens(sevenDaysLater);
for (ShopConfig shop : shops) {
try {
refreshShopToken(shop.getId());
log.info("Token refreshed for shop: {}", shop.getStoreName());
} catch (Exception e) {
log.error("Failed to refresh token for shop: {}", shop.getStoreName(), e);
// 发送告警通知
}
}
}
API 调用时检查并刷新
public String getValidAccessToken(String storeId) {
ShopConfig shop = shopConfigMapper.selectByStoreId(storeId);
if (shop == null) {
throw new BusinessException("Shop not found: " + storeId);
}
// 检查是否即将过期(提前1小时)
DateTime oneHourLater = DateTime.now().plusHours(1);
if (shop.getTokenExpiresAt().isBefore(oneHourLater)) {
// 刷新 Token
refreshShopToken(shop.getId());
// 重新查询
shop = shopConfigMapper.selectByStoreId(storeId);
}
return shop.getAccessToken();
}
⚠️ 关键要点
1. 端点差异
| 用途 | 端点 |
|---|---|
| 获取 Token(初始授权) | https://partners.shoplazza.com/partner/oauth/token |
| 刷新 Token | https://{store_domain}.myshoplaza.com/admin/oauth/token |
2. 必需参数
- ✅
client_id - ✅
client_secret - ✅
refresh_token - ✅
grant_type: "refresh_token" - ✅
redirect_uri← 这个很重要,之前测试失败就是因为缺少这个!
3. 更新数据库
刷新成功后,必须更新数据库中的:
access_tokenrefresh_tokentoken_expires_at
📊 测试结果
✅ 测试成功!
- 旧 Access Token:
6GAjQbN51YS-N5l0e2Oxlc19iVn4X8FCeuHQ7df4DSA - 新 Access Token:
R77yIJLfYy5mGWs_MGLsw7dT67iAIeHxwJga5psB5Yg - 新 Refresh Token:
R3bAhXUSAFZ7V5Kzk7YEJXdepNAQbefi85QyG4XiEkY - 新过期时间:
1794594294(2026-11-14)
🔗 参考文档
- 店匠官方文档:刷新 Token 章节
- OAuth 2.0 标准:RFC 6749