目錄
java微信支付v3系列——1.微信支付準備工作
java微信支付v3系列——2.微信支付基本配置
java微信支付v3系列——3.訂單創(chuàng)建準備操作
java微信支付v3系列——4.創(chuàng)建訂單的封裝及使用
java微信支付v3系列——5.微信支付成功回調(diào)
java微信支付v3系列——6.微信支付查詢訂單API
java微信支付v3系列——7.微信支付之申請退款
java微信支付v3系列——8.微信支付之退款成功回調(diào)
java微信支付v3系列——9.微信支付之商家轉(zhuǎn)賬API
正文
同樣的通知可能會多次發(fā)送給商戶系統(tǒng)。商戶系統(tǒng)必須能夠正確處理重復(fù)的通知。 推薦的做法是,當(dāng)商戶系統(tǒng)收到通知進行處理時,先檢查對應(yīng)業(yè)務(wù)數(shù)據(jù)的狀態(tài),并判斷該通知是否已經(jīng)處理。如果未處理,則再進行處理;如果已處理,則直接返回結(jié)果成功。在對業(yè)務(wù)數(shù)據(jù)進行狀態(tài)檢查和處理之前,要采用數(shù)據(jù)鎖進行并發(fā)控制,以避免函數(shù)重入造成的數(shù)據(jù)混亂。
如果在所有通知頻率后沒有收到微信側(cè)回調(diào),商戶應(yīng)調(diào)用查詢訂單接口確認訂單狀態(tài)。
特別提醒:商戶系統(tǒng)對于開啟結(jié)果通知的內(nèi)容一定要做簽名驗證,并校驗通知的信息是否與商戶側(cè)的信息一致,防止數(shù)據(jù)泄露導(dǎo)致出現(xiàn)“假通知”,造成資金損失。
該鏈接是通過基礎(chǔ)下單接口中的請求參數(shù)“notify_url”來設(shè)置的,要求必須為https地址。請確?;卣{(diào)URL是外部可正常訪問的,且不能攜帶后綴參數(shù),否則可能導(dǎo)致商戶無法接收到微信的回調(diào)通知信息。
微信驗簽工具類
啥也不說了,直接復(fù)制即可,也沒什么注意事項。
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);
/**
* 應(yīng)答超時時間,單位為分鐘
*/
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));
// 拒絕過期應(yīng)答
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";
}
}
對稱解密方法
創(chuàng)建 WxPayCallbackUtil 微信支付成功回調(diào)類,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) {
// 通知數(shù)據(jù)
Map<String, String> resourceMap = (Map) bodyMap.get("resource");
// 數(shù)據(jù)密文
String ciphertext = resourceMap.get("ciphertext");
// 隨機串
String nonce = resourceMap.get("nonce");
// 附加數(shù)據(jù)
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("解密失敗");
}
}
}
封裝微信返回的數(shù)據(jù)對象
微信返回給我們的數(shù)據(jù)是json格式的,我們需要將其轉(zhuǎn)換成java對象,方便我們調(diào)用。
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 微信支付成功回調(diào)返回的數(shù)據(jù)
* @date 2022/8/4
*/
@Data
@Slf4j
public class WxchatCallbackSuccessData {
/**
* 商戶訂單號
*/
private String orderId;
/**
* 微信支付系統(tǒng)生成的訂單號
*/
private String transactionId;
/**
* 交易狀態(tài)
* SUCCESS:支付成功
* REFUND:轉(zhuǎn)入退款
* NOTPAY:未支付
* CLOSED:已關(guān)閉
* 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);
}
}
支付成功回調(diào)使用方法
我們先不看回調(diào)是如何封裝的,先來看使用方法。
@Autowired
private WxPayConfig wxPayConfig;
@Autowired
private Verifier verifier;
@ApiOperation("微信支付回調(diào)接口")
@PostMapping("/wx/callback")
public String courseNative(HttpServletRequest request, HttpServletResponse response) {
return WxPayCallbackUtil.wxPaySuccessCallback(request, response, verifier, wxPayConfig, callbackData -> {
// TODO 處理你的業(yè)務(wù)邏輯,下面說一下一般業(yè)務(wù)邏輯處理方法
log.info("微信支付返回的信息:{}", callbackData);
// 1.根據(jù)訂單id獲取訂單信息
// 2.判斷金額是否相符,如果不相符則調(diào)用退款接口,并取消該訂單,通知客戶支付金額不符
// 3.查詢訂單狀態(tài)是否是未支付,如果是未支付則改為已支付,填充其他邏輯,
// 4.如果是其他狀態(tài)綜合你的業(yè)務(wù)邏輯來處理
// 5.如果是虛擬物品,則對應(yīng)充值,等等其他邏輯
});
}
封裝后就這么一句話,就獲取到了微信返回給我們的數(shù)據(jù),其中callbackData就是上面封裝的WxchatCallbackSuccessData對象,我們只需要在回調(diào)方法中完成我們的業(yè)務(wù)邏輯即可。
支付成功回調(diào)方法封裝
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 {
/**
* 微信支付創(chuàng)建訂單回調(diào)方法
* @param verifier 證書
* @param wxPayConfig 微信配置
* @param businessCallback 回調(diào)方法,用于處理業(yè)務(wù)邏輯
* @return json格式的string數(shù)據(jù),直接返回給微信
*/
public static String wxPaySuccessCallback(HttpServletRequest request, HttpServletResponse response, Verifier verifier, WxPayConfig wxPayConfig, Consumer<WxchatCallbackSuccessData> businessCallback) {
Gson gson = new Gson();
// 1.處理通知參數(shù)
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.獲取明文數(shù)據(jù)
String plainText = decryptFromResource(bodyMap,wxPayConfig);
HashMap<String,Object> plainTextMap = gson.fromJson(plainText, HashMap.class);
log.info("plainTextMap:{}",plainTextMap);
// 4.封裝微信返回的數(shù)據(jù)
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())) {
// 執(zhí)行業(yè)務(wù)邏輯
businessCallback.accept(callbackData);
}
// 5.成功應(yīng)答
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) {
// 通知數(shù)據(jù)
Map<String, String> resourceMap = (Map) bodyMap.get("resource");
// 數(shù)據(jù)密文
String ciphertext = resourceMap.get("ciphertext");
// 隨機串
String nonce = resourceMap.get("nonce");
// 附加數(shù)據(jù)
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("解密失敗");
}
}
}
注意看方法的最后一個參數(shù),Consumer<WxchatCallbackSuccessData> businessCallback
,這是一個jdk自帶的函數(shù)式接口,通過接口回調(diào)的方式,完善業(yè)務(wù)邏輯。函數(shù)式接口或者回調(diào)函數(shù)不理解的可以觀看jdk自帶函數(shù)接口以及其系列文章,當(dāng)然您也可以直接使用。文章來源:http://www.zghlxwxcb.cn/news/detail-796826.html
麻煩點個贊再走吧~~文章來源地址http://www.zghlxwxcb.cn/news/detail-796826.html
到了這里,關(guān)于java微信支付v3系列——5.微信支付成功回調(diào)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!