feat(pdd): 二次上架走 goods.information.update,下架走 sale.status.set

- 请求带 pddGoodsId>0 时使用顶层表单调用 pdd.goods.information.update(与 goods.add 同形态)
- 移除 information-update-reshelf-enabled 配置与开关
- 业务成功以 goods_update_response.is_success 为准
- 扩展 ExternalGoodsUpsert/Delist 请求与发布/下架链路

Made-with: Cursor
This commit is contained in:
huangyujie 2026-03-27 11:19:33 +08:00
parent 22152216dd
commit 4eac7824fe
12 changed files with 271 additions and 20 deletions

View File

@ -26,7 +26,7 @@ import java.util.List;
* <p>不查询 {@code o_shop}{@code shopId} 仅作业务侧店铺维度标识写入 {@code o_goods}/{@code o_goods_sku}</p>
* <ul>
* <li>{@code POST /external/goods/upsert} 商品上架/同步新建默认 {@code o_goods.status=1}</li>
* <li>{@code POST /external/goods/delist} 本地下架{@code status=2}不调各平台下架 API</li>
* <li>{@code POST /external/goods/delist} 本地下架{@code status=2}可选 {@code pddGoodsId}+{@code pddPopAuth} 调拼多多 {@code pdd.goods.sale.status.set}{@code is_onsale=0}</li>
* </ul>
*
* @author guochengyu
@ -114,7 +114,7 @@ public class ExternalGoodsController extends BaseController {
}
/**
* 本地下架 {@code o_goods.status} 置为 2已下架不调用拼多多等平台的下架接口
* 本地下架 {@code o_goods.status} 置为 2若传 {@code pddGoodsId} 则同步拼多多在售状态 {@code external.pdd.sale-status-set-enabled}
*/
@PostMapping("/delist")
public AjaxResult delist(@RequestBody ExternalGoodsDelistRequest req) {
@ -127,6 +127,13 @@ public class ExternalGoodsController extends BaseController {
if (!StringUtils.hasText(req.getOutGoodsId())) {
return AjaxResult.error("参数错误outGoodsId不能为空");
}
if (req.getPddGoodsId() != null && req.getPddGoodsId() > 0 && externalPddProperties.isSaleStatusSetEnabled()) {
var auth = req.getPddPopAuth();
if (auth == null || !StringUtils.hasText(auth.getAppKey()) || !StringUtils.hasText(auth.getAppSecret())
|| !StringUtils.hasText(auth.getAccessToken())) {
return AjaxResult.error("参数错误:传 pddGoodsId 下架拼多多时pddPopAuth 需提供 appKey、appSecret、accessToken");
}
}
try {
externalGoodsAppService.delistGoods(req);
return AjaxResult.success();

View File

@ -38,6 +38,8 @@ external:
# 拼多多 POPupsert 落库后是否调用 pdd.goods.add须与 maindata yundt.maindata.erp-open.default-category-code 等对齐)
pdd:
publish-enabled: true
# 下架:请求体带 pddGoodsId+pddPopAuth 时走 pdd.goods.sale.status.setis_onsale=0
sale-status-set-enabled: true
gateway-url: https://gw-api.pinduoduo.com/api/router
# 发布前可选拉类目规则(诊断用)
auto-fetch-cat-rule: false

View File

@ -3,7 +3,7 @@ package cn.qihangerp.model.request;
import lombok.Data;
/**
* 外部系统商品下架本地 {@code o_goods.status=2}不调用各平台开放 API
* 外部系统商品下架本地 {@code o_goods.status=2}可选同步拼多多 {@code pdd.goods.sale.status.set}{@code is_onsale=0}
*
* @author guochengyu
*/
@ -15,4 +15,13 @@ public class ExternalGoodsDelistRequest {
/** 外部商品 ID幂等键与 upsert 的 outGoodsId 一致 */
private String outGoodsId;
/**
* 拼多多 {@code goods_id} {@link #pddPopAuth} 同时传入且开启 {@code external.pdd.sale-status-set-enabled}
* 先调 {@code pdd.goods.sale.status.set} 再落库下架
*/
private Long pddGoodsId;
/** 拼多多 POP 凭证,与 upsert 的 {@code pddPopAuth} 结构一致 */
private ExternalGoodsUpsertRequest.PddPopAuth pddPopAuth;
}

View File

@ -32,6 +32,15 @@ public class ExternalGoodsUpsertRequest {
*/
private String outGoodsId;
/**
* 拼多多侧商品 ID{@code goods_id}调用方在本店该商品货架已上架且 extension 已存 pddGoodsId时传入
* 服务端将走 {@code pdd.goods.information.update} {@code pdd.goods.add} 相同的顶层表单字段形态而不再 {@code pdd.goods.add}
* {@code publish-enabled=true}
*/
@JsonProperty("pddGoodsId")
@JsonAlias({"pdd_goods_id"})
private Long pddGoodsId;
/**
* 商品标题
*/

View File

@ -37,6 +37,9 @@ public class ExternalGoodsUpsertResultVo implements Serializable {
*/
private Long pddGoodsId;
/** {@code GOODS_ADD} / {@code SALE_STATUS_SET}(与拼多多发布链路一致) */
private String pddPublishLane;
// ---- 店铺凭证与自动拉取类目/spec platform=PDD 时有意义 ----
/** REQUEST请求体 pddPopAuth/ NONE与 {@code o_shop} 无关 */

View File

@ -26,6 +26,11 @@ public class PddPublishLaneResultVo implements Serializable {
/** 拼多多 {@code goods_add_response.goods_id};仅 add 成功且能解析时有值 */
private Long pddGoodsId;
/**
* {@code GOODS_ADD}{@code pdd.goods.add}{@code SALE_STATUS_SET}{@code pdd.goods.sale.status.set}在售状态
*/
private String publishLane;
/** REQUEST / NONE */
private String shopCredentialSource;

View File

@ -108,7 +108,8 @@ public class ExternalGoodsAppServiceImpl implements ExternalGoodsAppService {
.pddCatRuleFetched(null)
.pddCatRuleSnippet(null)
.pddSpecAutoResolved(null)
.pddAutoResolveDetail(null);
.pddAutoResolveDetail(null)
.pddPublishLane(null);
if ("PDD".equalsIgnoreCase(req.getPlatform())) {
OGoods g = goodsService.getById(goodsId);
@ -143,16 +144,18 @@ public class ExternalGoodsAppServiceImpl implements ExternalGoodsAppService {
out.pddCatRuleSnippet(lane.getCatRuleSnippet());
out.pddSpecAutoResolved(lane.getSpecAutoResolved());
out.pddAutoResolveDetail(lane.getAutoResolveDetail());
out.pddPublishLane(lane.getPublishLane());
}
ExternalGoodsUpsertResultVo vo = out.build();
if ("PDD".equalsIgnoreCase(req.getPlatform())) {
log.info("[external/upsert] PDD summary shopId={} outGoodsId={} erpGoodsId={} attempted={} success={} credentialSource={} message={} goodsAddSnippet={} catRuleFetched={} catRuleSnippet={} specAutoResolved={} autoResolveDetail={}",
log.info("[external/upsert] PDD summary shopId={} outGoodsId={} erpGoodsId={} attempted={} success={} lane={} credentialSource={} message={} goodsAddSnippet={} catRuleFetched={} catRuleSnippet={} specAutoResolved={} autoResolveDetail={}",
req.getShopId(),
req.getOutGoodsId(),
vo.getGoodsId(),
vo.getPddPublishAttempted(),
vo.getPddPublishSuccess(),
vo.getPddPublishLane(),
vo.getShopCredentialSource(),
truncateForLog(vo.getPddPublishMessage(), 600),
truncateForLog(vo.getPddResponseSnippet(), 500),
@ -188,6 +191,12 @@ public class ExternalGoodsAppServiceImpl implements ExternalGoodsAppService {
if (existing == null) {
throw new IllegalArgumentException("商品不存在shopId=" + req.getShopId() + ", outGoodsId=" + req.getOutGoodsId());
}
PddPublishLaneResultVo popDelist = externalPddPublishService.pddSaleStatusSetForDelist(req);
if (Boolean.TRUE.equals(popDelist.getAttempted()) && !Boolean.TRUE.equals(popDelist.getSuccess())) {
throw new IllegalArgumentException(StringUtils.hasText(popDelist.getMessage())
? popDelist.getMessage()
: "拼多多下架在售状态失败");
}
// 1 销售中 2 已下架 o_goods.status 注释一致
existing.setStatus(2);
existing.setUpdateBy("external");

View File

@ -22,6 +22,11 @@ public class ExternalPddProperties {
*/
private boolean publishEnabled = false;
/**
* 是否允许调用 {@code pdd.goods.sale.status.set}下架时 {@code is_onsale=0}
*/
private boolean saleStatusSetEnabled = true;
/**
* 发品前是否将外链图片经 POP 图片上传接口上传到拼多多图床并用返回 URL 替换
* {@code carousel_gallery}/{@code detail_gallery}/{@code sku_list[].thumb_url}去重上传

View File

@ -2,6 +2,7 @@ package cn.qihangerp.service.external.pdd;
import cn.qihangerp.model.entity.OGoods;
import cn.qihangerp.model.entity.OGoodsSku;
import cn.qihangerp.model.request.ExternalGoodsDelistRequest;
import cn.qihangerp.model.request.ExternalGoodsUpsertRequest;
import cn.qihangerp.model.vo.PddPublishLaneResultVo;
import cn.qihangerp.service.external.shop.PddShopCredential;
@ -11,7 +12,9 @@ import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 拼多多发布POP 凭证仅来自本次请求的 {@link ExternalGoodsUpsertRequest#getPddPopAuth()}不依赖 {@code o_shop}
@ -161,30 +164,63 @@ public class ExternalPddPublishService {
cred, gateway, catId, resolveResult, autoFetchedCatRuleRaw, props, req);
try {
log.info("[PDD] pdd.goods.add begin shopId={} outGoodsId={} catId={} skuCount={} noSpecBook={}",
req.getShopId(), req.getOutGoodsId(), catId, skuRows.size(), noSpecBookPublish);
String goodsAddRootJson = noSpecBookPublish
? paramBuilder.buildParamJsonNoSpecBook(goods, skus, req, props, catRuleRawForGoodsProps)
: paramBuilder.buildParamJson(goods, skus, req, props, effectiveOverrides, catRuleRawForGoodsProps);
goodsAddRootJson = pddGoodsImageRehostService.rehostExternalImagesInGoodsAddJson(
goodsAddRootJson, gateway, cred.getAppKey(), cred.getAppSecret(), cred.getAccessToken());
String raw = pddPopClient.invokeGoodsAdd(gateway, cred.getAppKey(), cred.getAppSecret(), cred.getAccessToken(),
goodsAddRootJson);
boolean ok = !PddOpenApiSupport.isError(raw);
String errMsg = ok ? null : PddOpenApiSupport.formatError(raw);
Long pddGoodsId = ok ? PddOpenApiSupport.parseGoodsIdFromGoodsAddResponse(raw) : null;
boolean useInfoUpdate = req.getPddGoodsId() != null && req.getPddGoodsId() > 0;
String raw;
String lane;
String okMsg;
String failPrefix;
if (useInfoUpdate) {
log.info("[PDD] pdd.goods.information.update begin shopId={} outGoodsId={} pddGoodsId={} catId={} skuCount={} noSpecBook={}",
req.getShopId(), req.getOutGoodsId(), req.getPddGoodsId(), catId, skuRows.size(), noSpecBookPublish);
String updateJson = paramBuilder.toInformationUpdateParamJson(goodsAddRootJson, req.getPddGoodsId());
raw = pddPopClient.invokeGoodsInformationUpdate(gateway, cred.getAppKey(), cred.getAppSecret(),
cred.getAccessToken(), updateJson);
lane = "INFORMATION_UPDATE";
okMsg = "pdd.goods.information.update 成功";
failPrefix = "pdd.goods.information.update 失败";
} else {
log.info("[PDD] pdd.goods.add begin shopId={} outGoodsId={} catId={} skuCount={} noSpecBook={}",
req.getShopId(), req.getOutGoodsId(), catId, skuRows.size(), noSpecBookPublish);
raw = pddPopClient.invokeGoodsAdd(gateway, cred.getAppKey(), cred.getAppSecret(), cred.getAccessToken(),
goodsAddRootJson);
lane = "GOODS_ADD";
okMsg = "pdd.goods.add 调用成功";
failPrefix = "pdd.goods.add 失败";
}
boolean ok;
String errMsg;
if (useInfoUpdate) {
ok = PddOpenApiSupport.isInformationUpdateBizOk(raw);
errMsg = ok ? null
: (PddOpenApiSupport.isError(raw)
? PddOpenApiSupport.formatError(raw)
: PddOpenApiSupport.summarizeInformationUpdateFailure(raw));
} else {
ok = !PddOpenApiSupport.isError(raw);
errMsg = ok ? null : PddOpenApiSupport.formatError(raw);
}
Long pddGoodsId = ok
? (useInfoUpdate ? req.getPddGoodsId() : PddOpenApiSupport.parseGoodsIdFromGoodsAddResponse(raw))
: null;
if (ok) {
log.info("[PDD] pdd.goods.add end shopId={} outGoodsId={} success=true pddGoodsId={} msg={} snippet={}",
req.getShopId(), req.getOutGoodsId(), pddGoodsId, "pdd.goods.add 调用成功",
log.info("[PDD] {} end shopId={} outGoodsId={} success=true pddGoodsId={} snippet={}",
useInfoUpdate ? "information.update" : "pdd.goods.add",
req.getShopId(), req.getOutGoodsId(), pddGoodsId,
PddOpenApiSupport.snippet(raw, 800));
} else {
log.warn("[PDD] pdd.goods.add end shopId={} outGoodsId={} success=false err={} snippet={}",
log.warn("[PDD] {} end shopId={} outGoodsId={} success=false err={} snippet={}",
useInfoUpdate ? "information.update" : "pdd.goods.add",
req.getShopId(), req.getOutGoodsId(), errMsg, PddOpenApiSupport.snippet(raw, 800));
}
return PddPublishLaneResultVo.builder()
.attempted(true)
.success(ok)
.message(ok ? "pdd.goods.add 调用成功" : ("pdd.goods.add 失败: " + errMsg))
.message(ok ? okMsg : (failPrefix + ": " + errMsg))
.goodsAddSnippet(PddOpenApiSupport.snippet(raw, 2000))
.pddGoodsId(pddGoodsId)
.shopCredentialSource(cred.getSource())
@ -192,6 +228,7 @@ public class ExternalPddPublishService {
.catRuleSnippet(catRuleSnippet)
.specAutoResolved(specAuto)
.autoResolveDetail(autoDetail)
.publishLane(lane)
.build();
} catch (Exception e) {
log.warn("拼多多发布异常 shopId={} outGoodsId={} err={}", req.getShopId(), req.getOutGoodsId(), e.getMessage(), e);
@ -204,6 +241,82 @@ public class ExternalPddPublishService {
.catRuleSnippet(catRuleSnippet)
.specAutoResolved(specAuto)
.autoResolveDetail(autoDetail)
.publishLane("GOODS_ADD")
.build();
}
}
/**
* 下架{@code pdd.goods.sale.status.set}{@code is_onsale=0}与开放平台表单顶层参数一致
*/
public PddPublishLaneResultVo pddSaleStatusSetForDelist(ExternalGoodsDelistRequest req) {
if (req == null || req.getPddGoodsId() == null || req.getPddGoodsId() <= 0) {
return PddPublishLaneResultVo.builder()
.attempted(false)
.success(null)
.message("未传 pddGoodsId跳过拼多多下架在售状态")
.build();
}
if (!props.isSaleStatusSetEnabled()) {
return PddPublishLaneResultVo.builder()
.attempted(false)
.success(null)
.message("external.pdd.sale-status-set-enabled=false未调用 pdd.goods.sale.status.set")
.build();
}
PddShopCredential cred = credentialFromUpsertAuth(req.getPddPopAuth());
if (cred == null || !StringUtils.hasText(cred.getAppKey()) || !StringUtils.hasText(cred.getAppSecret())
|| !StringUtils.hasText(cred.getAccessToken())) {
return PddPublishLaneResultVo.builder()
.attempted(false)
.success(false)
.message("下架同步拼多多需提供 pddPopAuthappKey、appSecret、accessToken")
.shopCredentialSource("NONE")
.build();
}
String gateway = StringUtils.hasText(cred.getGatewayUrl()) ? cred.getGatewayUrl() : props.getGatewayUrl();
return invokePddGoodsSaleStatusSet(req.getShopId(), req.getOutGoodsId(), cred, gateway, req.getPddGoodsId(), 0);
}
private PddPublishLaneResultVo invokePddGoodsSaleStatusSet(Long shopId, String outGoodsId, PddShopCredential cred,
String gateway, long goodsId, int isOnsale) {
try {
log.info("[PDD] pdd.goods.sale.status.set begin shopId={} outGoodsId={} goodsId={} is_onsale={}",
shopId, outGoodsId, goodsId, isOnsale);
Map<String, String> biz = new LinkedHashMap<>(2);
biz.put("goods_id", String.valueOf(goodsId));
biz.put("is_onsale", String.valueOf(isOnsale));
String raw = pddPopClient.invokeTopLevelBiz(gateway, cred.getAppKey(), cred.getAppSecret(), cred.getAccessToken(),
"pdd.goods.sale.status.set", biz);
boolean ok = !PddOpenApiSupport.isError(raw);
String errMsg = ok ? null : PddOpenApiSupport.formatError(raw);
if (ok) {
log.info("[PDD] pdd.goods.sale.status.set end shopId={} outGoodsId={} goodsId={} is_onsale={} snippet={}",
shopId, outGoodsId, goodsId, isOnsale, PddOpenApiSupport.snippet(raw, 400));
} else {
log.warn("[PDD] pdd.goods.sale.status.set end shopId={} outGoodsId={} goodsId={} fail err={} snippet={}",
shopId, outGoodsId, goodsId, errMsg, PddOpenApiSupport.snippet(raw, 800));
}
return PddPublishLaneResultVo.builder()
.attempted(true)
.success(ok)
.message(ok ? "pdd.goods.sale.status.set 成功" : ("pdd.goods.sale.status.set 失败: " + errMsg))
.goodsAddSnippet(PddOpenApiSupport.snippet(raw, 2000))
.pddGoodsId(goodsId)
.shopCredentialSource(cred.getSource())
.catRuleFetched(false)
.specAutoResolved(false)
.publishLane("SALE_STATUS_SET")
.build();
} catch (Exception e) {
log.warn("pdd.goods.sale.status.set 异常 shopId={} outGoodsId={} goodsId={} err={}",
shopId, outGoodsId, goodsId, e.getMessage(), e);
return PddPublishLaneResultVo.builder()
.attempted(true)
.success(false)
.message("pdd.goods.sale.status.set 异常: " + e.getMessage())
.shopCredentialSource(cred.getSource())
.publishLane("SALE_STATUS_SET")
.build();
}
}
@ -212,7 +325,13 @@ public class ExternalPddPublishService {
if (req == null || req.getPddPopAuth() == null) {
return null;
}
ExternalGoodsUpsertRequest.PddPopAuth a = req.getPddPopAuth();
return credentialFromUpsertAuth(req.getPddPopAuth());
}
private PddShopCredential credentialFromUpsertAuth(ExternalGoodsUpsertRequest.PddPopAuth a) {
if (a == null) {
return null;
}
PddShopCredential c = new PddShopCredential();
c.setAppKey(trimToNull(a.getAppKey()));
c.setAppSecret(trimToNull(a.getAppSecret()));

View File

@ -151,6 +151,23 @@ public class PddGoodsAddParamBuilder {
return JSON.toJSONString(root);
}
/**
* {@code pdd.goods.add} JSON 上注入拼多多 {@code goods_id}并移除 {@code out_goods_id}
* {@code pdd.goods.information.update} {@code pdd.goods.add} 相同方式展开为<strong>表单顶层</strong>提交
*/
public String toInformationUpdateParamJson(String goodsAddRootJson, long pddGoodsId) {
if (!StringUtils.hasText(goodsAddRootJson)) {
throw new IllegalStateException("goodsAddRootJson 不能为空");
}
JSONObject root = JSON.parseObject(goodsAddRootJson);
if (root == null) {
throw new IllegalStateException("无法解析为 JSON 对象");
}
root.remove("out_goods_id");
root.put("goods_id", pddGoodsId);
return root.toJSONString();
}
/**
* 拼多多无规格发品如图书 {@code input_max_spec_num=0}仅一条 {@code sku_list}
* POP 文档一致{@code spec_id_list} <strong>字符串</strong>形式的 JSON 数组无规格为 {@code "[]"}有规格如 {@code "[25]"}

View File

@ -157,6 +157,52 @@ public final class PddOpenApiSupport {
}
}
/**
* {@code pdd.goods.information.update} {@code error_response}若存在 {@code goods_update_response} 则须 {@code is_success=true}
*/
public static boolean isInformationUpdateBizOk(String body) {
if (!StringUtils.hasText(body)) {
return false;
}
try {
JSONObject root = JSON.parseObject(body);
if (root == null) {
return false;
}
if (root.containsKey("error_response")) {
return false;
}
JSONObject gr = root.getJSONObject("goods_update_response");
if (gr != null && !gr.getBooleanValue("is_success")) {
return false;
}
return true;
} catch (Exception e) {
return false;
}
}
/** 业务失败摘要(含 {@code goods_update_response} 未成功时) */
public static String summarizeInformationUpdateFailure(String body) {
try {
JSONObject root = JSON.parseObject(body);
if (root == null) {
return body;
}
JSONObject gr = root.getJSONObject("goods_update_response");
if (gr != null && !gr.getBooleanValue("is_success")) {
StringBuilder sb = new StringBuilder("goods_update_response.is_success=false");
if (gr.containsKey("goods_commit_id")) {
sb.append(", goods_commit_id=").append(gr.get("goods_commit_id"));
}
return sb.toString();
}
return formatError(body);
} catch (Exception e) {
return body;
}
}
public static String formatError(String body) {
try {
JSONObject o = JSON.parseObject(body);

View File

@ -20,8 +20,8 @@ import java.util.Map;
* <p>每次请求无论成功失败均打日志 HTTP 状态耗时响应片段client_id 脱敏</p>
* <p><b>入参形态与官方 POP curl / Java SDK 一致</b></p>
* <ul>
* <li>{@link #invokeGoodsAdd} {@code pdd.goods.add}业务字段全部在表单顶层{@code sku_list}{@code carousel_gallery} 等为 JSON 字符串
* <b></b> {@code param_json}<b></b> {@code version}</li>
* <li>{@link #invokeGoodsAdd} / {@link #invokeGoodsInformationUpdate} {@code pdd.goods.add}{@code pdd.goods.information.update}
* JSON 展开为表单顶层与官方 curl / Java SDK 一致<b></b> {@code param_json}<b></b> {@code version}</li>
* <li>{@link #invokeTopLevelBiz} {@code pdd.goods.cat.rule.get} {@code cat_id}/{@code goods_id} 等顶层字段</li>
* <li>{@link #invokeGoodsImageUpload} {@code pdd.goods.image.upload}urlencoded{@code image} {@code data:image/*;base64,} + Base64</li>
* <li>{@link #invoke} 仅当接口要求整包 {@code param_json} 时使用少数场景</li>
@ -58,8 +58,28 @@ public class PddPopClient {
}
/**
* 业务参数整包放入 {@code param_json}并带 {@code version=V1}仅适用于仍要求该形态的接口
* {@code pdd.goods.information.update} {@link #invokeGoodsAdd} 相同将根 JSON 展开为顶层表单官方示例 {@code sku_list}{@code goods_id} 等与 {@code type} 同级
*/
public String invokeGoodsInformationUpdate(String gatewayUrl, String clientId, String clientSecret, String accessToken,
String informationUpdateRootJson) throws Exception {
Map<String, String> biz = PddOpenApiSupport.flattenPopTopLevelFromRootJson(informationUpdateRootJson);
String logPayload = PddOpenApiSupport.snippet(informationUpdateRootJson, 400);
if (!StringUtils.hasText(gatewayUrl)) {
throw new IllegalArgumentException("gatewayUrl 不能为空");
}
Map<String, String> params = buildBaseParams(clientId, accessToken, "pdd.goods.information.update", false);
if (biz != null) {
for (Map.Entry<String, String> e : biz.entrySet()) {
if (e.getKey() == null || e.getValue() == null) {
continue;
}
params.put(e.getKey(), e.getValue());
}
}
return postSignedAndLog(gatewayUrl, clientId, clientSecret, "pdd.goods.information.update", params, logPayload,
Duration.ofSeconds(60));
}
public String invoke(String gatewayUrl, String clientId, String clientSecret, String accessToken,
String type, String paramJson) throws Exception {
if (!StringUtils.hasText(gatewayUrl)) {