JAVA项目实战 -微信支付开发
微信支付丰富着人们日常生活,下面我们来依据微信提供的API文档尝试着学习编写微信支付工具类,避免重复造轮子。
1.JSAPI:
JSAPI支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付。应用场景有:
- ◆ 用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付
- ◆ 用户的好友在朋友圈、聊天窗口等分享商家页面连接,用户点击链接打开商家页面,完成支付
- ◆ 将商户页面转换成二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付
2.Native支付
Native支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。
3.H5支付
H5支付主要是在手机、ipad等移动设备中通过浏览器来唤起微信支付的支付产品。
4.小程序支付
小程序支付是专门被定义使用在小程序中的支付产品。目前在小程序中能且只能使用小程序支付的方式来唤起微信支付。
5.App支付
APP支付又称移动端支付,是商户通过在移动端应用APP中集成开放SDK调起微信支付模块完成支付的模式。
注:微信支付功能开发 :1.必须拥有一个微信公众号;2.具备开通微信支付功能;3登录微信商户平台(https://pay.weixin.qq.com/index.php/core/home/login),设置商户号,支付秘钥和下载证书(退款接口需要携带证书请求)。
===========================================================================
Utils工具类代码
================================================================
package com.sf.detectprocess.util.pay;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.sf.detectprocess.controller.param.*;
import com.sf.detectprocess.core.constant.SystemConstants;
import com.sf.detectprocess.core.constant.WXPayConstants;
import com.sf.detectprocess.core.exception.WeChatPayException;
import com.sf.detectprocess.entity.Order;
import com.sf.detectprocess.entity.OrderPay;
import com.sf.detectprocess.mapper.OrderMapper;
import com.sf.detectprocess.mapper.OrderPayMapper;
import com.sf.detectprocess.service.LimsService;
import com.sf.detectprocess.util.FuntionUtils;
import com.sf.detectprocess.util.R;
import com.sun.org.apache.regexp.internal.RE;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;
/**
* @description: 微信支付接口工具类
* @author: zhucj
* @date: 2019-09-25 10:37
*/
@Slf4j
@Component
public class WeChatPayUtils {
/**
* 公众号或小程序或app端 appid
*/
@Value("${wx.pay.appId}")
private String appId;
/**
* 公众号或小程序 key
*/
@Value("${wx.pay.key}")
private String key;
/**
* 商户号 (微信支付商户号)
*/
@Value("${wx.pay.mchNo}")
private String mchNo;
/**
* 通知地址
*/
@Value("${wx.pay.notifyUrl}")
private String notifyUrl;
/**
* 安全证书密码(默认为商户号)
*/
@Value("${wx.pay.lcePassWord}")
private String lcePassWord;
@ApiOperation(value = "请求微信统一支付接口" )
@ApiImplicitParams({
@ApiImplicitParam(name = "outTradeNo", required = true, value = "商户订单号(32个字符内)", dataType= "String"),
@ApiImplicitParam(name = "totalFee", required = true, value = "标价金额", dataType = "String"),
@ApiImplicitParam(name = "body", required = true, value = "商品描述", dataType = "String"),
@ApiImplicitParam(name = "tradeType", required = true, value = "交易类型 JSAPI:JSAPI支付(或小程序支付)," +
"NATIVE:Native支付, APP:app支付, MWEB:H5支付", dataType = "String"),
@ApiImplicitParam(name = "openId", required = false, value = "用户openId(JSAPI/小程序/必传)",dataType = "String"),
@ApiImplicitParam(name = "spbillCreateIp", required = false, value = "APP和网页支付提交用户端ip,Native支付不需要传)", dataType = "String"),
@ApiImplicitParam(name = "productId", required = false, value = "二维码中包含商品Id(Native支付必传,其他支付不传)", dataType = "String")
})
public R unifiedOrder(UnfiedOrderParam map) throws WeChatPayException {
/* 签名参数的map对象 */
SortedMap<String, String> packageParams = new TreeMap<String, String>();
// 请求预下单对象
UnifiedOrderRequest unifiedOrderRequest = new UnifiedOrderRequest();
//=>1.appId 商户Id
unifiedOrderRequest.setAppid(appId);
//TODO:判断当前微信支付类型 APP端/JSAPI/H5/Native/MWEB
if (Objects.equals(WXPayConstants.APP,map.getTradeType())){
//TODO:App端支付 必传用户终端IP 和单独的appId(应用Id)
if (isNull(map.getSpbillCreateIp())){
throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE, WXPayConstants.SPBILL_CREATE_APP_ERROR);
}
// => 2.终端Ip app支付由前端传
unifiedOrderRequest.setSpbill_create_ip(map.getSpbillCreateIp());
} else if (Objects.equals(WXPayConstants.JSAPI,map.getTradeType())) {
//TODO:JSAPI支付 必传openId和用户终端IP
if (isNull(map.getOpenId())) {
throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.OPENID_JSAPI_ERROR);
}
if (isNull(map.getSpbillCreateIp())){
throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.SPBILL_CREATE_JSAPI_ERROR);
}
unifiedOrderRequest.setOpenid(map.getOpenId());
// => 2.终端Ip 小程序或公众号支付由前端传
unifiedOrderRequest.setSpbill_create_ip(map.getSpbillCreateIp());
//仅 JSAPI支付才传openId
packageParams.put("openid", unifiedOrderRequest.getOpenid());
}else if (Objects.equals(WXPayConstants.NATIVE,map.getTradeType())){
//TODO:Native扫码支付 必传prductId(商品Id)
if (isNull(map.getProductId())){
throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.PRODUCT_NATIVE_ERROR);
}
unifiedOrderRequest.setProduct_id(map.getProductId());
packageParams.put("product_id", unifiedOrderRequest.getProduct_id());
//TODO:Native 本地获取终端Ip
String localIp = WXPayUtil.getLocalIp();
//=> 2.终端Ip
unifiedOrderRequest.setSpbill_create_ip(localIp);
}else if (Objects.equals(WXPayConstants.MWEB,map.getTradeType())){
//TODO:H5 前端必传Ip
if (isNull(map.getSpbillCreateIp())){
throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.SPBILL_CREATE_HWEB_ERROR);
}
// => 2.终端Ip H5支付由前端传
unifiedOrderRequest.setSpbill_create_ip(map.getSpbillCreateIp());
}else {
throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,"交易类型tradeType="+map.getTradeType()+"暂时不支持,请核对参数是否正确!");
}
// => 3.商品描述
unifiedOrderRequest.setBody(map.getBody());
// => 4.商户号
unifiedOrderRequest.setMchId(mchNo);
// 生成随机字符串 => 5.随机字符串
unifiedOrderRequest.setNonceStr(WXPayUtil.generateNonceStr());
// => 6.异步通知地址
unifiedOrderRequest.setNotify_url(notifyUrl);
// => 7.商户订单号
unifiedOrderRequest.setOut_trade_no(map.getOutTradeNo());
// => 8.支付金额 需要扩大100倍(1代表支付时是0.01)
unifiedOrderRequest.setTotal_fee(WXPayUtil.changeY2F(map.getTotalFee()));
// => 9.交易类型
unifiedOrderRequest.setTrade_type(map.getTradeType());
//TODO: 封装签名map
packageParams.put("appid", unifiedOrderRequest.getAppid());
packageParams.put("body", unifiedOrderRequest.getBody());
packageParams.put("mch_id", unifiedOrderRequest.getMchId());
packageParams.put("nonce_str", unifiedOrderRequest.getNonceStr());
packageParams.put("notify_url", unifiedOrderRequest.getNotify_url());
packageParams.put("out_trade_no", unifiedOrderRequest.getOut_trade_no());
packageParams.put("spbill_create_ip", unifiedOrderRequest.getSpbill_create_ip());
packageParams.put("total_fee", unifiedOrderRequest.getTotal_fee());
packageParams.put("trade_type", unifiedOrderRequest.getTrade_type());
try {
String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5);
// =>10.签名
unifiedOrderRequest.setSign(signature);
packageParams.put("sign",signature);
} catch (Exception e) {
log.error("签名异常:{}",e.getMessage());
throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR);
}
try {
//调统一下单接口
String s = WXPayUtil.doPost(1, WXPayUtil.mapToXml(packageParams), false, null);
UnifiedOrderResponse resposeStr = JSON.parseObject(s, UnifiedOrderResponse.class);
//预下单后 返回前端封装map
TreeMap respMap = new TreeMap();
if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){
if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){
//TODO: 依据交易类型返回前端参数
switch (resposeStr.getTrade_type()) {
case WXPayConstants.NATIVE: {
//Native扫码直接返回二维码链接
respMap.put("codeUrl", resposeStr.getCode_url());
break;
}
case WXPayConstants.APP : {
//App端 需要商户号进行签名 返回
respMap.put("appid", resposeStr.getAppid());
respMap.put("partnerid", resposeStr.getMch_id());
respMap.put("prepayid", resposeStr.getPrepay_id());
respMap.put("timeStamp",String.valueOf(WXPayUtil.getCurrentTimestamp()));
respMap.put("nonce_str", WXPayUtil.generateNonceStr());
respMap.put("package","Sign=WXPay");
respMap.put("sign", WXPayUtil.generateSignature(respMap,key,WXPayConstants.SignType.MD5));
break;
}
case WXPayConstants.JSAPI: {
respMap.put("appId", resposeStr.getAppid());
respMap.put("timeStamp",String.valueOf(WXPayUtil.getCurrentTimestamp()));
respMap.put("nonceStr",resposeStr.getNonce_str());
respMap.put("package","prepay_id="+resposeStr.getPrepay_id());
respMap.put("signType",WXPayConstants.MD5);
respMap.put("paySign",WXPayUtil.generateSignature(respMap,key,WXPayConstants.SignType.MD5));
respMap.put("prepayid",resposeStr.getPrepay_id());
break;
}
case WXPayConstants.MWEB : {
// h5支付链接地址
respMap.put("payUrl", resposeStr.getMweb_url());
break;
}
default:
break;
}
return R.ok(respMap);
}else {
return R.error(resposeStr.getErr_code_des());
}
}else {
return R.error(resposeStr.getReturn_msg());
}
} catch (Exception e) {
log.error("预订单异常:{}"+e.getMessage());
throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"预订单异常");
}
}
@ApiOperation(value = "请求微信订单查询接口" )
@ApiImplicitParam(name = "outTradeNo", required = true, value = "商户订单号(32个字符内)", dataType= "String")
public R orderQuery(String outTradeNo) throws WeChatPayException {
/* 签名map */
SortedMap<String, String> packageParams = new TreeMap<String, String>();
packageParams.put("appid",appId);
packageParams.put("mch_id",mchNo);
packageParams.put("out_trade_no",outTradeNo);
packageParams.put("nonce_str",WXPayUtil.generateNonceStr());
//获得签名
try {
String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5);
packageParams.put("sign",signature);
} catch (Exception e) {
log.error("签名异常:{}",e.getMessage());
throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR);
}
try {
//调微信查询订单服务接口
String s = WXPayUtil.doPost(2, WXPayUtil.mapToXml(packageParams), false, null);
OrderQueryResponse resposeStr = JSON.parseObject(s, OrderQueryResponse.class);
//查询返回结果封装map
Map respMap = new HashMap();
if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){
if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){
respMap.put("statusDesc",resposeStr.getTrade_state_desc());
respMap.put("data",resposeStr);
/**
* 自己设置的status => 1:已支付 ,2:转入退款 3:未支付 4:已关闭 5:已撤销 6:支付失败
* */
if (Objects.equals(WXPayConstants.TradeState.SUCCESS.getName(),resposeStr.getTrade_state())){
respMap.put("status",1);
}else if (Objects.equals(WXPayConstants.TradeState.REFUND.getName(),resposeStr.getTrade_state())){
respMap.put("status",2);
}else if (Objects.equals(WXPayConstants.TradeState.NOTPAY.getName(),resposeStr.getTrade_state())){
respMap.put("status",3);
}else if (Objects.equals(WXPayConstants.TradeState.CLOSED.getName(),resposeStr.getTrade_state())){
respMap.put("status",4);
}else if (Objects.equals(WXPayConstants.TradeState.REVOKED.getName(),resposeStr.getTrade_state())){
respMap.put("status",5);
}else if (Objects.equals(WXPayConstants.TradeState.PAYERROR.getName(),resposeStr.getTrade_state())){
respMap.put("status",6);
}
return R.ok(respMap,"查询成功");
}else {
return R.error(resposeStr.getErr_code_des());
}
}else {
return R.error(resposeStr.getReturn_msg());
}
} catch (Exception e) {
log.error("查询订单异常:{}"+e.getMessage());
throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"查询订单异常");
}
}
@ApiOperation(value = "请求微信关闭订单接口" )
@ApiImplicitParam(name = "outTradeNo", required = true, value = "商户订单号(32个字符内)", dataType = "String")
public R closeOrder(String outTradeNo) throws WeChatPayException {
/* 签名map */
SortedMap<String, String> packageParams = new TreeMap<String, String>();
packageParams.put("appid",appId);
packageParams.put("mch_id",mchNo);
packageParams.put("out_trade_no",outTradeNo);
packageParams.put("nonce_str",WXPayUtil.generateNonceStr());
//获得签名
try {
String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5);
packageParams.put("sign",signature);
} catch (Exception e) {
log.error("签名异常:{}",e.getMessage());
throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR);
}
try {
//请求微信关闭订单服务接口
String s = WXPayUtil.doPost(4, WXPayUtil.mapToXml(packageParams), false, null);
UnifiedOrderResponse resposeStr = JSON.parseObject(s,UnifiedOrderResponse.class);
if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){
if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){
return R.ok(null,"订单关闭成功");
}else {
return R.error(resposeStr.getErr_code_des());
}
}else {
return R.error(resposeStr.getReturn_msg());
}
} catch (Exception e) {
log.error("关闭订单异常:{}"+e.getMessage());
throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"关闭订单异常");
}
}
@ApiOperation(value = "请求微信退款订单接口" )
@ApiImplicitParams({
@ApiImplicitParam(name = "transactionId", required = true, value = "微信订单号", dataType = "String"),
@ApiImplicitParam(name = "outRefundNo", required = true, value = "商户退款单号(32个字符内 )", dataType = "String"),
@ApiImplicitParam(name = "totalFee", required = true, value = "订单金额", dataType = "String"),
@ApiImplicitParam(name = "refundFee", required = true, value = "退款金额", dataType = "String"),
@ApiImplicitParam(name = "refundDesc", required = false, value = "退款原因", dataType = "String")
})
public R refund(RefundParam map) throws WeChatPayException {
/* 签名map */
SortedMap<String, String> packageParams = new TreeMap<String, String>();
packageParams.put("appid",appId);
packageParams.put("mch_id",mchNo);
packageParams.put("nonce_str",WXPayUtil.generateNonceStr());
packageParams.put("out_refund_no",map.getOutRefundNo());
packageParams.put("total_fee",WXPayUtil.changeY2F(map.getTotalFee()));
packageParams.put("refund_fee",WXPayUtil.changeY2F(map.getRefundFee()));
packageParams.put("transaction_id",map.getTransactionId());
//获得签名
try {
String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5);
packageParams.put("sign",signature);
} catch (Exception e) {
log.error("签名异常:{}",e.getMessage());
throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR);
}
try {
//调统一请求退款服务接口
String s = WXPayUtil.doPost(3,WXPayUtil.mapToXml(packageParams), true,lcePassWord);
RefundResp resposeStr = JSON.parseObject(s, RefundResp.class);
if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){
if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){
/**
* ##订单已退款 此处更新数据库订单状态和退款信息 ###
*/
return R.ok(null,"订单退款成功");
}else {
return R.error(resposeStr.getErr_code_des());
}
}else {
return R.error(resposeStr.getReturn_msg());
}
} catch (Exception e) {
log.error("订单退款异常:{}"+