JAVA微信支付

1、准备支付所需配置

  

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
 * 微信配置类
 */
@Configuration
@Data
public class WxpayConfig {

    /**
     * 移动应用appid
     */
    @Value("${wxpay.appid}")
    private String appId;

    /**
     * 移动应用秘钥
     */
    @Value("${wxpay.appsecret}")
    private String appsecret;


    /**
     * 公众号appid
     */
    @Value("${wxpay.officialcAcounts.appid}")
    private String officialcAcountsAppId;

    /**
     * 公众号秘钥
     */
    @Value("${wxpay.officialcAcounts.appsecret}")
    private String officialcAcountsAppsecret;
    /**
     * 商户号id
     */
    @Value("${wxpay.mer_id}")
    private String mchId;

    /**
     * 支付key
     */
    @Value("${wxpay.key}")
    private String key;

    /**
     * 微信支付回调url
     */
    @Value("${wxpay.callback}")
    private String payCallbackUrl;


    /**
     * 统一下单url
     */
    private static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    /**
     * 退款地址
     */
    private static final String REFUND_URL ="https://api.mch.weixin.qq.com/secapi/pay/refund";


    public static String getUnifiedOrderUrl() {
        return UNIFIED_ORDER_URL;
    }

    public static String getRefundUrl() {
        return REFUND_URL;
    }
}

2、APP支付

/**
     * 手机端调取APP支付  返回调起微信app所需的参数
     */
    @GetMapping("/mobilePayment")
    public APIResult<WxPayReqVo> mobilePayment(@RequestParam(value = "orderId") String orderId, HttpServletRequest request, HttpServletResponse response) throws Exception {
        APIResult<CourseOrderVo> result = productService.findCourseOrderById(orderId);
        if (result.getSuccess()) {
            CourseOrderVo courseOrderVo = result.getData();
            //查看是否作废
            Date orderInvalidDate = courseOrderVo.getOrderInvalidDate();
            if (new Date().getTime() > orderInvalidDate.getTime()) {
                //已过期
                CourseOrderDTO courseOrderDTO = new CourseOrderDTO();
                courseOrderDTO.setId(courseOrderVo.getId());
                courseOrderDTO.setOrderStatus(OrderStatusEnum.DISCARD);
                productService.updateCourseOrder(courseOrderDTO);
                return APIResult.error(new BaseCode(APICode._C_OPERATE_ERROR, "订单已过期,请重新下单"));
            }
            //查看是否已经在微信平台下单
            APIResult<WxPayReqVo> wxPayReqVoAPIResult = productService.findWxPayReqByOrderId(courseOrderVo.getId());
            if (wxPayReqVoAPIResult.getSuccess()) {
                return APIResult.ok(wxPayReqVoAPIResult.getData());
            }
            //1、调取统一下单方法 获取参数
            String orderStr = unifiedOrder(courseOrderVo, "APP", "", request, response);

            //商户服务器生成支付订单,先调用统一下单API(详见第7节)生成预付单,获取到prepay_id后将参数再次签名传输给APP发起支付。以下是调起微信支付的关键代码:
            if (orderStr.indexOf("SUCCESS") != -1) {
                //2、xml转map (WXPayUtil工具类)
                Map<String, String> unifiedOrderMap = WXPayUtil.xmlToMap(orderStr);
                System.out.println(unifiedOrderMap.toString());
                String prepayId = unifiedOrderMap.get("prepay_id");
                if (!StringUtils.isEmpty(prepayId)) {
                    String uuid = UUIDUtil.getUUID();
                    //本来生成的时间戳是13位,但是ios必须是10位,所以截取了一下
                    String timeStamp = String.valueOf(System.currentTimeMillis()).substring(0, 10);
                    SortedMap<String, String> parameterMap2 = new TreeMap<String, String>();
                    parameterMap2.put("appid", wxpayConfig.getAppId());
                    parameterMap2.put("partnerid", wxpayConfig.getMchId());
                    parameterMap2.put("prepayid", prepayId);
                    parameterMap2.put("package", "Sign=WXPay");
                    parameterMap2.put("noncestr", uuid);
                    parameterMap2.put("timestamp", timeStamp);
                    String sign = WXPayUtil.createSign(parameterMap2, wxpayConfig.getKey());
                    parameterMap2.put("sign", sign);

                    WxPayReqDTO wxPayReqDTO = new WxPayReqDTO();
                    wxPayReqDTO.setCourseOrderId(courseOrderVo.getId());
                    wxPayReqDTO.setAppId(wxpayConfig.getAppId());
                    wxPayReqDTO.setPartnerId(wxpayConfig.getMchId());
                    wxPayReqDTO.setPrepayId(prepayId);
                    wxPayReqDTO.setNonceStr(uuid);
                    wxPayReqDTO.setTimeStamp(timeStamp);
                    wxPayReqDTO.setPackageValue("Sign=WXPay");
                    wxPayReqDTO.setPayType("APP");
                    wxPayReqDTO.setSign(sign);
                    String param = JSON.toJSONString(parameterMap2);
                    System.out.println("调起微信APP所需的参数:" + param);
                    return productService.createWxPayReq(wxPayReqDTO);
                }
            }
            return APIResult.error(new BaseCode(APICode._C_OPERATE_ERROR, "支付失败"));
        }
        return APIResult.error(new BaseCode(APICode._C_NOT_EXISTS, "订单信息不存在"));
    }

3、H5支付(待验证)

/**
     * h5支付  返回调起微信app所需的参数
     */
    @GetMapping("/H5Payment")
    public APIResult<String> H5Payment(@RequestParam(value = "orderId") String orderId, HttpServletRequest request, HttpServletResponse response) throws Exception {
        APIResult<CourseOrderVo> result = productService.findCourseOrderById(orderId);
        if (result.getSuccess()) {
            CourseOrderVo courseOrderVo = result.getData();
            //查看是否作废
            Date orderInvalidDate = courseOrderVo.getOrderInvalidDate();
            if (new Date().getTime() > orderInvalidDate.getTime()) {
                //已过期
                CourseOrderDTO courseOrderDTO = new CourseOrderDTO();
                courseOrderDTO.setId(courseOrderVo.getId());
                courseOrderDTO.setOrderStatus(OrderStatusEnum.DISCARD);
                productService.updateCourseOrder(courseOrderDTO);
                return APIResult.error(new BaseCode(APICode._C_OPERATE_ERROR, "订单已过期,请重新下单"));
            }
            //查看是否已经在微信平台下单
            APIResult<WxPayReqVo> wxPayReqVoAPIResult = productService.findWxPayReqByOrderId(courseOrderVo.getId());
            if (wxPayReqVoAPIResult.getSuccess()) {
                return APIResult.ok(wxPayReqVoAPIResult.getData());
            }
            //1、调取统一下单方法 获取参数
            String orderStr = unifiedOrder(courseOrderVo, "MWEB", "", request, response);
            //以下内容是返回前端页面的json数据
            String mweb_url = "";//跳转链接
            if (orderStr.indexOf("SUCCESS") != -1) {
                //2、xml转map (WXPayUtil工具类)

                Map<String, String> unifiedOrderMap = WXPayUtil.xmlToMap(orderStr);
                System.out.println(unifiedOrderMap.toString());
                mweb_url = (String) unifiedOrderMap.get("mweb_url");
                //支付完返回浏览器跳转的地址,如跳到查看订单页面
                String redirect_url = "http://edappstest.vtronedu.com/#/complete";
                String redirect_urlEncode = URLEncoder.encode(redirect_url, "utf-8");//对上面地址urlencode
                mweb_url = mweb_url + "&redirect_url=" + redirect_urlEncode;//拼接返回地址
                return APIResult.ok(mweb_url);
            } else {
                return APIResult.error(new BaseCode(APICode._C_OPERATE_ERROR, "支付失败"));
            }
        }
        return APIResult.error(new BaseCode(APICode._C_NOT_EXISTS, "订单信息不存在"));
    }

4、JSAPI支付

   JSAPI支付需要获取微信的openid,前端获取code(参考https://www.cnblogs.com/ganws/p/11139149.html),回调后端的接口再获取openid

@GetMapping("/getOpenId")
    public APIResult<String> getOpenId(@RequestParam(value = "code") String code) {
        log.info("code====" + code);
        //获取openId
        String appid = wxpayConfig.getOfficialcAcountsAppId();  // appid
        String secret = wxpayConfig.getOfficialcAcountsAppsecret();//秘钥
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appid + "&secret=" + secret + "&code=" + code + "&grant_type=authorization_code";
        String s = HttpUtil.sendPost(url, "");
        log.info("getOpenId请求结果===" + s);
        JSONObject jsonObject = JSONObject.parseObject(s);
        log.info("转为JSONObject===" + jsonObject.toString());
        String openid = jsonObject.getString("openid");
        log.info("openid====" + openid);
        return APIResult.ok(openid);
    }

 

/**
     * 微信中的网页 调起微信
     */
    @GetMapping("/JSAPIPayment")
    public APIResult<WxPayReqVo> JSAPIPayment(@RequestParam(value = "orderId") String orderId, @RequestParam(value = "openId") String openId, HttpServletRequest request, HttpServletResponse response) throws Exception {
        APIResult<CourseOrderVo> result = productService.findCourseOrderById(orderId);
        if (result.getSuccess()) {
            CourseOrderVo courseOrderVo = result.getData();
            //查看是否作废
            Date orderInvalidDate = courseOrderVo.getOrderInvalidDate();
            if (new Date().getTime() > orderInvalidDate.getTime()) {
                //已过期
                CourseOrderDTO courseOrderDTO = new CourseOrderDTO();
                courseOrderDTO.setId(courseOrderVo.getId());
                courseOrderDTO.setOrderStatus(OrderStatusEnum.DISCARD);
                productService.updateCourseOrder(courseOrderDTO);
                return APIResult.error(new BaseCode(APICode._C_OPERATE_ERROR, "订单已过期,请重新下单"));
            }
            //查看是否已经在微信平台下单
            APIResult<WxPayReqVo> wxPayReqVoAPIResult = productService.findWxPayReqByOrderId(courseOrderVo.getId());
            if (wxPayReqVoAPIResult.getSuccess()) {
                return APIResult.ok(wxPayReqVoAPIResult.getData());
            }
            //1、调取统一下单方法 获取参数
            String orderStr = unifiedOrder(courseOrderVo, "JSAPI", openId, request, response);

            //商户服务器生成支付订单,先调用统一下单API(详见第7节)生成预付单,获取到prepay_id后将参数再次签名传输给APP发起支付。以下是调起微信支付的关键代码:
            if (orderStr.indexOf("SUCCESS") != -1) {
                //2、xml转map (WXPayUtil工具类)
                Map<String, String> unifiedOrderMap = WXPayUtil.xmlToMap(orderStr);
                System.out.println(unifiedOrderMap.toString());
                String prepayId = unifiedOrderMap.get("prepay_id");
                if (!StringUtils.isEmpty(prepayId)) {
                    String uuid = UUIDUtil.getUUID();
                    //本来生成的时间戳是13位,但是ios必须是10位,所以截取了一下
                    String timeStamp = String.valueOf(System.currentTimeMillis()).substring(0, 10);
                    SortedMap<String, String> parameterMap2 = new TreeMap<String, String>();
                    parameterMap2.put("appId", wxpayConfig.getOfficialcAcountsAppId());
                    parameterMap2.put("timeStamp", timeStamp);
                    parameterMap2.put("nonceStr", uuid);
                    parameterMap2.put("signType", "MD5");
                    parameterMap2.put("package", "prepay_id=" + prepayId);
                    String sign = WXPayUtil.createSign(parameterMap2, wxpayConfig.getKey());
                    parameterMap2.put("sign", sign);

                    WxPayReqDTO wxPayReqDTO = new WxPayReqDTO();
                    wxPayReqDTO.setCourseOrderId(courseOrderVo.getId());
                    wxPayReqDTO.setAppId(wxpayConfig.getOfficialcAcountsAppId());
                    wxPayReqDTO.setPartnerId(wxpayConfig.getMchId());
                    wxPayReqDTO.setPrepayId(prepayId);
                    wxPayReqDTO.setPackageValue("prepay_id=" + prepayId);
                    wxPayReqDTO.setNonceStr(uuid);
                    wxPayReqDTO.setTimeStamp(timeStamp);
                    wxPayReqDTO.setPayType("JSAPI");
                    wxPayReqDTO.setSign(sign);
                    String param = JSON.toJSONString(parameterMap2);
                    System.out.println("调起微信APP所需的参数:" + param);
                    return productService.createWxPayReq(wxPayReqDTO);
                }
            }
            return APIResult.error(new BaseCode(APICode._C_OPERATE_ERROR, "支付失败"));
        }
        return APIResult.error(new BaseCode(APICode._C_NOT_EXISTS, "订单信息不存在"));
    }

5、统一下单接口

/**
     * 统一下单方法
     *
     * @return
     */
    public String unifiedOrder(CourseOrderVo courseOrderVo, String tradeType, String openId, HttpServletRequest request, HttpServletResponse response) throws Exception {

        //4.1、生成签名 按照开发文档需要按字典排序,所以用SortedMap
        SortedMap<String, String> params = new TreeMap<>();

        params.put("appid", wxpayConfig.getAppId());         //公众账号ID
        params.put("mch_id", wxpayConfig.getMchId());       //商户号
        params.put("nonce_str", UUIDUtil.getUUID()); //随机字符串
        params.put("body", "课程订单");       // 商品描述
        params.put("out_trade_no", courseOrderVo.getCourseOrderNo());//商户订单号,商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一
        BigDecimal bigDecimal = courseOrderVo.getFinalPrice();
        String finalPrice = String.valueOf(bigDecimal);
        double d = Double.valueOf(finalPrice) * 100;
        String totalFee = String.valueOf(d);
        params.put("total_fee", "1");//标价金额    分
        params.put("spbill_create_ip", HttpUtil.getClientIp(request));
        params.put("notify_url", wxpayConfig.getPayCallbackUrl());  //通知地址
        params.put("trade_type", tradeType); //交易类型 JSAPI 公众号支付 NATIVE 扫码支付 APP APP支付

        //4.2、sign签名 具体规则:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
        String sign = WXPayUtil.createSign(params, wxpayConfig.getKey());

        if (tradeType.equals("MWEB") || tradeType.equals("JSAPI")) {
            params.put("appid", wxpayConfig.getOfficialcAcountsAppId());
            params.put("openid", openId);
            sign = WXPayUtil.createSign(params, wxpayConfig.getKey());
        }
        params.put("sign", sign);
        //4.3、map转xml ( WXPayUtil工具类)
        String payXml = WXPayUtil.mapToXml(params);

        //4.4、回调微信的统一下单接口(HttpUtil工具类)
        String orderStr = HttpUtil.sendPost(wxpayConfig.getUnifiedOrderUrl(), payXml);
        System.out.println(orderStr);
        return orderStr;

    }

6、支付完成微信回调通知接口 接口需对外开放

/**
     * 微信支付回调
     * 该链接是通过【统一下单API】中提交的参数notify_url设置,如果链接无法访问,商户将无法接收到微信通知。
     * notify_url不能有参数,外网可以直接访问,不能有访问控制(比如必须要登录才能操作)。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action”
     * 支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答。
     * 对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。
     * (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒)
     * 注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
     * 推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
     * 特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,防止数据泄漏导致出现“假通知”,造成资金损失。
     */
    @RequestMapping(value = "/callback")
    public String orderCallback(HttpServletRequest request, HttpServletResponse response) throws Exception {
        InputStream inputStream = request.getInputStream();

        //BufferedReader是包装设计模式,性能更搞
        BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
        StringBuffer sb = new StringBuffer();
        //1、将微信回调信息转为字符串
        String line;
        while ((line = in.readLine()) != null) {
            sb.append(line);
        }
        in.close();
        inputStream.close();
        log.info("回调返回" + sb.toString());

        //2、将xml格式字符串格式转为map集合
        Map<String, String> callbackMap = WXPayUtil.xmlToMap(sb.toString());
        log.info("xml转map" + callbackMap.toString());

        //3、转为有序的map
        SortedMap<String, String> sortedMap = WXPayUtil.getSortedMap(callbackMap);

        Map<String, String> return_data = new HashMap<String, String>();
        //4、判断签名是否正确
        if (WXPayUtil.isCorrectSign(sortedMap, wxpayConfig.getKey())) {

            //5、判断回调信息是否成功
            if ("SUCCESS".equals(sortedMap.get("result_code"))) {
                //获取商户订单号
                //商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一
                String outTradeNo = sortedMap.get("out_trade_no");
                log.info("内部订单号" + outTradeNo);
                //6、数据库查找订单,如果存在则根据订单号更新该订单
                APIResult<CourseOrderVo> result = courseOrderService.findCourseOrderBySerialNo(outTradeNo);
                if (result.getSuccess()) {
                    CourseOrderVo courseOrderVo = result.getData();
                    //修改订单状态为支付
                    courseOrderService.payment(courseOrderVo.getId());

                    //下发购买成功短信

                    return_data.put("return_code", "SUCCESS");
                    return_data.put("return_msg", "OK");
                    return WXPayUtil.mapToXml(return_data);
                }
            }
        }
        //7、通知微信订单处理失败
        return_data.put("return_code", "FAIL");
        return_data.put("return_msg", "return_code不正确");
        return WXPayUtil.mapToXml(return_data);
    }

7、退款

/**
     * 申请退款
     *
     * @return
     * @throws Exception
     */
    @GetMapping("/refund")
    public APIResult<String> wxPayRefund(@RequestParam(value = "orderId") String orderId,
                                         @RequestParam(value = "refundReason", required = false) String refundReason) throws Exception {
        APIResult<CourseOrderVo> result = courseOrderService.findCourseOrderById(orderId);
        if (result.getSuccess()) {
            CourseOrderVo courseOrderVo = result.getData();
            String nonceStr = UUIDUtil.getUUID();//生成32位随机字符串
            SortedMap<String, String> parameters = new TreeMap<String, String>();
            parameters.put("appid", wxpayConfig.getAppId());         //公众账号ID
            parameters.put("mch_id", wxpayConfig.getMchId());       //商户号
            parameters.put("nonce_str", nonceStr);
            parameters.put("out_trade_no", courseOrderVo.getCourseOrderNo());
            //parameters.put("transaction_id", transaction_id);
            parameters.put("out_refund_no", nonceStr);
            parameters.put("fee_type", "CNY");
            BigDecimal bigDecimal = courseOrderVo.getFinalPrice();
            String finalPrice = String.valueOf(bigDecimal);
            double d = Double.valueOf(finalPrice) * 100;
            String totalFee = String.valueOf(d);
            parameters.put("total_fee", "1");
            parameters.put("refund_fee", "1");//部退款金额
            parameters.put("sign", WXPayUtil.createSign(parameters, wxpayConfig.getKey()));
            String data = WXPayUtil.mapToXml(parameters);
            String doRefund = doRefund(wxpayConfig.getMchId(), wxpayConfig.getRefundUrl(), data);
            Map<String, String> returnMap = WXPayUtil.xmlToMap(doRefund);
            if (doRefund.indexOf("SUCCESS") != -1) {
                //修改订单状态
                courseOrderService.refund(courseOrderVo.getId(), refundReason);
                //发送通知
               return APIResult.ok(returnMap.get("return_msg"));

            } else {
                return APIResult.error(new BaseCode(APICode._C_OPERATE_ERROR, returnMap.get("return_msg")));
            }

        }
        return APIResult.error(new BaseCode(APICode._C_NOT_EXISTS, "订单信息不存在"));
    }

    public String doRefund(String mchId, String url, String data) throws Exception {
        /**
         * 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
         */
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        //这里自行实现我是使用数据库配置将证书上传到了服务器可以使用 FileInputStream读取本地文件
        Resource resource = resourceLoader.getResource("classpath:apiclient_cert.p12");
        InputStream inputStream = resource.getInputStream();
        try {
            //这里写密码..默认是你的MCHID
            keyStore.load(inputStream, mchId.toCharArray());
        } finally {
            inputStream.close();
        }
        SSLContext sslcontext = SSLContexts.custom()
                //这里也是写密码的
                .loadKeyMaterial(keyStore, mchId.toCharArray())
                .build();
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                sslcontext,
                SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        CloseableHttpClient httpclient = HttpClients.custom()
                .setSSLSocketFactory(sslsf)
                .build();
        try {
            HttpPost httpost = new HttpPost(url);
            httpost.setEntity(new StringEntity(data, "UTF-8"));
            CloseableHttpResponse response = httpclient.execute(httpost);
            try {
                HttpEntity entity = response.getEntity();
                //接受到返回信息
                String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                EntityUtils.consume(entity);
                return jsonStr;
            } finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }
    }

 

posted @ 2020-05-29 15:52  TIMEAWAY  阅读(1387)  评论(0编辑  收藏  举报