微信支付,你想知道的一切都在這裏

無論是個人還是企業,業務變現,除了廣告最好的方式就是支付收款。我們經常使用的微信支付如何快速完成技術對接呢?如何同時支持國內支付與境外支付呢?如何做跨城冗災呢?乾貨多屁話少 ,接下來慢慢聊。

接入步驟

微信支付接入大概步驟如下:
1、獲取支付接口 URL
2、構建請求參數
3、發起請求
4、喚起支付
5、支付異步通知處理

步驟一中獲取支付接口 URL,需要考慮這幾點

1、如何同時支持國內微信支付與境外微信支付
2、如何同時支持普通的商戶模式以及服務商模式

步驟二中經常遇到的問題就是參數簽名驗證問題

1、MD5 加密
2、HMAC-SHA256 加密

步驟三中難點在於微信支付雙向證書的處理

步驟四中預付訂單二次簽名異常以及喚起支付提示各種配置錯誤

步驟五中異步通知驗證簽名、訂單重複通知以及敏感數據的解密問題

以上接入步驟中,你踩過那些坑呢?歡迎評論區分享交流

Talk is cheap. Show me the code

獲取微信支付接口 URL

有人會說「這不很簡單麼」官方文檔接口中就有提供。對你說的沒錯,那如何做到一套系統同時支持國內微信支付與境外微信支付,又如何做跨城冗災方案呢?

微信域名

根據業務區域的不同微信提供了不同的域名來支持

  • api.mch.weixin.qq.com(建議接入點:中國國內)
  • api2.mch.weixin.qq.com(建議接入點:中國國內備用)
  • apihk.mch.weixin.qq.com(建議接入點:東南亞)
  • apius.mch.weixin.qq.com(建議接入點:其它)
  • api.mch.weixin.qq.com/sandboxnew(特殊:仿真測試)

聰明的你,不難就會想到枚舉,具體跨城冗災方案可以參考微信支付商戶系統跨城冗災升級指引

/**
 * <p>IJPay 讓支付觸手可及,封裝了微信支付、支付寶支付、銀聯支付常用的支付方式以及各種常用的接口。</p>
 *
 * <p>不依賴任何第三方 mvc 框架,僅僅作爲工具使用簡單快速完成支付模塊的開發,可輕鬆嵌入到任何系統裏。 </p>
 *
 * <p>IJPay 交流羣: 723992875</p>
 *
 * <p>Node.js 版: https://gitee.com/javen205/TNW</p>
 *
 * <p>微信支付可用域名枚舉</p>
 *
 * @author Javen
 */
public enum WxDomain {
    /**
     * 中國國內
     */
    CHINA("https://api.mch.weixin.qq.com"),
    /**
     * 中國國內(備用域名)
     */
    CHINA2("https://api2.mch.weixin.qq.com"),
    /**
     * 東南亞
     */
    HK("https://apihk.mch.weixin.qq.com"),
    /**
     * 其它
     */
    US("https://apius.mch.weixin.qq.com");

    
    /**
     * 域名
     */
    private final String domain;

    WxDomain(String domain) {
        this.domain = domain;
    }

    public String getType() {
        return domain;
    }
}

至此獲取微信支付接口 URL 已解決掉了核心問題。剩下的就是根據不同的支付方式來拼接具體的支付接口 URL。

微信支付常用接口

付款碼支付、JSAPI支付、Native支付、App支付、H5支付、小程序支付、紅包、企業付款、酒店押金、刷臉支付常用的支付方式以及支付工具不完全統計接口大概有 90+

package com.ijpay.wxpay.enums;

/**
 * <p>IJPay 讓支付觸手可及,封裝了微信支付、支付寶支付、銀聯支付常用的支付方式以及各種常用的接口。</p>
 *
 * <p>不依賴任何第三方 mvc 框架,僅僅作爲工具使用簡單快速完成支付模塊的開發,可輕鬆嵌入到任何系統裏。 </p>
 *
 * <p>IJPay 交流羣: 723992875</p>
 *
 * <p>Node.js 版: https://gitee.com/javen205/TNW</p>
 *
 * <p>微信支付接口枚舉</p>
 *
 * @author Javen
 */
public enum WxApiType {
    /**
     * 沙箱環境
     */
    SAND_BOX_NEW("/sandboxnew"),
    /**
     * 獲取沙箱環境驗籤祕鑰
     */
    GET_SIGN_KEY("/sandboxnew/pay/getsignkey"),
    /**
     * 統一下單
     */
    UNIFIED_ORDER("/pay/unifiedorder"),
    /**
     * 提交付款碼支付
     */
    MICRO_PAY("/pay/micropay"),
    /**
     * 查詢訂單
     */
    ORDER_QUERY("/pay/orderquery"),
    /**
     * 關閉訂單
     */
    CLOSE_ORDER("/pay/closeorder"),
    /**
     * 撤銷訂單
     */
    REVERSE("/secapi/pay/reverse"),
    /**
     * 申請退款
     */
    REFUND("/secapi/pay/refund"),
    /**
     * 查詢退款
     */
    REFUND_QUERY("/pay/refundquery"),
    /**
     * 下載對賬單
     */
    DOWNLOAD_BILL("/pay/downloadbill"),
    /**
     * 下載資金對賬單
     */
    DOWNLOAD_FUND_FLOW("/pay/downloadfundflow"),
    /**
     * 交易保障
     */
    REPORT("/payitil/report"),
    /**
     * 轉換短鏈接
     */
    SHORT_URL("/tools/shorturl"),
    /**
     * 授權碼查詢 openId
     */
    AUTH_CODE_TO_OPENID("/tools/authcodetoopenid"),
    /**
     * 拉取訂單評價數據
     */
    BATCH_QUERY_COMMENT("/billcommentsp/batchquerycomment"),
    /**
     * 企業付款
     */
    TRANSFER("/mmpaymkttransfers/promotion/transfers"),
    /**
     * 查詢企業付款
     */
    GET_TRANSFER_INFO("/mmpaymkttransfers/gettransferinfo"),
    /**
     * 企業付款到銀行卡
     */
    TRANSFER_BANK("/mmpaysptrans/pay_bank"),
    /**
     * 查詢企業付款到銀行卡
     */
    GET_TRANSFER_BANK_INFO("/mmpaysptrans/query_bank"),
    /**
     * 獲取 RSA 加密公鑰
     */
    GET_PUBLIC_KEY("/risk/getpublickey"),
    /**
     * 發放紅包
     */
    SEND_RED_PACK("/mmpaymkttransfers/sendredpack"),
    /**
     * 發放裂變紅包
     */
    SEND_GROUP_RED_PACK("/mmpaymkttransfers/sendgroupredpack"),
    /**
     * 查詢紅包記錄
     */
    GET_HB_INFO("/mmpaymkttransfers/gethbinfo"),
    /**
     * 小程序發紅包
     */
    SEND_MINI_PROGRAM_HB("/mmpaymkttransfers/sendminiprogramhb"),
    /**
     * 發放代金券
     */
    SEND_COUPON("/mmpaymkttransfers/send_coupon"),
    /**
     * 查詢代金券批次
     */
    QUERY_COUPON_STOCK("/mmpaymkttransfers/query_coupon_stock"),
    /**
     * 查詢代金券信息
     */
    QUERY_COUPONS_INFO("/mmpaymkttransfers/querycouponsinfo"),
    /**
     * 請求單次分賬
     */
    PROFIT_SHARING("/secapi/pay/profitsharing"),
    /**
     * 請求多次分賬
     */
    MULTI_PROFIT_SHARING("/secapi/pay/multiprofitsharing"),
    /**
     * 查詢分賬結果
     */
    PROFIT_SHARING_QUERY("/pay/profitsharingquery"),
    /**
     * 添加分賬接收方
     */
    PROFITS_HARING_ADD_RECEIVER("/pay/profitsharingaddreceiver"),
    /**
     * 刪除分賬接收方
     */
    PROFIT_SHARING_REMOVE_RECEIVER("/pay/profitsharingremovereceiver"),
    /**
     * 完結分賬
     */
    PROFIT_SHARING_FINISH("/secapi/pay/profitsharingfinish"),
    /**
     * 分賬回退
     */
    PROFIT_SHARING_RETURN("/secapi/pay/profitsharingreturn"),
    /**
     * 分賬回退結果查詢
     */
    PROFIT_SHARING_RETURN_QUERY("/pay/profitsharingreturnquery"),
    /**
     * 支付押金(人臉支付)
     */
    DEPOSIT_FACE_PAY("/deposit/facepay"),
    /**
     * 支付押金(付款碼支付)
     */
    DEPOSIT_MICRO_PAY("/deposit/micropay"),
    /**
     * 查詢訂單(押金)
     */
    DEPOSIT_ORDER_QUERY("/deposit/orderquery"),
    /**
     * 撤銷訂單(押金)
     */
    DEPOSIT_REVERSE("/deposit/reverse"),
    /**
     * 消費押金
     */
    DEPOSIT_CONSUME("/deposit/consume"),
    /**
     * 申請退款(押金)
     */
    DEPOSIT_REFUND("/deposit/refund"),
    /**
     * 查詢退款(押金)
     */
    DEPOSIT_REFUND_QUERY("deposit/refundquery"),
    /**
     * 公衆號純簽約
     */
    ENTRUST_WEB("/papay/entrustweb"),
    /**
     * 公衆號純簽約(服務商模式)
     */
    PARTNER_ENTRUST_WEB("/papay/partner/entrustweb"),
    /**
     * APP純簽約
     */
    PRE_ENTRUST_WEB("/papay/preentrustweb"),
    /**
     * APP純簽約(服務商模式)
     */
    PARTNER_PRE_ENTRUST_WEB("/papay/partner/preentrustweb"),
    /**
     * H5純簽約
     */
    H5_ENTRUST_WEB("/papay/h5entrustweb"),
    /**
     * H5純簽約(服務商模式)
     */
    PARTNER_H5_ENTRUST_WEB("/papay/partner/h5entrustweb"),
    /**
     * 支付中籤約
     */
    PAY_CONTRACT_ORDER("/pay/contractorder"),
    /**
     * 查詢簽約關係
     */
    QUERY_ENTRUST_CONTRACT("/papay/querycontract"),
    /**
     * 查詢簽約關係(服務商模式)
     */
    PARTNER_QUERY_ENTRUST_CONTRACT("/papay/partner/querycontract"),
    /**
     * 代扣申請扣款
     */
    PAP_PAY_APPLY("/pay/pappayapply"),
    /**
     * 代扣申請扣款(服務商模式)
     */
    PARTNER_PAP_PAY_APPLY("/pay/partner/pappayapply"),
    /**
     * 查詢代扣訂單
     */
    PAP_ORDER_QUERY("/pay/paporderquery"),
    /**
     * 查詢代扣訂單
     */
    PARTNER_PAP_ORDER_QUERY("/pay/partner/paporderquery"),
    /**
     * 代扣申請解約
     */
    DELETE_ENTRUST_CONTRACT("/papay/deletecontract"),
    /**
     * 代扣申請解約(服務商模式)
     */
    PARTNER_DELETE_ENTRUST_CONTRACT("/papay/partner/deletecontract"),
    /**
     * 刷臉支付
     */
    FACE_PAY("/pay/facepay"),
    /**
     * 查詢刷臉支付訂單
     */
    FACE_PAY_QUERY("/pay/facepayqueryy"),
    /**
     * 撤銷刷臉支付訂單
     */
    FACE_PAY_REVERSE("/secapi/pay/facepayreverse"),
    /**
     * 小微商戶申請入駐
     */
    MICRO_SUBMIT("/applyment/micro/submit"),
    /**
     * 查詢申請狀態
     */
    GET_MICRO_SUBMIT_STATE("/applyment/micro/getstate"),
    /**
     * 提交升級申請
     */
    MICRO_SUBMIT_UPGRADE("/applyment/micro/submitupgrade"),
    /**
     * 查詢升級申請單狀態
     */
    GET_MICRO_UPGRADE_STATE("/applyment/micro/getupgradestate"),
    /**
     * 查詢提現狀態
     */
    QUERY_AUTO_WITH_DRAW_BY_DATE("/fund/queryautowithdrawbydate"),
    /**
     * 修改結算銀行卡
     */
    MICRO_MODIFY_ARCHIVES("/applyment/micro/modifyarchives"),
    /**
     * 重新發起提現
     */
    RE_AUTO_WITH_DRAW_BY_DATE("/fund/reautowithdrawbydate"),
    /**
     * 修改聯繫信息
     */
    MICRO_MODIFY_CONTACT_INFO("/applyment/micro/modifycontactinfo"),
    /**
     * 小微商戶關注功能配置
     */
    ADD_RECOMMEND_CONF("/secapi/mkt/addrecommendconf"),
    /**
     * 小微商戶開發配置新增支付目錄
     */
    ADD_SUB_DEV_CONFIG("/secapi/mch/addsubdevconfig"),
    /**
     * 小微商戶開發配置查詢
     */
    QUERY_SUB_DEV_CONFIG("/secapi/mch/querysubdevconfig");

    /**
     * 類型
     */
    private final String type;

    WxApiType(String type) {
        this.type = type;
    }

    public String getType() {
        return type;
    }
}

獲取完整URL 方案

同時支持任意接口任意域名的切換,爲跨城冗災打下良好基礎

	/**
     * 獲取接口請求的 URL
     *
     * @param wxApiType {@link WxApiType} 支付 API 接口枚舉
     * @return {@link String} 返回完整的接口請求URL
     */
    public static String getReqUrl(WxApiType wxApiType) {
        return getReqUrl(wxApiType, null, false);
    }

    /**
     * 獲取接口請求的 URL
     *
     * @param wxApiType {@link WxApiType} 支付 API 接口枚舉
     * @param isSandBox 是否是沙箱環境
     * @return {@link String} 返回完整的接口請求URL
     */
    public static String getReqUrl(WxApiType wxApiType, boolean isSandBox) {
        return getReqUrl(wxApiType, null, isSandBox);
    }

    /**
     * 獲取接口請求的 URL
     *
     * @param wxApiType {@link WxApiType} 支付 API 接口枚舉
     * @param wxDomain  {@link WxDomain} 支付 API 接口域名枚舉
     * @param isSandBox 是否是沙箱環境
     * @return {@link String} 返回完整的接口請求URL
     */
    public static String getReqUrl(WxApiType wxApiType, WxDomain wxDomain, boolean isSandBox) {
        if (wxDomain == null) {
            wxDomain = WxDomain.CHINA;
        }
        return wxDomain.getType()
                .concat(isSandBox ? WxApiType.SAND_BOX_NEW.getType() : "")
                .concat(wxApiType.getType());
    }

構建請求參數

Model 構建實現機制

這裏構建請求參數使用的是 Lombok + Java 反射機制來實現。

封裝 Model 自動生成簽名

BaseModel 實現將 Lombok builder 後對象中的屬性以及值轉爲 Map並提供創建簽名的方法自動生成 sign (同時支持 MD5 以及 HMAC-SHA256)。以微信支付中的統一下單爲例代碼如下

public class BaseModel {

    /**
     * 將建構的 builder 轉爲 Map
     *
     * @return 轉化後的 Map
     */
    public Map<String, String> toMap() {
        String[] fieldNames = getFiledNames(this);
        HashMap<String, String> map = new HashMap<String, String>(fieldNames.length);
        for (int i = 0; i < fieldNames.length; i++) {
            String name = fieldNames[i];
            String value = (String) getFieldValueByName(name, this);
            if (StrUtil.isNotEmpty(value)) {
                map.put(name, value);
            }
        }
        return map;
    }

    /**
     * 構建簽名 Map
     *
     * @param partnerKey API KEY
     * @param signType   {@link SignType} 簽名類型
     * @return 構建簽名後的 Map
     */
    public Map<String, String> createSign(String partnerKey, SignType signType) {
        return createSign(partnerKey,signType,true);
    }

    /**
     * 構建簽名 Map
     *
     * @param partnerKey   API KEY
     * @param signType     {@link SignType} 簽名類型
     * @param haveSignType 簽名是否包含 sign_type 字段
     * @return 構建簽名後的 Map
     */
    public Map<String, String> createSign(String partnerKey, SignType signType, boolean haveSignType) {
        return WxPayKit.buildSign(toMap(), partnerKey, signType,haveSignType);
    }

    /**
     * 獲取屬性名數組
     *
     * @param obj 對象
     * @return 返回對象屬性名數組
     */
    public String[] getFiledNames(Object obj) {
        Field[] fields = obj.getClass().getDeclaredFields();
        String[] fieldNames = new String[fields.length];
        for (int i = 0; i < fields.length; i++) {
            fieldNames[i] = fields[i].getName();
        }
        return fieldNames;
    }

    /**
     * 根據屬性名獲取屬性值
     *
     * @param fieldName 屬性名稱
     * @param obj       對象
     * @return 返回對應屬性的值
     */
    public Object getFieldValueByName(String fieldName, Object obj) {
        try {
            String firstLetter = fieldName.substring(0, 1).toUpperCase();
            String getter = new StringBuffer().append("get")
                    .append(firstLetter)
                    .append(fieldName.substring(1))
                    .toString();
            Method method = obj.getClass().getMethod(getter, new Class[]{});
            return method.invoke(obj, new Object[]{});
        } catch (Exception e) {
            return null;
        }
    }

}

UnifiedOrderModel 微信統一下單

/**
 * <p>IJPay 讓支付觸手可及,封裝了微信支付、支付寶支付、銀聯支付常用的支付方式以及各種常用的接口。</p>
 *
 * <p>不依賴任何第三方 mvc 框架,僅僅作爲工具使用簡單快速完成支付模塊的開發,可輕鬆嵌入到任何系統裏。 </p>
 *
 * <p>IJPay 交流羣: 723992875</p>
 *
 * <p>Node.js 版: https://gitee.com/javen205/TNW</p>
 *
 * <p>統一下單 Model</p>
 *
 * @author Javen
 */
package com.ijpay.wxpay.model;

import com.ijpay.core.model.BaseModel;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class UnifiedOrderModel extends BaseModel {
    private String appid;
    private String mch_id;
    private String sub_appid;
    private String sub_mch_id;
    private String device_info;
    private String nonce_str;
    private String sign;
    private String sign_type;
    private String body;
    private String detail;
    private String attach;
    private String out_trade_no;
    private String fee_type;
    private String total_fee;
    private String spbill_create_ip;
    private String time_start;
    private String time_expire;
    private String goods_tag;
    private String notify_url;
    private String trade_type;
    private String product_id;
    private String limit_pay;
    private String openid;
    private String sub_openid;
    private String receipt;
    private String scene_info;
}

UnifiedOrderModel 中是熟悉字段完全來自官方接口文檔。遺憾的是 Model 不能遵守小駝峯式命名規則。

簽名算法實現

MD5 以及 HMAC-SHA256 簽名算法實現使用的是 Hutool 提供的工具類來實現

	public static String hmacSha256(String data, String key) {
        return SecureUtil.hmac(HmacAlgorithm.HmacSHA256, key).digestHex(data, CharsetUtil.UTF_8);
    }

    public static String md5(String data) {
        return SecureUtil.md5(data);
    }

構建簽名邏輯如下

	/**
     * 構建簽名
     *
     * @param params     需要簽名的參數
     * @param partnerKey 密鑰
     * @param signType   簽名類型
     * @return 簽名後的 Map
     */
    public static Map<String, String> buildSign(Map<String, String> params, String partnerKey, SignType signType) {
        return buildSign(params,partnerKey,signType,true);
    }

    /**
     * 構建簽名
     *
     * @param params       需要簽名的參數
     * @param partnerKey   密鑰
     * @param signType     簽名類型
     * @param haveSignType 簽名是否包含 sign_type 字段
     * @return 簽名後的 Map
     */
    public static Map<String, String> buildSign(Map<String, String> params, String partnerKey, SignType signType, boolean haveSignType) {
        if(haveSignType){
            params.put(FIELD_SIGN_TYPE, signType.getType());
        }
        String sign = createSign(params, partnerKey, signType);
        params.put(FIELD_SIGN, sign);
        return params;
    }

	/**
     * 生成簽名
     *
     * @param params     需要簽名的參數
     * @param partnerKey 密鑰
     * @param signType   簽名類型
     * @return 簽名後的數據
     */
    public static String createSign(Map<String, String> params, String partnerKey, SignType signType) {
        if (signType == null) {
            signType = SignType.MD5;
        }
        // 生成簽名前先去除sign
        params.remove(FIELD_SIGN);
        String tempStr = PayKit.createLinkString(params);
        String stringSignTemp = tempStr + "&key=" + partnerKey;
        if (signType == SignType.MD5) {
            return md5(stringSignTemp).toUpperCase();
        } else {
            return hmacSha256(stringSignTemp, partnerKey).toUpperCase();
        }
    }

通過 Model 構建 xml 數據

通過上面的封裝後不到 20 行代碼就可以通過 Model 構建出微信支付接口所需要的 xml 數據

        WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();

        Map<String, String> params = UnifiedOrderModel
                .builder()
                .appid(wxPayApiConfig.getAppId())
                .mch_id(wxPayApiConfig.getMchId())
                .nonce_str(WxPayKit.generateStr())
                .body("IJPay 讓支付觸手可及-公衆號支付")
                .attach("Node.js 版:https://gitee.com/javen205/TNW")
                .out_trade_no(WxPayKit.generateStr())
                .total_fee("1000")
                .spbill_create_ip(ip)
                .notify_url(notifyUrl)
                .trade_type(TradeType.JSAPI.getTradeType())
                .openid(openId)
                .build()
                .createSign(wxPayApiConfig.getPartnerKey(), SignType.HMACSHA256);
        String xml = WxPayKit.toXml(params);
        log.info(xml);

發起請求

由於篇幅原因這裏不做詳細介紹,請參考 擴展 Http 請求

喚起支付

這裏常見的問題就是預付訂單二次簽名異常以及喚起支付提示各種配置錯誤,比如授權目錄沒有配置

預付訂單二次簽名封裝

	/**
     * <p>公衆號支付-預付訂單再次簽名</p>
     * <p>注意此處簽名方式需與統一下單的簽名類型一致</p>
     *
     * @param prepayId   預付訂單號
     * @param appId      應用編號
     * @param partnerKey API Key
     * @param signType   簽名方式
     * @return 再次簽名後的 Map
     */
    public static Map<String, String> prepayIdCreateSign(String prepayId, String appId, String partnerKey, SignType signType) {
        Map<String, String> packageParams = new HashMap<String, String>(6);
        packageParams.put("appId", appId);
        packageParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
        packageParams.put("nonceStr", String.valueOf(System.currentTimeMillis()));
        packageParams.put("package", "prepay_id=" + prepayId);
        if (signType == null) {
            signType = SignType.MD5;
        }
        packageParams.put("signType", signType.getType());
        String packageSign = WxPayKit.createSign(packageParams, partnerKey, signType);
        packageParams.put("paySign", packageSign);
        return packageParams;
    }

    /**
     * <p>APP 支付-預付訂單再次簽名</p>
     * <p>注意此處簽名方式需與統一下單的簽名類型一致</p>
     *
     * @param appId      應用編號
     * @param partnerId  商戶號
     * @param prepayId   預付訂單號
     * @param partnerKey API Key
     * @param signType   簽名方式
     * @return 再次簽名後的 Map
     */
    public static Map<String, String> appPrepayIdCreateSign(String appId, String partnerId, String prepayId, String partnerKey, SignType signType) {
        Map<String, String> packageParams = new HashMap<String, String>(8);
        packageParams.put("appid", appId);
        packageParams.put("partnerid", partnerId);
        packageParams.put("prepayid", prepayId);
        packageParams.put("package", "Sign=WXPay");
        packageParams.put("noncestr", String.valueOf(System.currentTimeMillis()));
        packageParams.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
        if (signType == null) {
            signType = SignType.MD5;
        }
        String packageSign = createSign(packageParams, partnerKey, signType);
        packageParams.put("sign", packageSign);
        return packageParams;
    }

    /**
     * <p>小程序-預付訂單再次簽名</p>
     * <p>注意此處簽名方式需與統一下單的簽名類型一致</p>
     *
     * @param appId      應用編號
     * @param prepayId   預付訂單號
     * @param partnerKey API Key
     * @param signType   簽名方式
     * @return 再次簽名後的 Map
     */
    public static Map<String, String> miniAppPrepayIdCreateSign(String appId, String prepayId, String partnerKey, SignType signType) {
        Map<String, String> packageParams = new HashMap<String, String>(6);
        packageParams.put("appId", appId);
        packageParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
        packageParams.put("nonceStr", String.valueOf(System.currentTimeMillis()));
        packageParams.put("package", "prepay_id=" + prepayId);
        if (signType == null) {
            signType = SignType.MD5;
        }
        packageParams.put("signType", signType.getType());
        String packageSign = createSign(packageParams, partnerKey, signType);
        packageParams.put("paySign", packageSign);
        return packageParams;
    }

具體使用案例請參考 IJPay-Demo-SpringBoot

支付異步通知

目前微信支付異步通知有兩種:支付結果異步通知、微信退款異步通知

注意事項:
1、及時響應對應的應答
2、異步通知需要根據訂單號做去重處理
3、支付結果的異步通知簽名方法必須與統一下單的簽名方式保持一致
4、微信退款異步通知出現 java.security.InvalidKeyException: Illegal key size 異常 解決方案

驗證簽名封裝

/**
     * 支付異步通知時校驗 sign
     *
     * @param params     參數
     * @param partnerKey 支付密鑰
     * @param signType   {@link SignType}
     * @return
     */
    public static boolean verifyNotify(Map<String, String> params, String partnerKey, SignType signType) {
        String sign = params.get("sign");
        String localSign = createSign(params, partnerKey, signType);
        return sign.equals(localSign);
    }

微信支付結果異步通知業務處理邏輯的僞代碼

	/**
     * 異步通知
     */
    @RequestMapping(value = "/payNotify", method = {RequestMethod.POST, RequestMethod.GET})
    @ResponseBody
    public String payNotify(HttpServletRequest request) {
        String xmlMsg = HttpKit.readData(request);
        log.info("支付通知=" + xmlMsg);
        Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);

        String returnCode = params.get("return_code");

        // 注意重複通知的情況,同一訂單號可能收到多次通知,請注意一定先判斷訂單狀態
        // 注意此處簽名方式需與統一下單的簽名類型一致
        if (WxPayKit.verifyNotify(params, WxPayApiConfigKit.getWxPayApiConfig().getPartnerKey(), SignType.HMACSHA256)) {
            if (WxPayKit.codeIsOk(returnCode)) {
                // 更新訂單信息
                // 發送通知等
                Map<String, String> xml = new HashMap<String, String>(2);
                xml.put("return_code", "SUCCESS");
                xml.put("return_msg", "OK");
                return WxPayKit.toXml(xml);
            }
        }
        return null;
    }

微信退款數據解密

微信退款數據解密詳細步驟請參考官方文檔,以下是使用 Hutool 提供 SecureUtil 實現

	/**
     * AES 解密
     *
     * @param base64Data 需要解密的數據
     * @param key        密鑰
     * @return 解密後的數據
     */
    public static String decryptData(String base64Data, String key) {
        return SecureUtil.aes(md5(key).toLowerCase().getBytes()).decryptStr(base64Data);
    }

微信退款通知業務處理邏輯的僞代碼

	/**
     * 退款通知
     */
    @RequestMapping(value = "/refundNotify", method = {RequestMethod.POST, RequestMethod.GET})
    @ResponseBody
    public String refundNotify(HttpServletRequest request) {
        String xmlMsg = HttpKit.readData(request);
        log.info("退款通知=" + xmlMsg);
        Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);

        String returnCode = params.get("return_code");
        // 注意重複通知的情況,同一訂單號可能收到多次通知,請注意一定先判斷訂單狀態
        if (WxPayKit.codeIsOk(returnCode)) {
            String reqInfo = params.get("req_info");
            String decryptData = WxPayKit.decryptData(reqInfo, WxPayApiConfigKit.getWxPayApiConfig().getPartnerKey());
            log.info("退款通知解密後的數據=" + decryptData);
            // 更新訂單信息
            // 發送通知等
            Map<String, String> xml = new HashMap<String, String>(2);
            xml.put("return_code", "SUCCESS");
            xml.put("return_msg", "OK");
            return WxPayKit.toXml(xml);
        }
        return null;
    }

個人能力有限如有錯誤歡迎指正,如有遺漏歡迎補充。如有疑問歡迎留言一起交流討論。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章