源码地址:前端后端都有
链接: https://pan.baidu.com/s/1Nx-jLJ1gaZD0rmoGOw9MhA
提取码: qwer
APIV2、APIV3一个模式



- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0modelVersion>
- <packaging>jarpackaging>
-
- <groupId>org.examplegroupId>
- <artifactId>weixinZFartifactId>
- <version>1.0-SNAPSHOTversion>
-
- <properties>
- <maven.compiler.source>8maven.compiler.source>
- <maven.compiler.target>8maven.compiler.target>
- properties>
-
-
- <parent>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-parentartifactId>
- <version>2.1.9.RELEASEversion>
- <relativePath/>
- parent>
-
- <dependencies>
-
- <dependency>
- <groupId>io.springfoxgroupId>
- <artifactId>springfox-swagger2artifactId>
- <version>2.7.0version>
- dependency>
-
- <dependency>
- <groupId>io.springfoxgroupId>
- <artifactId>springfox-swagger-uiartifactId>
- <version>2.7.0version>
- dependency>
-
- <dependency>
- <groupId>log4jgroupId>
- <artifactId>log4jartifactId>
- <version>1.2.14version>
- dependency>
-
- <dependency>
- <groupId>com.github.wechatpay-apiv3groupId>
- <artifactId>wechatpay-apache-httpclientartifactId>
- <version>0.3.0version>
- dependency>
- <dependency>
- <groupId>com.alipay.sdkgroupId>
- <artifactId>alipay-sdk-javaartifactId>
- <version>4.31.65.ALLversion>
- dependency>
-
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-dependenciesartifactId>
- <version>2.1.9.RELEASEversion>
- <type>pomtype>
- <scope>importscope>
- dependency>
-
- <dependency>
- <groupId>com.alibabagroupId>
- <artifactId>fastjsonartifactId>
- dependency>
-
- <dependency>
- <groupId>mysqlgroupId>
- <artifactId>mysql-connector-javaartifactId>
- dependency>
-
- <dependency>
- <groupId>com.baomidougroupId>
- <artifactId>mybatis-plus-boot-starterartifactId>
- <version>3.4.3version>
- dependency>
-
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-webartifactId>
- dependency>
-
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-configuration-processorartifactId>
- <optional>trueoptional>
- dependency>
-
-
- <dependency>
- <groupId>org.projectlombokgroupId>
- <artifactId>lombokartifactId>
- <scope>providedscope>
- dependency>
-
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-testartifactId>
-
- <exclusions>
- <exclusion>
- <groupId>org.junit.jupitergroupId>
- <artifactId>junit-jupiter-engineartifactId>
- exclusion>
- exclusions>
- dependency>
- <dependency>
- <groupId>com.alibabagroupId>
- <artifactId>fastjsonartifactId>
- <version>1.2.60version>
- <scope>compilescope>
- dependency>
-
- <dependency>
- <groupId>com.google.code.gsongroupId>
- <artifactId>gsonartifactId>
- dependency>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-autoconfigureartifactId>
- dependency>
-
-
-
- dependencies>
-
- <build>
- <plugins>
-
- <plugin>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-maven-pluginartifactId>
- <version>2.1.9.RELEASEversion>
- <executions>
- <execution>
- <goals>
- <goal>repackagegoal>
- goals>
- execution>
- executions>
-
- <configuration>
- <includeSystemScope>trueincludeSystemScope>
- <mainClass>com.wx.WXapplicationmainClass>
- configuration>
- plugin>
-
- plugins>
- build>
-
-
- project>
- server:
- port: 8090
-
- spring:
- application:
- name: weixinZF
- datasource:
- #高版本驱动使用
- driver-class-name: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql://127.0.0.1:3306/payment_demo?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
- #设定用户名和密码
- username: root
- password: root
-
- jackson:
- date-format: yyyy-MM-dd HH:mm:ss
- time-zone: GMT+8
-
- #SpringBoot整合Mybatis
- mybatis-plus:
- #指定别名包
- type-aliases-package: com.jt.pojo
- #扫描指定路径下的映射文件
- mapper-locations: classpath:/mapper/*.xml
- #开启驼峰映射
- configuration:
- log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #sql日志
- map-underscore-to-camel-case: true
- # 一二级缓存默认开始 所以可以简化
- #打印mysql日志
- logging:
- level:
- com.jt.mapper: debug
订单表
- CREATE TABLE `t_order_info` (
- `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '订单id',
- `title` varchar(256) DEFAULT NULL COMMENT '订单标题',
- `order_no` varchar(50) DEFAULT NULL COMMENT '商户订单编号',
- `user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
- `product_id` bigint(20) DEFAULT NULL COMMENT '支付产品id',
- `total_fee` int(11) DEFAULT NULL COMMENT '订单金额(分)',
- `code_url` varchar(50) DEFAULT NULL COMMENT '订单二维码连接',
- `order_status` varchar(10) DEFAULT NULL COMMENT '订单状态',
- `create_time` datetime DEFAULT current_timestamp() COMMENT '创建时间',
- `update_time` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp() COMMENT '更新时间',
- `payment_type` varchar(255) DEFAULT NULL COMMENT '支付类型(支付宝~微信)',
- PRIMARY KEY (`id`) USING BTREE
- ) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
记录表
- CREATE TABLE `t_payment_info` (
- `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '支付记录id',
- `order_no` varchar(50) DEFAULT NULL COMMENT '商户订单编号',
- `transaction_id` varchar(50) DEFAULT NULL COMMENT '支付系统交易编号',
- `payment_type` varchar(20) DEFAULT NULL COMMENT '支付类型',
- `trade_type` varchar(20) DEFAULT NULL COMMENT '交易类型',
- `trade_state` varchar(50) DEFAULT NULL COMMENT '交易状态',
- `payer_total` int(11) DEFAULT NULL COMMENT '支付金额(分)',
- `content` text DEFAULT NULL COMMENT '通知参数',
- `create_time` datetime DEFAULT current_timestamp() COMMENT '创建时间',
- `update_time` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp() COMMENT '更新时间',
- PRIMARY KEY (`id`) USING BTREE
- ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
商品表
- CREATE TABLE `t_product` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品id',
- `title` varchar(20) DEFAULT NULL COMMENT '商品名称',
- `price` int(11) DEFAULT NULL COMMENT '价格(分)',
- `create_time` datetime DEFAULT current_timestamp() COMMENT '创建时间',
- `update_time` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp() COMMENT '更新时间',
- PRIMARY KEY (`id`) USING BTREE
- ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
退款表
- CREATE TABLE `t_refund_info` (
- `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '退款单id',
- `order_no` varchar(50) DEFAULT NULL COMMENT '商户订单编号',
- `refund_no` varchar(50) DEFAULT NULL COMMENT '商户退款单编号',
- `refund_id` varchar(50) DEFAULT NULL COMMENT '支付系统退款单号',
- `total_fee` int(11) DEFAULT NULL COMMENT '原订单金额(分)',
- `refund` int(11) DEFAULT NULL COMMENT '退款金额(分)',
- `reason` varchar(50) DEFAULT NULL COMMENT '退款原因',
- `refund_status` varchar(10) DEFAULT NULL COMMENT '退款状态',
- `content_return` text DEFAULT NULL COMMENT '申请退款返回参数',
- `content_notify` text DEFAULT NULL COMMENT '退款结果通知参数',
- `create_time` datetime DEFAULT current_timestamp() COMMENT '创建时间',
- `update_time` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp() COMMENT '更新时间',
- PRIMARY KEY (`id`) USING BTREE
- ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
wxpay.properties
- # 微信支付相关参数
- # 商户号
- wxpay.mch-id=11111111
- # 商户API证书序列号
- wxpay.mch-serial-no=1111111111111111
- # 商户私钥文件
- wxpay.private-key-path=apiclient_key.pem
- # APIv3密钥
- wxpay.api-v3-key=111111111111
- # APPID
- wxpay.appid=111111111111111
- # 微信服务器地址
- wxpay.domain=https://api.mch.weixin.qq.com
- # 接收结果通知地址 使用内网穿透工具获取
- wxpay.notify-domain=http://pw46ia.natappfree.cc
将私钥放置项目下

http请求客户端工具类
- package com.wx.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
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
param) { - this.url = url;
- this.param = param;
- }
-
- public HttpClientUtils(String url) {
- this.url = url;
- }
-
- public void setParameter(Map
map) { - param = map;
- }
-
- public void addParameter(String key, String value) {
- if (param == null)
- param = new HashMap
(); - 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
nvps = new LinkedList(); - 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;
- }
-
- }
参数转换字符串工具类
- package com.wx.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();
- }
- }
- }
- }
- }
订单号工具类:我们需要为我们的订单生成一个编号
- package com.wx.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;
- }
-
- }
微信验签应答工具类
- package com.wx.util;
-
-
- import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
- import org.apache.http.HttpEntity;
- import org.apache.http.client.methods.CloseableHttpResponse;
- import org.apache.http.util.EntityUtils;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
-
- import javax.servlet.http.HttpServletRequest;
- import java.io.IOException;
- import java.nio.charset.StandardCharsets;
- import java.time.DateTimeException;
- import java.time.Duration;
- import java.time.Instant;
-
- import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;
-
- /**
- * @author xy-peng
- */
- public class WechatPay2ValidatorForRequest {
-
- protected static final Logger log = LoggerFactory.getLogger(WechatPay2ValidatorForRequest.class);
- /**
- * 应答超时时间,单位为分钟
- */
- protected static final long RESPONSE_EXPIRED_MINUTES = 5;
- protected final Verifier verifier;
- protected final String requestId;
- protected final String body;
-
-
- public WechatPay2ValidatorForRequest(Verifier verifier, String requestId, String body) {
- this.verifier = verifier;
- this.requestId = requestId;
- this.body = body;
- }
-
- protected static IllegalArgumentException parameterError(String message, Object... args) {
- message = String.format(message, args);
- return new IllegalArgumentException("parameter error: " + message);
- }
-
- protected static IllegalArgumentException verifyFail(String message, Object... args) {
- message = String.format(message, args);
- return new IllegalArgumentException("signature verify fail: " + message);
- }
-
- public final boolean validate(HttpServletRequest request) throws IOException {
- try {
- //处理请求参数
- validateParameters(request);
-
- //构造验签名串
- String message = buildMessage(request);
-
- String serial = request.getHeader(WECHAT_PAY_SERIAL);
- String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
-
- //验签
- if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
- throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
- serial, message, signature, requestId);
- }
- } catch (IllegalArgumentException e) {
- log.warn(e.getMessage());
- return false;
- }
-
- return true;
- }
-
- protected final void validateParameters(HttpServletRequest request) {
-
- // NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
- String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};
-
- String header = null;
- for (String headerName : headers) {
- header = request.getHeader(headerName);
- if (header == null) {
- throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
- }
- }
-
- //判断请求是否过期
- String timestampStr = header;
- try {
- Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
- // 拒绝过期请求
- if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
- throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
- }
- } catch (DateTimeException | NumberFormatException e) {
- throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
- }
- }
-
- protected final String buildMessage(HttpServletRequest request) throws IOException {
- String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
- String nonce = request.getHeader(WECHAT_PAY_NONCE);
- return timestamp + "\n"
- + nonce + "\n"
- + body + "\n";
- }
-
- protected final String getResponseBody(CloseableHttpResponse response) throws IOException {
- HttpEntity entity = response.getEntity();
- return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
- }
-
- }
- package com.wx.vo;
-
- import lombok.Data;
- import lombok.experimental.Accessors;
-
- import java.util.HashMap;
- import java.util.Map;
-
- @Data
- @Accessors(chain = true)
- public class R {
-
- private Integer code; //响应码
- private String message; //响应消息
- private Map
data = new HashMap<>(); -
- public static R ok(){
- R r = new R();
- r.setCode(200);
- r.setMessage("成功");
- return r;
- }
-
- public static R error(){
- R r = new R();
- r.setCode(201);
- r.setMessage("失败");
- return r;
- }
-
- public R data(String key, Object value){
- this.data.put(key, value);
- return this;
- }
-
- }

- package com.wx.entity;
-
- import com.baomidou.mybatisplus.annotation.IdType;
- import com.baomidou.mybatisplus.annotation.TableId;
- import lombok.Data;
-
- import java.util.Date;
-
- @Data
- public class BaseEntity {
-
- //定义主键策略:跟随数据库的主键自增
- @TableId(value = "id", type = IdType.AUTO)
- private String id; //主键
-
- private Date createTime;//创建时间
-
- private Date updateTime;//更新时间
- }
- package com.wx.entity;
-
- import com.baomidou.mybatisplus.annotation.TableName;
- import lombok.Data;
-
- @Data
- @TableName("t_order_info")
- public class OrderInfo extends BaseEntity{
-
- private String title;//订单标题
-
- private String orderNo;//商户订单编号
-
- private Long userId;//用户id
-
- private Long productId;//支付产品id
-
- private Integer totalFee;//订单金额(分)
-
- private String codeUrl;//订单二维码连接
-
- private String orderStatus;//订单状态
- }
- package com.wx.entity;
-
- import com.baomidou.mybatisplus.annotation.TableName;
- import lombok.Data;
-
- @Data
- @TableName("t_payment_info")
- public class PaymentInfo extends BaseEntity{
-
- private String orderNo;//商品订单编号
-
- private String transactionId;//支付系统交易编号
-
- private String paymentType;//支付类型
-
- private String tradeType;//交易类型
-
- private String tradeState;//交易状态
-
- private Integer payerTotal;//支付金额(分)
-
- private String content;//通知参数
- }
- package com.wx.entity;
-
- import com.baomidou.mybatisplus.annotation.TableName;
- import lombok.Data;
-
- @Data
- @TableName("t_product")
- public class Product extends BaseEntity{
-
- private String title; //商品名称
-
- private Integer price; //价格(分)
- }
- package com.wx.entity;
-
- import com.baomidou.mybatisplus.annotation.TableName;
- import lombok.Data;
-
- @Data
- @TableName("t_refund_info")
- public class RefundInfo extends BaseEntity{
-
- private String orderNo;//商品订单编号
-
- private String refundNo;//退款单编号
-
- private String refundId;//支付系统退款单号
-
- private Integer totalFee;//原订单金额(分)
-
- private Integer refund;//退款金额(分)
-
- private String reason;//退款原因
-
- private String refundStatus;//退款单状态
-
- private String contentReturn;//申请退款返回参数
-
- private String contentNotify;//退款结果通知参数
- }
为了开发方便,我们预先在项目中定义一些枚举。枚举中定义的内容包括接口地址,支付状态等信息

API接口地址,封装了微信支付的所有接口
- package com.wx.enums.wxpay;
-
- import lombok.AllArgsConstructor;
- import lombok.Getter;
-
- /**
- * api接口地址
- */
- @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;
- }
封装了通知接口地址
- package com.wx.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;
- }
退款类型
- package com.wx.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;
- }
支付类型
- package com.wx.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;
- }
支付状态
- package com.wx.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;
- }
付款类型
- package com.wx.enums;
-
- import lombok.AllArgsConstructor;
- import lombok.Getter;
-
- @AllArgsConstructor
- @Getter
- public enum PayType {
- /**
- * 微信
- */
- WXPAY("微信"),
-
-
- /**
- * 支付宝
- */
- ALIPAY("支付宝");
-
- /**
- * 类型
- */
- private final String type;
- }
- package com.wx.config;
-
- import org.mybatis.spring.annotation.MapperScan;
- import org.mybatis.spring.annotation.MapperScans;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.transaction.annotation.EnableTransactionManagement;
-
- @Configuration
- @MapperScan("com.wx.mapper")
- @EnableTransactionManagement
- public class MybatisPlusConfig {
- }

继承BaseMapper<>

- package com.wx.controller;
-
- import com.wx.entity.Product;
- import com.wx.service.ProductService;
- import com.wx.vo.R;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- import org.springframework.web.bind.annotation.CrossOrigin;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- import javax.annotation.Resource;
- import java.util.List;
-
- @CrossOrigin
- @RestController
- @RequestMapping("/api/product")
- @Api(tags = "商品管理")
- public class ProductController {
-
-
- @Resource
- private ProductService productService;
-
-
- @GetMapping("/list")
- public R list(){
- List
list = productService.list(); - return R.ok().data("productList",list);
- }
- }
结果:

- package com.wx.config;
-
- import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
- import com.wechat.pay.contrib.apache.httpclient.auth.*;
- 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;
-
-
- @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;
-
- // APPID
- private String appid;
-
- // 微信服务器地址
- private String domain;
-
- // 接收结果通知地址
- private String notifyDomain;
-
- // APIv2密钥
- private String partnerKey;
-
- /**
- * 获取商户的私钥文件
- * @param filename
- * @return
- */
- private PrivateKey getPrivateKey(String filename){
-
- try {
- return PemUtil.loadPrivateKey(new FileInputStream(filename));
- } catch (FileNotFoundException e) {
- throw new RuntimeException("私钥文件不存在", e);
- }
- }
-
- /**
- * 获取签名验证器.定时更新签名证书
- * @return
- */
- @Bean
- public 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;
- }
-
- /**
- * 获取HttpClient,无需进行应答签名验证,跳过验签的流程
- */
- @Bean(name = "wxPayNoSignClient")
- public CloseableHttpClient getWxPayNoSignClient(){
-
- //获取商户私钥
- PrivateKey privateKey = getPrivateKey(privateKeyPath);
-
- //用于构造HttpClient
- WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
- //设置商户信息
- .withMerchant(mchId, mchSerialNo, privateKey)
- //无需进行签名验证、通过withValidator((response) -> true)实现
- .withValidator((response) -> true);
-
- // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
- CloseableHttpClient httpClient = builder.build();
-
- log.info("== getWxPayNoSignClient END ==");
-
- return httpClient;
- }
-
- }
测试获取支付参数:
- import com.wx.config.WxPayConfig;
- import com.wx.vo.R;
- import io.swagger.annotations.Api;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- import javax.annotation.Resource;
- import java.security.PrivateKey;
-
- @Api(tags = "测试控制器")
- @RestController
- @RequestMapping("/api/test")
- public class TestController {
-
- @Resource
- private WxPayConfig wxPayConfig;
-
- @GetMapping
- public R getWxPayConfig(){
-
- String mchId = wxPayConfig.getMchId();
- String privateKeyPath = wxPayConfig.getPrivateKeyPath();
- // PrivateKey privateKey = wxPayConfig.getPrivateKey("apiclient_key.pem");
- // System.out.println("privateKey = " + privateKey);
-
- return R.ok().data("mchId",mchId).data("privateKeyPath",privateKeyPath);
- }
- }
swagger测试获取

前面的依赖是完整的,已经添加
- <dependency>
- <groupId>com.github.wechatpay-apiv3groupId>
- <artifactId>wechatpay-apache-httpclientartifactId>
- <version>0.3.0version>
- dependency>
证书密钥使用说明:上面的配置中已经建立,此处再说明一下
https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay3_0.shtml

获取签名验证器
接口规则
- <dependency>
- <groupId>com.google.code.gsongroupId>
- <artifactId>gsonartifactId>
- dependency>
- package com.wx.controller;
-
-
- import com.google.gson.JsonSyntaxException;
- import com.wx.service.WxPayService;
- import com.wx.util.HttpUtils;
- import com.wx.util.WechatPay2ValidatorForRequest;
- import com.wx.vo.R;
- import com.google.gson.Gson;
- import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.web.bind.annotation.*;
-
- import javax.annotation.Resource;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import java.security.GeneralSecurityException;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.concurrent.TimeUnit;
-
-
- @CrossOrigin
- @RestController
- @RequestMapping("/api/wx-pay")
- @Api(tags = "网站微信支付API")
- @Slf4j
- public class WxPayController {
-
- @Resource
- private WxPayService wxPayService;
-
- @Resource
- private Verifier verifier;
-
- @ApiOperation("调用统一下单API,生成支付二维码")
- @PostMapping("native/{productId}")
- public R nativePay(@PathVariable Long productId) throws Exception {//传递商品id
- log.info("发起支付请求");
- //返回支付二维码链接和订单号
- Map
map = wxPayService.nativePay(productId); -
-
- return R.ok().setData(map);
- }
-
- /**
- * 接收微信的通知,支付成功处理,失败处理
- * @param request
- * @param response
- * @return
- */
- @PostMapping("/native/notify")
- public String nativeNotify(HttpServletRequest request, HttpServletResponse response){
-
- Gson gson = new Gson();
- Map
map = new HashMap<>();// -
- try {
- //处理通知参数
- String body = HttpUtils.readData(request);
- Map
bodyMap = gson.fromJson(body, HashMap.class); - log.info("支付通知的id =====》 {}",bodyMap.get("id"));
- log.info("支付通知的完整数据 =====》 {}",body);
- String requestId = bodyMap.get("id").toString();
-
- // 签名的验证 针对请求的 因为与微信交互,传递信息需要进行验证
- 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("通知验签成功");
- //处理订单 将具有密文数据的bodyMap进行解密获取参数。并存入数据库,存入日志
- wxPayService.processOrder(bodyMap);
-
- //成功应答
- response.setStatus(200);
- map.put("code","SUCCESS");
- map.put("message","成功");
- return gson.toJson(map);
-
- } catch (JsonSyntaxException | IOException | GeneralSecurityException e) {
- e.printStackTrace();
- //失败应答
- response.setStatus(500);
- map.put("code","ERROR");
- map.put("message","失败");
- return gson.toJson(map);
- }
- }
- @ApiOperation("用户取消订单")
- @PostMapping("/cancel/{orderNo}")
- public R cancel(@PathVariable String orderNo) throws Exception {
-
- log.info("取消订单");
-
- wxPayService.canceOrder(orderNo);
- return R.ok().setMessage("订单已经取消");
- }
-
- @ApiOperation("微信支付查询订单")
- @GetMapping("/query/{orderNo}")
- public R queryOrder(@PathVariable String orderNo) throws IOException {
-
- log.info("查询订单");
- String result = wxPayService.queryOrder(orderNo);
- return R.ok().setMessage("查询成功").data("result",result);
- }
-
-
- @ApiOperation("申请退款")
- @PostMapping("/refunds/{orderNo}/{reason}")
- public R refunds(@PathVariable String orderNo, @PathVariable String reason) throws Exception {
-
- log.info("申请退款");
- wxPayService.refund(orderNo, reason);
- return R.ok();
- }
-
-
- /**
- * 查询退款
- * @param refundNo
- * @return
- * @throws Exception
- */
- @ApiOperation("查询退款")
- @GetMapping("/query-refund/{refundNo}")
- public R queryRefund(@PathVariable String refundNo) throws Exception {
-
- log.info("查询退款");
-
- String result = wxPayService.queryRefund(refundNo);
- return R.ok().setMessage("查询成功").data("result", result);
- }
-
- /**
- * 退款结果通知
- * 退款状态改变后,微信会把相关退款结果发送给商户。
- */
- @ApiOperation("退款结果通知")
- @PostMapping("/refunds/notify")
- public String refundsNotify(HttpServletRequest request, HttpServletResponse response){
-
- log.info("退款通知执行");
- Gson gson = new Gson();
- Map
map = new HashMap<>();//应答对象 -
- try {
- //处理通知参数
- String body = HttpUtils.readData(request);
- Map
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.processRefund(bodyMap);
-
- //成功应答
- 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);
- }
- }
- @ApiOperation("获取账单url")
- @GetMapping("/querybill/{billDate}/{type}")
- public R queryTradeBill(
- @PathVariable String billDate,
- @PathVariable String type) throws Exception {
-
- log.info("获取账单url");
-
- String downloadUrl = wxPayService.queryBill(billDate, type);
- return R.ok().setMessage("获取账单url成功").data("downloadUrl", downloadUrl);
- }
-
- @ApiOperation("下载账单")
- @GetMapping("/downloadbill/{billDate}/{type}")
- public R downloadBill(
- @PathVariable String billDate,
- @PathVariable String type) throws Exception {
-
- log.info("下载账单");
- String result = wxPayService.downloadBill(billDate, type);
-
- return R.ok().data("result", result);
- }
-
- }
- package com.wx.controller;
-
- import com.wx.entity.OrderInfo;
- import com.wx.enums.OrderStatus;
- import com.wx.service.OrderInfoService;
- import com.wx.vo.R;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- import org.springframework.web.bind.annotation.*;
-
- import javax.annotation.Resource;
- import java.util.List;
-
- @CrossOrigin
- @Api(tags = "商品订单管理")
- @RestController
- @RequestMapping("/api/order-info")
- public class OrderInfoController {
-
- @Resource
- private OrderInfoService orderInfoService;
-
-
- @GetMapping("/list")
- public R list(){
- List
list = orderInfoService.listOrderByCreateTimeDesc(); -
- return R.ok().data("list",list);
- }
-
- /**
- * 查询订单状态
- * @param orderNo
- * @return
- */
- @ApiOperation("查询订单状态")
- @GetMapping("/query-order-status/{orderNo}")
- public R queryOrderStatus(@PathVariable String orderNo){
-
- String orderStatus = orderInfoService.getOrderStatus(orderNo);
- if (OrderStatus.SUCCESS.getType().equals(orderStatus)){
- return R.ok().setCode(0).setMessage("支付成功");//支付成功
- }
- return R.ok().setCode(101).setMessage("支付中...");
- }
-
- }
- package com.wx.controller;
-
- import com.wx.entity.Product;
- import com.wx.service.ProductService;
- import com.wx.vo.R;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- import org.springframework.web.bind.annotation.CrossOrigin;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- import javax.annotation.Resource;
- import java.util.List;
-
- @CrossOrigin
- @RestController
- @RequestMapping("/api/product")
- @Api(tags = "商品管理")
- public class ProductController {
-
-
- @Resource
- private ProductService productService;
-
-
- @GetMapping("/list")
- public R list(){
- List
list = productService.list(); - return R.ok().data("productList",list);
- }
- }
- package com.wx.service;
-
-
- import com.baomidou.mybatisplus.extension.service.IService;
- import com.wx.entity.OrderInfo;
- import com.wx.enums.OrderStatus;
-
- import java.util.List;
-
- public interface OrderInfoService extends IService
{ -
- OrderInfo createOrderByProductId(Long productId,String type);//获取订单信息并存入数据库中
-
- //订单号 二维码地址
- void saveCodeUrl(String orderNo,String codeUrl);//因为扫码有俩个小时的时间,所以进行数据库的更新
-
- List
listOrderByCreateTimeDesc(); //查询订单列表,并倒序 -
- void updateStatusByOrderNo(String orderNo, OrderStatus orderStatus);//更改订单状态
-
- String getOrderStatus(String orderNo);//处理重复的通知
-
- List
getNopayOrderByDuration(int minutes,String type);//定时任务 -
- OrderInfo getOrderByOrderNo(String orderNo);// //根据订单号获取订单信息
- }
- package com.wx.service;
-
- public interface PaymentInfoService {
-
- void createPaymentInfo(String plainText);//记录支付日志
- }
- package com.wx.service;
-
-
- import com.baomidou.mybatisplus.extension.service.IService;
- import com.wx.entity.Product;
-
- public interface ProductService extends IService
{ -
- }
- package com.wx.service;
-
-
- import com.baomidou.mybatisplus.extension.service.IService;
- import com.wx.entity.RefundInfo;
- import com.wx.enums.PayType;
-
- import java.util.List;
-
- public interface RefundInfoService extends IService
{ -
- RefundInfo createRefundByOrderNo(String orderNo, String reason);//根据订单编号创建退款
-
- void updateRefund(String content);//更新退款单
-
- List
getNoRefundOrderByDuration(int minutes, String type);//找出申请退款超过5分钟并且未成功的退款单 -
- RefundInfo createRefundByOrderNoForAliPay(String orderNo, String reason);
-
- void updateRefundForAliPay(String refundNo, String content, String refundStatus);
- }
- package com.wx.service;
-
- import java.io.IOException;
- import java.security.GeneralSecurityException;
- import java.util.Map;
-
- public interface WxPayService {
-
- Map
nativePay(Long productId) throws Exception; -
- //解密
- void processOrder(Map
bodyMap) throws GeneralSecurityException; -
- void canceOrder(String orderNo) throws Exception;//取消订单
-
- String queryOrder(String orderNo) throws IOException;//微信支付查询订单
-
- void checkOrderStatus(String orderNo) throws Exception;//查询核实订单状态
-
- void refund(String orderNo, String reason) throws Exception;//申请退款
-
- String queryRefund(String refundNo) throws Exception;//查询退款
-
- void processRefund(Map
bodyMap) throws Exception;//退款结果通知 -
- void checkRefundStatus(String refundNo) throws Exception;//核实订单状态:调用微信支付查询退款接口
-
- String queryBill(String billDate, String type) throws Exception;//获取账单url
-
- String downloadBill(String billDate, String type) throws Exception;//下载账单
- }
- package com.wx.service.impl;
-
-
- import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import com.wx.entity.OrderInfo;
- import com.wx.entity.Product;
- import com.wx.enums.OrderStatus;
- import com.wx.mapper.OrderInfoMapper;
- import com.wx.mapper.ProductMapper;
- import com.wx.service.OrderInfoService;
- import com.wx.util.OrderNoUtils;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.stereotype.Service;
-
- import javax.annotation.Resource;
- import java.time.Duration;
- import java.time.Instant;
- import java.util.List;
-
- @Slf4j
- @Service
- public class OrderInfoServiceImpl extends ServiceImpl
implements OrderInfoService { -
- @Resource
- private ProductMapper productMapper;
-
- @Resource
- private OrderInfoMapper orderInfoMapper;
-
- /**
- * 生成订单到数据库中
- * @param productId
- * @return
- */
- @Override
- public OrderInfo createOrderByProductId(Long productId,String type) {
-
-
- //查找已存在但未支付的订单
- OrderInfo orderInfo = this.getNoPayOrderByProductId(productId,type);
- if (orderInfo != null){
- return orderInfo;
- }
-
- //获取商品信息
- Product product = productMapper.selectById(productId);
-
- //生成订单
- orderInfo = new OrderInfo();
- orderInfo.setTitle(product.getTitle());
- orderInfo.setOrderNo(OrderNoUtils.getOrderNo());//设置订单号
- orderInfo.setProductId(productId);
- orderInfo.setTotalFee(product.getPrice()); //设置订单金额类型 分
- orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType()); //订单状态
- // orderInfo.setUserId(); 真实项目中需要存入用户的id,谁下的订单
- // orderInfo.setCodeUrl(); //二维码链接
- orderInfo.setPaymentType(type);
- baseMapper.insert(orderInfo);
-
- return orderInfo;
-
- }
-
-
- //查找已存在但未支付的订单.如果订单存在且没有支付则返回没支付的订单,防止重复创建订单对象
- private OrderInfo getNoPayOrderByProductId(Long productId,String type) {
-
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("product_id",productId);
- queryWrapper.eq("order_status",OrderStatus.NOTPAY.getType());
- queryWrapper.eq("payment_type",type);
- // queryWrapper.eq("user_id",userId); //再根据用户的id获取用户的订单
- OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
-
- return orderInfo;
- }
-
- /**
- * 存储订单二维码,在数据库中直接更改
- * @param orderNo
- * @param codeUrl
- */
- @Override //订单号 二维码
- public void saveCodeUrl(String orderNo, String codeUrl) {
-
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("order_no",orderNo);
-
- OrderInfo orderInfo = new OrderInfo();
- orderInfo.setCodeUrl(codeUrl);
- baseMapper.update(orderInfo,queryWrapper);
- }
-
- /**
- * 查询订单列表,并倒序查询
- * @return
- */
- @Override
- public List
listOrderByCreateTimeDesc() { //订单管理 -
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - queryWrapper.orderByDesc("create_time");
-
- return baseMapper.selectList(queryWrapper);
- }
-
- /**
- * 根据订单号更新订单状态
- * @param orderNo
- * @param orderStatus
- */
- @Override
- public void updateStatusByOrderNo(String orderNo, OrderStatus orderStatus) {
-
- log.info("更新订单状态 ===》"+orderStatus.getType());
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("order_no", orderNo);
-
- OrderInfo orderInfo = new OrderInfo();
- orderInfo.setOrderStatus(orderStatus.getType());
-
- baseMapper.update(orderInfo,queryWrapper);
- }
-
- /**
- * 处理未支付的订单
- * @param orderNo
- * @return
- */
- @Override
- public String getOrderStatus(String orderNo) {
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("order_no", orderNo);
- OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
- if (orderInfo == null){
- return null;
- }
-
- return orderInfo.getOrderStatus();
- }
-
- /**
- * 查询创建超过minutes分钟并且未支付的订单
- * @param minutes
- * @return
- */
- @Override
- public List
getNopayOrderByDuration(int minutes,String type) { -
- Instant instant = Instant.now().minus(Duration.ofMillis(minutes));//时间实例,用当前时间减去输入的时间
-
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("order_status",OrderStatus.NOTPAY.getType());
- queryWrapper.le("create_time",instant);//小于
- queryWrapper.eq("payment_type",type);//根据支付的类型查询 微信or支付宝
-
- List
orderInfos = baseMapper.selectList(queryWrapper); -
- return orderInfos;
- }
-
-
- /**
- * 根据订单号获取订单
- * @param orderNo
- * @return
- */
- @Override
- public OrderInfo getOrderByOrderNo(String orderNo) {
-
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("order_no", orderNo);
- OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
-
- return orderInfo;
- }
- }
- package com.wx.service.impl;
-
-
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import com.google.gson.Gson;
- import com.wx.entity.PaymentInfo;
- import com.wx.enums.PayType;
- import com.wx.mapper.PaymentInfoMapper;
- import com.wx.service.PaymentInfoService;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.stereotype.Service;
-
- import java.math.BigDecimal;
- import java.util.HashMap;
- import java.util.Map;
-
- @Service
- @Slf4j
- public class PaymentInfoServiceImpl extends ServiceImpl
implements PaymentInfoService { -
- @Override
- public void createPaymentInfo(String plainText) {
-
- log.info("记录微信支付日志");
- Gson gson = new Gson();
- HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
-
- //获取商户的订单号
- String orderNo = plainTextMap.get("out_trade_no").toString();
- //微信支付单号 如果支付有问题可以通过支付单号进行处理
- String transactionId = plainTextMap.get("transaction_id").toString();
- //支付类型,因为可能是通过网页,或者app及其他进行支付
- String tradeType = plainTextMap.get("trade_type").toString();
- //支付状态
- String tradeState = plainTextMap.get("trade_state").toString();
- //用户支付的金额
- Map
amount = (Map) plainTextMap.get("amount"); - Integer payerTotal = ((Double) amount.get("payer_total")).intValue();
-
- PaymentInfo paymentInfo = new PaymentInfo();
- paymentInfo.setOrderNo(orderNo);
- paymentInfo.setPaymentType(PayType.WXPAY.getType());
- paymentInfo.setTransactionId(transactionId);
- paymentInfo.setTradeType(tradeType);
- paymentInfo.setTradeState(tradeState);
- paymentInfo.setPayerTotal(payerTotal);
- paymentInfo.setContent(plainText);
-
- baseMapper.insert(paymentInfo);//插入数据库中
-
- }
-
- /**
- * 记录支付宝日志
- * @param params
- */
- @Override
- public void createPaymentInfoForAlipay(Map
params) { -
- String orderNo = params.get("out_trade_no");//获取订单号
- String tradeNo = params.get("trade_no"); //业务编号
- String tradeStatus = params.get("trade_status");//交易状态
- String totalAmount = params.get("total_amount");
- int totalAmoutInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue();//交易金额
-
- PaymentInfo paymentInfo = new PaymentInfo();
- paymentInfo.setOrderNo(orderNo);
- paymentInfo.setPaymentType(PayType.ALIPAY.getType());
- paymentInfo.setTransactionId(tradeNo);
- paymentInfo.setTradeType("电脑网站支付");
- paymentInfo.setTradeState(tradeStatus);
- paymentInfo.setPayerTotal(totalAmoutInt);
-
- Gson gson = new Gson();
- String json = gson.toJson(params, HashMap.class);
- paymentInfo.setContent(json);
-
- baseMapper.insert(paymentInfo);//存储到日志表格中
-
- }
- }
- package com.wx.service.impl;
-
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import com.wx.entity.Product;
- import com.wx.mapper.ProductMapper;
- import com.wx.service.ProductService;
- import org.springframework.stereotype.Service;
-
- @Service
- public class ProductServiceImpl extends ServiceImpl
implements ProductService { -
- }
- package com.wx.service.impl;
-
-
- import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import com.google.gson.Gson;
- import com.wx.entity.OrderInfo;
- import com.wx.entity.RefundInfo;
- import com.wx.enums.PayType;
- import com.wx.enums.wxpay.WxRefundStatus;
- import com.wx.mapper.RefundInfoMapper;
- import com.wx.service.OrderInfoService;
- import com.wx.service.RefundInfoService;
- import com.wx.util.OrderNoUtils;
- import org.springframework.stereotype.Service;
-
- import javax.annotation.Resource;
- import java.time.Duration;
- import java.time.Instant;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-
- @Service
- public class RefundInfoServiceImpl extends ServiceImpl
implements RefundInfoService { -
- @Resource
- private OrderInfoService orderInfoService;
-
- /**
- * 根据订单号创建退款订单
- * @param orderNo
- * @return
- */
- @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;
- }
-
-
- /**
- * 记录退款记录
- * @param content
- */
- @Override
- public void updateRefund(String content) {
-
- //将json字符串转换成Map
- Gson gson = new Gson();
- Map
resultMap = gson.fromJson(content, HashMap.class); -
- //根据退款单编号修改退款单
- QueryWrapper
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);
- }
-
- /**
- * 找出申请退款超过minutes分钟并且未成功的退款单
- * @param minutes
- * @return
- */
- @Override
- public List
getNoRefundOrderByDuration(int minutes,String tpye) { -
- //minutes分钟之前的时间
- Instant instant = Instant.now().minus(Duration.ofMinutes(minutes));
-
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("refund_status", WxRefundStatus.PROCESSING.getType());
- queryWrapper.eq("payment_type", PayType.WXPAY.getType());
- queryWrapper.le("create_time", instant);
- List
refundInfoList = baseMapper.selectList(queryWrapper); - return refundInfoList;
- }
-
- /**
- * 根据订单号创建退款订单
- * @param orderNo
- * @return
- */
- @Override
- public RefundInfo createRefundByOrderNoForAliPay(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;
- }
-
- /**
- * 更新退款记录
- * @param refundNo
- * @param content
- * @param refundStatus
- */
- @Override
- public void updateRefundForAliPay(String refundNo, String content, String refundStatus) {
-
- //根据退款单编号修改退款单
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("refund_no", refundNo);
-
- //设置要修改的字段
- RefundInfo refundInfo = new RefundInfo();
- refundInfo.setRefundStatus(refundStatus);//退款状态
- refundInfo.setContentReturn(content);//将全部响应结果存入数据库的content字段
-
- //更新退款单
- baseMapper.update(refundInfo, queryWrapper);
-
- }
-
- }
- package com.wx.service.impl;
-
- import com.google.gson.Gson;
- import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
- import com.wx.config.WxPayConfig;
- import com.wx.entity.OrderInfo;
- import com.wx.entity.RefundInfo;
- import com.wx.enums.OrderStatus;
- import com.wx.enums.PayType;
- import com.wx.enums.wxpay.WxApiType;
- import com.wx.enums.wxpay.WxNotifyType;
- import com.wx.enums.wxpay.WxRefundStatus;
- import com.wx.enums.wxpay.WxTradeState;
- import com.wx.service.OrderInfoService;
- import com.wx.service.PaymentInfoService;
- import com.wx.service.RefundInfoService;
- import com.wx.service.WxPayService;
- import com.wx.util.OrderNoUtils;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.http.client.methods.CloseableHttpResponse;
- import org.apache.http.client.methods.HttpGet;
- import org.apache.http.client.methods.HttpPost;
- import org.apache.http.entity.StringEntity;
- import org.apache.http.impl.client.CloseableHttpClient;
- import org.apache.http.util.EntityUtils;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
- import org.springframework.util.StringUtils;
-
- import javax.annotation.Resource;
- import java.io.IOException;
- import java.nio.charset.StandardCharsets;
- import java.security.GeneralSecurityException;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.concurrent.locks.ReentrantLock;
-
- @Service
- @Slf4j
- public class WxPayServiceImpl implements WxPayService {
-
-
- @Resource //注入配置中的对象 保函了验签的过程
- private CloseableHttpClient wxPayClient;
-
- @Resource
- private WxPayConfig wxPayConfig;
-
- @Resource
- private OrderInfoService orderInfoService; //将订单存入数据库中
-
- @Resource
- private PaymentInfoService paymentInfoService;
-
- @Resource
- private RefundInfoService refundsInfoService;
-
- @Resource
- private CloseableHttpClient wxPayNoSignClient; //无需应答签名
-
-
- private final ReentrantLock lock = new ReentrantLock(); //可重入锁
-
- /**
- * 创建订单,调用Native支付接口
- * @param productId
- * @return code_url(二维码地址) 和 订单号
- * @throws Exception
- */
- @Override
- public Map
nativePay(Long productId) throws Exception { - /**
- * 如果调用不成功需要去商户平台登录该商户号,在产品中心-我的产品-开通“公众号支付”,这样就可以用于小程序支付了 。
- */
-
- log.info("生成订单");
-
- //生成订单 TODO : 存入数据库
- OrderInfo orderInfo= orderInfoService.createOrderByProductId(productId, PayType.WXPAY.getType());
- String codeUrl = orderInfo.getCodeUrl();
- if (orderInfo != null && codeUrl!=null){
- log.info("订单已保存,二维码已经存在");
- //如果第一次创建订单则不会进入,因为数据库没有相应的二维码数据
- //如果第二次调用则有数据,就直接进行返回数据库存储的数据
-
- //返回二维码
- Map
map = new HashMap<>(); - map.put("codeUrl",codeUrl);
- map.put("orderNo",orderInfo.getOrderNo());
-
- return map;
-
- }
- log.info("调用统一下单API");
-
-
- //调用统一下单API
- HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));//放入远程链接地址
-
- // 请求body参数
- Gson gson = new Gson();
- Map paramsMap = new HashMap<>();
- paramsMap.put("appid",wxPayConfig.getAppid());//应用ID
- paramsMap.put("mchid",wxPayConfig.getMchId());//商户号
- paramsMap.put("description",orderInfo.getTitle());//商品描述 用了上面的title
- paramsMap.put("out_trade_no",orderInfo.getOrderNo());//订单号
- paramsMap.put("notify_url",wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));//通知地址
-
- Map amountMap = new HashMap<>();
- amountMap.put("total",orderInfo.getTotalFee());//金额
- amountMap.put("currency","CNY"); //货币类型
-
- paramsMap.put("amount",amountMap);
- String jsonParams = gson.toJson(paramsMap);//转换成json的格式
- 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) { //处理成功,无返回Body
- System.out.println("成功");
- } else {
- System.out.println("Native下单失败,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString);
- throw new IOException("request failed");
- }
- //响应结果
- HashMap
resultMap = gson.fromJson(bodyAsString, HashMap.class); - //二维码
- codeUrl = resultMap.get("code_url");
- System.out.println("resultMap = " + resultMap);
-
- //保存新二维码
- String orderNo = orderInfo.getOrderNo();//订单号
- orderInfoService.saveCodeUrl(orderNo,codeUrl);
-
- Map
map = new HashMap<>(); - map.put("codeUrl",codeUrl);
- map.put("orderNo",orderInfo.getOrderNo());
-
- return map;
-
- } finally {
- response.close();
- }
-
- }
-
- @Override
- public void processOrder(Map
bodyMap) throws GeneralSecurityException { -
- log.info("处理订单");
- //解密报文
- String plainText = decryptFromResource(bodyMap);
-
- //将明文转换成map
- Gson gson = new Gson();
- HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
- String orderNo = plainTextMap.get("out_trade_no").toString();//获取商户订单号
-
- /**
- *在对业务数据进行状态检查和处理之前
- * 要采用数据锁进行并发控制
- * 以避免函数重入造成的数据混乱
- */
- //尝试获取锁,成功获取则立即返回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();
- }
- }
- }
-
- /**
- * 取消订单
- * @param orderNo
- */
- @Override
- public void canceOrder(String orderNo) throws Exception {
-
- //调用微信支付的关单接口
- this.closeOrder(orderNo);
-
- //更新商户端的订单状态
- orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.CANCEL);
-
- }
-
- /**
- * 微信支付查询订单
- * @param orderNo
- * @return
- */
- @Override
- public String queryOrder(String orderNo) throws IOException {
-
- log.info("查询订单接口调用 ===》");
- //因为路径中有占位符,所以进行替换
- String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(),orderNo);
- url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());
-
- HttpGet httpGet = new HttpGet(url);
- httpGet.setHeader("Accept", "application/json");
- //完成签名并执行请求
- CloseableHttpResponse response = wxPayClient.execute(httpGet);
- try {
- String bodyAsString = EntityUtils.toString(response.getEntity());//响应头
- int statusCode = response.getStatusLine().getStatusCode();//响应状态
- if (statusCode == 200) { //处理成功
- log.info("成功 = " + bodyAsString);
- } else if (statusCode == 204) { //处理成功,无返回Body
- System.out.println("成功");
- } else {
- System.out.println("Native下单失败,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString);
- throw new IOException("request failed");
- }
-
-
- return bodyAsString;
-
- } finally {
- response.close();
- }
- }
-
- /**
- * 根据订单号查询微信支付查单接口,核实订单状态
- * 如果订单已经支付,则更新商户端订单状态
- * 如果订单未支付,则调用关单接口关闭订单,并更新商户端订单状态
- * @param orderNo
- */
- @Override
- public void checkOrderStatus(String orderNo) throws Exception {
-
- log.warn("根据订单号核实订单状态 ===》"+orderNo);
-
- //调用微信支付查单接口
- String result = this.queryOrder(orderNo);
- Gson gson = new Gson();
- HashMap 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
- * @param reason
- * @throws IOException
- */
- @Override
- public void refund(String orderNo, String reason) throws Exception {
-
- log.info("创建退款单记录");
- //根据订单编号创建退款单
- RefundInfo refundsInfo = refundsInfoService.createRefundByOrderNo(orderNo, reason);
-
- log.info("调用退款API");
-
- //调用统一下单API
- String 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();
- }
- }
-
-
-
- /**
- * 查询退款接口调用
- * @param refundNo
- * @return
- */
- @Override
- public String queryRefund(String refundNo) throws Exception {
-
- log.info("查询退款接口调用 ===> {}", refundNo);
-
- String url = String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo);
- url = wxPayConfig.getDomain().concat(url);
-
- //创建远程Get 请求对象
- HttpGet httpGet = new HttpGet(url);
- httpGet.setHeader("Accept", "application/json");
-
- //完成签名并执行请求
- CloseableHttpResponse response = wxPayClient.execute(httpGet);
-
- 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);
- }
-
- return bodyAsString;
-
- } finally {
- response.close();
- }
- }
-
- /**
- * 根据退款单号核实退款单状态
- * @param refundNo
- * @return
- */
- @Override
- public void checkRefundStatus(String refundNo) throws Exception {
-
- log.warn("根据退款单号核实退款单状态 ===> {}", refundNo);
-
- //调用查询退款单接口
- String result = this.queryRefund(refundNo);
-
- //组装json请求体字符串
- Gson gson = new Gson();
- Map
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);
- }
- }
-
-
- /**
- * 申请账单
- * @param billDate
- * @param type
- * @return
- * @throws Exception
- */
- @Override
- public String queryBill(String billDate, String type) throws Exception {
- log.warn("申请账单接口调用 {}", billDate);
-
- String url = "";
- if("tradebill".equals(type)){
- url = WxApiType.TRADE_BILLS.getType();
- }else if("fundflowbill".equals(type)){
- url = WxApiType.FUND_FLOW_BILLS.getType();
- }else{
- throw new RuntimeException("不支持的账单类型");
- }
-
- url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);
-
- //创建远程Get 请求对象
- HttpGet httpGet = new HttpGet(url);
- httpGet.addHeader("Accept", "application/json");
-
- //使用wxPayClient发送请求得到响应
- CloseableHttpResponse response = wxPayClient.execute(httpGet);
-
- 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);
- }
-
- //获取账单下载地址
- Gson gson = new Gson();
- Map
resultMap = gson.fromJson(bodyAsString, HashMap.class); - return resultMap.get("download_url");
-
- } finally {
- response.close();
- }
- }
-
- /**
- * 下载账单
- * @param billDate
- * @param type
- * @return
- * @throws Exception
- */
- @Override
- public String downloadBill(String billDate, String type) throws Exception {
- log.warn("下载账单接口调用 {}, {}", billDate, type);
-
- //获取账单url地址
- String downloadUrl = this.queryBill(billDate, type);
- //创建远程Get 请求对象
- HttpGet httpGet = new HttpGet(downloadUrl);
- httpGet.addHeader("Accept", "application/json");
-
- //使用wxPayClient发送请求得到响应
- CloseableHttpResponse response = wxPayNoSignClient.execute(httpGet);
-
- 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);
- }
-
- return bodyAsString;
-
- } finally {
- response.close();
- }
- }
-
- /**
- * 处理退款单 退款通知
- */
- @Override
- public void processRefund(Map
bodyMap) throws Exception { -
- log.info("退款单");
-
- //解密报文
- String plainText = decryptFromResource(bodyMap);
-
- //将明文转换成map
- Gson 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();
- }
- }
- }
-
- /**
- * 关单接口的调用
- * @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
paramMap = new HashMap<>(); - paramMap.put("mchid",wxPayConfig.getMchId());
- String jsonParams = gson.toJson(paramMap);
- 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) { //处理成功,无返回Body
- System.out.println("成功204");
- } else {
- System.out.println("Native下单失败,响应码 = " + statusCode);
- throw new IOException("request failed");
- }
-
-
- } finally {
- response.close();
- }
- }
-
- /**
- * 对称解密
- * @param bodyMap
- * @return
- */
- private String decryptFromResource(Map
bodyMap) throws GeneralSecurityException { - log.info("密文解密");
- //通知数据
- Map
resourceMap = (Map)bodyMap.get("resource"); - //获取数据中的密文
- String ciphertext = resourceMap.get("ciphertext");
- //随机串
- String nonce = resourceMap.get("nonce");
- //附加数据
- String associatedData = resourceMap.get("associated_data");
-
- //解密工具
- AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
- String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8)
- , nonce.getBytes(StandardCharsets.UTF_8)
- , ciphertext);
- log.info("明文===》{}",plainText);
-
- return plainText;
- }
- }
- package com.wx.task;
-
- import com.wx.entity.OrderInfo;
- import com.wx.entity.RefundInfo;
- import com.wx.enums.PayType;
- import com.wx.service.OrderInfoService;
- import com.wx.service.RefundInfoService;
- import com.wx.service.WxPayService;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.scheduling.annotation.Scheduled;
- import org.springframework.stereotype.Component;
-
- import javax.annotation.Resource;
- import java.io.IOException;
- import java.util.List;
-
- @Slf4j
- @Component
- public class WxPayTask {
-
- @Resource
- private OrderInfoService orderInfoService;
-
- @Resource
- private WxPayService wxPayService;
-
- @Resource
- private RefundInfoService refundInfoService;
-
- /**
- * 从第0秒开始,每隔30秒执行一次,查询创建超过五分钟,并且未支付的订单
- */
- //@Scheduled(cron = "0/30 * * * * ?")
- public void orderConfirm() throws Exception {
- log.info("定时任务启动====");
-
- List
orderInfoList = orderInfoService.getNopayOrderByDuration(5, PayType.WXPAY.getType()); -
- for (OrderInfo orderInfo : orderInfoList){
- String orderNo = orderInfo.getOrderNo();
- log.warn("超时订单 === > {}", orderNo);
-
- //核实订单状态:调用微信支付查单接口
- wxPayService.checkOrderStatus(orderNo);
- }
- }
- /**
- * 从第0秒开始每隔30秒执行1次,查询创建超过5分钟,并且未成功的退款单
- * */
- //@Scheduled(cron = "0/30 * * * * ?")
- public void refundConfirm() throws Exception {
- log.info("refundConfirm 被执行......");
- //找出申请退款超过5分钟并且未成功的退款单
- List
refundInfoList = refundInfoService.getNoRefundOrderByDuration(5, PayType.WXPAY.getType()); - for (RefundInfo refundInfo : refundInfoList) {
- String refundNo = refundInfo.getRefundNo();
- log.warn("超时未退款的退款单号 ===> {}", refundNo);
-
- //核实订单状态:调用微信支付查询退款接口
- wxPayService.checkRefundStatus(refundNo);
- }
- }
- }



引入参数文件,获取参数并组装到client中。后面方便调用
- package com.wx.config;
-
-
- import com.alipay.api.*;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.context.annotation.PropertySource;
- import org.springframework.core.env.Environment;
-
- import javax.annotation.Resource;
-
- /**
- * 加载支付宝配置参数文件
- */
- @Configuration
- //加载配置文件
- @PropertySource("classpath:alipay-sandbox.properties")
- public class AlipayClientConfig {
-
- @Resource
- private Environment config;//注入此对象,方便读取配置文件中数据
-
- @Bean
- public AlipayClient alipayClient() throws AlipayApiException {
-
- AlipayConfig alipayConfig = new AlipayConfig();
-
- //设置网关地址
- alipayConfig.setServerUrl(config.getProperty("alipay.gateway-url"));
- //设置应用Id
- alipayConfig.setAppId(config.getProperty("alipay.app-id"));
- //设置应用私钥
- alipayConfig.setPrivateKey(config.getProperty("alipay.merchant-private-key"));
- //设置请求格式,固定值json
- alipayConfig.setFormat(AlipayConstants.FORMAT_JSON);
- //设置字符集
- alipayConfig.setCharset(AlipayConstants.CHARSET_UTF8);
- //设置支付宝公钥
- alipayConfig.setAlipayPublicKey(config.getProperty("alipay.alipay-public-key"));
- //设置签名类型
- alipayConfig.setSignType(AlipayConstants.SIGN_TYPE_RSA2);
- //构造client
- AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig);
-
- return alipayClient;
- }
- }
alipay-sandbox.properties
支付宝参数:此处用的是沙箱模式
- # 支付宝支付相关参数
-
- # 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
- alipay.app-id=2021000121632501
-
- # 商户PID,卖家支付宝账号ID
- alipay.seller-id=2088621987731295
-
- # 支付宝网关
- alipay.gateway-url=https://openapi.alipaydev.com/gateway.do
-
- # 商户私钥,您的PKCS8格式RSA2私钥
- alipay.merchant-private-key=MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCq309YMdt/Kt2leisVuMbA6fTSmc2s9iY6wtuCDSbqz3RK187qsZepa2S7l6J16BWKXak0QIus70ZCGZ61U//ToQqDXc3JKlKvp19Pcq8YpvzByv0Z2FdtvGi9tbjX1icB2Xt/6uO9BYuixi9d3e1kzx9/M2RDiVuPmTSPvmIGJLYSmjsmJO1FOCyZQa5X/d0no5Ko4vKtV/DanqoqWNsOGpoU7bCFA/Y+PtS4xSEUgnsSWjymEQlfSublENadXhSLEP144ZrHKRDdFwTrua64KbQVFR5dnXvcVd4ERCD5C2Vtl+b3qx1puYlCxFPXp/dgC6f4iqQNZCj+W4m3NqmVAgMBAAECggEASVD34ofB/paN8+qvgep+nVfFTHfh4EzdqmjhdrPd9vJ8m4BtsBXzVSZXWoZ9lsm2NGBrsZfgVpt0Mfh8OKGKK2v17tfY7G/Uern+E0DKEHHWEfDfGK/TE6q75mqKnVGt+wUuEHzgqsIuX/FZcZU/vvmAMjwC0Vemib7a5rJxrOBvP40siA/e9se4PwmQHqfXH5J6vyJna6dH1r4f+sxhWdCb4O1VxZgI52J7rMStYGqwnEMKv5h7aB2zpq6BQbcblvNw6hBA80sn+F+LJM0Auebqk+HX/wZXHKsJVoRYEtCUNhl4YoNo5V3U3WYci1JXPJ+Op6PMI8n4iJZSTj6YIQKBgQDnDh2DkPR5RCLjJ1F+Kq5EotDNwLA21/xibLHE/gTT9kdxfKdSckZjOVp+nlMQ2Z+L8khD3YfRDD4sUheL8fKA22G9GnY31/4c2/XsjWPogr0BpgxFRt954OyPIoL+FQLkZnH05MOY5bq9N9/gfuuF3txCTJgMUYEWTba3Q52hkwKBgQC9Ud2wwwhVED5x80Tl4z4QVKro3ubbdat+IiCLOAOoW1IyRG+HV3CbG82DMT0F6h3YRBaRtC/UUoeh/YFpsYjhH30SghiM7N9l4Sk4X9z4eMvYklE02P81TOOukTmJzugHxtwb6k2YZC4LOu7+S2sRc0kTmUfX1CgmZ2L07ochNwKBgQCf4m6d6iKh/3o8wapsqdApgpkGp73IVbE50ok5DaX9nsBVUbLfJGB8rOVoFNraIB19U8yZ2aPwDo6/UJcmqefrLuP1XWhMwFQBWFxWsoheDooHqAV5ss9VoUVQzsriU1vK/PECS4LmPKH56b4rtOf5nPvBjQryCzxOWLyFGG7trQKBgElOCakH25IUWBmHOIZLFxz7q7G/nWQci+qrDC7b4Y6uzYTpOsYM9W0Zttm1lwtTO3sh4htIybxMuHfg0Ns8AuQobSVdemQW0+l+5ZcOh2EuZL/W59quqyLYQtC1KrJRi0Z3mYK1lpYLNEjk6OVODocTPJh6IXdQjrtQDOEJ+wjBAoGBAJopA4T5OnunBZkvYZPXya747m6U4nJFIcS0lJfM9ZGP2hw/mCOhXb2khL789v4G78kwUgfnVAcPPyyG1jjFtPiWb9+DrANZijBWb6HxnvTRytjeROlxIgXiLmb7MqTZHLujyb3H80rhTu3rqk8Jp1Yx26sEKKJXz3rc5KkSVOFt
-
- # 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥
- alipay.alipay-public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgjkP008ZFSXVqWoSClXeKDTX7leNDVabMQcexv3TxZY4KYNUSd091BeYlE59feUaGTcehHc9r3N48jaqbZJyX8M2ogdqFlA/8iep22WcQu5ybIEUX45L40ClYqiqKLYpj/uuPFrekEKdZrS1DxaawDaazGypFFzpz/Lf6ijjbDeQhVsSqaPDAZEqmGWUo6oF1bahCpYJb9q/orqaihqA1vb7oRm7k3n8e76H6O1xxDVNenIsi4tit0wlZ6XneOVxnzEgsk0NAGa8BEH2gKrkVycVgBAUxjr7yWVyJuL0pYJkHnQbg6WxLDaDhe8iqGC1faSGqlB4PcIJp+pXHwv0DwIDAQAB
-
- # 接口内容加密秘钥,对称秘钥
- alipay.content-key=DNxJbSgGPbQwXL3jnKw42A==
-
- # 页面跳转同步通知页面路径
- alipay.return-url=http://localhost:8080/#/success
-
- # 服务器异步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
- # 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置
- alipay.notify-url=http://ddme2g.natappfree.cc/api/ali-pay/trade/notify
-
-
-
-
-
- package com.wx.controller;
-
- import com.alipay.api.AlipayApiException;
- import com.alipay.api.AlipayConstants;
- import com.alipay.api.internal.util.AlipaySignature;
- import com.wx.entity.OrderInfo;
- import com.wx.service.AliPayService;
- import com.wx.service.OrderInfoService;
- import com.wx.vo.R;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.core.env.Environment;
- import org.springframework.web.bind.annotation.*;
-
- import javax.annotation.Resource;
- import java.io.IOException;
- import java.math.BigDecimal;
- import java.util.Map;
-
- @RestController
- @CrossOrigin
- @RequestMapping("/api/ali-pay")
- @Api(tags = "支付宝支付")
- @Slf4j
- public class AliPayController {
-
- @Resource
- private AliPayService aliPayService;
-
- @Resource
- private Environment config;
-
- @Resource
- private OrderInfoService orderInfoService;
-
- @ApiOperation("统一收单下单支付页面接口调用")
- @PostMapping("/trade/page/pay/{productId}")
- public R tradePage(@PathVariable Long productId){
-
- log.info("统一收单下单支付页面接口调用");
- //支付报开放平台接受 request 请求对象后
- //会被开放者生成一个html形式的from表单,包含自动提交的脚本
- String formStr = aliPayService.tradeCteate(productId);
-
- //我们将from表单字符串返回给前端程序.之后前端将会调用自动提交脚本,进行表单的提交
- //此时,表单会自动提交的action属性所执行的支付宝开放平台中,从而为用户展示一个支付页面
- return R.ok().data("formStr",formStr);
- }
-
- @ApiOperation("支付通知")
- @PostMapping("/trade/notify")
- public String tradeNotify(@RequestParam Map
params) { -
- log.info("支付通知正在进行");
- log.info("通知参数 ===》 :"+params);
-
- String result = "failure";
-
- try {
- //异步通知验签
- boolean signVerified = AlipaySignature.rsaCheckV1(
- params,
- config.getProperty("alipay.alipay-public-key"),
- AlipayConstants.CHARSET_UTF8,
- AlipayConstants.SIGN_TYPE_RSA2);//调用SDK验证签名
- if(!signVerified){
-
- //验签失败则记录异常日志,并在response中返回failure.
- log.error("异步通知验签失败");
- return result;
- }
-
-
- //验签成功后
- log.info("支付成功异步通知验签成功!");
-
- //按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验
- //1.商家需要验证该通知数据中的 out_trade_no 是否为商家系统中创建的订单号。
- String outTradeNo = params.get("out_trade_no");
- OrderInfo oeder = orderInfoService.getOrderByOrderNo(outTradeNo);
- if (oeder == null){
- log.error("订单不存在");
- return result;
- }
-
- //2.判断 total_amount 是否确实为该订单的实际金额(即商家订单创建时的金额)。
- String totalAmount = params.get("total_amount");
- int totalAmoutInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue();
- int totalFeeInt = oeder.getTotalFee().intValue();
- if (totalAmoutInt != totalFeeInt){
- log.error("金额校验失败");
- return result;
- }
- //3.校验通知中的 seller_id(或者 seller_email) 是否为 out_trade_no 这笔单据的对应的操作方(有的时候,一个商家可能有多个 seller_id/seller_email)。
- String sellerId = params.get("seller_id");
- String sellerIdPro = config.getProperty("alipay.seller-id");
- if (!sellerId.equals(sellerIdPro)){
- log.error("商家pid校验失败");
- return result;
- }
- //4.验证 app_id 是否为该商家本身。
- String appId = params.get("app_id");
- String appIdProperty = config.getProperty("alipay.app-id");
- if (!appId.equals(appIdProperty)){
- log.error("appid校验失败");
- return result;
- }
-
- //只有交易通知状态为 TRADE_SUCCESS 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。
- String tradeStatus = params.get("trade_status");
- if (!"TRADE_SUCCESS".equals(tradeStatus)){
- log.error("支付未成功");
- return result;
- }
- //处理业务,修改订单状态,记录支付日志
- aliPayService.processOrder(params);
-
- //校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure
-
-
-
-
- //向支付宝返回成功的标识,否则会一直不间断的发送通知给我们
- result = "success";
- } catch (AlipayApiException e) {
- e.printStackTrace();
- }
- return result;
- }
-
- /**
- * 用户取消订单
- * @param orderNo
- * @return
- */
- @ApiOperation("用户取消订单")
- @PostMapping("/trade/close/{orderNo}")
- public R cancel(@PathVariable String orderNo){
- log.info("用户取消订单");
-
- aliPayService.cancelOrder(orderNo);
- return R.ok().setMessage("用户已取消订单");
- }
-
- /**
- * 查询订单
- * @param orderNo
- * @return
- */
- @ApiOperation("支付宝支付查询订单")
- @GetMapping("/trade/query/{orderNo}")
- public R queryOrder(@PathVariable String orderNo){
-
- log.info("查询订单");
- String result = aliPayService.queryOrder(orderNo);
- return R.ok().setMessage("查询成功").data("result",result);
- }
-
- /**
- * 申请退款
- * @param orderNo
- * @param reason
- * @return
- */
- @ApiOperation("申请退款")
- @PostMapping("/trade/refund/{orderNo}/{reason}")
- public R refunds(@PathVariable String orderNo, @PathVariable String reason){
-
- log.info("申请退款");
- aliPayService.refund(orderNo, reason);
- return R.ok();
- }
-
- /**
- * 查询退款
- * @param orderNo
- * @return
- * @throws Exception
- */
- @ApiOperation("查询退款")
- @GetMapping("/trade/fastpay/refund/{orderNo}")
- public R queryRefund(@PathVariable String orderNo) throws Exception {
-
- log.info("查询退款");
-
- String result = aliPayService.queryRefund(orderNo);
- return R.ok().setMessage("查询成功").data("result", result);
- }
-
- /**
- * 根据账单类型和日期获取账单url地址
- *
- * @param billDate
- * @param type
- * @return
- */
- @ApiOperation("获取账单url")
- @GetMapping("/bill/downloadurl/query/{billDate}/{type}")
- public R queryTradeBill(
- @PathVariable String billDate,
- @PathVariable String type) {
- log.info("获取账单url");
- String downloadUrl = aliPayService.queryBill(billDate, type);
- return R.ok().setMessage("获取账单url成功").data("downloadUrl", downloadUrl);
- }
-
-
- }
支付类型
- package com.wx.enums.wxpay;
-
- import lombok.AllArgsConstructor;
- import lombok.Getter;
-
- @AllArgsConstructor
- @Getter
- public enum AliPayTradeState {
-
- /**
- * 支付成功
- */
- SUCCESS("TRADE_SUCCESS"),
-
- /**
- * 未支付
- */
- NOTPAY("WAIT_BUYER_PAY"),
-
- /**
- * 已关闭
- */
- CLOSED("TRADE_CLOSED"),
-
- /**
- * 退款成功
- */
- REFUND_SUCCESS("REFUND_SUCCESS"),
-
- /**
- * 退款失败
- */
- REFUND_ERROR("REFUND_ERROR");
-
- /**
- * 类型
- */
- private final String type;
- }
- package com.wx.service.impl;
-
- import com.alibaba.fastjson.JSONObject;
- import com.alipay.api.AlipayApiException;
- import com.alipay.api.AlipayClient;
- import com.alipay.api.DefaultAlipayClient;
- import com.alipay.api.request.*;
- import com.alipay.api.response.*;
- import com.google.gson.Gson;
- import com.google.gson.JsonSyntaxException;
- import com.google.gson.internal.LinkedTreeMap;
- import com.wx.entity.OrderInfo;
- import com.wx.entity.RefundInfo;
- import com.wx.enums.OrderStatus;
- import com.wx.enums.PayType;
- import com.wx.enums.wxpay.AliPayTradeState;
- import com.wx.service.AliPayService;
- import com.wx.service.OrderInfoService;
- import com.wx.service.PaymentInfoService;
- import com.wx.service.RefundInfoService;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.core.env.Environment;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
-
- import javax.annotation.Resource;
- import java.math.BigDecimal;
- import java.util.HashMap;
- import java.util.LinkedHashMap;
- import java.util.Map;
- import java.util.concurrent.locks.ReentrantLock;
-
- @Service
- @Slf4j
- public class AliPayServiceImpl implements AliPayService {
-
-
- @Resource
- private OrderInfoService orderInfoService;
-
- @Resource
- private AlipayClient alipayClient;
-
- @Resource
- private Environment config;
-
- @Resource
- private PaymentInfoService paymentInfoService;
-
- @Resource
- private RefundInfoService refundsInfoService;
-
- private final ReentrantLock lock = new ReentrantLock();
- /**
- * 统一收单下单支付页面接口调用
- * @param productId
- * @return
- */
- @Transactional(rollbackFor = Exception.class)
- @Override
- public String tradeCteate(Long productId) {
-
- try {
- //生成订单
- log.info("生成订单");
- OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId, PayType.ALIPAY.getType());
-
- //调用支付宝接口
- AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
- //配置需要的公共请求参数
- //支付完成后,支付宝发起异步通知的地址
- request.setNotifyUrl(config.getProperty("alipay.notify-url"));
- //支付完成后,我们想让页面跳转回成功的页面,配置returnUrl
- request.setReturnUrl(config.getProperty("alipay.return-url"));
-
- //组装当前业务方法的请求参数
- JSONObject bizContent = new JSONObject();
- bizContent.put("out_trade_no", orderInfo.getOrderNo());
- //因为微信是分,这里支付宝是元,所以进行更改
- BigDecimal total = new BigDecimal(orderInfo.getTotalFee().toString()).divide(new BigDecimal("100"));
- bizContent.put("total_amount", total);
- bizContent.put("subject", orderInfo.getTitle());
- bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
-
- request.setBizContent(bizContent.toString());
-
- //执行请求,调用支付宝接口
- AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
-
- if(response.isSuccess()){
- log.info("调用成功,返回结果 ===> " + response.getBody());
- return response.getBody();
- } else {
- log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
- throw new RuntimeException("创建支付交易失败");
- }
- } catch (AlipayApiException e) {
- e.printStackTrace();
- throw new RuntimeException("创建支付交易失败");
- }
- }
-
- /**
- * 处理业务,修改订单状态,记录支付日志
- * @param params
- */
- @Transactional(rollbackFor = Exception.class)
- @Override
- public void processOrder(Map
params) { - log.info("处理订单");
-
- //获取订单号
- String orderNo = params.get("out_trade_no");
-
- /**
- * 在对业务数据进行状态检查和处理之前
- * 要采用数据锁进行控制
- * 以避免函数重入造成数据混乱
- */
- if (lock.tryLock()) {
- try {
-
- //处理重复的通知
- //接口调用的幂等性:无论接口被调用多少次,以下业务只执行一次
- String orderStatus = orderInfoService.getOrderStatus(orderNo);
- if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {//如果不等于未支付
- return;
- }
-
- //更新订单状态
- orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
-
- //记录订单日志
- paymentInfoService.createPaymentInfoForAlipay(params);
- }finally {
- //释放锁
- lock.unlock();
- }
- }
- }
-
- /**
- * 用户取消支付宝订单
- * @param orderNo
- */
- @Override
- public void cancelOrder(String orderNo) {
-
- //调用支付宝提供的统一收单交易关闭接口
- this.closeOrder(orderNo);
-
-
- //更新用户订单状态
- orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.CANCEL);
- }
-
- /**
- * 支付宝查询订单
- * @param orderNo
- * @return 返回订单查询结果 如果返回null则表示订单不存在
- */
- @Override
- public String queryOrder(String orderNo) {
-
- try {
- log.info("查询接口调用 === 》"+orderNo);
-
- AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
- JSONObject bizContent = new JSONObject();
- bizContent.put("out_trade_no", orderNo);
- request.setBizContent(bizContent.toString());
-
- AlipayTradeQueryResponse response = alipayClient.execute(request);
- if(response.isSuccess()){
- log.info("调用成功,返回结果 ===> " + response.getBody());
- return response.getBody();
-
- } else {
- log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
- //throw new RuntimeException("查询接口调用失败");//因为会出现只点击但未扫码,支付宝没有创建订单的情况,就会报错。所以直接取消。这样直接把这个订单改成取消
- return null;
- }
- } catch (AlipayApiException e) {
- e.printStackTrace();
- throw new RuntimeException("查询接口调用失败");
- }
-
- }
-
- /**
- * 根据订单号查询支付宝支付查单接口,核实订单状态
- * 如果订单未创建,则直接更新商户端订单状态
- * 如果订单已经支付,则更新商户端订单状态
- * 如果订单未支付,则调用关单接口关闭订单,并更新商户端订单状态
- * @param orderNo
- */
- @Override
- public void checkOrderStatus(String orderNo) {
-
- log.warn("根据订单号核实订单状态 ===》"+orderNo);
-
- String result = this.queryOrder(orderNo);
-
- //订单未创建
- if (result == null){
- log.warn("核实订单未创建 ===》"+orderNo);
-
- //更新本地订单状态
- orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.CLOSED);
- }
-
- try {
- //解析查单响应结果
- Gson gson = new Gson();
- HashMap
resultMap = gson.fromJson(result, HashMap.class); - LinkedTreeMap alipayTradeQueryResponse = resultMap.get("alipay_trade_query_response");
- String tradeStatus = alipayTradeQueryResponse.get("trade_status").toString();//获取订单状态
-
- //判断订单是否是未支付的订单
- if (AliPayTradeState.NOTPAY.getType().equals(tradeStatus)){
- log.info("未支付订单==== 》"+orderNo);
-
- //如果订单未支付,则调用关单接口关闭订单,并更新商户端订单状态
- this.closeOrder(orderNo);
-
- //并更新商户端订单状态
- orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.CLOSED);
- log.info("更改订单状态==== 》"+OrderStatus.CLOSED);
-
- }
- if(AliPayTradeState.SUCCESS.getType().equals(tradeStatus)) {
- log.warn("核实订单已支付 ===> {}", orderNo);
-
- //如果订单已支付,则更新商户端订单状态
- orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
-
- //并记录支付日志
- paymentInfoService.createPaymentInfoForAlipay(alipayTradeQueryResponse);
- }
- } catch (NullPointerException e) {
- log.info("支付宝未创建订单,改为超时已关闭"+orderNo);
- }
-
- }
-
- /**
- * 关单接口的调用
- * @param orderNo
- */
- private void closeOrder(String orderNo) {
-
- try {
- log.info("关单接口的调用,订单号:"+orderNo);
-
- AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
- JSONObject bizContent = new JSONObject();
- bizContent.put("out_trade_no", orderNo);
- request.setBizContent(bizContent.toString());
- //调用关单接口
- AlipayTradeCloseResponse response = alipayClient.execute(request);
-
-
- if(response.isSuccess()){
- log.info("调用成功,返回结果 ===> " + response.getBody());
-
- } else {
- log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
- // throw new RuntimeException("关单接口调用失败");//因为会出现只点击但未扫码,支付宝没有创建订单的情况,就会报错。所以直接取消。这样直接把这个订单改成取消
- }
- } catch (AlipayApiException e) {
- e.printStackTrace();
- throw new RuntimeException("关单接口调用失败");
- }
-
- }
-
-
- /**
- * 退款
- * @param orderNo
- * @param reason
- */
- @Transactional(rollbackFor = Exception.class)
- @Override
- public void refund(String orderNo, String reason) {
-
- try {
- log.info("调用退款API");
-
- //创建退款单
- RefundInfo refundInfo = refundsInfoService.createRefundByOrderNoForAliPay(orderNo, reason);
-
- //调用统一收单交易退款接口
- AlipayTradeRefundRequest request = new AlipayTradeRefundRequest ();
-
- //组装当前业务方法的请求参数
- JSONObject bizContent = new JSONObject();
- bizContent.put("out_trade_no", orderNo);//订单编号
- BigDecimal refund = new BigDecimal(refundInfo.getRefund().toString()).divide(new BigDecimal("100"));
- //BigDecimal refund = new BigDecimal("2").divide(new BigDecimal("100"));
- bizContent.put("refund_amount", refund);//退款金额:不能大于支付金额
- bizContent.put("refund_reason", reason);//退款原因(可选)
-
- request.setBizContent(bizContent.toString());
-
- //执行请求,调用支付宝接口
- AlipayTradeRefundResponse response = alipayClient.execute(request);
-
- if(response.isSuccess()){
- log.info("调用成功,返回结果 ===> " + response.getBody());
-
- //更新订单状态
- orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);
-
- //更新退款单
- refundsInfoService.updateRefundForAliPay(
- refundInfo.getRefundNo(),
- response.getBody(),
- AliPayTradeState.REFUND_SUCCESS.getType()); //退款成功
-
- } else {
- log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
-
- //更新订单状态
- orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_ABNORMAL);
-
- //更新退款单
- refundsInfoService.updateRefundForAliPay(
- refundInfo.getRefundNo(),
- response.getBody(),
- AliPayTradeState.REFUND_ERROR.getType()); //退款失败
- }
-
-
- } catch (AlipayApiException e) {
- e.printStackTrace();
- throw new RuntimeException("创建退款申请失败");
- }
- }
-
- /**
- * 查询退款
- * @param orderNo
- * @return
- */
- @Override
- public String queryRefund(String orderNo) {
-
- try {
- log.info("查询退款接口调用 ===> {}", orderNo);
-
- AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest();
- JSONObject bizContent = new JSONObject();
- bizContent.put("out_trade_no", orderNo);
- bizContent.put("out_request_no", orderNo);
- request.setBizContent(bizContent.toString());
-
- AlipayTradeFastpayRefundQueryResponse response = alipayClient.execute(request);
- if(response.isSuccess()){
- log.info("调用成功,返回结果 ===> " + response.getBody());
- return response.getBody();
- } else {
- log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
- //throw new RuntimeException("查单接口的调用失败");
- return null;//订单不存在
- }
-
- } catch (AlipayApiException e) {
- e.printStackTrace();
- throw new RuntimeException("查单接口的调用失败");
- }
- }
-
- /**
- * 申请账单
- * @param billDate
- * @param type
- * @return
- */
- @Override
- public String queryBill(String billDate, String type) {
-
- try {
-
- AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest();
- JSONObject bizContent = new JSONObject();
- bizContent.put("bill_type", type);
- bizContent.put("bill_date", billDate);
- request.setBizContent(bizContent.toString());
- AlipayDataDataserviceBillDownloadurlQueryResponse response = alipayClient.execute(request);
-
- if(response.isSuccess()){
- log.info("调用成功,返回结果 ===> " + response.getBody());
-
- //获取账单下载地址
- Gson gson = new Gson();
- HashMap
resultMap = gson.fromJson(response.getBody(), HashMap.class); - LinkedTreeMap billDownloadurlResponse = resultMap.get("alipay_data_dataservice_bill_downloadurl_query_response");
- String billDownloadUrl = (String)billDownloadurlResponse.get("bill_download_url");
-
- return billDownloadUrl;
- } else {
- log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
- throw new RuntimeException("申请账单失败");
- }
-
- } catch (AlipayApiException e) {
- e.printStackTrace();
- throw new RuntimeException("申请账单失败");
- }
- }
-
- }
- package com.wx.service;
-
- import com.alipay.api.AlipayApiException;
-
- import java.util.Map;
-
- public interface AliPayService {
- String tradeCteate(Long productId);//统一收单下单支付页面接口调用
-
- void processOrder(Map
params) ;//处理业务,修改订单状态,记录支付日志 -
- void cancelOrder(String orderNo);//支付宝用户取消订单
-
- String queryOrder(String orderNo);//支付宝查询订单
-
- void checkOrderStatus(String orderNo);//处理超时的订单
-
- void refund(String orderNo, String reason);//申请退款
-
- String queryRefund(String orderNo);
-
- String queryBill(String billDate, String type);//下载账单
- }
- package com.wx.task;
-
- import com.google.gson.Gson;
- import com.wx.entity.OrderInfo;
- import com.wx.enums.PayType;
- import com.wx.service.AliPayService;
- import com.wx.service.OrderInfoService;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.scheduling.annotation.Scheduled;
- import org.springframework.stereotype.Component;
-
- import javax.annotation.Resource;
- import java.util.List;
-
- @Slf4j
- @Component
- public class AliPayTask {
-
- @Resource
- private OrderInfoService orderInfoService;
-
- @Resource
- private AliPayService aliPayService;
-
- /**
- * 从第0秒开始每隔30秒执行1次,查询创建超过5分钟,并且未支付的订单
- * @throws Exception
- */
- @Scheduled(cron = "0/30 * * * * ?")
- public void orderConfirm() throws Exception {
- log.info("支付宝定时任务启动====");
-
- List
orderInfoList = orderInfoService.getNopayOrderByDuration(5, PayType.ALIPAY.getType()); -
- for (OrderInfo orderInfo : orderInfoList){
- String orderNo = orderInfo.getOrderNo();
- log.warn("超时订单 === > {}", orderNo);
-
- //核实订单状态:调用支付宝支付查单接口
- aliPayService.checkOrderStatus(orderNo);
- }
-
-
- }
- }
alipay.trade.page.pay(统一收单下单并支付页面接口)
NATAPP -
https://natapp.cn/login内网穿透教程:
NATAPP1分钟快速新手图文教程 - NATAPP-内网穿透 基于ngrok的国内高速内网映射工具
https://natapp.cn/article/natapp_newbie