admin管理员组

文章数量:1559105

一、前言

微信支付账号类型分为商户平台和j9九游会老哥俱乐部交流区的合作伙伴平台,今天主要是梳理商品平台微信支付流程。

商品平台文档地址,(在接入前建议仔细阅读这份文档,会少走很多弯路!!!)

小程序下单 - 小程序支付 | 微信支付商户文档中心

二、接入流程

以下是我springboot项目接入微信支付v3流程,如果还有更好的集成方式,欢迎留言探讨。

2.1、pom.xml导入微信支付sdk



    com.github.wechatpay-apiv3
    wechatpay-apache-httpclient
    0.3.0

2.2、application.yaml配置微信支付相关配置

# 微信支付相关参数
wxpay: 
# 商户号
  mch-id: 1888888888
# 商户api证书序列号
  mch-serial-no: 188d8d888888888a476b7deb5f820082570ca4ba  
# apiv3密钥
  api-v3-key: 88888888
# appid
  appid: wx88888ff5ee888f88
# 微信服务器地址
  domain: https://api.mch.weixin.qq
# 微信支付回调地址 https和外网可以访问的地址
  notify-domain: https://35p3811d81.yicp.fun
# 商户私钥 此处注意换行,回车等不可见字符
  private-key: biievgibadanbgkqhkig9w0baqefaascbkgwggskageaaoibaqdv9hl2qvkpvaafbih4scv6dfbws/rem hhfwq8mv1xtt7kzumasebrfoe3nshx5oi zpnr/nnhsh9vtyubbh0lce7ad oltlnvtszxlorupoegcxsokanhxd9gfgpkx/i8rkdnnoktesbxgf8tgrqnibycwwggjmuangveajqkvn3pc 4wrlfm/x1gwuegovxnukuayu7vpgn ukauc1buadwm6eoka

2.3、wxpaycontroller.java(回调通知接口一定要从权限拦截器中剔除)

  /**
     * 微信支付api
     */
    @postmapping("/save")
    @resubmit
    public apiresponse savewxpayorder(@requestbody orderwxpayappreq orderwxpayappreq) throws exception {
        orderwxpayappreq.setuserid(getuserid());
        return apiresponse.ok(orderwxpayservice.savewxpayorder(orderwxpayappreq));
    }
    /**
     * 微信退款api
     */
    @postmapping("/refunds/apply")
    @resubmit
    public apiresponse refunds(@requestbody orderwxpayappreq orderwxpayappreq) throws exception {
        log.info("申请退款");
        orderwxpayappreq.setuserid(getuserid());
        return apiresponse.ok(orderwxpayservice.wxrefund(orderwxpayappreq));
    }
    
    /**
     * 微信支付回调通知
     */
    @postmapping("/callback/pay/notify")
    public apiresponse paycallback(httpservletrequest request) {
        gson gson = new gson();
        try {
            //处理通知参数
            string body = httputils.readdata(request);
            map bodymap = gson.fromjson(body, hashmap.class);
            string requestid = (string) bodymap.get("id");
            //签名的验证
            wechatpay2validatorforrequest wechatpay2validatorforrequest
                    = new wechatpay2validatorforrequest(verifier, requestid, body);
            if (!wechatpay2validatorforrequest.validate(request)) {
                //失败应答
                return apiresponse.error("通知验签失败");
            }
            //处理订单
            orderwxpayservice.processorder(bodymap);
            //成功应答
            return apiresponse.ok("成功");
        } catch (exception e) {
            e.printstacktrace();
            //失败应答
            return apiresponse.error("失败");
        }
    }
    
     /**
     * 微信退款结果通知
     */
    @postmapping("/callback/refunds/notify")
    public apiresponse refundsnotify(httpservletrequest request) {
        log.info("退款通知执行");
        try {
            //处理通知参数
            string body = httputils.readdata(request);
            gson gson = new gson();
            map bodymap = gson.fromjson(body, hashmap.class);
            string requestid = (string) bodymap.get("id");
            //签名的验证
            wechatpay2validatorforrequest wechatpay2validatorforrequest
                    = new wechatpay2validatorforrequest(verifier, requestid, body);
            if (!wechatpay2validatorforrequest.validate(request)) {
                return apiresponse.error("通知验签失败");
            }
            //处理退款单
            orderwxpayservice.processrefund(bodymap);
            return apiresponse.ok("成功");
        } catch (exception e) {
            e.printstacktrace();
            //失败应答
            return apiresponse.error("失败");
        }
    }

2.4、wxpayservice.java

    @override
    public map savewxpayorder(orderwxpayappreq orderwxpayappreq) throws ioexception {
        log.info("调用统一下单api");
        list orderidlist = orderwxpayappreq.getorderidlist();
        if (collutil.isempty(orderidlist)) {
            throw new baseruntimeexception(ordererrorcodeenums.base_003);
        }
        //查询订单
        lambdaquerywrapper orderquerywrapper = new lambdaquerywrapper()
                .in(orderinfo::getorderid, orderidlist)
                .eq(orderinfo::getdeleted, boolean.false);
        list orderinfolist = orderinfomapper.selectlist(orderquerywrapper);
        if (collutil.isempty(orderinfolist)) {
            throw new baseruntimeexception(ordererrorcodeenums.order_001);
        }
        atomicreference totalpaymoney = new atomicreference<>(0l);
        string orderidstr = orderinfolist.stream().map(o -> {
            totalpaymoney.updateandget(v -> v   o.getpaymoney());
            return string.valueof(o.getorderid());
        }).collect(collectors.joining(","));
        //调用统一下单api
        httppost httppost = new httppost(wxpayconfig.getdomain().concat(wxapitype.jsapi_pay.gettype()));
        string paymentkey = ordernoutils.createorderno(ordernoutils.payment_serial_number_prefix);
        // key值有效期为1小时
        redisutil.set(rediskeys.orderkeys.order_payment_key   paymentkey, orderidstr, 3600);
        // 请求body参数
        gson gson = new gson();
        map paramsmap = new hashmap<>(commonconstants.default_map_capacity);
        paramsmap.put("appid", wxpayconfig.getappid());
        paramsmap.put("mchid", wxpayconfig.getmchid());
        paramsmap.put("description", orderidstr);
        paramsmap.put("out_trade_no", paymentkey);
        paramsmap.put("notify_url", wxpayconfig.getnotifydomain().concat(wxnotifytype.jsapi_notify.gettype()));
        map amountmap = new hashmap<>(commonconstants.default_map_capacity);
        long totalmoney = totalpaymoney.get();
        boolean enablevirtualpay = optional.ofnullable(enabled.getenablevirtualpay()).orelse(boolean.true);
        if (enablevirtualpay) {
            totalmoney = 1l;
        }
        amountmap.put("total", totalmoney);
        amountmap.put("currency", "cny");
        paramsmap.put("amount", amountmap);
        map payermap = new hashmap<>(commonconstants.default_map_capacity);
        final userthirdinfoapivo userthirdinfoapivo = userfeignapi.getuserthirdinforsp(orderwxpayappreq.getuserid(), "wx", wxpayconfig.getmanagerappid());
        payermap.put("openid", userthirdinfoapivo.getopenid());
        paramsmap.put("payer", payermap);
        //将参数转换成json字符串
        string jsonparams = gson.tojson(paramsmap);
        stringentity entity = new stringentity(jsonparams, "utf-8");
        entity.setcontenttype("application/json");
        httppost.setentity(entity);
        httppost.setheader("accept", "application/json");
        //完成签名并执行请求
        closeablehttpresponse response = wxpayclient.execute(httppost);
        //返回二维码
        map map = new hashmap<>(commonconstants.default_map_capacity);
        try {
            //响应体
            string bodyasstring = entityutils.tostring(response.getentity());
            log.info("成功, 支付接口返回结果:{} ", bodyasstring);
            //响应状态码
            int statuscode = response.getstatusline().getstatuscode();
            //响应结果
            map resultmap = gson.fromjson(bodyasstring, hashmap.class);
            //处理成功
            if (statuscode != 200 && statuscode != 204) {
                throw new baseruntimeexception(ordererrorcodeenums.order_002.code, resultmap.get("message"));
            }
            //预支付交易会话标识
            string prepayid = resultmap.get("prepay_id");
            string prepayidstr = "prepay_id="   prepayid;
            long epochsecond = localdatetime.now().toepochsecond(zoneoffset.of(" 8"));
            string timestamp = string.valueof(epochsecond);
            string noncestr = nonceutil.createnonce(32);
            //关联的公众号的appid
            map.put("appid", wxpayconfig.getappid());
            //时间戳
            map.put("timestamp", timestamp);
            //生成随机字符串
            map.put("noncestr", noncestr);
            map.put("package", prepayidstr);
            map.put("signtype", "rsa");
            map.put("paysign", wxpayconfig.getpaysign(timestamp, noncestr, prepayidstr));
        } finally {
            response.close();
        }
        return map;
    }
    
    @transactional(rollbackfor = exception.class)
    @override
    public boolean processorder(map bodymap) throws generalsecurityexception {
        //将明文转换成map
        hashmap plaintextmap = new gson().fromjson(decryptfromresource(bodymap), hashmap.class);
        string paymentkey = string.valueof(plaintextmap.get("out_trade_no"));
        object orderidobj = redisutil.get(rediskeys.orderkeys.order_payment_key   paymentkey);
        if (objectutil.isnull(orderidobj)) {
            return boolean.false;
        }
        // 处理支付成功后业务数据
        ... ...
        return boolean.true;
    }
    
      /**
     * 退款
     *
     * @param orderwxpayappreq
     * @return
     */
    @transactional(rollbackfor = exception.class)
    @override
    public boolean wxrefund(orderwxpayappreq orderwxpayappreq) throws ioexception {
        if (objectutil.isempty(orderwxpayappreq.getafterorderid())) {
            throw new baseruntimeexception(ordererrorcodeenums.base_003.code, ordererrorcodeenums.base_003.desc);
        }
        orderafter orderafter = orderaftermapper.selectbyid(orderwxpayappreq.getafterorderid());
        orderinfo orderinfo = orderinfomapper.selectbyid(orderafter.getorderid());
        if (objectutil.isnull(orderinfo.getpayserialnumber())) {
            return boolean.false;
        }
        // 请求body参数
        gson gson = new gson();
        map paramsmap = new hashmap<>(commonconstants.default_map_capacity);
        paramsmap.put("out_trade_no", orderinfo.getpayserialnumber());
        paramsmap.put("out_refund_no", string.valueof(orderafter.getafterorderid()));
        paramsmap.put("reason", orderwxpayappreq.getrefundreason());
        paramsmap.put("notify_url", wxpayconfig.getnotifydomain().concat(wxnotifytype.refund_notify.gettype()));
        map amountmap = new hashmap<>(commonconstants.default_map_capacity);
        final boolean enablevirtualpay = optional.ofnullable(enabled.getenablevirtualpay()).orelse(boolean.true);
        long refund = orderafter.getapplyrefundamount();
        long total = orderinfo.getpaymoney();
        if (enablevirtualpay) {
            refund = 1l;
            total = 1l;
        }
        amountmap.put("refund", refund);
        amountmap.put("total", total);
        amountmap.put("currency", "cny");
        paramsmap.put("amount", amountmap);
        //将参数转换成json字符串
        string jsonparams = gson.tojson(paramsmap);
        stringentity entity = new stringentity(jsonparams, "utf-8");
        entity.setcontenttype("application/json");
        //调用统一下单api
        string url = wxpayconfig.getdomain().concat(wxapitype.domestic_refunds.gettype());
        httppost httppost = new httppost(url);
        httppost.setentity(entity);
        httppost.setheader("accept", "application/json");
        //完成签名并执行请求,并完成验签
        closeablehttpresponse response = wxpayclient.execute(httppost);
        try {
            //解析响应结果
            string bodyasstring = entityutils.tostring(response.getentity());
            int statuscode = response.getstatusline().getstatuscode();
            map bodymap = gson.fromjson(bodyasstring, hashmap.class);
            if (statuscode != 200 && statuscode != 204) {
                throw new baseruntimeexception(ordererrorcodeenums.base_003.getcode(), string.valueof(bodymap.get("message")));
            }
            //保存退款日志
            return saverefundpaymentbillinfo(bodymap);
        } finally {
            response.close();
        }
    }
    
    @override
    @transactional(rollbackfor = exception.class)
    public boolean processrefund(map bodymap) throws generalsecurityexception, interruptedexception {
        //解密报文
        string plaintext = decryptfromresource(bodymap);
        //将明文转换成map
        hashmap plaintextmap = new gson().fromjson(plaintext, hashmap.class);
        string payserialnumber = (string) plaintextmap.get("out_trade_no");
        string outrefundno = (string) plaintextmap.get("out_refund_no");
        // 处理退款后的业务逻辑
        ... ...
        return boolean.true;
    }
    /**
     * 对称解密
     *
     * @param bodymap
     * @return
     */
    private string decryptfromresource(map bodymap) throws generalsecurityexception {
        log.info("密文解密");
        //通知数据
        map resourcemap = (map) bodymap.get("resource");
        //数据密文
        string ciphertext = resourcemap.get("ciphertext");
        //随机串
        string nonce = resourcemap.get("nonce");
        //附加数据
        string associateddata = resourcemap.get("associated_data");
        aesutil aesutil = new aesutil(wxpayconfig.getapiv3key().getbytes(standardcharsets.utf_8));
        string plaintext = aesutil.decrypttostring(associateddata.getbytes(standardcharsets.utf_8),
                nonce.getbytes(standardcharsets.utf_8),
                ciphertext);
        return plaintext;
    }

2.5、wxapitype.java

@allargsconstructor
@getter
public enum wxapitype {
	/**
	 * jsapi下单
	 * https://api.mch.weixin.qq/v3/pay/transactions/jsapi
	 */
	jsapi_pay("/v3/pay/transactions/jsapi"),
	/**
	 * 申请退款
	 * https://api.mch.weixin.qq/v3/refund/domestic/refunds
	 */
	domestic_refunds("/v3/refund/domestic/refunds");
	/**
	 * 类型
	 */
	private final string type;
}

2.6、wxpayconfig.java

/**
* 微信支付配置类
*/
@configuration
@configurationproperties(prefix = "wxpay")
@data
@slf4j
public class wxpayconfig {
    /**
     * 商户号
     */
    private string mchid;
    
    /**
     * 商户api证书序列号
     */
    private string mchserialno;
    /**
     * apiv3密钥
     */
    private string apiv3key;
    /**
     * appid
     */
    private string appid;
    /**
     * 微信服务器地址
     */
    private string domain;
    /**
     * 微信回调通知地址
     */
    private string notifydomain;
    /**
     * 商户密钥
     */
    private string privatekey;
    /**
     * 获取商户的私钥
     *
     * @return
     */
    public privatekey getprivatekey(string privatekey) {
        return pemutil.loadprivatekey(privatekey);
    }
    /**
     * 获取签名验证器
     *
     * @return
     */
    @bean
    public scheduledupdatecertificatesverifier getverifier() {
        log.info("获取签名验证器");
        //获取商户私钥
        privatekey privatekeyobj = getprivatekey(privatekey);
        //私钥签名对象
        privatekeysigner privatekeysigner = new privatekeysigner(mchserialno, privatekeyobj);
        //身份认证对象
        wechatpay2credentials wechatpay2credentials = new wechatpay2credentials(mchid, privatekeysigner);
        // 使用定时更新的签名验证器,不需要传入证书
        scheduledupdatecertificatesverifier verifier = new scheduledupdatecertificatesverifier(
                wechatpay2credentials,
                apiv3key.getbytes(standardcharsets.utf_8));
        return verifier;
    }
    /**
     * 获取http请求对象
     *
     */
    @bean(name = "wxpayclient")
    public closeablehttpclient getwxpayclient(scheduledupdatecertificatesverifier verifier) throws filenotfoundexception {
        log.info("获取httpclient");
        //获取商户私钥
        privatekey privatekeyobj = getprivatekey(privatekey);
        wechatpayhttpclientbuilder builder = wechatpayhttpclientbuilder.create()
                .withmerchant(mchid, mchserialno, privatekeyobj)
                .withvalidator(new wechatpay2validator(verifier));
        // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的httpclient
        // 通过wechatpayhttpclientbuilder构造的httpclient,会自动的处理签名和验签,并进行证书自动更新
        closeablehttpclient httpclient = builder.build();
        return httpclient;
    }
    public string getpaysign(string timestamp, string noncestr, string prepayidstr) {
        string paysign = "";
        try {
            signature sign = signature.getinstance("sha256withrsa");
            sign.initsign(getprivatekey(privatekey));
            string message = appid   "\n"   timestamp   "\n"   noncestr   "\n"   prepayidstr   "\n";
            sign.update(message.getbytes(standardcharsets.utf_8));
            paysign = base64.getencoder().encodetostring(sign.sign());
        } catch (nosuchalgorithmexception | invalidkeyexception | signatureexception e) {
            e.printstacktrace();
        }
        return paysign;
    }
    public static void main(string[] args) throws signatureexception, nosuchalgorithmexception, invalidkeyexception {
        wxpayconfig wxpayconfig = new wxpayconfig();
        long timestamp = 1414561699l;
        string noncestr = "5k8264iltkch16cq2502si8znmtm67vs";
        string packageval = "prepay_id=wx201410272009395522657a690389285100";
        string appid = "wx8b888b888e888d8a";
        string sign = wxpayconfig.getpaysign(string.valueof(timestamp), noncestr, packageval);
        system.out.println(sign);
    }
}

2.7、wxnotifytype.java

/**
 * 微信支付,退款回调地址
 * 1.此处注意接口路径地址
 * 2.在权限控制拦截器中放开接口限制
 */
@allargsconstructor
@getter
public enum wxnotifytype {
    /**
     * 支付通知
     */
    jsapi_notify("/app/order/wx/pay/callback/pay/notify"),
    /**
     * 退款结果通知
     */
    refund_notify("/app/order/wx/pay/callback/refunds/notify");
    /**
     * 类型
     */
    private final string type;
}

三、参考资料

产品介绍 - jsapi支付 | 微信支付商户文档中心

本文标签: 版本springboot