前言
微信支付官方文檔: 小程序開發與支付、服務商的關系,參考這個文檔
1、第三方(服務商)自己申請賬號,自己開發,生成指定內頁給特約商戶用,該模式簡稱中心化模式。
2、以特約商戶身份申請小程序appId,第三方完成開發,該模式簡稱外包模式。 3、通過開放平台第三方開發者代特約商戶進行小程序的開發,該模式簡稱第三方模式。
本文適用於中心化模式,服務商自己開發一個小程序,但是收款是直接受到對應的特約商戶賬戶中,不收到服務商自己賬戶中。
基本流程
- 服務商再微信后台申請微信小程序
- 小程序開通微信支付或綁定已開通微信支付的商戶號
特約商戶需要操作的流程
- 服務商再微信支付商戶后台,為特約商戶開通微信服務商模式下的微信支付賬戶
- 特約商戶在收到微信郵件發送的登錄賬號和密碼,登錄自己的微信支付商戶后台,綁定小程序的appId
- 提交審核,被拒絕的話再次提交審核,直到審核通過
- 服務商管理后台中找到"待關聯商戶號"並確認
- 登錄微信支付服務商商戶后台,手動為特約商戶綁定與服務商主體或特約商戶主體一致的公眾號,APP或小程序的appId。最多配置5個,手工配置路徑:"服務商商戶平台-服務商功能-子商戶管理-開發配置-特約商戶APPID配置"。
支付部分
- appid:注意這里是服務號的appid,不是小程序的
- mch_id:這里是用服務商的id 在我的賬號一欄可以找到
- sub_appid: 這里才是小程序的appid
- sub_mch_id: 這里對應特約商戶號id 付款到對應商戶的憑證就是這個 在注冊特約商戶的時候郵件里可以找到 這里建議配置到數據庫動態傳遞
- nonce_str: 隨機字符串
- body: 這里隨意填寫,也可以填寫商品名稱
- out_trade_no: 訂單號
- total_fee: 這里必須是整數,單位是分
- trade_type: 公眾平台支付或小程序支付填寫:JSAPI,如果是APP的填寫:APP
- sub_openid: 此參數是在發起支付前在小程序內調起wx.login 方法獲得code 然后后台通過置換 獲得用戶openid
- spbill_create_ip:這里可以隨意填寫
- notify_url: 支付回調的地址
- sign: 此參數為簽名參數 需要將需要傳遞的參數進行排序並且進行md5簽名,需要注意的是需添加參數key 即之前修改的服務商api密鑰
好了 參數分析完畢 在后台調用統一下單方法 不出意外是成功的,下單代碼如下:
Controller部分
@RestController
@RequestMapping("/payment")
public class WxPayController {
private Logger logger = LoggerFactory.getLogger(WxLoginController.class);
@Autowired
private AppletOrderService appletOrderService;
@Autowired
private WxPayProperties wxPayProperties;
@ResponseBody
@PostMapping(value = "/appletWxPay", produces = "application/json;charset=UTF-8")
public Map<Object, Object> appletWxPay(@RequestParam String openId, String totalFee) throws Exception {
logger.info("[WxPayController].appletWxPay...openId:{}", openId);
SortedMap<Object, Object> resultMap = new TreeMap<Object, Object>();
String body = "測試";
String out_trade_no = String.valueOf(IdWorker.getInstance().nextId());
PreOrderResult preOrderResult = appletOrderService.placeOrder(body, out_trade_no, totalFee, openId);
System.out.println(preOrderResult);
if(WxContants.SUCCESS.equals(preOrderResult.getReturn_code()) && WxContants.SUCCESS.equals(preOrderResult.getResult_code())){
resultMap.put("appId", wxPayProperties.getApp_id());
resultMap.put("timeStamp", Long.toString(System.currentTimeMillis()/1000));
resultMap.put("nonceStr", UUID.randomUUID().toString().replaceAll("-", "").toUpperCase());
resultMap.put("package", "prepay_id="+preOrderResult.getPrepay_id());
resultMap.put("signType", "MD5");
resultMap.put("sign", SignUtils.createSignByMd5(resultMap, wxPayProperties.getKey()));
resultMap.put("returnCode", "SUCCESS");
resultMap.put("returnMsg", "OK");
logger.info("【小程序支付】統一下單成功,返回參數:"+resultMap);
}else{
resultMap.put("returnCode", preOrderResult.getReturn_code());
resultMap.put("returnMsg", preOrderResult.getReturn_msg());
logger.info("【小程序支付】統一下單失敗,失敗原因:{}" + preOrderResult.getReturn_msg());
}
logger.info("[WxPayController].appletWxPay...CodeUrl:{}", preOrderResult.getCode_url());
return resultMap;
}
}
Serivice部分
@Service
public class AppletOrderServiceImpl implements AppletOrderService{
@Autowired
private WxPayProperties wxPayProperties;
@Override
public PreOrderResult placeOrder(String body, String out_trade_no, String total_fee, String openId) throws Exception {
// 生成預付單對象
PreOrder preOrder = new PreOrder();
preOrder.setAppid(wxPayProperties.getApp_id());
preOrder.setMch_id(wxPayProperties.getMch_id());
preOrder.setSub_appid(wxPayProperties.getSub_app_id());
preOrder.setSub_mch_id(wxPayProperties.getSub_mch_id());
String nonce_str = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
preOrder.setNonce_str(nonce_str);
preOrder.setBody(body);
preOrder.setOut_trade_no(out_trade_no);
preOrder.setTotal_fee(new BigDecimal(total_fee));
preOrder.setSpbill_create_ip(wxPayProperties.getSpbill_create_ip());
preOrder.setNotify_url(wxPayProperties.getNotify_url());
preOrder.setTrade_type(WxContants.TRADE_TYPE);
preOrder.setSub_openid(openId);
SortedMap<Object, Object> p = new TreeMap<Object, Object>();
p.put("appid", wxPayProperties.getApp_id());
p.put("mch_id", wxPayProperties.getMch_id());
p.put("sub_appid", wxPayProperties.getSub_app_id());
p.put("sub_mch_id", wxPayProperties.getSub_mch_id());
p.put("body", body);
p.put("nonce_str", nonce_str);
p.put("out_trade_no", out_trade_no);
p.put("total_fee", total_fee);
p.put("spbill_create_ip", wxPayProperties.getSpbill_create_ip());
p.put("notify_url", wxPayProperties.getNotify_url());
p.put("trade_type", WxContants.TRADE_TYPE);
p.put("sub_openid", openId);
// 簽名
String sign = SignUtils.createSignByMd5(p, wxPayProperties.getKey());
preOrder.setSign(sign);
String xml = XmlUtil.object2Xml(preOrder, PreOrder.class);
// 調用下單地址
String returnXml = HttpUtil.sendPost(WxContants.pay_url, xml);
System.out.println(returnXml);
// XML轉換為Object
PreOrderResult preOrderResult = (PreOrderResult) XmlUtil.xml2Object(returnXml, PreOrderResult.class);
// XML轉換為Object
// 一般企業開發中支付流水入庫,支付狀態更新這些都需要做,此出省略
return preOrderResult;
}
@Override
public PayResult getWxPayResult(HttpServletRequest request) throws Exception {
InputStream inStream = request.getInputStream();
BufferedReader in = null;
String result = "";
in = new BufferedReader(
new InputStreamReader(inStream));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
PayResult pr = (PayResult)XmlUtil.xml2Object(result, PayResult.class);
System.out.println(pr.toString());
return pr;
}
}
小程序配置部分
@Component
public class WxPayProperties {
@Value("${wxpay.app_id}")
private String app_id;
@Value("${wxpay.sub_app_id}")
private String sub_app_id;
@Value("${wxpay.spbill_create_ip}")
private String spbill_create_ip;
@Value("${wxpay.key}")
private String key;
@Value("${wxpay.mch_id}")
private String mch_id;
@Value("${wxpay.sub_mch_id}")
private String sub_mch_id;
@Value("${wxpay.notify_url}")
private String notify_url;
// 此處省略get/set方法
...
}
返回數據
{
"appId": "wxe670bb9ea4775345",
"nonceStr": "536D9056202D4292A909392320E2E5BB",
"package": "prepay_id=wx13143641616855cfa3275610dd2a070000",
"returnCode": "SUCCESS",
"returnMsg": "OK",
"sign": "C512D4025134C356BFA58A2F5699E198",
"signType": "MD5",
"timeStamp": "1610519802"
}
小程序端根據后台返回的參數,拉起支付,代碼如下:
wx.requestPayment({
'timeStamp': res.data.timeStamp,
'nonceStr': res.data.nonceStr,
'package': res.data.package,
'signType': res.data.signType,
'paySign': res.data.sign,
'success':function(res){},
'fail':function(res){},
'complete':function(res){}
})
點擊支付,總算是來到了這一步:

過程中,可能會遇到如下問題:

出現這個錯誤的原因是簽名不正確,多檢查檢查是哪一步出現了問題。
最后
一路踩了不少坑,總算還是成功了,因此將解決方法記錄下來,后面做小程序支付功能的小伙伴可以避免踩坑。
