目录
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;@Slf4jpublic 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@Slf4jpublic 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); }}
支付成功回调使用方法
我们先不看回调是如何封装的,先来看使用方法。
@Autowiredprivate WxPayConfig wxPayConfig;@Autowiredprivate 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;@Slf4jpublic 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); } // 5.成功应答 response.setStatus(200); final HashMap<String, Object> resultMap = new HashMap<>(); resultMap.put("code", "SUCCESS"); resultMap.put("message", "成功"); return gson.toJson(resultMap); } /** * 对称解密 */ 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("解密失败"); } }}
注意看方法的最后一个参数,Consumer<WxchatCallbackSuccessData> businessCallback
,这是一个jdk自带的函数式接口,通过接口回调的方式,完善业务逻辑。函数式接口或者回调函数不理解的可以观看jdk自带函数接口以及其系列文章,当然您也可以直接使用。
麻烦点个赞再走吧~~