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();
        }
    }

 


免責聲明!

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



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