微信小程序支付(服務商模式)解決


前言

微信支付官方文檔: 小程序開發與支付、服務商的關系,參考這個文檔

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){}
})

點擊支付,總算是來到了這一步:

image-20210113144740860

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

https://note.youdao.com/yws/api/personal/file/75A86A993B104245B319B1F2D7B53BFE?method=download&shareKey=295148895adc01c00c7578849dd50e26

出現這個錯誤的原因是簽名不正確,多檢查檢查是哪一步出現了問題。

最后

一路踩了不少坑,總算還是成功了,因此將解決方法記錄下來,后面做小程序支付功能的小伙伴可以避免踩坑。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM