微信支付及支付回调
Posted 猫的幻想曲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了微信支付及支付回调相关的知识,希望对你有一定的参考价值。
1.微信支付
通过微信平台为商家提供代收款服务
1.1微信支付的业务--商户注册微信支付业务:
1.2申请支付订单--商户向支付平台申请支付链接
支付订单,并不是用户提交的商品订单,而是商品向微信支付平台申请的支付链接
1.2.1导入微信支付的依赖
wxpay的maven依赖:
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
1.2.2创建微信支付配置类,配置商品信息
创建一类,实现WXPayConfig接口
重写三分方法,分别设置商品AppID\\商户ID\\商户密钥
package com.qfedu.config;
import com.github.wxpay.sdk.WXPayConfig;
import java.io.InputStream;
/**
* @Description:
* @Author : Jerry
* @create : 2022-07-02 18:16
*/
public class MyPayConfig implements WXPayConfig
@Override
public String getAppID()
return "wx632c8f211f8122c6";
@Override
public String getMchID()
return "1497984412";
@Override
public String getKey()
return "sbNCm1JnevqI36LrEaxFwcaT0hkGxFnc";
@Override
public InputStream getCertStream()
return null;
@Override
public int getHttpConnectTimeoutMs()
return 0;
@Override
public int getHttpReadTimeoutMs()
return 0;
1.2.3设置订单的参数
//设置当前订单信息
HashMap<String,String> data = new HashMap<>();
data.put("fee_type","CNY"); //支付币种
data.put("total_fee","0.1"); //支付总金额
data.put("body","咪咪虾条"); // 商品描述
//使用当前用户订单的编号作为当前支付交易的交易号
data.put("out_trade_no", orderId);
data.put("trade_type","NATIVE"); //交易类型
data.put("notify_url","/pay/success"); //设置支付完成时的回调方法接口
修改OrderService的实现类:
package com.qfedu.fmmall.service.impl;
import com.qfedu.fmmall.dao.OrderItemMapper;
import com.qfedu.fmmall.dao.OrdersMapper;
import com.qfedu.fmmall.dao.ProductSkuMapper;
import com.qfedu.fmmall.dao.ShoppingCartMapper;
import com.qfedu.fmmall.entity.OrderItem;
import com.qfedu.fmmall.entity.Orders;
import com.qfedu.fmmall.entity.ProductSku;
import com.qfedu.fmmall.entity.ShoppingCartVO;
import com.qfedu.fmmall.service.OrderService;
import com.qfedu.fmmall.vo.R;
import com.qfedu.fmmall.vo.ResStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.*;
/**
* @Description:
* @Author : Jerry
* @create : 2022-07-01 17:46
*/
@Service
public class OrderServiceImpl implements OrderService
@Autowired
private ShoppingCartMapper shoppingCartMapper;
@Autowired
private OrdersMapper ordersMapper;
@Autowired
private OrderItemMapper orderItemMapper;
@Autowired
private ProductSkuMapper productSkuMapper;
/**
* 保存订单业务
* @param cids 在购物车列表页面选择得购物车记录的ID
* @param order
* @return
*/
@Transactional
public Map<String,String> addOrder(String cids, Orders order)
Map<String,String> map = new HashMap<>();
//1.校验库存:根据cids查询当前订单中关联的购物车记录详情(包括库存)
String[] arr = cids.split(",");
List<Integer> cidList = new ArrayList<>();
for (int i = 0; i<arr.length; i++)
cidList.add(Integer.parseInt(arr[i]));
List<ShoppingCartVO> list = shoppingCartMapper.selectShopCartByCids(cidList);
boolean f = true;
String untitled = "";
for (ShoppingCartVO sc: list)
if(Integer.parseInt(sc.getCartNum())>sc.getSkuStock())
f = false;
//获取所有商品名称,以,分割拼接称字符串
untitled = untitled+sc.getProductName()+",";
if(f)
//2. 保存订单
//收货人信息:姓名,电话,地址,商品总价格,支付方式,订单创建时间
//订单初始状态(待支付 1)
order.setUntitled(untitled);
order.setCreateTime(new Date());
order.setStatus("1");
//生成订单编号
String orderId = UUID.randomUUID().toString().replace("-", " ");
order.setOrderId(orderId);
ordersMapper.insert(order);
//3.生成商品快照
for(ShoppingCartVO sc:list)
int cnum = Integer.parseInt(sc.getCartNum());
String itemId = System.currentTimeMillis()+""+(new Random().nextInt(89999)+10000);//增大容错率
OrderItem orderItem = new OrderItem(itemId, orderId, sc.getProductId(), sc.getProductName(), sc.getProductImg(), sc.getSkuId(), sc.getSkuName(), new BigDecimal(sc.getSellPrice())
, cnum, new BigDecimal(sc.getSellPrice()*cnum), new Date(), new Date(), 0);
orderItemMapper.insert(orderItem);
//4.扣减库存:根据套餐id修改套餐库存量
for (ShoppingCartVO sc: list)
String skuId = sc.getSkuId();
int newStock = sc.getSkuStock() - Integer.parseInt(sc.getCartNum());
//updateByExampleSelective只添加修改的,其他的不变
ProductSku productSku = new ProductSku();
productSku.setSkuId(skuId);
productSku.setStock(newStock);
productSkuMapper.updateByPrimaryKeySelective(productSku);
//5.删除购物车:当购物车中得记录购买成功之后,购物车中对应做删除操作
for(int cid:cidList)
shoppingCartMapper.deleteByPrimaryKey(cid);
map.put("orderId",orderId);
map.put("productNames",untitled);
return map;
else
//表示库存不足
return null;
OrderController:(微信支付实现)
package com.qfedu.controller;
import com.github.wxpay.sdk.WXPay;
import com.qfedu.config.MyPayConfig;
import com.qfedu.fmmall.entity.Orders;
import com.qfedu.fmmall.service.OrderService;
import com.qfedu.fmmall.vo.R;
import com.qfedu.fmmall.vo.ResStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* @Description:
* @Author : Jerry
* @create : 2022-07-01 19:40
*/
@RestController
@CrossOrigin
@RequestMapping("/order")
public class OrderController
@Autowired
private OrderService orderService;
@PostMapping("/add")
public R add(String cids, @RequestBody Orders order)
R r = null;
try
Map<String, String> orderInfo = orderService.addOrder(cids, order);
String orderId = orderInfo.get("orderId");
if(orderId!=null)
//设置当前订单信息
HashMap<String,String> data = new HashMap<>();
data.put("fee_type","CNY"); //支付币种
data.put("total_fee",order.getActualAmount()*100+""); //支付总金额
data.put("body",orderInfo.get("productNames")); // 商品描述
//使用当前用户订单的编号作为当前支付交易的交易号
data.put("out_trade_no", orderId);
data.put("trade_type","NATIVE"); //交易类型
data.put("notify_url","/pay/success"); //设置支付完成时的回调方法接口
//发送请求,获取响应
//微信支付:申请支付连接
WXPay wxPay = new WXPay(new MyPayConfig());
Map<String, String> resp = wxPay.unifiedOrder(data);
orderInfo.put("payUrl",resp.get("code_url"));
//orderInfo中包含了:订单编号,购买的商品名称,支付链接
r = new R(ResStatus.OK,"提交订单成功!!!",orderInfo);
else
r = new R(ResStatus.NO,"提交订单失败!!!",null);
catch (Exception e)
r = new R(ResStatus.NO,"提交订单失败!!!",null);
return r;
2.支付回调
支付回调:当用户支付成功之后,支付平台会向我们指定的服务器接口发送请求传递订单支付状态数据
2.1 创建一个控制器定义回调接口
package com.qfedu.controller;
import com.github.wxpay.sdk.WXPayUtil;
import com.qfedu.fmmall.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* @Description: 回调接口:当用户支付成功之后,微信支付平台就会请求这个接口,将支付状态的数据传递过来
* @Author : Jerry
* @create : 2022-07-02 18:44
*/
@RestController
@RequestMapping("/pay")
public class PayController
@Autowired
private OrderService orderService;
/**
* 1.接收微信支付平台传递的数据(使用request的输入流接收)
*/
@PostMapping("/callback")
public String success(HttpServletRequest request) throws Exception
ServletInputStream is = request.getInputStream();
byte[] bs = new byte[1024];
int len = -1;
StringBuilder builder = new StringBuilder();
while ((len = is.read(bs)) != -1)
builder.append(new String(bs,0,len));
String s = builder.toString();
//使用帮助类将xml接口的字符串转换成map
Map<String,String> map = WXPayUtil.xmlToMap(s);
if(map!=null && "success".equalsIgnoreCase(map.get("result_code")))
//支付成功
//2.修改订单状态为“待发货/已支付”
String orderId = map.get("out_trade_no");
int i = orderService.updateOrderStatus(orderId, "2");
System.out.println("--orderId:"+orderId);
//3.响应微信支付平台
if(i>0)
HashMap<String,String> resp = new HashMap<>();
resp.put("return_code","success");
resp.put("return_msg","OK");
resp.put("appid",map.get("appid"));
resp.put("result_code","success");
return WXPayUtil.mapToXml(resp);
//支付失败
return null;
2.2 设置回调URL
在订单接口中申请支付链接的时候将回调接口的路径设置给微信支付平台
2.3 Ngrok实现内网穿透
网站:ngrok.cc
注册账号,申请隧道id
开通隧道:
获取隧道id:
下载客户端
2.4 前端通过轮询访问获取订单支付状态
流程图:
接口实现:
service接口:
package com.qfedu.fmmall.service;
import com.qfedu.fmmall.entity.Orders;
import com.qfedu.fmmall.vo.R;
import java.util.Map;
/**
* @Description: 订单接口
* @Author : Jerry
* @create : 2022-07-01 17:45
*/
public interface OrderService
public Map<String,String> addOrder(String cids, Orders order);
public int updateOrderStatus(String orderId,String status);
public R getOrderById(String orderId);
service实现类:
@Override
public R getOrderById(String orderId)
Orders orders = ordersMapper.selectByPrimaryKey(orderId);
return new R(ResStatus.OK,"success",orders.getStatus());
controller:
@GetMapping("/status/oid")
public R getOrderStatus(@PathVariable("oid") String orderId,
@RequestHeader("token")String token)
R r = orderService.getOrderById(orderId);
return r;
2.5 webSocket消息推送:
实现流程:
2.5.1 创建webSocket服务器
添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
添加websocket服务节点配置:(Java配置方式)
@Configuration
public class WebSocketConfig
@Bean
public ServerEndpointExporter getServerEndpointExporter()
return new ServerEndpointExporter();
创建websocket服务器:
package com.qfedu.websocket;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PostMapping;
import javax.websocket.OnClose;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Description:
* @Author : Jerry
* @create : 2022-07-03 13:06
*/
@Component
@ServerEndpoint("/webSocket/oid")
public class WebSocketServer
private static ConcurrentHashMap<String,Session> sessionMap = new ConcurrentHashMap<>();
/**
* 前端发送请求建立websocket连接,就会执行 @OnOpen 方法
*/
@OnOpen
public void open(@PathParam("oid") String orderId, Session session)
System.out.println("---------建立连接:"+orderId);
sessionMap.put(orderId,session);
/**
* 前端关闭页面或者主动关闭webSocket连接,都会执行close
*/
@OnClose
public void close(@PathParam("oid") String orderId)
sessionMap.remove(orderId);
public static void sendMsg(String orderId,String msg)
try
Session session = sessionMap.get(orderId);
session.getBasicRemote().sendText(msg);
catch (IOException e)
e.printStackTrace();
修改回调接口:
java微信支付v3系列——5.微信支付成功回调
目录
java微信支付v3系列——1.微信支付准备工作
java微信支付v3系列——2.微信支付基本配置
java微信支付v3系列——3.订单创建准备操作
java微信支付v3系列——4.创建订单的封装及使用
java微信支付v3系列——5.微信支付成功回调
java微信支付v3系列——6.微信支付查询订单API
java微信支付v3系列——7.微信支付之申请退款
java微信支付v3系列——8.微信支付之退款成功回调
java微信支付v3系列——9.微信支付之商家转账API
正文
同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
如果在所有通知频率后没有收到微信侧回调,商户应调用查询订单接口确认订单状态。
特别提醒:商户系统对于开启结果通知的内容一定要做签名验证,并校验通知的信息是否与商户侧的信息一致,防止数据泄露导致出现“假通知”,造成资金损失。
该链接是通过基础下单接口中的请求参数“notify_url”来设置的,要求必须为https地址。请确保回调URL是外部可正常访问的,且不能携带后缀参数,否则可能导致商户无法接收到微信的回调通知信息。
微信验签工具类
啥也不说了,直接复制即可,也没什么注意事项。
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
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.*;
public class WechatPayValidatorForRequest
protected static final Logger log = LoggerFactory.getLogger(WechatPayValidatorForRequest.class);
/**
* 应答超时时间,单位为分钟
*/
protected static final long RESPONSE_EXPIRED_MINUTES = 5;
protected final Verifier verifier;
protected final String body;
protected final String requestId;
public WechatPayValidatorForRequest(Verifier verifier, String body, String requestId)
this.verifier = verifier;
this.body = body;
this.requestId = requestId;
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, request.getHeader(REQUEST_ID));
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";
对称解密方法
创建 WxPayCallbackUtil 微信支付成功回调类,decryptFromResource用于解密微信
import com.card.config.WxPayConfig;
import com.card.exception.DefaultException;
import com.card.pay.domain.WxchatCallbackRefundData;
import com.card.pay.domain.WxchatCallbackSuccessData;
import com.card.utils.HttpUtils;
import com.card.utils.WechatPayValidatorForRequest;
import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
@Slf4j
public class WxPayCallbackUtil
/**
* 对称解密
*/
private static String decryptFromResource(HashMap<String, Object> bodyMap, WxPayConfig wxPayConfig)
// 通知数据
Map<String, String> resourceMap = (Map) bodyMap.get("resource");
// 数据密文
String ciphertext = resourceMap.get("ciphertext");
// 随机串
String nonce = resourceMap.get("nonce");
// 附加数据
String associateData = resourceMap.get("associated_data");
AesUtil aesUtil = new AesUtil(wxPayConfig.getKey().getBytes(StandardCharsets.UTF_8));
try
return aesUtil.decryptToString(associateData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
catch (GeneralSecurityException e)
e.printStackTrace();
throw new DefaultException("解密失败");
封装微信返回的数据对象
微信返回给我们的数据是json格式的,我们需要将其转换成java对象,方便我们调用。
import cn.hutool.core.date.DateUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.util.Date;
/**
* @author cv大魔王
* @version 1.0
* @description 微信支付成功回调返回的数据
* @date 2022/8/4
*/
@Data
@Slf4j
public class WxchatCallbackSuccessData
/**
* 商户订单号
*/
private String orderId;
/**
* 微信支付系统生成的订单号
*/
private String transactionId;
/**
* 交易状态
* SUCCESS:支付成功
* REFUND:转入退款
* NOTPAY:未支付
* CLOSED:已关闭
* REVOKED:已撤销(付款码支付)
* USERPAYING:用户支付中(付款码支付)
* PAYERROR:支付失败(其他原因,如银行返回失败)
*/
private String tradestate;
/**
* 支付完成时间
*/
private Date successTime;
/**
* 交易类型
* JSAPI:公众号支付
* NATIVE:扫码支付
* APP:APP支付
* MICROPAY:付款码支付
* MWEB:H5支付
* FACEPAY:刷脸支付
*/
private String tradetype;
/**
* 订单总金额
*/
private BigDecimal totalMoney;
public Date getSuccessTime()
return successTime;
public void setSuccessTime(String successTime)
// Hutool工具包的方法,自动识别一些常用格式的日期字符串
this.successTime = DateUtil.parse(successTime);
支付成功回调使用方法
我们先不看回调是如何封装的,先来看使用方法。
@Autowired
private WxPayConfig wxPayConfig;
@Autowired
private Verifier verifier;
@ApiOperation("微信支付回调接口")
@PostMapping("/wx/callback")
public String courseNative(HttpServletRequest request, HttpServletResponse response)
return WxPayCallbackUtil.wxPaySuccessCallback(request, response, verifier, wxPayConfig, callbackData ->
// TODO 处理你的业务逻辑,下面说一下一般业务逻辑处理方法
log.info("微信支付返回的信息:", callbackData);
// 1.根据订单id获取订单信息
// 2.判断金额是否相符,如果不相符则调用退款接口,并取消该订单,通知客户支付金额不符
// 3.查询订单状态是否是未支付,如果是未支付则改为已支付,填充其他逻辑,
// 4.如果是其他状态综合你的业务逻辑来处理
// 5.如果是虚拟物品,则对应充值,等等其他逻辑
);
封装后就这么一句话,就获取到了微信返回给我们的数据,其中callbackData就是上面封装的WxchatCallbackSuccessData对象,我们只需要在回调方法中完成我们的业务逻辑即可。
支付成功回调方法封装
import java.util.function.Consumer;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.card.pay.domain.WxchatCallbackSuccessData;
import com.card.utils.HttpUtils;
import com.card.utils.WechatPayValidatorForRequest;
@Slf4j
public class WxPayCallbackUtil
/**
* 微信支付创建订单回调方法
* @param verifier 证书
* @param wxPayConfig 微信配置
* @param businessCallback 回调方法,用于处理业务逻辑
* @return json格式的string数据,直接返回给微信
*/
public static String wxPaySuccessCallback(HttpServletRequest request, HttpServletResponse response, Verifier verifier, WxPayConfig wxPayConfig, Consumer<WxchatCallbackSuccessData> businessCallback)
Gson gson = new Gson();
// 1.处理通知参数
final String body = HttpUtils.readData(request);
HashMap<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
// 2.签名验证
WechatPayValidatorForRequest wechatForRequest = new WechatPayValidatorForRequest(verifier, body, (String) bodyMap.get("id"));
try
if (!wechatForRequest.validate(request))
// 通知验签失败
response.setStatus(500);
final HashMap<String, Object> map = new HashMap<>();
map.put("code", "ERROR");
map.put("message", "通知验签失败");
return gson.toJson(map);
catch (IOException e)
e.printStackTrace();
// 3.获取明文数据
String plainText = decryptFromResource(bodyMap,wxPayConfig);
HashMap<String,Object> plainTextMap = gson.fromJson(plainText, HashMap.class);
log.info("plainTextMap:",plainTextMap);
// 4.封装微信返回的数据
WxchatCallbackSuccessData callbackData = new WxchatCallbackSuccessData();
callbackData.setSuccessTime(String.valueOf(plainTextMap.get("success_time")));
callbackData.setOrderId(String.valueOf(plainTextMap.get("out_trade_no")));
callbackData.setTransactionId(String.valueOf(plainTextMap.get("transaction_id")));
callbackData.setTradestate(String.valueOf(plainTextMap.get("trade_state")));
callbackData.setTradetype(String.valueOf(plainTextMap.get("trade_type")));
String amount = String.valueOf(plainTextMap.get("amount"));
HashMap<String,Object> amountMap = gson.fromJson(amount, HashMap.class);
String total = String.valueOf(amountMap.get("total"));
callbackData.setTotalMoney(new BigDecimal(total).movePointLeft(2));
log.info("callbackData:",callbackData);
if ("SUCCESS".equals(callbackData.getTradestate()))
// 执行业务逻辑
businessCallback.accept(callbackData);
以上是关于微信支付及支付回调的主要内容,如果未能解决你的问题,请参考以下文章