springboot集成微信支付

一、先去微信申请相应的appid等,然后在yml文件增加相应配置

pay:
  wxpay:
    appID: ******
    mchID: *****
    key: *****
    notifyUrl: *****
    appSecret: *****

建立配置类:

@ConfigurationProperties(prefix = "pay.wxpay")
public class MyWXPayConfig implements WXPayConfig {

	private String appSecret;
	
	/** 公众账号ID */
	private String appID;

	/** 商户号 */
	private String mchID;

	/** API 密钥 */
	private String key;

	/** API 沙箱环境密钥 */
	private String sandboxKey;

	/** API证书绝对路径 */
	private String certPath;

	/** 退款异步通知地址 */
	private String notifyUrl;

	private Boolean useSandbox;

	/** HTTP(S) 连接超时时间,单位毫秒 */
	private int httpConnectTimeoutMs = 80000;

	/** HTTP(S) 读数据超时时间,单位毫秒 */
	private int httpReadTimeoutMs = 100000;

	/**
	 * 获取商户证书内容
	 *
	 * @return 商户证书内容
	 */
	@Override
	public InputStream getCertStream() {
		File certFile = new File(certPath);
		InputStream inputStream = null;
		try {
			inputStream = new FileInputStream(certFile);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		return inputStream;
	}

	@Override
	public String getKey() {
		if (useSandbox) {
			return sandboxKey;
		}

		return key;
	}

	public String getAppSecret() {
		return appSecret;
	}

	public void setAppSecret(String appSecret) {
		this.appSecret = appSecret;
	}

	public String getAppID() {
		return appID;
	}

	public void setAppID(String appID) {
		this.appID = appID;
	}

	public String getMchID() {
		return mchID;
	}

	public void setMchID(String mchID) {
		this.mchID = mchID;
	}

	public String getSandboxKey() {
		return sandboxKey;
	}

	public void setSandboxKey(String sandboxKey) {
		this.sandboxKey = sandboxKey;
	}

	public String getCertPath() {
		return certPath;
	}

	public void setCertPath(String certPath) {
		this.certPath = certPath;
	}

	public String getNotifyUrl() {
		return notifyUrl;
	}

	public void setNotifyUrl(String notifyUrl) {
		this.notifyUrl = notifyUrl;
	}

	public Boolean getUseSandbox() {
		return useSandbox;
	}

	public void setUseSandbox(Boolean useSandbox) {
		this.useSandbox = useSandbox;
	}

	public int getHttpConnectTimeoutMs() {
		return httpConnectTimeoutMs;
	}

	public void setHttpConnectTimeoutMs(int httpConnectTimeoutMs) {
		this.httpConnectTimeoutMs = httpConnectTimeoutMs;
	}

	public int getHttpReadTimeoutMs() {
		return httpReadTimeoutMs;
	}

	public void setHttpReadTimeoutMs(int httpReadTimeoutMs) {
		this.httpReadTimeoutMs = httpReadTimeoutMs;
	}

	public void setKey(String key) {
		this.key = key;
	}

}

二、建立生成支付二维码的类:

public class PayUtil {

    /**
     * 根据url生成二位图片对象
     *
     * @param codeUrl
     * @return
     * @throws WriterException
     */
    public static BufferedImage getQRCodeImge(String codeUrl) throws WriterException {
        Map<EncodeHintType, Object> hints = new Hashtable();
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
        hints.put(EncodeHintType.CHARACTER_SET, "UTF8");
        int width = 256;
        BitMatrix bitMatrix = (new MultiFormatWriter()).encode(codeUrl, BarcodeFormat.QR_CODE, width, width, hints);
        BufferedImage image = new BufferedImage(width, width, 1);
        for(int x = 0; x < width; ++x) {
            for(int y = 0; y < width; ++y) {
                image.setRGB(x, y, bitMatrix.get(x, y) ? -16777216 : -1);
            }
        }

        return image;
    }
}

三、建立服务类

/**
 * 对WXPay的简单封装,处理支付密切相关的逻辑.
 * @author wangCJ
 *
 */
public class WXPayClient extends WXPay {

    /** 密钥算法 */
    private static final String ALGORITHM = "AES";
    /** 加解密算法/工作模式/填充方式 */
    private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS5Padding";
    /** 用户支付中,需要输入密码 */
    private static final String ERR_CODE_USERPAYING = "USERPAYING";
    private static final String ERR_CODE_AUTHCODEEXPIRE = "AUTHCODEEXPIRE";
    /** 交易状态: 未支付 */
    private static final String TRADE_STATE_NOTPAY = "NOTPAY";

    /** 用户输入密码,尝试30秒内去查询支付结果 */
    private static Integer remainingTimeMs = 10000;

    private WXPayConfig config;

    public WXPayClient(WXPayConfig config, WXPayConstants.SignType signType, boolean useSandbox) {
        super(config, signType, useSandbox);
        this.config = config;
    }

    /**
     *
     * 刷卡支付
     *
     * 对WXPay#microPay(Map)增加了当支付结果为USERPAYING时去轮询查询支付结果的逻辑处理
     *
     * 注意:该方法没有处理return_code=FAIL的情况,暂时不考虑网络问题,这种情况直接返回错误
     *
     * @param reqData
     * @return
     * @throws Exception
     */
    public Map<String, String> microPayWithPOS(Map<String, String> reqData) throws Exception {
        // 开始时间(毫秒)
        long startTimestampMs = System.currentTimeMillis();

        Map<String, String> responseMapForPay = super.microPay(reqData);
        // // 先判断 协议字段返回(return_code),再判断 业务返回,最后判断 交易状态(trade_state)
        // 通信标识,非交易标识
        String returnCode = responseMapForPay.get("return_code");
        if (WXPayConstants.SUCCESS.equals(returnCode)) {
            String errCode = responseMapForPay.get("err_code");
            // 余额不足,信用卡失效
            if (ERR_CODE_USERPAYING.equals(errCode) || "SYSTEMERROR".equals(errCode) || "BANKERROR".equals(errCode)) {
                Map<String, String> orderQueryMap = null;
                Map<String, String> requestData = new HashMap<>();
                requestData.put("out_trade_no", reqData.get("out_trade_no"));

                // 用户支付中,需要输入密码或系统错误则去重新查询订单API err_code, result_code, err_code_des
                // 每次循环时的当前系统时间 - 开始时记录的时间 > 设定的30秒时间就退出
                while (System.currentTimeMillis() - startTimestampMs < remainingTimeMs) {
                    // 商户收银台得到USERPAYING状态后,经过商户后台系统调用【查询订单API】查询实际支付结果。
                    orderQueryMap = super.orderQuery(requestData);
                    String returnCodeForQuery = orderQueryMap.get("return_code");
                    if (WXPayConstants.SUCCESS.equals(returnCodeForQuery)) {
                        // 通讯成功
                        String tradeState = orderQueryMap.get("trade_state");
                        if (WXPayConstants.SUCCESS.equals(tradeState)) {
                            // 如果成功了直接将查询结果返回
                            return orderQueryMap;
                        }
                        // 如果支付结果仍为USERPAYING,则每隔5秒循环调用【查询订单API】判断实际支付结果
                        Thread.sleep(1000);
                    }
                }
                // 如果用户取消支付或累计30秒用户都未支付,商户收银台退出查询流程后继续调用【撤销订单API】撤销支付交易。
                String tradeState = orderQueryMap.get("trade_state");
                if (TRADE_STATE_NOTPAY.equals(tradeState) || ERR_CODE_USERPAYING.equals(tradeState) || ERR_CODE_AUTHCODEEXPIRE.equals(tradeState)) {
                    Map<String, String> reverseMap = this.reverse(requestData);
                    String returnCodeForReverse = reverseMap.get("return_code");
                    String resultCode = reverseMap.get("result_code");
                    if (WXPayConstants.SUCCESS.equals(returnCodeForReverse) && WXPayConstants.SUCCESS.equals(resultCode)) {
                        // 如果撤销成功,需要告诉客户端已经撤销订单了
                        responseMapForPay.put("err_code_des", "用户取消支付或尚未支付,后台已经撤销该订单,请重新支付!");
                    }
                }
            }
        }
        return responseMapForPay;
    }

    /**
     * 从request的inputStream中获取参数
     * @param request
     * @return
     * @throws Exception
     */
    public Map<String, String> getNotifyParameter(HttpServletRequest request) throws Exception {
        InputStream inputStream = request.getInputStream();
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int length = 0;
        while ((length = inputStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, length);
        }
        outSteam.close();
        inputStream.close();

        // 获取微信调用我们notify_url的返回信息
        String resultXml = new String(outSteam.toByteArray(), "utf-8");
        Map<String, String> notifyMap = WXPayUtil.xmlToMap(resultXml);

        return notifyMap;
    }

    /**
     * 解密退款通知
     *
     * <a href="https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_16&index=11>退款结果通知文档</a>
     * @return
     * @throws Exception
     */
    public Map<String, String> decodeRefundNotify(HttpServletRequest request) throws Exception {
        // 从request的流中获取参数
        Map<String, String> notifyMap = this.getNotifyParameter(request);
        String reqInfo = notifyMap.get("req_info");
        //(1)对加密串A做base64解码,得到加密串B
        byte[] bytes = new BASE64Decoder().decodeBuffer(reqInfo);
        //(2)对商户key做md5,得到32位小写key* ( key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 )
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
        SecretKeySpec key = new SecretKeySpec(WXPayUtil.MD5(config.getKey()).toLowerCase().getBytes(), ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, key);

        //(3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding)
        // java.security.InvalidKeyException: Illegal key size or default parameters
        // https://www.cnblogs.com/yaks/p/5608358.html
        String responseXml = new String(cipher.doFinal(bytes),"UTF-8");
        Map<String, String> responseMap = WXPayUtil.xmlToMap(responseXml);
        return responseMap;
    }

    /**
     * 获取沙箱环境验签秘钥API
     * <a href="https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=23_1">获取验签秘钥API文档</a>
     * @return
     * @throws Exception
     */
    public Map<String, String> getSignKey() throws Exception {
        Map<String, String> reqData = new HashMap<>();
        reqData.put("mch_id", config.getMchID());
        reqData.put("nonce_str", WXPayUtil.generateNonceStr());
        String sign = WXPayUtil.generateSignature(reqData, config.getKey(), WXPayConstants.SignType.MD5);
        reqData.put("sign", sign);
        String responseXml = this.requestWithoutCert("https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey", reqData,
                config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs());

        Map<String, String> responseMap = WXPayUtil.xmlToMap(responseXml);

        return responseMap;
    }


}

四、建立controller

@RestController
@RequestMapping("/web/wxpay")
public class WXPayPrecreateController {
	@Autowired
	private WXPay wxPay;

	@Autowired
	private WXPayClient wxPayClient;

	@Autowired
	private MyWXPayConfig myWXPayConfig;

	@Autowired
	private OrderService orderService;

	@Autowired
	private PayWebSocket payWebSocket;

	/**
	 * 扫码支付 - 统一下单
	 *
	 * <a href="https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1">
	 * 扫码支付API</a>
	 */
	// @PostMapping("/order")
	@ApiOperation(value = "createCode", notes = "返回支付二维码")
	@GetMapping("/common/createCode")
	public void precreate(Order order, HttpServletResponse response)
			throws Exception {
		order = orderService.selectListSelective(order).get(0);
		Map<String, String> reqData = new HashMap<>();
		reqData.put("appid", myWXPayConfig.getAppID());
		reqData.put("mch_id", myWXPayConfig.getMchID());
		// 订单号
		reqData.put("out_trade_no", order.getOrderCode());
		reqData.put("trade_type", "NATIVE");
		reqData.put("product_id", order.getFilmId() + "");
		reqData.put("body", "商户下单");
		// 交易结束时间
		// SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
		// reqData.put("time_expire", dateFormat.format(new
		// Date().getTime()+20*60*1000L));
		// 订单总金额,单位为分
		reqData.put("total_fee", order.getOrderPrice().longValue() + "");
		// APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
		reqData.put("spbill_create_ip", "129.226.53.55");
		// 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
		reqData.put("notify_url", myWXPayConfig.getNotifyUrl());
		// 自定义参数, 可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB"
		reqData.put("device_info", "WEB");
		// 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
		reqData.put("attach", "");
		// sign签名
		reqData.put("sign", myWXPayConfig.getKey());
		// 随机字符串
		reqData.put("nonce_str", RandomStringUtils.randomAlphanumeric(10));
		/**
		 * { code_url=weixin://wxpay/bizpayurl?pr=vvz4xwC, trade_type=NATIVE,
		 * return_msg=OK, result_code=SUCCESS, return_code=SUCCESS,
		 * prepay_id=wx18111952823301d9fa53ab8e1414642725 }
		 */
		Map<String, String> responseMap = wxPay.unifiedOrder(reqData);
		System.out.println(responseMap);
		String returnCode = responseMap.get("return_code");
		String resultCode = responseMap.get("result_code");
		if (WXPayConstants.SUCCESS.equals(returnCode)
				&& WXPayConstants.SUCCESS.equals(resultCode)) {
			String prepayId = responseMap.get("prepay_id");
			String codeUrl = responseMap.get("code_url");
			BufferedImage image = PayUtil.getQRCodeImge(codeUrl);
			response.setContentType("image/jpeg");
			response.setHeader("Pragma", "no-cache");
			response.setHeader("Cache-Control", "no-cache");
			// response.setIntHeader("Expires",-1);
			ImageIO.write(image, "JPEG", response.getOutputStream());
		}

	}

	/**
	 *
	 * @param request
	 * @return
	 * @throws Exception
	 */
	@RequestMapping("/common/notify")
	public void precreateNotify(HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		Map<String, String> reqData = wxPayClient.getNotifyParameter(request);

		/**
		 * { transaction_id=4200000138201806180751222945,
		 * nonce_str=aaaf3fe4d3aa44d8b245bc6c97bda7a8, bank_type=CFT,
		 * openid=xxx, sign=821A5F42F5E180ED9EF3743499FBCF13, fee_type=CNY,
		 * mch_id=xxx, cash_fee=1, out_trade_no=186873223426017, appid=xxx,
		 * total_fee=1, trade_type=NATIVE, result_code=SUCCESS,
		 * time_end=20180618131247, is_subscribe=N, return_code=SUCCESS }
		 */
		// 特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。
		boolean signatureValid = wxPay.isPayResultNotifySignatureValid(reqData);
		if (signatureValid) {
			/**
			 * 注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
			 * 推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,
			 * 判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。
			 * 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
			 */
			String orderCode = reqData.get("out_trade_no");
			Order order = new Order();
			order.setOrderCode(orderCode);
			order = orderService.selectListSelective(order).get(0);
			if(order.getOrderState()==1){
				if ("SUCCESS".equals(reqData.get("result_code"))) {
					order.setOrderState(3);
				} else {
					order.setOrderState(2);
				}
				orderService.updateByPrimaryKeySelective(order);
				// 通知浏览器结果
				payWebSocket.sendMessageTo(JSON.toJSONString(order), orderCode);
			}
		}
	}
}

其中precreate方法就是创建支付二维码的方法,precreateNotify方法为支付过后的回调地址

  • 6
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值