目前,網上關於百度收銀臺的博客寥寥無幾,我幾乎沒看到過,從頭到尾都是跟着官方文檔來的,中間遇到過一些小挫折,在百度客服小姐姐和百度技術小哥的指導下,最終還是調通了,併成功上線,好了不扯了,下面開始進入正題 !
本文默認各位讀者的公司都註冊過百度智能小程序賬號,並上線應用,只是單純來尋求百度收銀臺支付的Blog ~
【簡介】
百度收銀臺支付是百度面向有開發能力的智能小程序合作者提供的支付能力,聚合了度小滿、微信、支付寶等多種支付方式,方便開發者一站式快速接入多種支付渠道,讓百度用戶在智能小程序場景下,直接完成支付實現交易閉環,提升用戶支付體驗,提高訂單轉化率。
開通指引請參考官方文檔:https://smartprogram.baidu.com/docs/introduction/pay/
注:若您已入駐百度電商平臺,可以綁定已有電商平臺賬號,也可以開通新的支付賬號
也就是說,無需藉助百度電商平臺,可以直接在百度智能小程序平臺進行配置即可
【開發準備】
◆ 在平臺(本文平臺均指百度智能小程序平臺)的支付管理,配置開發者公鑰,私鑰保存在服務端代碼。
RSA公鑰私鑰生成工具,推薦:支付寶RAS密鑰生成器(如下代碼純屬測試數據)
生成的公鑰直接配置在平臺的開發者公鑰選項,私鑰會保存在rsa_private_key_pkcs8.pem文件(Java專屬)
◆ 配置支付回調地址,退款審覈地址,退款回調地址(如果回調接口部署在阿里雲或有網關准入限制,請參考文檔阿里雲安全組設置中的IP地址設置白名單,網址爲http://dianshang.baidu.com/platform/doclist/index.html#!/doc/nuomiplus_1_guide/aliyun_v2.md)
下面開始Java接入百度收銀臺,官方文檔:http://dianshang.baidu.com/platform/doclist/index.html#!/doc/nuomiplus_1_guide/mini_program_cashier/standard_interface/push_notice.md
【簽名工具類】
package com.maiji.cloud.utils;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URLEncoder;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
import com.nuomi.common.NuomiApiException;
import com.nuomi.common.NuomiConstants;
import com.nuomi.util.StreamUtil;
import com.nuomi.util.codec.Base64;
import org.apache.commons.lang.StringUtils;
/**
* 簽名工具類
* 目前只支持rsa方式 不支持rsa2
*/
public class NuomiSignature {
public static final String appKey = "";
public static final String dealId = "";
public static final String PUB_KEY = "";
public static final String PRIVATE_KEY = "";
public static String genSignWithRsa(String totalAmount, String tpOrderId) throws NuomiApiException {
HashMap apiParams = new HashMap();
apiParams.put("appKey", appKey);
apiParams.put("dealId", dealId);
apiParams.put("totalAmount", totalAmount);
apiParams.put("tpOrderId", tpOrderId);
return NuomiSignature.genSignWithRsa(apiParams, PRIVATE_KEY);
}
public static Boolean checkSignWithRsa(String totalAmount, String tpOrderId, String rsaSign) throws NuomiApiException {
HashMap apiParams = new HashMap();
apiParams.put("appKey", appKey);
apiParams.put("dealId", dealId);
apiParams.put("totalAmount", totalAmount);
apiParams.put("tpOrderId", tpOrderId);
return NuomiSignature.checkSignWithRsa(apiParams, PUB_KEY, rsaSign);
}
/**
* 獲取簽名
*
* @param sortedParams 排序後的參數
* @param privateKey 私鑰
* @return 返回簽名後的字符串
* @throws NuomiApiException
*/
public static String genSignWithRsa(Map<String, String> sortedParams, String privateKey) throws NuomiApiException {
String sortedParamsContent = getSignContent(sortedParams);
return rsaSign(sortedParamsContent, privateKey, NuomiConstants.CHARSET_UTF8);
}
/**
* 簽名驗證
*
* @param sortedParams
* @param pubKey
* @param sign
* @return
* @throws NuomiApiException
*/
public static boolean checkSignWithRsa(Map<String, String> sortedParams, String pubKey, String sign) throws NuomiApiException {
String sortedParamsContent = getSignContent(sortedParams);
return doCheck(sortedParamsContent, sign, pubKey, NuomiConstants.CHARSET_UTF8);
}
/**
* @param sortedParams 已經排序的字符串
* @return 返回簽名後的字符串
*/
public static String getSignContent(Map<String, String> sortedParams) {
StringBuffer content = new StringBuffer();
List<String> keys = new ArrayList<String>(sortedParams.keySet());
Collections.sort(keys);
int index = 0;
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = sortedParams.get(key);
content.append((index == 0 ? "" : "&") + key + "=" + value);
index++;
}
return content.toString();
}
/**
* sha1WithRsa 加簽
*
* @param content 需要加密的字符串
* @param privateKey 私鑰
* @param charset 字符編碼類型 如:utf8
* @return
* @throws NuomiApiException
*/
public static String rsaSign(String content, String privateKey, String charset) throws NuomiApiException {
try {
PrivateKey priKey = getPrivateKeyFromPKCS8(NuomiConstants.SIGN_TYPE_RSA,
new ByteArrayInputStream(privateKey.getBytes()));
java.security.Signature signature = java.security.Signature
.getInstance(NuomiConstants.SIGN_ALGORITHMS);
signature.initSign(priKey);
if (StringUtils.isEmpty(charset)) {
signature.update(content.getBytes());
} else {
signature.update(content.getBytes(charset));
}
byte[] signed = signature.sign();
return new String(Base64.encodeBase64(signed));
} catch (InvalidKeySpecException ie) {
throw new NuomiApiException("RSA私鑰格式不正確,請檢查是否正確配置了PKCS8格式的私鑰", ie);
} catch (Exception e) {
throw new NuomiApiException("RSAcontent = " + content + "; charset = " + charset, e);
}
}
public static PrivateKey getPrivateKeyFromPKCS8(String algorithm, InputStream ins) throws Exception {
if (ins == null || StringUtils.isEmpty(algorithm)) {
return null;
}
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
byte[] encodedKey = StreamUtil.readText(ins).getBytes();
encodedKey = Base64.decodeBase64(encodedKey);
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
}
/**
* RSA驗簽名檢查
*
* @param content 待簽名數據
* @param sign 簽名值
* @param publicKey 分配給開發商公鑰
* @param encode 字符集編碼
* @return 布爾值
* @throws NuomiApiException
*/
private static boolean doCheck(String content, String sign, String publicKey, String encode) throws NuomiApiException {
try {
KeyFactory keyFactory = KeyFactory.getInstance(NuomiConstants.SIGN_TYPE_RSA);
byte[] bytes = publicKey.getBytes();
byte[] encodedKey = Base64.decodeBase64(bytes);
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
java.security.Signature signature = java.security.Signature.getInstance(NuomiConstants.SIGN_ALGORITHMS);
signature.initVerify(pubKey);
signature.update(content.getBytes(encode));
boolean bverify = signature.verify(Base64.decodeBase64(sign.getBytes()));
return bverify;
} catch (Exception e) {
throw new NuomiApiException("RSA私鑰格式不正確,請檢查是否正確配置了PKCS8格式的私鑰", e);
}
}
}
【支付&覈銷&退款工具類】
package com.maiji.cloud.utils;
import cn.hutool.core.util.NumberUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import com.maiji.cloud.response.BdXCXResDto;
import com.nuomi.common.NuomiApiException;
import org.springframework.web.client.RestTemplate;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
/**
* @Description: 百度收銀臺工具類
* @Author: zhaobin
* @Date: 2020/6/10
*/
public class BaiduUtil {
public static final String appKey = "";
public static final String dealId = "";
public static final String PUB_KEY = "";
public static final String PRIVATE_KEY = "";
private static String url = "https://nop.nuomi.com/nop/server/rest";
/**
* 百度收銀臺 - 退款接口
*
* @param orderId
* @param userId
* @param orderNo
* @param amount
* @return
* @throws NuomiApiException
*/
public static JSONObject bdRefund(String orderId, String userId, String orderNo, Double amount, String bizRefundBatchId, String refundReason) throws Exception {
String totalAmountStr = NumberUtil.toStr(amount * 100);
Map<String, String> param = new HashMap<>();
param.put("method", "nuomi.cashier.applyorderrefund");
param.put("orderId", orderId);
param.put("userId", userId);
param.put("refundType", "1");
param.put("refundReason", refundReason);
param.put("tpOrderId", orderNo);
param.put("appKey", appKey);
param.put("applyRefundMoney", totalAmountStr); //退款金額,單位:分
param.put("bizRefundBatchId", bizRefundBatchId); //業務方退款批次id,退款業務流水唯一編號
String sign = NuomiSignature.genSignWithRsa(param, NuomiSignature.PRIVATE_KEY);
String refundApplyUrl = url + "?method=nuomi.cashier.applyorderrefund&orderId=" + orderId + "&userId=" + userId + "&refundType=1&refundReason=" + refundReason +
"&tpOrderId=" + orderNo + "&appKey=" + appKey + "&applyRefundMoney=" + totalAmountStr + "&bizRefundBatchId=" + bizRefundBatchId + "&rsaSign=" + sign;
JSONObject forObject = new RestTemplate().getForObject(refundApplyUrl, JSONObject.class);
return forObject;
}
public static JSONObject cancelHx(String orderId, String userId) throws Exception {
Map<String, String> param = new HashMap<>();
param.put("method", "nuomi.cashier.syncorderstatus");
param.put("orderId", orderId);
param.put("userId", userId);
param.put("type", "3");
param.put("appKey", appKey);
String sign = NuomiSignature.genSignWithRsa(param, NuomiSignature.PRIVATE_KEY);
String finalUrl = url + "?method=nuomi.cashier.syncorderstatus&orderId=" + orderId + "&userId=" + userId + "&type=3&appKey=MMUBBH&rsaSign=" + sign;
JSONObject forObject = new RestTemplate().getForObject(finalUrl, JSONObject.class);
return forObject;
}
}
【接口通用響應實體】
@NoArgsConstructor
@Data
@Accessors(chain=true)
public class BdXCXResDto<T> {
private T data;
private Integer errno;
private String msg;
public BdXCXResDto(Integer errno, String msg) {
this.errno = errno;
this.msg = msg;
}
}
【調起百度收銀臺】
百度收銀臺沒有預支付,直接由小程序端調起百度收銀臺發起支付,不過他們需要一個參數 “rsaSign” 需要調後臺接口返回:
@PostMapping("/rsaSign")
public BaseDataResDto<String> rsaSign (@RequestBody BaseDataReqDto<BaiduXCXReqData> baseDataReqDto) {
Double totalAmount = baseDataReqDto.getData().getTotalAmount();
String tpOrderId = baseDataReqDto.getData().getTpOrderId();
String totalAmountStr = NumberUtil.toStr(totalAmount * 100);
try {
String sign = NuomiSignature.genSignWithRsa(totalAmountStr, tpOrderId);
} catch (NuomiApiException e) {
e.printStackTrace();
return new BaseDataResDto<>(Status.ERROR);
}
return new BaseDataResDto<String>(Status.SUCCESS).setData(sign);
}
【通知支付狀態】
◆ 百度收銀臺主動發起通知,該方式纔會被啓用
業務方智能小程序跳轉至百度收銀臺,輸入正確的交易密碼之後,即訂單支付成功後,百度收銀臺會主動調用業務方的的支付回調地址(開發者平臺註冊的支付回調地址)通知業務方該訂單支付成功。
/**
* 實際開發可以直接用 @ResponseBody Map<String, String> param 接收
* 因爲我在SpringCloud做了一層攔截,所以該博客直接用@RequestParam一個個接收
*/
@PostMapping("bdXCXCallBack")
public BdXCXResDto<Map<String, Object>> bdXCXCallBack(@RequestParam Integer totalMoney, @RequestParam String tpOrderId,
@RequestParam String rsaSign, @RequestParam Long orderId, @RequestParam Long userId) {
return capitalMainLogService.bdXCXCallBack(totalMoney,tpOrderId,rsaSign,orderId,userId);
}
@Override
public BdXCXResDto<Map<String, Object>> bdXCXCallBack(Integer totalMoney, String tpOrderId, String rsaSign, Long orderId, Long userId) {
BdXCXResDto<Map<String, Object>> bdXCXResDto = new BdXCXResDto<>(0, "success");
Map<String, Object> map = Maps.newHashMap();
map.put("isConsumed", 2);
try {
// 根據訂單號查看訂單信息
ShopingOrder shopingOrder = shopingOrderService.selectOne(new EntityWrapper<ShopingOrder>().eq("order_no", tpOrderId));
if (BooleanUtils.isFalse(NuomiSignature.checkSignWithRsa(totalMoney.toString(), tpOrderId, NuomiSignature.genSignWithRsa( NumberUtil.toStr(shopingOrder.getAmount() * 100), tpOrderId)))) {
logger.info("bdXCXCallBack ===>> 簽名驗證失敗");
map.put("isErrorOrder", 1);
return bdXCXResDto.setData(map);
}
// 已經付款
if (Arrays.asList(1, 2, 3, 5, 6).contains(shopingOrder.getStatus())) return bdXCXResDto.setData(map);
shopingOrder.setStatus(1).setPayType(5).setPayId(userId + "").setPayDate(new Date()).setPrepayId(orderId + "");
// 修改支付狀態爲成功
shopingOrderMapper.updateById(shopingOrder);
logger.info("百度收銀臺已支付");
// 其他業務邏輯...
} catch (Exception e) {
e.printStackTrace();
map.put("isErrorOrder", 1);
return bdXCXResDto.setData(map);
}
return bdXCXResDto.setData(map);
}
返回參數說明
名稱 | 類型 | 是否必須 | 示例值 | 描述 |
---|---|---|---|---|
errno | Integer | 是 | 0 | 返回碼 |
msg | String | 是 | success | 返回信息 |
data | Object | 是 | {"isConsumed":0} | 返回數據 |
data字段爲JSON格式,參數如下:
名稱 | 類型 | 是否必須 | 示例值 | 描述 |
---|---|---|---|---|
isConsumed | Integer | 是 | 2 | 是否標記覈銷 |
isErrorOrder | Integer | 否 | 1 | 是否異常訂單(如需主動發起異常退款,需將此字段設置爲1) |
isConsumed字段參數枚舉值如下:
取值 | 描述 |
---|---|
1 | 未消費 |
2 | 已消費 |
注意: isConsumed重要性:爲必傳參數(不傳會觸發異常退款),用來標記該訂單是否已消費。 小程序接入爲支付成功即消費場景,該字段需設置爲2。(字段不設置爲2訂單同樣會變更爲“已消費”)如isConsumed值不返回2,“已消費”狀態的訂單金額不能順利進入企業餘額。
返回(RESPONSE) DEMO:
{"errno":0,"msg":"success","data":{"isConsumed":2}}
如處理支付回調的過程中開發者端參數異常、其他異常,返回以下參數進行異常退款:
{"errno": 0,"msg": "success","data": {"isErrorOrder": 1,"isConsumed": 2}
小程序場景isConsumed返回值一定要爲2,(字段不設置爲2訂單不會變更爲“已消費”)不按照要求值返回參數,用戶已付款金額不能順利進入企業餘額。
【申請退款】
◆ 業務方可以通過該接口申請訂單退款,僅限在百度電商開放平臺支付的訂單。
◆ 特別說明:
防止資金池金額小於退款金額時退款失敗的情況,建議根據業務退款情況,在“管理中心——支付服務設置——我的服務——服務——財務設置”設置“每日退款上限(元)”和“打款預留(元)”。
每日退款上限(元) :設置每日退款上限。當日退款達到設置金額時,當日新發起的退款都會失敗。
打款預留(元):結款日資金池預留的不打款的金額,保證資金池有金額退款。
發起部分退款時,訂單必須是覈銷狀態。
@PostMapping("executeRefund")
public BaseResDto executeRefund(@RequestBody BaseDataReqDto<String> baseDataReqDto) {
String orderRefundId = baseDataReqDto.getData();
if (StringUtil.isBlank(orderRefundId))
return new BaseResDto(Status.PARAMETERERROR);
return capitalMainLogService.executeRefund(orderRefundId);
}
@Override
public BaseResDto executeRefund(String orderRefundId) {
ShoppingOrderRefundEntity shoppingOrderRefund = shopingOrderRefundService
.selectById(orderRefundId).setRefundMiddleTime(new Date()).setStatus(3);//退款中
String orderId = shoppingOrderRefund.getOrderId();
ShopingOrder shopingOrder = shopingOrderMapper.selectById(orderId) .setRefundStatus(3);//退款中
Double refundMoney = shoppingOrderRefund.getRefundMoney();
Double amount = shopingOrder.getAmount();
if (refundManey > amount)
return BaseResDto.baseResDto(Status.ERROR, "退款金額錯誤!");
try{
// 先取消覈銷 (官方客服回覆:新服務不需要關注取消覈銷接口,請直接調用申請退款接口。)
JSONObject hx = BaiduUtil.cancelHx(shopingOrder.getPrepayId(), shopingOrder.getPayId());
if((Integer)hx.get("errno") != 0)
BaseResDto.baseResDto(Status.ERROR, "百度收銀臺退款失敗,錯誤碼:" + hx.get("errno"));
// 調用百度API申請退款
JSONObject refundApply = BaiduUtil.bdRefund(shopingOrder.getPrepayId(), shopingOrder.getPayId(),
shopingOrder.getOrderNo(), refundManey, shoppingOrderRefund.getUuId(), shoppingOrderRefund.getRefundReason());
if ((Integer)refundApply.get("errno") != 0)
return BaseResDto.baseResDto(Status.ERROR, "百度收銀臺退款失敗");
}
} catch (Exception e) {
e.printStackTrace();
return BaseResDto.baseResDto(Status.ERROR, "百度收銀臺退款失敗異常!");
}
if (!shopingOrderService.updateById(shopingOrder))
return BaseResDto.baseResDto(Status.ERROR, "修改訂單退款狀態失敗!");
if (!shopingOrderRefundService.updateById(shoppingOrderRefund))
return BaseResDto.baseResDto(Status.ERROR, "修改退款記錄退款狀態失敗!");
return new BaseResDto(Status.SUCCESS);
}
返回說明
名稱 | 類型 | 是否必須 | 示例值 | 描述 |
---|---|---|---|---|
errno | Integer | 是 | 0 | 返回碼 |
msg | String | 是 | success | 返回信息 |
data | Object | 是 | [] | 返回數據 |
返回示例
{
"errno": 0,
"msg": "success",
"data": {
"refundBatchId": "152713835",//平臺退款批次號
"refundPayMoney": "9800" //平臺可退退款金額【分爲單位】
}
}
【請求業務方退款審覈】
使用場景:
◆ 當用戶的某個訂單申請了退款後,百度收銀臺會主動調用業務方的退款審覈地址(開發者平臺註冊的退款審覈地址)詢問訂單是否可退
◆ 用戶支付成功後,百度收銀臺通過通知業務方支付成功接口通知業務方,業務方反饋給百度收銀臺的字符不是合法json或解析出來的errno不爲0時,系統會自動發起退款,此時百度收銀臺也會調用業務方的退款審覈接口詢問業務方訂單是否可以退款
/** 百度小程序退款審覈地址 */
@PostMapping("bdRefund")
public BdXCXResDto<Map<String, Object>> bdRefund(@RequestParam String tpOrderId) {
return capitalMainLogService.bdRefund(tpOrderId);
}
@Override
public BdXCXResDto<Map<String, Object>> bdRefund(String tpOrderId) {
BdXCXResDto<Map<String, Object>> bdXCXResDto = new BdXCXResDto<>(0, "success");
Map<String, Object> map = Maps.newHashMap();
try {
ShopingOrder shopingOrder = shopingOrderService.selectOne(new EntityWrapper<ShopingOrder>().eq("order_no", tpOrderId));
if (shopingOrder == null) {
// 訂單不存在
map.put("auditStatus", 2); // 審覈不通過,不能退款
map.put("calculateRes", new JSONObject().put("refundPayMoney", 0));
return bdXCXResDto.setData(map);
}
// if (BooleanUtils.isFalse(NuomiSignature.checkSignWithRsa(shopingOrder.getAmount() + "", tpOrderId, rsaSign))) {
// logger.info("CapitalMainLogServiceImpl.bdRefund ===>> 簽名驗證失敗"); //如果金額不對,則會導致驗籤失敗,所以後續不用判斷金額是否匹配
// map.put("auditStatus", 2); // 審覈不通過,不能退款
// map.put("calculateRes", new JSONObject().put("refundPayMoney", 0));
// return bdXCXResDto.setData(map);
// }
map.put("auditStatus", 1); // 審覈通過可退款
map.put("calculateRes", new JSONObject().put("refundPayMoney",
shopingOrderMapper.getRefundMoneyByOrderId(shopingOrder.getUuId())));
} catch (Exception e) {
e.printStackTrace();
map.put("auditStatus", 2); // 審覈不通過,不能退款
map.put("calculateRes", new JSONObject().put("refundPayMoney", 0));
return bdXCXResDto.setData(map);
}
return bdXCXResDto.setData(map);
}
返回(響應)DEMO:
{"errno":0,"msg":"success","data":{"auditStatus":1, "calculateRes":{"refundPayMoney":100}}}
refundPayMoney的值是以分爲單位的整數,如不嚴格按照文檔提示操作,會導致退款審覈失敗。
【通知退款狀態】
百度收銀臺調用業務方的退款審覈接口成功,且業務方返回允許退款後,平臺會去做退款操作,當訂單退款成功後,百度收銀臺會主動調用業務方的退款回調地址(開發者平臺註冊的退款回調地址)通知業務方該訂單退款成功。
通知觸發條件:退款成功後,平臺會調用該接口,將退款成功消息通知到業務方。
/** 百度小程序退款回調地址 */
@PostMapping("bdRefundCallBack")
public BdXCXResDto<Map<String, Object>> bdRefundCallBack(@RequestParam String tpOrderId) {
return capitalMainLogService.bdRefundCallBack(tpOrderId);
}
@Override
public BdXCXResDto<Map<String, Object>> bdRefundCallBack(String tpOrderId) {
BdXCXResDto<Map<String, Object>> bdXCXResDto = new BdXCXResDto<>(0, "success");
try {
// String tpOrderId = param.get("tpOrderId"); //訂單號
// String rsaSign = param.get("rsaSign");
ShopingOrder shopingOrder = shopingOrderService.selectOne(new EntityWrapper<ShopingOrder>().eq("order_no", tpOrderId));
if (shopingOrder == null) {
// 訂單不存在
return bdXCXResDto.setData(new JSONObject());
}
// if (BooleanUtils.isFalse(NuomiSignature.checkSignWithRsa(shopingOrder.getAmount() + "", tpOrderId, rsaSign))) {
// return bdXCXResDto.setData(new JSONObject()); //驗籤失敗
// }
// 已經退款
if (shopingOrder.getStatus() == 4)
return bdXCXResDto.setData(new JSONObject());
// 修改訂單退款狀態
shopingOrder.setRefundStatus(4);
shopingOrderService.updateById(shopingOrder)
// 修改退款狀態
EntityWrapper<ShoppingOrderRefundEntity> entityWrapper = new EntityWrapper<>();
entityWrapper.eq("order_id", shopingOrder.getUuId());
ShoppingOrderRefundEntity shoppingOrderRefund = shopingOrderRefundService.selectOne(entityWrapper);
shoppingOrderRefund.setRefundFinishTime(new Date()).setStatus(4);
shopingOrderRefundService.updateById(shoppingOrderRefund)
// 其他業務邏輯...
} catch (Exception e) {
e.printStackTrace();
return bdXCXResDto.setData(new JSONObject()); //失敗(官網也沒說失敗如何返回,乾脆來個空json)
}
return bdXCXResDto.setData(new JSONObject()); //成功返回
}
返回(響應)DEMO:
{"errno":0,"msg":"success","data":{}}
至此,百度收銀臺付款,退款接口都整合完畢了,最後,我想吐槽一下百度收銀臺的文檔真的太差勁了,很多參數和概念都模棱兩可,而且客服每天上午十點才上班,下午五點就下班了,找客服問問題都是以郵件的形式轉接給百度的技術人員,交流起來時間成本還是挺大的,不得不說,還是支付寶和微信香啊 ~