在沙箱环境获取AppId、商户PID、支付私钥/公钥等信息,不需要自己创建应用。
地址:https://openhome.alipay.com/develop/sandbox/app
AppId、商户PID:

公钥、私钥信息:

<dependency>
<groupId>com.alipay.sdkgroupId>
<artifactId>alipay-sdk-javaartifactId>
<version>4.31.48.ALLversion>
dependency>
<dependency>
<groupId>commons-logginggroupId>
<artifactId>commons-loggingartifactId>
<version>1.2version>
dependency>
alipay:
# appid
appId:
# 商户PID,卖家支付宝账号ID
sellerId:
# 私钥 pkcs8格式的,rsc中的私钥:https://openhome.alipay.com/platform/appDaily.htm?tab=info
privateKey:
# 支付宝公钥:https://openhome.alipay.com/platform/appDaily.htm?tab=info
publicKey:
# 服务器异步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
notifyUrl: http://zhbexg.natappfree.cc/alipay/notify
# 页面跳转同步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
returnUrl: https://zhbexg.natappfree.cc/alipay/return
# 请求网关地址
# 正式为:"https://openapi.alipay.com/gateway.do"
serverUrl: https://openapi.alipaydev.com/gateway.do
AlipayConfig:
@Component
@Data
@ConfigurationProperties(prefix = "alipay")
public class AlipayConfig {
/**
* 商户appid
*/
private String appId;
/**
* 商户PID,卖家支付宝账号ID
*/
private String sellerId;
/**
* 私钥 pkcs8格式的,rsc中的私钥
*/
private String privateKey;
/**
* 支付宝公钥
*/
private String publicKey;
/**
* 请求网关地址
*/
private String serverUrl;
/**
* 页面跳转同步通知(可以直接返回前端页面、或者通过后端进行跳转)
*/
private String returnUrl;
/**
* 服务器异步通知
*/
private String notifyUrl;
/**
* 获得初始化的AlipayClient
*
* @return
*/
@Bean
public AlipayClient alipayClient() {
// 获得初始化的AlipayClient
return new DefaultAlipayClient(serverUrl, appId, privateKey,
AlipayConstants.FORMAT_JSON, AlipayConstants.CHARSET_UTF8,
publicKey, AlipayConstants.SIGN_TYPE_RSA2);
}
}
PayTypeEnum :
@Getter
@AllArgsConstructor
public enum PayTypeEnum {
/**
* PC
*/
PC("pc","FAST_INSTANT_TRADE_PAY"),
/**
* H5
*/
H5("h5","QUICK_WAP_WAY"),
/**
* App
*/
APP("app","QUICK_MSECURITY_PAY");
String type;
String code;
public static String getCode(String type) {
for (PayTypeEnum payType : PayTypeEnum.values()) {
if (payType.getType().equals(type)) {
return payType.getCode();
}
}
return null;
}
}
AliPayApi:
public class AliPayApi {
public static String h5Pay(AlipayClient alipayClient, String bizContent, String returnUrl, String notifyUrl, String appAuthToken) throws AlipayApiException {
// 相应请求API的request
AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();
// 在公共参数中设置回跳和通知地址
alipayRequest.setReturnUrl(returnUrl);
alipayRequest.setNotifyUrl(notifyUrl);
alipayRequest.setBizContent(bizContent);
// 服务商模式,第三方授权的token
if (appAuthToken != null) {
alipayRequest.putOtherTextParam("app_auth_token", appAuthToken);
}
AlipayTradeWapPayResponse payResponse = alipayClient.pageExecute(alipayRequest);
return readData(payResponse.isSuccess(), payResponse.getBody());
}
public static String appPay(AlipayClient alipayClient, String bizContent, String returnUrl, String notifyUrl, String appAuthToken) throws AlipayApiException {
// 相应请求API的request
AlipayTradeAppPayRequest alipayRequest = new AlipayTradeAppPayRequest();
// 在公共参数中设置回跳和通知地址
alipayRequest.setReturnUrl(returnUrl);
alipayRequest.setNotifyUrl(notifyUrl);
alipayRequest.setBizContent(bizContent);
// 服务商模式,第三方授权的token
if (appAuthToken != null) {
alipayRequest.putOtherTextParam("app_auth_token", appAuthToken);
}
AlipayTradeAppPayResponse payResponse = alipayClient.pageExecute(alipayRequest);
return readData(payResponse.isSuccess(), payResponse.getBody());
}
public static String pcPay(AlipayClient alipayClient, String bizContent, String returnUrl, String notifyUrl, String appAuthToken) throws AlipayApiException {
// 相应请求API的request
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
// 在公共参数中设置回跳和通知地址
alipayRequest.setReturnUrl(returnUrl);
alipayRequest.setNotifyUrl(notifyUrl);
alipayRequest.setBizContent(bizContent.toString());
// 服务商模式,第三方授权的token
if (appAuthToken != null) {
alipayRequest.putOtherTextParam("app_auth_token", appAuthToken);
}
AlipayTradePagePayResponse payResponse = alipayClient.pageExecute(alipayRequest);
return readData(payResponse.isSuccess(), payResponse.getBody());
}
private static String readData(boolean isSuccess, String body) {
if (isSuccess) {
return body;
} else {
throw new RuntimeException(body);
}
}
}
PayController:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayClient;
import com.alipay.api.request.*;
import com.alipay.api.response.*;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.lhz.config.AlipayConfig;
import com.lhz.enums.PayTypeEnum;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
/**
* @Author:
* @Description:
**/
@Api(tags = "统一支付接口")
@Controller
@Slf4j
public class TestController {
@Resource
private AlipayClient alipayClient;
@Resource
private AlipayConfig alipayConfig;
/**
* 下单交易接口,在测试时因为没有App环境,没有对App进行验证,H5和PC都有测试完整的流程
* 下单接口会返回一个字符串,改字符串为支付宝支付的一个表单页面,由前端直接加载即可
*
* @param type pc、h5、app
* @throws IOException
* @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbs?scene=21
*/
@ApiOperation(value = "支付统一下单", notes = "支付统一下单")
@ApiOperationSupport(order = 1)
@GetMapping("/pay/{type}")
@ResponseBody
public Map<String, String> pay(@PathVariable("type") String type) {
String body = null;
long outTradeNo = System.currentTimeMillis();
log.info("支付订单,订单号:" + outTradeNo);
// 封装商品信息
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", outTradeNo);
bizContent.put("total_amount", 0.01);
bizContent.put("subject", "测试商品");
// H5:QUICK_WAP_WAY,PC:FAST_INSTANT_TRADE_PAY,APP:QUICK_MSECURITY_PAY
String payCode = PayTypeEnum.getCode(type);
bizContent.put("product_code", payCode);
// 用户付款中途退出返回商户网站的地址
bizContent.put("quit_url", "https://www.baidu.com");
// 商品明细信息,按需传入
JSONArray goodsDetail = new JSONArray();
JSONObject goods = new JSONObject();
goods.put("goods_id", "goodsNo1");
goods.put("goods_name", "子商品1");
// 商品数量
goods.put("quantity", 1);
goods.put("price", 0.01);
goods.put("body", "商品描述信息");
goodsDetail.add(goods);
bizContent.put("goods_detail", goodsDetail);
// 扩展信息,按需传入
JSONObject extendParams = new JSONObject();
extendParams.put("sys_service_provider_id", "2088511833207846");
bizContent.put("extend_params", extendParams);
// H5、App公共参数
if (payCode.equals(PayTypeEnum.H5.getCode())) {
// 公用回传参数,如果请求时传递了该参数,则返回给商户时会回传该参数
// 本参数必须进行UrlEncode之后才可以发送给支付宝
bizContent.put("passback_params", "passbackParams");
body = requestPay(PayTypeEnum.H5.getCode(), bizContent.toString());
}
// App独有参数
if (payCode.equals(PayTypeEnum.APP.getCode())) {
// 公用回传参数,如果请求时传递了该参数,则返回给商户时会回传该参数
// 本参数必须进行UrlEncode之后才可以发送给支付宝
bizContent.put("passback_params", "passbackParams");
// 外部指定买家
JSONObject extUserInfo = new JSONObject();
// 指定买家姓名,need_check_info=T时该参数才有效
extUserInfo.put("name", "李四");
// 指定买家手机号
extUserInfo.put("mobile", "17607235621");
/*指定买家证件类型, need_check_info=T时该参数才有效
* IDENTITY_CARD:身份证;
* PASSPORT:护照;
* OFFICER_CARD:军官证;
* SOLDIER_CARD:士兵证;
* HOKOU:户口本。如有其它类型需要支持,请与支付宝工作人员联系。
*/
extUserInfo.put("cert_type", "123");
// 买家证件号
extUserInfo.put("cert_no", "123");
// 允许的最小买家年龄,need_check_info=T时该参数才有效,为整数,必须大于等于0
extUserInfo.put("min_age", 10);
// 是否强制校验买家信息;值为T、F
extUserInfo.put("need_check_info", "F");
bizContent.put("ext_user_info", extUserInfo);
/*
* 返回参数选项,数组格式, 商户通过传递该参数来定制同步需要额外返回的信息字段
* fund_bill_list:交易支付使用的资金渠道;
* voucher_detail_list:交易支付时使用的所有优惠券信息;
* discount_goods_detail:交易支付所使用的单品券优惠的商品优惠信息;
* mdiscount_amount:商家优惠金额
*/
bizContent.put("query_options", "[fund_bill_list]");
body = requestPay(PayTypeEnum.APP.getCode(), bizContent.toString());
}
// PC独有参数
if (payCode.equals(PayTypeEnum.PC.getCode())) {
/*
* 请求后页面的集成方式。
* 枚举值:
* ALIAPP:支付宝钱包内
* PCWEB:PC端访问
* 默认值为PCWEB。
*/
bizContent.put("integration_type", "PCWEB");
// 请求来源地址。如果使用ALIAPP的集成方式,用户中途取消支付会返回该地址
bizContent.put("request_from_url", "https://www.alipay.com/");
// 商户门店编号
bizContent.put("store_id", "NJ_001");
/*
// 开票信息
JSONObject invoiceInfo = new JSONObject();
InvoiceKeyInfo invoiceKeyInfo = new InvoiceKeyInfo();
// 开票关键信息
invoiceInfo.put("key_info", invoiceKeyInfo);
// 开票内容
invoiceInfo.put("details", "[{\"code\":\"100294400\",\"name\":\"服饰\",\"num\":\"2\",\"sumPrice\":\"200.00\",\"taxRate\":\"6%\"}]");
bizContent.put("invoice_info", invoiceInfo);
*/
body = requestPay(PayTypeEnum.PC.getCode(), bizContent.toString());
}
log.info("支付表单:{}", body);
Map<String, String> map = new HashMap<>();
map.put("body", body);
return map;
}
private String requestPay(String payType, String bizContent) {
try {
String body = null;
if (PayTypeEnum.H5.getCode().equals(payType)) {
body = AliPayApi.h5Pay(alipayClient, bizContent, alipayConfig.getReturnUrl(), alipayConfig.getNotifyUrl(), null);
}
if (PayTypeEnum.APP.getCode().equals(payType)) {
body = AliPayApi.appPay(alipayClient, bizContent, alipayConfig.getReturnUrl(), alipayConfig.getNotifyUrl(), null);
}
if (PayTypeEnum.PC.getCode().equals(payType)) {
body = AliPayApi.pcPay(alipayClient, bizContent, alipayConfig.getReturnUrl(), alipayConfig.getNotifyUrl(), null);
}
return body;
} catch (Exception e) {
log.error(e.getMessage());
throw new RuntimeException("创建支付交易失败");
}
}
/**
* @param tradeNum 商户网站唯一订单号(out_trade_no) 或 支付宝交易号(trade_no)
* @throws Exception
* @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbt
*/
@ApiOperation(value = "统一查询订单", notes = "统一查询订单")
@ApiOperationSupport(order = 2)
@GetMapping("/query/{tradeNum}")
@ResponseBody
public Object query(@PathVariable("tradeNum") String tradeNum) throws Exception {
//创建API对应的request类
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
JSONObject bizContent = new JSONObject();
// 商户网站唯一订单号
bizContent.put("out_trade_no", tradeNum);
// 支付宝交易号
//bizContent.put("trade_no", tradeNum);
/*
* 指定接口同步响应额外返回的信息字段,数组格式
* trade_settle_info:返回的交易结算信息,包含分账、补差等信息;
* fund_bill_list:交易支付使用的资金渠道;
* voucher_detail_list:交易支付时使用的所有优惠券信息;
* discount_goods_detail:交易支付所使用的单品券优惠的商品优惠信息;
* mdiscount_amount:商家优惠金额;
*/
// bizContent.put("query_options", []);
request.setBizContent(bizContent.toString());
AlipayTradeQueryResponse response = alipayClient.execute(request);
// 打印结果
log.debug(response.getBody());
// 根据response中的结果继续业务逻辑处理
if (response.isSuccess()) {
log.info("调用支付宝订单查询成功,结果:{}", JSON.toJSONString(response));
log.info(response.getSubMsg());
/**
* 获取状态交易状态:
* WAIT_BUYER_PAY(交易创建,等待买家付款)
* TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)
* TRADE_SUCCESS(交易支付成功)
* TRADE_FINISHED(交易结束,不可退款)
*/
String tradeStatus = response.getTradeStatus();
log.info("交易状态:" + tradeStatus);
log.info("支付宝交易号:" + response.getTradeNo());
log.info("商家订单号:" + response.getOutTradeNo());
log.info("商家订单号:" + response.getOutTradeNo());
log.info("买家支付宝账号:" + response.getBuyerLogonId());
log.info("买家在支付宝的用户id:" + response.getBuyerUserId());
log.info("交易的订单金额:" + response.getTotalAmount());
// 入参的query_options中指定时才返回该字段信息
log.info("交易支付使用的资金渠道:" + response.getFundBillList());
log.info("商户店铺的名称:" + response.getStoreName());
} else {
log.error("调用支付宝失败");
log.error(response.getSubCode());
log.error(response.getSubMsg());
}
return "调用查询订单";
}
/**
* @param tradeNum 商户网站唯一订单号(out_trade_no) 或 支付宝交易号(trade_no)
* @return
* @throws Exception
* @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbu
*/
@ApiOperation(value = "关闭订单", notes = "关闭订单")
@ApiOperationSupport(order = 3)
@PostMapping("/close/{tradeNum}")
@ResponseBody
public Object close(@PathVariable("tradeNum") String tradeNum) throws Exception {
//创建API对应的request类
AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
JSONObject bizContent = new JSONObject();
// 商户网站唯一订单号
bizContent.put("out_trade_no", tradeNum);
// 支付宝交易号
//bizContent.put("trade_no", tradeNum);
// 商家操作员编号 id,由商家自定义
bizContent.put("operator_id", "admin");
request.setBizContent(bizContent.toString());
AlipayTradeCloseResponse response = alipayClient.execute(request);
// 打印结果
log.debug(response.getBody());
// 根据response中的结果继续业务逻辑处理
if (response.isSuccess()) {
log.info("调用支付宝成功");
log.info(response.getSubMsg());
log.info("支付宝交易号:" + response.getTradeNo());
log.info("商家订单号:" + response.getOutTradeNo());
} else {
log.error("调用支付宝失败");
log.error(response.getSubCode());
log.error(response.getSubMsg());
}
return "调用关闭订单";
}
/**
* @return
* @throws Exception 同一笔交易的退款至少间隔3s后发起
* @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbx
*/
@ApiOperation(value = "订单退款", notes = "订单退款")
@ApiOperationSupport(order = 3)
@GetMapping("/refund/{tradeNum}")
@ResponseBody
public Object refund(@PathVariable("tradeNum") String tradeNum) throws Exception {
//创建API对应的request类
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
JSONObject bizContent = new JSONObject();
// 商户网站唯一订单号
bizContent.put("out_trade_no", tradeNum);
// 支付宝交易号
// bizContent.put("trade_no", "2021081722001419121412730660");
// 退款金额
bizContent.put("refund_amount", 0.01);
// 退款原因
bizContent.put("refund_reason", "申请退款");
// 退款请求号,标识一次退款请求,需要保证在交易号下唯一,如需部分退款,则此参数必传。
// 针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更。
bizContent.put("out_request_no", "1122334455");
request.setBizContent(bizContent.toString());
AlipayTradeRefundResponse response = alipayClient.execute(request);
// 打印结果
log.debug(response.getBody());
// 根据response中的结果继续业务逻辑处理
if (response.isSuccess()) {
log.info("调用支付宝成功");
log.info(response.getSubMsg());
/**
* 本次退款是否发生了资金变化
* Y 表示退款成功
*/
log.info("是否发生了资金变化:" + response.getFundChange());
log.info("支付宝交易号:" + response.getTradeNo());
log.info("商家订单号:" + response.getOutTradeNo());
log.info("已退款的总金额:" + response.getRefundFee());
log.info("买家支付宝账号:" + response.getBuyerLogonId());
log.info("买家在支付宝的用户id:" + response.getBuyerUserId());
log.info("买家在支付宝的用户id:" + response.getBuyerUserId());
} else {
log.error("调用支付宝失败");
log.error(response.getSubCode());
log.error(response.getSubMsg());
}
return "调用订单退款";
}
/**
* @return
* @throws Exception
* @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbv
*/
@ApiOperation(value = "订单退款查询", notes = "订单退款查询")
@ApiOperationSupport(order = 4)
@GetMapping("/refund/query/{tradeNum}")
@ResponseBody
public Object refundQuery(@PathVariable("tradeNum") String tradeNum) throws Exception {
//创建API对应的request类
AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest();
JSONObject bizContent = new JSONObject();
// 商户网站唯一订单号
bizContent.put("out_trade_no", tradeNum);
// 支付宝交易号
// bizContent.put("trade_no", "2021081722001419121412730660");
// 退款请求号,标识一次退款请求,需要保证在交易号下唯一,如需部分退款,则此参数必传。
// 针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更。
bizContent.put("out_request_no", "1122334455");
/**
* 商户通过上送该参数来定制同步需要额外返回的信息字段,数组格式。
* refund_detail_item_list:退款使用的资金渠道;
* deposit_back_info:触发银行卡冲退信息通知,设置该类型后会回调收单退款冲退通知;
*/
// bizContent.put("query_options", []);
request.setBizContent(bizContent.toString());
AlipayTradeFastpayRefundQueryResponse response = alipayClient.execute(request);
// 打印结果
log.debug(response.getBody());
// 根据response中的结果继续业务逻辑处理
if (response.isSuccess()) {
log.info("调用支付宝成功");
log.info(response.getSubMsg());
/**
* 退款状态
* REFUND_SUCCESS 退款处理成功;
* 未返回该字段表示退款请求未收到或者退款失败;
* 注:建议商户在退款发起后间隔10秒以上再发起退款查询请求。
*/
String refundStatus = response.getRefundStatus();
log.info("支付宝交易号:" + response.getTradeNo());
log.info("商家订单号:" + response.getOutTradeNo());
log.info("本笔退款对应的退款请求号:" + response.getOutRequestNo());
log.info("该笔退款所对应的交易的订单金额:" + response.getTotalAmount());
log.info("本次退款请求,对应的退款金额:" + response.getRefundAmount());
log.info("退分账明细信息:" + response.getRefundRoyaltys());
log.info("银行卡冲退信息:" + response.getDepositBackInfo());
} else {
log.error("调用支付宝失败");
log.error(response.getSubCode());
log.error(response.getSubMsg());
}
return "调用订单退款查询";
}
/**
* @return
* @throws Exception
* @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivbw
*/
@ApiOperation(value = "查询对账单下载地址", notes = "查询对账单下载地址")
@ApiOperationSupport(order = 5)
@GetMapping("/bill")
@ResponseBody
public Object bill() throws Exception {
//创建API对应的request类
AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest();
JSONObject bizContent = new JSONObject();
/*
* 账单类型,通过接口可以获取以下账单类型,支持:
* trade:商户基于支付宝交易收单的业务账单;
* signcustomer:基于商户支付宝余额收入及支出等资金变动的账务账单。
*/
bizContent.put("bill_type", "trade");
/*
* 账单时间:
* 日账单格式为yyyy-MM-dd,最早可下载2016年1月1日开始的日账单;
* 月账单格式为yyyy-MM,最早可下载2016年1月开始的月账单。
*/
bizContent.put("bill_date", "2022-07-10");
request.setBizContent(bizContent.toString());
AlipayDataDataserviceBillDownloadurlQueryResponse response = alipayClient.execute(request);
// 打印结果
log.debug(response.getBody());
// 根据response中的结果继续业务逻辑处理
if (response.isSuccess()) {
log.info("调用支付宝成功");
log.info(response.getSubMsg());
log.info("账单地址:" + response.getBillDownloadUrl());
return response.getBillDownloadUrl();
} else {
log.error("调用支付宝失败");
log.error(response.getSubCode());
log.error(response.getSubMsg());
}
return "调用查询对账单下载地址";
}
/**
* @return
* @throws Exception
* @deprecated 具体API参数说明参考:https://opendocs.alipay.com/open/02ivby
*/
@ApiOperation(value = "收单退款冲退", notes = "收单退款冲退")
@ApiOperationSupport(order = 99)
@GetMapping("/completed")
@ResponseBody
public void completed(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("收单退款冲退");
PrintWriter out = response.getWriter();
//乱码解决,这段代码在出现乱码时使用
request.setCharacterEncoding("utf-8");
try {
//获取支付宝GET过来反馈信息
Map<String, String> params = new HashMap<>(8);
Map<String, String[]> requestParams = request.getParameterMap();
for (Map.Entry<String, String[]> stringEntry : requestParams.entrySet()) {
String[] values = stringEntry.getValue();
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
params.put(stringEntry.getKey(), valueStr);
}
// 比如:解析到的biz_content内容如下,此处代码因为无法通过银行卡退款测试,所以模拟已经解析除了biz_content值
// biz_content={"trade_no":"2022021922001486510501693053","out_trade_no":"1645257277192","out_request_no":"HZ01RF001","dback_status":"S","dback_amount":"1.01","bank_ack_time":"2020-06-02 14:03:48","est_bank_receipt_time":"2020-06-02 14:03:48"}
String str = "{\"trade_no\":\"2022021922001486510501693053\",\"out_trade_no\":\"1645257277192\",\"out_request_no\":\"HZ01RF001\",\"dback_status\":\"S\",\"dback_amount\":\"1.01\",\"bank_ack_time\":\"2020-06-02 14:03:48\",\"est_bank_receipt_time\":\"2020-06-02 14:03:48\"}";
// 将biz_content值转为Map对象
Map map = JSON.parseObject(str, Map.class);
log.info("支付宝交易号:" + map.get("trade_no"));
log.info("商家订单号:" + map.get("out_trade_no"));
log.info("退款请求号:" + map.get("out_request_no"));
// 银行卡冲退状态。S-成功,F-失败。银行卡冲退失败,资金自动转入用户支付宝余额
log.info("银行卡冲退状态:" + map.get("dback_status"));
log.info("银行卡冲退金额:" + map.get("dback_amount"));
log.info("银行响应时间:" + map.get("bank_ack_time"));
log.info("预估银行入账时间:" + map.get("est_bank_receipt_time"));
out.print("success");
} catch (Exception e) {
out.print("fail");
}
}
}
NotifyController :
import com.alipay.api.AlipayConstants;
import com.alipay.api.internal.util.AlipaySignature;
import com.lhz.config.AlipayConfig;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author:
* @Description:
**/
@Api(tags = "回调接口(API3)")
@RestController
@Slf4j
public class NotifyController {
private final ReentrantLock lock = new ReentrantLock();
@Resource
private AlipayConfig alipayConfig;
/**
* 服务器异步通知页面路径,接收支付宝回调后,再封装页面数据,直接返回相应页面到前端
* 支付、退款都会通过此回调接口
*
* @param request
* @param response
* @throws IOException
* @deprecated API地址:https://opendocs.alipay.com/open/203/105286
*/
@PostMapping("/notify")
public void notifyUrl(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("异步通知");
PrintWriter out = response.getWriter();
if (lock.tryLock()) {
try {
//乱码解决,这段代码在出现乱码时使用
request.setCharacterEncoding("utf-8");
//获取支付宝POST过来反馈信息
Map<String, String> params = new HashMap<>(8);
Map<String, String[]> requestParams = request.getParameterMap();
for (Map.Entry<String, String[]> stringEntry : requestParams.entrySet()) {
String[] values = stringEntry.getValue();
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
params.put(stringEntry.getKey(), valueStr);
}
//调用SDK验证签名
boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayConfig.getPublicKey(), AlipayConstants.CHARSET_UTF8, AlipayConstants.SIGN_TYPE_RSA2);
if (!signVerified) {
log.error("验签失败");
out.print("fail");
return;
}
log.warn("=========== 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱 ===========");
// 通知ID
String notifyId = new String(params.get("notify_id").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
// 通知时间
String notifyTime = new String(params.get("notify_time").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
//商户订单号,之前生成的带用户ID的订单号
String outTradeNo = new String(params.get("out_trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
//支付宝交易号
String tradeNo = new String(params.get("trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
//付款金额
String totalAmount = new String(params.get("total_amount").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
//交易状态
String tradeStatus = new String(params.get("trade_status").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
/*
* 交易状态
* TRADE_SUCCESS 交易完成
* TRADE_FINISHED 支付成功
* WAIT_BUYER_PAY 交易创建
* TRADE_CLOSED 交易关闭
*/
log.info("tradeStatus:" + tradeStatus);
if ("TRADE_FINISHED".equals(tradeStatus)) {
/*此处可自由发挥*/
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//如果有做过处理,不执行商户的业务程序
//注意:
//退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
} else if ("TRADE_SUCCESS".equals(tradeStatus)) {
// TODO 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
log.warn("=========== 根据订单号,做幂等处理 ===========");
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//如果有做过处理,不执行商户的业务程序
// 此处代表交易已经成果,编写实际页面代码
// 比如:重置成功,那么往数据库中写入重置金额
log.info("notifyId:" + notifyId);
log.info("notifyTime:" + notifyTime);
log.info("trade_no:" + tradeNo);
log.info("outTradeNo:" + outTradeNo);
log.info("totalAmount:" + totalAmount);
}
} finally {
//要主动释放锁
lock.unlock();
}
}
out.print("success");
}
/**
* 完成支付后的同步通知
* 页面跳转同步通知页面路径,接收支付宝回调后,再封装页面数据,直接返回相应页面到前端
*
* @param request
* @param response
* @throws IOException
*/
@GetMapping("/return")
public String returnUrl(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("同步通知");
//乱码解决,这段代码在出现乱码时使用
request.setCharacterEncoding("utf-8");
//获取支付宝GET过来反馈信息
Map<String, String> params = new HashMap<>(8);
Map<String, String[]> requestParams = request.getParameterMap();
for (Map.Entry<String, String[]> stringEntry : requestParams.entrySet()) {
String[] values = stringEntry.getValue();
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
params.put(stringEntry.getKey(), valueStr);
}
// 通知ID
String notifyId = new String(params.get("notify_id").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
// 通知时间
String notifyTime = new String(params.get("notify_time").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
//商户订单号,之前生成的带用户ID的订单号
String outTradeNo = new String(params.get("out_trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
//支付宝交易号
String tradeNo = new String(params.get("trade_no").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
//付款金额
String totalAmount = new String(params.get("total_amount").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
log.info("notifyId:" + notifyId);
log.info("notifyTime:" + notifyTime);
log.info("trade_no:" + tradeNo);
log.info("outTradeNo:" + outTradeNo);
log.info("totalAmount:" + totalAmount);
//调用SDK验证签名
boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayConfig.getPublicKey(), AlipayConstants.CHARSET_UTF8, AlipayConstants.SIGN_TYPE_RSA2);
if (signVerified) {
System.out.println("验签成功-跳转到成功后页面");
//跳转至支付成功后的页面,
return "redirect:https://baidu.com";
} else {
System.out.println("验签失败-跳转到充值页面让用户重新充值");
return "redirect:https://google.com";
}
}
}