微信支付和支付宝支付异步回调篇
前言: 第一章大概说明了,微信和支付宝大概支付的流程,这篇做个补充. 一般支付都要回调时补充自定义业务参数.
自定义业务参数
- 支付宝
参数 | 类型 | 是否必填 | 最大长度 | 描述 | 示例值 |
passback_params | String | 可选 | 512 | 公用回传参数。 如果请求时传递了该参数,支付宝会在异步通知时将该参数原样返回。 本参数必须进行UrlEncode之后才可以发送给支付宝。 | merchantBizType%3d3C%26merchantBizNo%3d2016010101111 |
注意: passback_params:公用回传参数,如果请求时传递了该参数,则返回给商户时会回传该参数。支付宝只会在异步通知时将该参数原样返回。
本参数必须进行UrlEncode之后才可以发送给支付宝 (注意不能为json)
示例:
//示例 merchantBizType=3C&merchantBizNo=2016010101111
String attach= "orderId="+payRequest.getOrderId()+"&payTradeType="+payRequest.getTradeType();
String passback_params = URLEncoder.encode(attach, "UTF-8");
alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
+ "\"total_amount\":\""+ total_amount +"\","
+ "\"subject\":\""+ subject +"\","
+ "\"body\":\""+ body +"\","
+ "\"passback_params\":\""+ passback_params +"\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
//请求
String result = alipayClient.pageExecute(alipayRequest).getBody();
- 微信
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
附加数据 | attach | 否 | String(127) | 深圳分店 | 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 |
注意: 与支付宝不同的是,这个字段可以为json,并且主动查询,和异步回调都会返回,只要注意类型长度就行。
示例:
//附加数据
String attach= "{\"orderId\":"+request.getOrderId()+",\"payTradeType\":"+request.getTradeType()+"}";
orderRequest = WxPayUnifiedOrderRequest.newBuilder().body(request.getBody())
// .totalFee(request.getPayAmount().multiply(new BigDecimal(PayConstant.multiple)).intValue())
.totalFee((int)request.getPayAmount())
.spbillCreateIp(request.getSpbillCreateIp())
.notifyUrl(wxCustomConfig.getWexinNotifyUrl())
.tradeType(tradeTypeStr)
.outTradeNo(request.getRequestOrderSn())
.attach(attach)
.productId(request.getSkuId())
.deviceInfo((PayTradeTypeEnum.PC_PAY_TRADE_TYPE.getCode().intValue() == request.getTradeType()) ? "WEB" : "")
.build();
//创建预付订单
result = this.createOrder(orderRequest);
异步回调
配置中得notify_url
- 微信
异步回调,有两种,一种是微信公众号(统称H5),还有一种是原生APP(开放平台)
微信异步回调文档地址: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8
响应成功返回 “OK” 失败返回 “NO”
@PostMapping(value = "app/order")
@ApiOperation("微信H5回调")
public String wxAppParseOrderNotifyResult(HttpServletRequest httpRequest, HttpServletResponse response) {
String xmlData = null;
InputStream is = null;
try {
is = httpRequest.getInputStream();
xmlData = IOUtils.toString(is, StandardCharsets.UTF_8);
log.info("Log-in-wx-parseOrderNotifyResult,接收参数:{}", WxNotifyResponse.filterIllegalityChar(xmlData));
if (StringUtils.isEmpty(xmlData)) {
return ackXml(FAIL_ACK_FLAG, response);
}
//app 类型
return wxCallBackInfo(xmlData, PayChannelEnum.PayConfigTypeEnum.APP.getCode(), response);
} catch (Exception e) {
if (is != null) {
try {
is.close();
} catch (IOException e1) {
}
}
log.error("ERROR-IN-parseOrderNotifyResult,xmlData:{}", xmlData);
}
return ackXml(FAIL_ACK_FLAG, response);
}
@PostMapping(value = "h5/order")
@ApiOperation("微信H5回调")
public String wxH5ParseOrderNotifyResult(HttpServletRequest httpRequest, HttpServletResponse response) {
String xmlData = null;
InputStream is = null;
try {
is = httpRequest.getInputStream();
xmlData = IOUtils.toString(is, StandardCharsets.UTF_8);
log.info("Log-in-wx-parseOrderNotifyResult,接收参数:{}", WxNotifyResponse.filterIllegalityChar(xmlData));
if (StringUtils.isEmpty(xmlData)) {
return ackXml(FAIL_ACK_FLAG, response);
}
//h5 类型
return wxCallBackInfo(xmlData, PayChannelEnum.PayConfigTypeEnum.H5.getCode(), response);
} catch (Exception e) {
if (is != null) {
try {
is.close();
} catch (IOException e1) {
}
}
log.error("ERROR-IN-parseOrderNotifyResult,xmlData:{}", xmlData);
}
return ackXml(FAIL_ACK_FLAG, response);
}
private String ackXml(String msg, HttpServletResponse response) {
String responseContent = null;
if (SUCCESS_ACK_FLAG.equalsIgnoreCase(msg)) {
responseContent = WxNotifyResponse.successAckXml(SUCCESS_ACK_FLAG);
} else {
responseContent = WxNotifyResponse.failAckXml(FAIL_ACK_FLAG);
}
//response.getWriter().println(responseContent);
return responseContent;
}
//业务逻辑处理
public String wxCallBackInfo(String xmlData, Integer configType, HttpServletResponse response) throws IOException {
//xml 数据转map
ReturnValue<PayOrderNotifyResponse> wxPayRcvContent = thirdApi.getWxPayRcvContent(xmlData, configType);
log.info("Log-in-wx-parseOrderNotifyResult,处理接收参数:{}", JSONUtils.toString(wxPayRcvContent));
if (wxPayRcvContent.isFailed() || Objects.isNull(wxPayRcvContent.getValue())) {
return ackXml(FAIL_ACK_FLAG, response);
}
PayOrderNotifyResponse wxPayRcvContentValue = wxPayRcvContent.getValue();
String attach = wxPayRcvContentValue.getAttach();
PayOrderResult payOrderResult = JSONUtils.parseObject(attach, PayOrderResult.class);
String outTradeNo = wxPayRcvContentValue.getOutTradeNo();
String requestId = UUID.randomUUID().toString();
boolean lockStatus = false;
int count = 1;
while (!lockStatus) {
lockStatus = redisBean.lock().tryLock(RedisKey.getWxPayCallBackKey(outTradeNo), requestId, TimeUnit.SECONDS.toSeconds(30));
if (lockStatus) {
if ("SUCCESS".equals(wxPayRcvContentValue.getResultCode())) {
log.info("[微信][加锁]成功 ,尝试:{}次。outTradeNo:{}", count, RedisKey.getAliPayCallBackKey(outTradeNo));
PayRequest request = new PayRequest();
request.setPayType(PayChannelEnum.PayTypeEnum.WEIPAY.getCode());
request.setRequestOrderSn(outTradeNo);
request.setPayAmount(wxPayRcvContent.getValue().getTotalFee());
request.setThirdTradeNo(wxPayRcvContent.getValue().getTransactionId());
request.setPayStatus(PayStatusEnum.SUCCESS.equals(wxPayRcvContent.getValue().getResultCode()) ? PayStatusEnum.PayOrderStatusEnum.SUCCESS_PAY_STATUS_PAY.getCode() : PayStatusEnum.PayOrderStatusEnum.FAIL_PAY_STATUS_PAY.getCode());
//返回的时间 yyyyMMddHHmmSS
request.setPaySuccessTime(DateUtils.toDate(wxPayRcvContent.getValue().getTimeEnd(), DateUtils.FORMAT_FOUR).getTime());
log.info("微信回调自定义参数:");
if(Objects.nonNull(payOrderResult)){
request.setOrderId(payOrderResult.getOrderId());
request.setTradeType(payOrderResult.getPayTradeType());
}
try {
ReturnValue<Boolean> messageVo = payApi.updateNotifyPay(request);
log.info("Log-in-wx-parseOrderNotifyResult,处理更新支付结果参数:{},更新结果:{}", JSONUtils.toString(request), JSONUtils.toString(messageVo));
if (messageVo.isSuccessful()) {
log.info("回调成功:返回微信格式消息给微信");
return ackXml(SUCCESS_ACK_FLAG, response);
}
} catch (Exception e) {
log.error("更新支付流水失败:{}", JSONUtils.toString(request), e);
} finally {
redisBean.lock().tryReleaseLock(RedisKey.getWxPayCallBackKey(outTradeNo), requestId);
}
} else {
//释放锁
redisBean.lock().tryReleaseLock(RedisKey.getWxPayCallBackKey(outTradeNo), requestId);
}
} else {
log.info("[微信][加锁]失败,尝试:{}次。outTradeNo:{}", count, RedisKey.getAliPayCallBackKey(outTradeNo));
count++;
}
if (count > 3) {
//redisBean.lock().tryReleaseLock(RedisKey.getAliPayCallBackKey(outTradeNo), requestId);
return ackXml(FAIL_ACK_FLAG, response);
}
}
return ackXml(FAIL_ACK_FLAG, response);
//微信须返回这样得格式
/* private final String successXmlBack= "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg></xml>";
private final String failureXmlBack= "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[报文为空]]></return_msg></xml> ";*/
- 支付宝
支付宝回调大致与微信一样。
业务处理正常返回 “success” 反之失败返回 “failure”
异步文档地址 : https://opendocs.alipay.com/open/204/105301
/**
* {返回格式
* "memo" : "xxxxx",
* "result" : "{
* \"alipay_trade_app_pay_response\":{
* \"code\":\"10000\",
* \"msg\":\"Success\",
* \"app_id\":\"2014072300007148\",
* \"out_trade_no\":\"081622560194853\",
* \"trade_no\":\"2016081621001004400236957647\",
* \"total_amount\":\"0.01\",
* \"seller_id\":\"2088702849871851\",
* \"charset\":\"utf-8\",
* \"timestamp\":\"2016-10-11 17:43:36\"
* },
* \"sign\":\"NGfStJf3i3ooWBuCDIQSumOpaGBcQz+aoAqyGh3W6EqA/gmyPYwLJ2REFijY9XPTApI9YglZyMw+ZMhd3kb0mh4RAXMrb6mekX4Zu8Nf6geOwIa9kLOnw0IMCjxi4abDIfXhxrXyj********\",
* \"sign_type\":\"RSA2\"
* }",
* "resultStatus" : "9000"
* } 异步文档地址 : https://opendocs.alipay.com/open/204/105301
*
* @param httpRequest 请求参数
*/
@PostMapping(value = "app/order")
@ApiOperation("支付宝APP回调")
public String aliAppOrderNotifyResult(HttpServletRequest httpRequest) throws AlipayApiException, UnsupportedEncodingException {
return aliCallbackInfo(httpRequest, PayChannelEnum.PayConfigTypeEnum.APP.getCode());
}
@PostMapping(value = "h5/order")
@ApiOperation("支付宝H5回调")
public String aliH5OrderNotifyResult(HttpServletRequest httpRequest) throws AlipayApiException, UnsupportedEncodingException {
return aliCallbackInfo(httpRequest, PayChannelEnum.PayConfigTypeEnum.H5.getCode());
}
//urlget 参数转map
public static Map<String,String> urlParamsToMap(String urlparam){
Map<String,String> map = new HashMap<String,String>();
String[] param = urlparam.split("&");
for(String keyvalue:param){
String[] pair = keyvalue.split("=");
if(pair.length==2){
map.put(pair[0], pair[1]);
}
}
return map;
}
/**
* 程序执行完后必须打印输出“success”(不包含引号)。如果商户反馈给支付宝的字符不是 success 这7个字符,支付宝服务器会不断重发通知,
* 直到超过 24 小时 22 分钟。一般情况下,25 小时以内完成 8 次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h)。
* 注意,回调的时候是用的支付宝公钥而不是应用公钥
*/
private String aliCallbackInfo(HttpServletRequest httpRequest, Integer confType) throws AlipayApiException, BusinessException, UnsupportedEncodingException {
Map<String, String> params = convertRequestParamsToMap(httpRequest);
PayRequest payRequest = new PayRequest();
log.info("支付宝回调,{}", JSONUtils.toString(params));
// 商户订单号(流水每次生成的订单号)
String outTradeNo = params.get("out_trade_no");
String thirdTradeNo = params.get("trade_no");
String totalAmount = params.get("total_amount");
String msg = params.get("msg");
String code = params.get("code");
//获取自定义回调参数
String passback_params = URLDecoder.decode(params.get("passback_params"), "UTF-8");
//step1: 验证订单是否存在
OrderPayFlow byRequestTradeNo = orderPayFlowService.getByRequestTradeNo(outTradeNo);
if (Objects.isNull(byRequestTradeNo)) {
log.error("订单不存在 outTradeNo :{}", outTradeNo);
//throw new BusinessException(ORDER_ERROR_CODE_3300003.getCode(), ORDER_ERROR_CODE_3300003.getDesc());
return fail;
}
//step2: 验证订单是否支付
if (byRequestTradeNo.getStatus()) {
log.info("订单已支付,已主动查询,返回支付宝success");
// throw new BusinessException(ORDER_ERROR_CODE_3300004.getCode(), ORDER_ERROR_CODE_3300004.getDesc());
return success;
}
AliCustomConfig alipayConfig = null;
if (confType.equals(PayChannelEnum.PayConfigTypeEnum.APP.getCode())) {
alipayConfig = aliPayAppConfig;
} else {
alipayConfig = aliPayH5Config;
}
// step3 : 验证签名 调用SDK验证签名
boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayConfig.getAliPayPublicKey(),
alipayConfig.getCharset(), alipayConfig.getSignType());
// steo 4: 成功返回 ,更改订单状态
String key = outTradeNo;
String requestId = UUID.randomUUID().toString();
boolean lockStatus = false;
int count = 1;
while (!lockStatus) {
lockStatus = redisBean.lock().tryLock(RedisKey.getAliPayCallBackKey(key), requestId, TimeUnit.SECONDS.toSeconds(30));
if (lockStatus) {
if (signVerified) {
payRequest.setPayType(PayChannelEnum.PayTypeEnum.ALIPAY.getCode());
payRequest.setRequestOrderSn(outTradeNo);
double amount = Double.parseDouble(totalAmount) * 100;
payRequest.setPayAmount((long) amount);
payRequest.setThirdTradeNo(thirdTradeNo);
payRequest.setPayStatus(PayStatusEnum.PayOrderStatusEnum.SUCCESS_PAY_STATUS_PAY.getCode());
payRequest.setPaySuccessTime(System.currentTimeMillis());
try {
log.info("[支付宝]开始更新流水和订单状态:{}", JSONUtils.toString(payRequest));
ReturnValue<Boolean> messageVo = payApi.updateNotifyPay(payRequest);
if (messageVo.isSuccessful()) {
log.info("回调成功:返回给支付宝");
return success;
}
} catch (Exception e) {
log.error("开始更新流水和订单状态失败:{}", JSONUtils.toString(payRequest));
} finally {
redisBean.lock().tryReleaseLock(RedisKey.getAliPayCallBackKey(key), requestId);
}
} else {
redisBean.lock().tryReleaseLock(RedisKey.getAliPayCallBackKey(key), requestId);
log.error("验证签名失败,信息 code:{} ,msg:{}", code, msg);
return fail;
}
} else {
log.info("[支付宝][加锁]失败,尝试:{}次。outTradeNo:{}", count, RedisKey.getAliPayCallBackKey(key));
count++;
}
if (count > 3) {
//redisBean.lock().tryReleaseLock(RedisKey.getAliPayCallBackKey(key), requestId);
return fail;
}
}
return fail;
}
/**
* 将request中的参数转换成Map
*
* @param request 入参
* @return map 参数
*/
public static Map<String, String> convertRequestParamsToMap(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
Map<String, String[]> requestParams = request.getParameterMap();
for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
String name = (String) iter.next();
String[] values = requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
params.put(name, valueStr);
}
return params;
}
两种回调大致处理逻辑相同,主要看你自己得回调业务逻辑怎么走。喜欢可以点赞,板砖不易!