微信流程图

项目结构

前期准备

复制证书

将证书中名为apiclient_key.pem的文件复制到你的项目中

依赖

<!-- 微信支付SDK-->
<dependency><groupId>com.github.wechatpay-apiv3</groupId><artifactId>wechatpay-apache-httpclient</artifactId><version>0.3.0</version>
</dependency>

properties文件(yaml也可以自己换一下)

# 微信支付相关参数
# 下面两个用来标识用户
# 商户号
wxpay.mch-id= ******************
# APPID
wxpay.appid= ******************   # 接下来两个用来确保SSL(内容未作任何加密,只做了签名.)
# 商户API证书序列号
wxpay.mch-serial-no= ******************# 商户私钥文件(第一步)
wxpay.private-key-path= apiclient_key.pem
# APIv3密钥(在微信支付回调通知和商户获取平台证书使用APIv3密钥)
wxpay.api-v3-key= ******************# 接下来两个是相关地址
# 微信服务器地址
wxpay.domain= https://api.mch.weixin.qq.com
# 接收结果通知地址
# 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置
wxpay.notify-domain= 填自己的回调地址

写配置文件WxPayConfig(建议新建一个config文件夹存放配置文件)

package com.leng.paymentdemo.config;import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.ScheduledUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;/*** 微信支付信息配置* @author Admin*/
@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix="wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {// 商户号private String mchId;// 商户API证书序列号private String mchSerialNo;// 商户私钥文件private String privateKeyPath;// APIv3密钥private String apiV3Key;// APPIDprivate String appid;// 微信服务器地址private String domain;// 接收结果通知地址private String notifyDomain;//商户APIv2 keyprivate String partnerKey;/*** 获取商户的私钥文件* @param filename* @return*/public PrivateKey getPrivateKey(String filename){try {return PemUtil.loadPrivateKey(new FileInputStream(filename));} catch (FileNotFoundException e) {throw new RuntimeException("私钥文件不存在",e);}}/*** 获取签名验证器* @return*/@Beanpublic ScheduledUpdateCertificatesVerifier getVerifier(){log.info("获取签名验证器");//获取商户私钥PrivateKey privateKey = getPrivateKey(privateKeyPath);//私钥签名对象PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);//身份认证对象WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);// 使用定时更新的签名验证器,不需要传入证书ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(wechatPay2Credentials,apiV3Key.getBytes(StandardCharsets.UTF_8));return verifier;}/*** 获取http请求对象* @param verifier* @return*/@Bean(name = "wxPayClient")public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier){log.info("获取httpClient");//获取商户私钥PrivateKey privateKey = getPrivateKey(privateKeyPath);WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, privateKey).withValidator(new WechatPay2Validator(verifier));// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新CloseableHttpClient httpClient = builder.build();return httpClient;}}

自定义统一返回结果(建议创建一个response存放)

CustomizeResultCode

package com.leng.paymentdemo.response;public interface CustomizeResultCode {/*** 获取错误代码* @return 错误状态码*/Integer getCode();/*** 获取错误信息* @return 错误信息*/String getMessage();
}

Result

package com.leng.paymentdemo.response;import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;import java.util.HashMap;
import java.util.Map;/*** 自定义的统一返回结果类*/
@Data
@Accessors(chain = true) //允许当前类进行链式操作
public class Result {@ApiModelProperty(value = "是否成功")private Boolean success;@ApiModelProperty(value = "返回码")private Integer code;@ApiModelProperty(value = "返回消息")private String message;@ApiModelProperty(value = "返回数据")private Map<String, Object> data = new HashMap<>();/*** 构造方法私有化,里面的方法都是静态方法* 达到保护属性的作用*/private Result(){}/*** 这里是使用链式编程* @return 返回访问成功后的结果集*/public static Result ok(){Result result=new Result();result.setSuccess(true);result.setCode(ResultCode.SUCCESS.getCode());result.setMessage(ResultCode.SUCCESS.getMessage());return result;}/*** 失败* @return*/public static Result error(){Result result=new Result();result.setSuccess(false);result.setCode(ResultCode.COMMON_FAIL.getCode());result.setMessage(ResultCode.COMMON_FAIL.getMessage());return result;}/*** 根基错误类型返回* @param resultCoe* @return*/public static Result error(ResultCode resultCoe){Result result=new Result();result.setSuccess(false);result.setCode(resultCoe.getCode());result.setMessage(resultCoe.getMessage());return result;}public Result success(Boolean success){this.setSuccess(success);return this;}public Result message(String message){this.setMessage(message);return this;}public Result code(Integer code){this.setCode(code);return this;}public Result data(String key,Object value){this.data.put(key,value);return this;}
}

ResultCode

package com.leng.paymentdemo.response;
/*** @Author:* @Description: 返回码定义* 规定:* #200表示成功* #1001~1999 区间表示参数错误* #2001~2999 区间表示用户错误* #3001~3999 区间表示接口异常* #后面对什么的操作自己在这里注明就行了*/
public enum ResultCode implements CustomizeResultCode {/* 成功 */SUCCESS(200,"成功"),/* 默认失败 */COMMON_FAIL(999,"失败"),/* 参数错误:1000~1999 */PARAM_NOT_VALID(1001,"参数无效"),PARAM_IS_BLANK(1002,"参数无效"),PARAM_TYPE_ERROR(1003,"参数类型错误"),PARAM_NOT_COMPLETE(1004,"参数缺失"),/* 用户错误 */USER_NOT_LOGIN(2001,"用户未登录"),USER_ACCOUNT_EXPIRED(2002,"账户已过期"),USER_CREDENTIALS_ERROR(2003,"密码错误"),USER_CREDENTIALS_EXPIRED(2004,"密码过期"),USER_ACCOUNT_DISABLE(2005,"账户不可用"),USER_ACCOUNT_LOCKED(2006,"账户被锁定"),USER_ACCOUNT_NOT_EXIST(2007,"账户不存在"),USER_ACCOUNT_ALREADY_EXIST(2008,"账户已存在"),USER_ACCOUNT_USE_BY_OTHERS(2009,"账户下线"),/* 部门错误 */DEPARTMENT_NOT_EXIST(3007,"部门不存在"),DEPARTMENT_ALREADY_EXIST(3008,"部门已存在"),/* 业务错误 */NO_PERMISSION(3001,"没有权限"),BUCKET_IS_EXISTS(3002,"bucket已经存在了"),/* 算数异常 */ARITHMETIC_EXCEPTION(9001,"算数异常"),NULL_POINTER_EXCEPTION(9002,"空指针异常");private Integer code;private String message;ResultCode(Integer code,String message){this.code=code;this.message=message;}@Overridepublic Integer getCode() {return code;}@Overridepublic String getMessage() {return message;}
}

定义一个全局异常处理器(exception)

BusinessException

package com.leng.paymentdemo.exception;import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class BusinessException extends RuntimeException{@ApiModelProperty(value = "状态码")private Integer code;@ApiModelProperty(value = "错误信息")private String errMsg;}

GlobalExceptionHandler

package com.leng.paymentdemo.exception;import com.leng.paymentdemo.response.Result;
import com.leng.paymentdemo.response.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.util.stream.Collectors;/***@作者: 冷俊杰*@类名: GlobalExceptionHandler类*@创建时间: 2022/8/22 14:21*@描述:*全局异常处理器,它只会处理controller层的异常*@修改原图*@修改作者*@修改时间*/
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {@ResponseBody@ExceptionHandler(value = ArithmeticException.class)public Result exception(ArithmeticException e) {System.out.println("出现了异常");return Result.error(ResultCode.ARITHMETIC_EXCEPTION);}@ResponseBody@ExceptionHandler(value = NullPointerException.class)public Result exception(NullPointerException e) {System.out.println("出现了异常");return Result.error(ResultCode.NULL_POINTER_EXCEPTION);}/*    @ResponseBody@ExceptionHandler(value = BusinessException.class)public Result exception(BusinessException e) {System.out.println("出现了异常");return Result.error().code(e.getCode()).message(e.getErrMsg());}*/@ResponseBody@ExceptionHandler(value = Exception.class)public Result exception(Exception e) {e.printStackTrace();log.error("出现了异常"+e.getMessage()+"-----------");return Result.error();}//处理Get请求中 使用@Valid 验证路径中请求实体校验失败后抛出的异常@ExceptionHandler(BindException.class)@ResponseBodypublic Result BindExceptionHandler(BindException e) {String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());return Result.error().message(message);}//处理请求参数格式错误 @RequestParam上validate失败后抛出的异常是javax.validation.ConstraintViolationException
/*    @ExceptionHandler(ConstraintViolationException.class)@ResponseBodypublic ResponseBean ConstraintViolationExceptionHandler(ConstraintViolationException e) {String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining());return ResponseBean.error(message);}*///处理请求参数格式错误 @RequestBody上validate失败后抛出的异常是MethodArgumentNotValidException异常。@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseBodypublic Result MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());return Result.error().message(message);}/*** 处理servlet异常* @param req* @param e* @return*/@ExceptionHandler(value = ServletException.class)@ResponseBodypublic  Result servletExceptionHandler(HttpServletRequest req, ServletException e){log.error("web服务器异常 {}",e.getMessage());return Result.error().message(e.getMessage());}/*** 处理自定义的业务异常* @param req* @param e* @return*/@ExceptionHandler(value = BusinessException.class)@ResponseBodypublic  Result bizExceptionHandler(HttpServletRequest req, BusinessException e){log.error("业务异常=>{}",e.getMessage());return Result.error().code(e.getCode()).message(e.getErrMsg());}/*** shiro的异常* @param e* @return*/
/*    @ExceptionHandler(ShiroException.class)public ResponseBean handle401(ShiroException e) {log.error("shiro异常=>{}",e.getMessage());return new ResponseBean(401, e.getMessage(), null);}*//*** 获取状态码* @param request* @return*/private HttpStatus getStatus(HttpServletRequest request) {Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");if (statusCode == null) {return HttpStatus.INTERNAL_SERVER_ERROR;}return HttpStatus.valueOf(statusCode);}
}

返回状态码(自定义)

OrderStatus类

package com.leng.paymentdemo.enums;import lombok.AllArgsConstructor;
import lombok.Getter;@AllArgsConstructor
@Getter
public enum OrderStatus {/*** 未支付*/NOTPAY("未支付"),/*** 支付成功*/SUCCESS("支付成功"),/*** 已关闭*/CLOSED("超时已关闭"),/*** 已取消*/CANCEL("用户已取消"),/*** 退款中*/REFUND_PROCESSING("退款中"),/*** 已退款*/REFUND_SUCCESS("已退款"),/*** 退款异常*/REFUND_ABNORMAL("退款异常");/*** 类型*/private final String type;
}

PayType

package com.leng.paymentdemo.enums;import lombok.AllArgsConstructor;
import lombok.Getter;@AllArgsConstructor
@Getter
public enum PayType {/*** 微信*/WXPAY("微信"),/*** 支付宝*/ALIPAY("支付宝");/*** 类型*/private final String type;
}

WxApiType

package com.leng.paymentdemo.enums.wxpay;import lombok.AllArgsConstructor;
import lombok.Getter;@AllArgsConstructor
@Getter
public enum WxApiType {/*** Native下单*/NATIVE_PAY("/v3/pay/transactions/native"),/*** Native下单*/NATIVE_PAY_V2("/pay/unifiedorder"),/*** 查询订单*/ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s"),/*** 关闭订单*/CLOSE_ORDER_BY_NO("/v3/pay/transactions/out-trade-no/%s/close"),/*** 申请退款*/DOMESTIC_REFUNDS("/v3/refund/domestic/refunds"),/*** 查询单笔退款*/DOMESTIC_REFUNDS_QUERY("/v3/refund/domestic/refunds/%s"),/*** 申请交易账单*/TRADE_BILLS("/v3/bill/tradebill"),/*** 申请资金账单*/FUND_FLOW_BILLS("/v3/bill/fundflowbill");/*** 类型*/private final String type;
}

WxNotifyType

package com.leng.paymentdemo.enums.wxpay;import lombok.AllArgsConstructor;
import lombok.Getter;@AllArgsConstructor
@Getter
public enum WxNotifyType {/*** 支付通知*/NATIVE_NOTIFY("/api/wx-pay/native/notify"),/*** 支付通知*/NATIVE_NOTIFY_V2("/api/wx-pay-v2/native/notify"),/*** 退款结果通知*/REFUND_NOTIFY("/api/wx-pay/refunds/notify");/*** 类型*/private final String type;
}

WxRefundStatus

package com.leng.paymentdemo.enums.wxpay;import lombok.AllArgsConstructor;
import lombok.Getter;@AllArgsConstructor
@Getter
public enum WxRefundStatus {/*** 退款成功*/SUCCESS("SUCCESS"),/*** 退款关闭*/CLOSED("CLOSED"),/*** 退款处理中*/PROCESSING("PROCESSING"),/*** 退款异常*/ABNORMAL("ABNORMAL");/*** 类型*/private final String type;
}

WxTradeState

package com.leng.paymentdemo.enums.wxpay;import lombok.AllArgsConstructor;
import lombok.Getter;@AllArgsConstructor
@Getter
public enum WxTradeState {/*** 支付成功*/SUCCESS("SUCCESS"),/*** 未支付*/NOTPAY("NOTPAY"),/*** 已关闭*/CLOSED("CLOSED"),/*** 转入退款*/REFUND("REFUND");/*** 类型*/private final String type;
}

utils工具类

HttpClientUtils

package com.leng.paymentdemo.util;import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;/*** http请求客户端*/
public class HttpClientUtils {private String url;private Map<String, String> param;private int statusCode;private String content;private String xmlParam;private boolean isHttps;public boolean isHttps() {return isHttps;}public void setHttps(boolean isHttps) {this.isHttps = isHttps;}public String getXmlParam() {return xmlParam;}public void setXmlParam(String xmlParam) {this.xmlParam = xmlParam;}public HttpClientUtils(String url, Map<String, String> param) {this.url = url;this.param = param;}public HttpClientUtils(String url) {this.url = url;}public void setParameter(Map<String, String> map) {param = map;}public void addParameter(String key, String value) {if (param == null)param = new HashMap<String, String>();param.put(key, value);}public void post() throws ClientProtocolException, IOException {HttpPost http = new HttpPost(url);setEntity(http);execute(http);}public void put() throws ClientProtocolException, IOException {HttpPut http = new HttpPut(url);setEntity(http);execute(http);}public void get() throws ClientProtocolException, IOException {if (param != null) {StringBuilder url = new StringBuilder(this.url);boolean isFirst = true;for (String key : param.keySet()) {if (isFirst) {url.append("?");isFirst = false;}else {url.append("&");}url.append(key).append("=").append(param.get(key));}this.url = url.toString();}HttpGet http = new HttpGet(url);execute(http);}/*** set http post,put param*/private void setEntity(HttpEntityEnclosingRequestBase http) {if (param != null) {List<NameValuePair> nvps = new LinkedList<NameValuePair>();for (String key : param.keySet())nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数}if (xmlParam != null) {http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));}}private void execute(HttpUriRequest http) throws ClientProtocolException,IOException {CloseableHttpClient httpClient = null;try {if (isHttps) {SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {// 信任所有public boolean isTrusted(X509Certificate[] chain,String authType)throws CertificateException {return true;}}).build();SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();} else {httpClient = HttpClients.createDefault();}CloseableHttpResponse response = httpClient.execute(http);try {if (response != null) {if (response.getStatusLine() != null)statusCode = response.getStatusLine().getStatusCode();HttpEntity entity = response.getEntity();// 响应内容content = EntityUtils.toString(entity, Consts.UTF_8);}} finally {response.close();}} catch (Exception e) {e.printStackTrace();} finally {httpClient.close();}}public int getStatusCode() {return statusCode;}public String getContent() throws ParseException, IOException {return content;}}

HttpUtils

package com.leng.paymentdemo.util;import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;public class HttpUtils {/*** 将通知参数转化为字符串* @param request* @return*/public static String readData(HttpServletRequest request) {BufferedReader br = null;try {StringBuilder result = new StringBuilder();br = request.getReader();for (String line; (line = br.readLine()) != null; ) {if (result.length() > 0) {result.append("\n");}result.append(line);}return result.toString();} catch (IOException e) {throw new RuntimeException(e);} finally {if (br != null) {try {br.close();} catch (IOException e) {e.printStackTrace();}}}}
}

OrderNoUtils

package com.leng.paymentdemo.util;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;/*** 订单号工具类** @author qy* @since 1.0*/
public class OrderNoUtils {/*** 获取订单编号* @return*/public static String getOrderNo() {return "ORDER_" + getNo();}/*** 获取退款单编号* @return*/public static String getRefundNo() {return "REFUND_" + getNo();}/*** 获取编号* @return*/public static String getNo() {SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");String newDate = sdf.format(new Date());String result = "";Random random = new Random();for (int i = 0; i < 3; i++) {result += random.nextInt(10);}return newDate + result;}}

使用

创建支付

官方接口文档可以参考微信支付-开发者文档 (qq.com)

新建一个WxPayController类,用来实现对下单,退款等操作

Native下单方法

/*** Native下单* @param productId* @return* @throws Exception*/
@ApiOperation("调用统一下单API,生成支付二维码")
@PostMapping("native/{productId}")
public Result nativePay(@PathVariable Long productId) throws Exception {log.info("发起支付请求 v3.....................");//通过map接受返回的支付二维码链接和订单号Map<String, Object> map = wxPayService.nativePay(productId);return Result.ok().setData(map);
}

service层

/*** 创建订单,调用Native支付接口*/
Map<String, Object> nativePay(Long productId) throws Exception;

service实现层

/*** 创建订单,调用Native支付接口*/
@Transactional(rollbackFor = Exception.class)
@Override
public Map<String, Object> nativePay(Long productId) throws Exception {log.info("生成订单");//生成订单并存入数据库OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId);String code_url = orderInfo.getCodeUrl();if (orderInfo != null && !StringUtils.isEmpty(code_url)) {log.info("订单已存在,二维码以保存");//创建返回值HashMap<String, Object> map = new HashMap<>();map.put("codeUrl", code_url);map.put("orderNo", orderInfo.getOrderNo());return map;}log.info("调用统一下单API");//调用统一下单APIHttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));// 请求body参数Gson gson = new Gson();Map paramsMap = new HashMap<>();paramsMap.put("appid", wxPayConfig.getAppid());//应用IDparamsMap.put("mchid", wxPayConfig.getMchId());//直连商户号paramsMap.put("description", orderInfo.getTitle());//商品描述paramsMap.put("out_trade_no", orderInfo.getOrderNo());//商户订单号paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));//通知地址HashMap<String, Object> amountMap = new HashMap<>();amountMap.put("total", orderInfo.getTotalFee());amountMap.put("currency", "CNY");paramsMap.put("amount", amountMap);//将参数转换成字符串形式String jsonParams = gson.toJson(paramsMap);log.info("请求参数" + jsonParams);StringEntity entity = new StringEntity(jsonParams,"utf-8");entity.setContentType("application/json");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();//处理成功if (statusCode == 200) {log.info("成功,返回结果y = " + bodyAsString);//处理成功,无返回Body} else if (statusCode == 204) {log.info("成功");} else {log.error("Native下单失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);throw new IOException("request failed");}//响应结果Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);//二维码code_url = resultMap.get("code_url");//保存二维码String orderNo = orderInfo.getOrderNo();orderInfoService.saveCodeUrl(orderNo, code_url);//创建返回值:二维码Map<String, Object> map = new HashMap<>();map.put("codeUrl", code_url);map.put("orderNo", orderInfo.getOrderNo());return map;} finally {//释放资源response.close();}
}

生成订单并存入数据库

servcie层

/*** 生成订单并存入数据库*/
OrderInfo createOrderByProductId(Long productId);

service实现层

/*** 生成订单并存入数据库*/
@Override
public OrderInfo createOrderByProductId(Long productId) {//查询已存在但是未支付的订单OrderInfo orderInfo = this.getNoPayOrderByProductId(productId);if (orderInfo != null) {return orderInfo;}//获取商品信息Product product = productMapper.selectById(productId);//生成订单orderInfo = new OrderInfo();orderInfo.setTitle(product.getTitle());//订单标题orderInfo.setOrderNo(OrderNoUtils.getOrderNo());//订单号orderInfo.setProductId(productId);//支付产品idorderInfo.setTotalFee(product.getPrice());//商品金额,单位是分orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType());//订单状态baseMapper.insert(orderInfo);return orderInfo;
}

存储订单二维码

servcie层

/*** 存储订单二维码*/
void saveCodeUrl(String orderNo, String codeUrl);

servcie实现层

/*** 存储订单二维码*/
@Override
public void saveCodeUrl(String orderNo, String codeUrl) {QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<OrderInfo>();queryWrapper.eq("order_no", orderNo);OrderInfo orderInfo = new OrderInfo();orderInfo.setCodeUrl(codeUrl);baseMapper.update(orderInfo, queryWrapper);
}

支付通知

官方接口文档:微信支付-开发者文档 (qq.com)

支付通知方法

/*** 支付通知* 微信支付通过支付通知接口将用户支付成功消息通知给商户*/
@PostMapping("/native/notify")
public String nativeNotify(HttpServletRequest request, HttpServletResponse response) {Gson gson = new Gson();//应答对象HashMap<String, String> map = new HashMap<>();try {//处理通知参数String body = HttpUtils.readData(request);log.info("支付通知的参数 ===> {}", body);Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);String requestId = (String) bodyMap.get("id");log.info("支付通知的id ===> {}", requestId);//签名的验证WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest = new WechatPay2ValidatorForRequest(verifier, requestId, body);if (!wechatPay2ValidatorForRequest.validate(request)) {log.error("验签失败");//失败应答response.setStatus(500);map.put("code", "ERROR");map.put("message", "验签失败");return gson.toJson(map);}log.info("验签成功");//处理订单wxPayService.processOrder(bodyMap);//应答超时//模拟接收微信端的重复通知//TimeUnit.SECONDS.sleep(5);//成功应答response.setStatus(200);map.put("code", "SUCCESS");map.put("message", "成功");return gson.toJson(map);} catch (Exception e) {e.printStackTrace();//失败应答response.setStatus(500);map.put("code", "ERROR");map.put("message", "失败");return gson.toJson(map);}
}

处理订单

service层

/*** 处理订单*/
void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException;

service实现层

/*** 处理订单*/
@Transactional(rollbackFor = Exception.class)
@Override
public void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException {log.info("处理订单");//解密报文String plainText = decryptFromResource(bodyMap);//将明文转换成mapGson gson = new Gson();HashMap hashMap = gson.fromJson(plainText, HashMap.class);String orderNo = (String) hashMap.get("out_trade_no");/*在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱*///尝试获取锁:// 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放if (lock.tryLock()) {try {//处理重复通知//保证接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的String orderStatus = orderInfoService.getOrderStatus(orderNo);if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {return;}//更新订单状态orderInfoService.updateStatusByorderNo(orderNo, OrderStatus.SUCCESS);//记录支付日志paymentInfoService.createPaymentInfo(plainText);} finally {//要主动释放锁lock.unlock();}}
}

查询订单

官方接口文档:微信支付-开发者文档 (qq.com)

/*** 查询订单** @param orderNo* @return* @throws URISyntaxException* @throws IOException*/
@ApiOperation("查询订单:测试订单状态用")
@GetMapping("query/{orderNo}")
public Result queryOrder(@PathVariable String orderNo) throws Exception {log.info("查询订单");String result = wxPayService.queryOrder(orderNo);return Result.ok().setMessage("查询成功").data("result", result);
}

service层

/*** 查单接口调用*/
String queryOrder(String orderNo) throws Exception;

service实现层

/*** 根据订单号查询微信支付查单接口,核实订单状态* 如果订单已支付,则更新商户端订单状态,并记录支付日志* 如果订单未支付,则调用关单接口关闭订单,并更新商户端订单状态** @param orderNo*/
@Transactional(rollbackFor = Exception.class)
@Override
public void checkOrderStatus(String orderNo) throws Exception {log.warn("根据订单号核实订单状态 ===> {}", orderNo);//调用微信支付查单接口String result = this.queryOrder(orderNo);Gson gson = new Gson();Map resultMap = gson.fromJson(result, HashMap.class);//获取微信支付端的订单状态Object tradeState = resultMap.get("trade_state");//判断订单状态if(WxTradeState.SUCCESS.getType().equals(tradeState)){log.warn("核实订单已支付 ===> {}", orderNo);//如果确认订单已支付则更新本地订单状态orderInfoService.updateStatusByorderNo(orderNo, OrderStatus.SUCCESS);//记录支付日志paymentInfoService.createPaymentInfo(result);}if(WxTradeState.NOTPAY.getType().equals(tradeState)){log.warn("核实订单未支付 ===> {}", orderNo);//如果订单未支付,则调用关单接口this.closeOrder(orderNo);//更新本地订单状态orderInfoService.updateStatusByorderNo(orderNo, OrderStatus.CLOSED);}
}

关单接口

/*** 关单接口的调用** @param orderNo*/
private void closeOrder(String orderNo) throws Exception {log.info("关单接口的调用,订单号 ===> {}", orderNo);//创建远程请求对象String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);url = wxPayConfig.getDomain().concat(url);HttpPost httpPost = new HttpPost(url);//组装json请求体Gson gson = new Gson();Map<String, String> paramsMap = new HashMap<>();paramsMap.put("mchid", wxPayConfig.getMchId());String jsonParams = gson.toJson(paramsMap);log.info("请求参数 ===> {}", jsonParams);//将请求参数设置到请求对象中StringEntity entity = new StringEntity(jsonParams, "utf-8");entity.setContentType("application/json");httpPost.setEntity(entity);httpPost.setHeader("Accept", "application/json");//完成签名并执行请求CloseableHttpResponse response = wxPayClient.execute(httpPost);try {int statusCode = response.getStatusLine().getStatusCode();//响应状态码if (statusCode == 200) { //处理成功log.info("成功200");} else if (statusCode == 204) { //处理成功,无返回Bodylog.info("成功204");} else {log.info("Native下单失败,响应码 = " + statusCode);throw new IOException("request failed");}} finally {response.close();}
}

申请退款

/*** 申请退款*/@ApiOperation("申请退款")@PostMapping("/refunds/{orderNo}/{reason}")public Result refunds(@PathVariable String orderNo, @PathVariable String reason)throws Exception {log.info("申请退款");wxPayService.refund(orderNo, reason);return Result.ok();}

service层

/*** 退款*/
void refund(String orderNo, String reason) throws Exception;

service实现层

 * 退款** @param orderNo* @param reason*/
@Transactional(rollbackFor = Exception.class)
@Override
public void refund(String orderNo, String reason) throws Exception {log.info("创建退款单记录");//根据订单编号创建退款单RefundInfo refundsInfo = refundsInfoService.createRefundByOrderNo(orderNo,reason);log.info("调用退款API");//调用统一下单APIString url =wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());HttpPost httpPost = new HttpPost(url);// 请求body参数Gson gson = new Gson();Map paramsMap = new HashMap();paramsMap.put("out_trade_no", orderNo);//订单编号paramsMap.put("out_refund_no", refundsInfo.getRefundNo());//退款单编号paramsMap.put("reason", reason);//退款原因paramsMap.put("notify_url",wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款通知地址Map amountMap = new HashMap();amountMap.put("refund", refundsInfo.getRefund());//退款金额amountMap.put("total", refundsInfo.getTotalFee());//原订单金额amountMap.put("currency", "CNY");//退款币种paramsMap.put("amount", amountMap);//将参数转换成json字符串String jsonParams = gson.toJson(paramsMap);log.info("请求参数 ===> {}" + jsonParams);StringEntity entity = new StringEntity(jsonParams, "utf-8");entity.setContentType("application/json");//设置请求报文格式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();if (statusCode == 200) {log.info("成功, 退款返回结果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("退款异常, 响应码 = " + statusCode + ", 退款返回结果 = " + bodyAsString);}//更新订单状态orderInfoService.updateStatusByorderNo(orderNo,OrderStatus.REFUND_PROCESSING);//更新退款单refundsInfoService.updateRefund(bodyAsString);} finally {response.close();}
}

创建退款订单

service层

/*** 根据订单号创建退款订单*/
RefundInfo createRefundByOrderNo(String orderNo, String reason);

service实现层

/*** 根据订单号创建退款订单** @param orderNo* @param reason*/
@Override
public RefundInfo createRefundByOrderNo(String orderNo, String reason) {//根据订单号获取订单信息OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo);//根据订单号生成退款订单RefundInfo refundInfo = new RefundInfo();refundInfo.setOrderNo(orderNo);//订单编号refundInfo.setRefundNo(OrderNoUtils.getRefundNo());//退款单编号refundInfo.setTotalFee(orderInfo.getTotalFee());//原订单金额(分)refundInfo.setRefund(orderInfo.getTotalFee());//退款金额(分)refundInfo.setReason(reason);//退款原因//保存退款订单baseMapper.insert(refundInfo);return refundInfo;
}

获取订单状态

/*** 根据订单号获取订单*/
OrderInfo getOrderByOrderNo(String orderNo);
/*** 根据订单号获取订单** @param orderNo*/
@Override
public OrderInfo getOrderByOrderNo(String orderNo) {QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();queryWrapper.eq("order_no", orderNo);OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);return orderInfo;
}

根据退款单号核实退款单状态

service层

/*** 根据退款单号核实退款单状态*/
void checkRefundStatus(String refundNo) throws Exception;

service实现层

/*** 根据退款单号核实退款单状态** @param refundNo*/
@Transactional(rollbackFor = Exception.class)
@Override
public void checkRefundStatus(String refundNo) throws Exception {log.warn("根据退款单号核实退款单状态 ===> {}", refundNo);//调用查询退款单接口String result = this.queryRefund(refundNo);//组装json请求体字符串Gson gson = new Gson();Map<String, String> resultMap = gson.fromJson(result, HashMap.class);//获取微信支付端退款状态String status = resultMap.get("status");String orderNo = resultMap.get("out_trade_no");if (WxRefundStatus.SUCCESS.getType().equals(status)) {log.warn("核实订单已退款成功 ===> {}", refundNo);//如果确认退款成功,则更新订单状态orderInfoService.updateStatusByorderNo(orderNo,OrderStatus.REFUND_SUCCESS);//更新退款单refundsInfoService.updateRefund(result);}if (WxRefundStatus.ABNORMAL.getType().equals(status)) {log.warn("核实订单退款异常 ===> {}", refundNo);//如果确认退款成功,则更新订单状态orderInfoService.updateStatusByorderNo(orderNo,OrderStatus.REFUND_ABNORMAL);//更新退款单refundsInfoService.updateRefund(result);}
}

更新退款状态

service层

/*** 记录退款记录*/
void updateRefund(String content);

service实现层

/*** 记录退款记录** @param content*/
@Override
public void updateRefund(String content) {//将json字符串转换成MapGson gson = new Gson();Map<String, String> resultMap = gson.fromJson(content, HashMap.class);//根据退款单编号修改退款单QueryWrapper<RefundInfo> queryWrapper = new QueryWrapper<>();queryWrapper.eq("refund_no", resultMap.get("out_refund_no"));//设置要修改的字段RefundInfo refundInfo = new RefundInfo();refundInfo.setRefundId(resultMap.get("refund_id"));//微信支付退款单号//查询退款和申请退款中的返回参数if(resultMap.get("status") != null){refundInfo.setRefundStatus(resultMap.get("status"));//退款状态refundInfo.setContentReturn(content);//将全部响应结果存入数据库的content字段}//退款回调中的回调参数if(resultMap.get("refund_status") != null){refundInfo.setRefundStatus(resultMap.get("refund_status"));//退款状态refundInfo.setContentNotify(content);//将全部响应结果存入数据库的content字段}//更新退款单baseMapper.update(refundInfo, queryWrapper);
}

处理退款

service层

/*** 处理退款单*/
void processRefund(Map<String, Object> bodyMap) throws Exception;

service实现层

/*** 处理退款单** @param bodyMap*/
@Transactional(rollbackFor = Exception.class)
@Override
public void processRefund(Map<String, Object> bodyMap) throws Exception {log.info("退款单");//解密报文String plainText = decryptFromResource(bodyMap);//将明文转换成mapGson gson = new Gson();HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);String orderNo = (String) plainTextMap.get("out_trade_no");if (lock.tryLock()) {try {String orderStatus = orderInfoService.getOrderStatus(orderNo);if (!OrderStatus.REFUND_PROCESSING.getType().equals(orderStatus)) {return;}//更新订单状态orderInfoService.updateStatusByorderNo(orderNo,OrderStatus.REFUND_SUCCESS);//更新退款单refundsInfoService.updateRefund(plainText);} finally {//要主动释放锁lock.unlock();}}
}

源码

GitHub

Gitee

Java后端对接微信支付(微信公众号、PC端扫码)相关推荐

  1. Java后端对接微信支付(微信小程序、APP、PC端扫码)非常全,包含查单、退款

    首先我们要明确目标,我们点击 微信支付官网 ,我们主要聚焦于这三种支付方式,其中JSPAI与APP主要与uniapp开发微信小程序与APP对接,而NATIVE主要与网页端扫码支付对接 1.三种支付统一 ...

  2. php微信公众号支付实例教程,php微信支付之公众号支付功能

    这篇文章主要为大家详细介绍了php微信支付之公众号支付功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 网上的很多PHP微信扫码支付接入教程都颇为复杂,且需要配置和引入较多的文件,本人通过整理后 ...

  3. JS_微信公众号开发调用扫码支付功能

    需要在公众号里面切入扫码功能 前端代码: <!DOCTYPE html> <html> <head lang="en"><meta cha ...

  4. PHP 支付PC端扫码支付、APP接口调起支付宝支付、微信公众号接入支付宝支付

    第一:第三方支付原理 第二:支付接口申请流程 地址:https://docs.open.alipay.com/270/105899/ : 参考地址:https://blog.csdn.net/nove ...

  5. 服务商快速进件V1.6.3源码帮助客户开通微信支付账号公众号应用

    源码下载:服务商快速进件V1.6.3源码帮助客户开通微信支付账号公众号应用-小程序文档类资源-CSDN下载

  6. 微信支付 php详解,微信支付之公众号支付详解

    本文主要和大家分享微信支付之公众号支付详解,随着微信支付的流行,大多产品都开发了自己的公众号.小程序等,产品的营销需要支付的支撑,最近做了个微信公号号支付,采坑无数,今天给大家分享一下,希望能帮助到大 ...

  7. 微信PC端扫码支付 java 模式二的扫码支付

    前言 这次分享的是java对接微信的支付接口,实现电脑端扫码支付后,跳转支付成功页面的例子.之所以分享是微信的Api太坑了.留下的文档也少,对接过程中容易出现各种各样的问题,在实现这扫码支付功能的时候 ...

  8. 微信支付—微信H5支付「PC端扫码支付」

    前言 微信支付-微信H5外部浏览器支付 微信支付-微信H5内部浏览器支付 微信支付-PC端扫码支付「本文」 本篇是微信支付系列的第三篇,PC端扫码支付. 开发环境:Java + SpringBoot ...

  9. 支付宝支付-支付宝PC端扫码支付

    前言 支付宝支付-沙箱环境使用 支付宝支付-支付宝PC端扫码支付「本文」 支付宝支付-手机浏览器H5支付「待写」 PC端扫码支付,其实就是就是 电脑网站支付,本文基于支付宝沙箱环境,不了解的可以看一下 ...

最新文章

  1. SQL Server中临时表与表变量的区别
  2. php中的单引号、双引号和转义字符
  3. 全国计算机等级考试题库二级C操作题100套(第80套)
  4. perl 5.10.0安装包下载
  5. html 显示闹钟,闹钟设置.html
  6. 数学_矩阵向量求导公式相关
  7. java 股票数据抓取_慢牛系列一:如何抓取股票数据
  8. linux删除缓存文件swp,Vi下删除SWP文件
  9. ios描述文件过期时间查看
  10. json文件批量转换xml
  11. zkServer.cmd报错invalid config exiting abnormally解决
  12. shopee跨境店铺怎么申请入驻-跨境知道
  13. Verse on Premises 1.0.5IF1发布
  14. Halcon_二维测量_Apply_bead_inspection_model
  15. scum无法启动此程序以为计算机丢失,人渣SCUM电脑卡顿怎么办?人渣SCUM低配电脑设置方法介绍...
  16. android 线性布局竖线,3.2.1 线性布局(1)
  17. 【什么是长尾关键词(Long Tail Keyword)】
  18. debian时间同步_如何在Debian 10上设置时间同步
  19. 纯干货:线上出现fullGC次数很多的排查思路以及实践总结
  20. 【PHP】PHP服务端支付宝支付及回调

热门文章

  1. java 气泡聊天消息_Java Swing实现的仿QQ气泡消息聊天窗口效果
  2. 3ds Max 子物体的编辑
  3. 如何使用gitHub进行合作开发
  4. 元宇宙行业风云突变 华尔街大鳄开始布局
  5. java计算机毕业设计建筑劳务监管平台源码+mysql数据库+系统+lw文档+部署
  6. 通过js获得input文本框输入的值
  7. 【校招VIP】产品思维能力之逻辑计算
  8. linux服务篇-DHCP服务
  9. **ubuntu安装ansible并且安装awx管理**
  10. 2021-08-22 tbpy帮助文档