Uniapp Android SpringBoot3 对接支付宝支付(最新教程附源码)
- 1、效果展示
- 2、后端实现
- 2.1 引入支付宝SDK依赖 pom.xml
- 2.2 配置 application.yml
- 2.3 支付宝相关代码
- 2.3.1 AlipayConfig.java
- 2.3.2 ZfbPayConfig.java
- 2.3.3 支付接口
- 2.3.4 支付回调处理接口(用于支付成功后,支付宝通知我们支付信息)
- 2.3.4 退款接口
- 3 前端代码
- 3.1 支付接口调用
- 3.2 支付方法
- 3.2 退款接口调用
1、效果展示
效果展示
2、后端实现
2.1 引入支付宝SDK依赖 pom.xml
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.40.0.ALL</version>
</dependency>
2.2 配置 application.yml
真实环境需要开通 APP 支付, 如果是沙箱环境的话不支持退款。
# 支付宝支付
alipay:
server_url: https://openapi.alipay.com/gateway.do
app_id: # 你的 appId
private_key: # 你的私钥
format: json
sellerId: 2088722047235165
charset: utf-8
alipay_public_key: # 你的公钥
sign_type: RSA2
notifyUrl: http://wtw867.natappfree.cc/app/shop/order/zfb-pay/notify # 支付成功通知地址
2.3 支付宝相关代码
2.3.1 AlipayConfig.java
package com.zhong.config;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Getter
@Setter
@ToString
@Component
@ConfigurationProperties(prefix = "alipay")
public class AlipayConfig extends com.alipay.api.AlipayConfig {
private String serverUrl;
private String appId;
private String privateKey;
private String format;
private String charset;
private String alipayPublicKey;
private String signType;
private String notifyUrl;
}
2.3.2 ZfbPayConfig.java
package com.zhong.config;
import com.alipay.api.AlipayApiException;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.domain.AlipayTradeRefundModel;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.zhong.model.entity.goods.GoodsOrderInfo;
import com.zhong.service.GoodsOrderInfoService;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Configuration
@Component
@Data
@Slf4j
public class ZfbPayConfig {
@Autowired
AlipayConfig alipayConfig;
@Autowired
private GoodsOrderInfoService goodsOrderInfoService;
private DefaultAlipayClient client() throws AlipayApiException {
return new DefaultAlipayClient(alipayConfig);
}
public String pay(GoodsOrderInfo order) {
String source = "";
try {
DefaultAlipayClient client = client();
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setSubject("云尚社区");
model.setOutTradeNo(order.getOrderNo());
model.setTotalAmount(String.valueOf(0.01));
// alipay 封装的接口调用
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
request.setBizModel(model);
request.setNotifyUrl(alipayConfig.getNotifyUrl());
AlipayTradeAppPayResponse response = client.sdkExecute(request);
source = response.getBody();
} catch (AlipayApiException e) {
goodsOrderInfoService.noPay(order.getOrderNo()); // 支付失败更新订单状态,可以根据您的具体业务做出调整
log.error("支付出现问题,详情:{}", e.getErrMsg());
e.printStackTrace();
}
log.info(source);
return source;
}
/**
* 退款
*
* @param tradeNo
* @param totalAmount
* @return
*/
public AlipayTradeRefundResponse refund(String tradeNo, String totalAmount) {
try {
DefaultAlipayClient client = client();
AlipayTradeRefundModel alipayTradeRefundModel = new AlipayTradeRefundModel();
alipayTradeRefundModel.setTradeNo(tradeNo);
alipayTradeRefundModel.setRefundAmount(totalAmount);
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
request.setBizModel(alipayTradeRefundModel);
AlipayTradeRefundResponse response = client.execute(request);
return response;
} catch (AlipayApiException e) {
log.error("退款出现问题,详情:{}", e.getErrMsg());
e.printStackTrace();
}
return null;
}
}
2.3.3 支付接口
全局统一返回结果类
package com.zhong.result;
import lombok.Data;
/**
* 全局统一返回结果类
*/
@Data
public class Result<T> {
//返回码
private Integer code;
//返回消息
private String message;
//返回数据
private T data;
public Result() {
}
private static <T> Result<T> build(T data) {
Result<T> result = new Result<>();
if (data != null)
result.setData(data);
return result;
}
public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
Result<T> result = build(body);
result.setCode(resultCodeEnum.getCode());
result.setMessage(resultCodeEnum.getMessage());
return result;
}
public static <T> Result<T> fail(Integer code, String message) {
Result<T> result = build(null);
result.setCode(code);
result.setMessage(message);
return result;
}
public static <T> Result<T> ok(T data) {
return build(data, ResultCodeEnum.SUCCESS);
}
public static <T> Result<T> ok() {
return Result.ok(null);
}
public static <T> Result<T> fail() {
return build(null, ResultCodeEnum.FAIL);
}
}
支付接口
我这里是多订单的,如果是但订单的话这里 payUrl = zfbPayConfig.pay(goodsOrderInfoArrayList.get(0)); 可以传入一个 orderInfo 方便zfbPayConfig.pay() 获取 tradeNo 和 totalAmount。
@Operation(summary = "支付商品订单")
@GetMapping(value = "/pay/zfb")
@ResponseBody
public Result pay(@RequestParam String id) throws AlipayApiException {
List<String> split = List.of(id.split(","));
String payUrl = "";
// 单个商品的情况
if (split.size() == 1) {
GoodsOrderInfo orderInfo = service.getById(id);
orderInfo.setTotalPrice((orderInfo.getTotalPrice()));
payUrl = zfbPayConfig.pay(orderInfo);
orderInfo.setCodeUrl(payUrl);
orderInfo.setOrderIds(id);
service.saveOrUpdate(orderInfo);
}
// 多个商品
else {
ArrayList<GoodsOrderInfo> goodsOrderInfoArrayList = new ArrayList<>();
BigDecimal price = new BigDecimal("0.00");
for (String shopInfoId : split) {
GoodsOrderInfo orderInfo = service.getById(shopInfoId);
goodsOrderInfoArrayList.add(orderInfo);
price = price.add(orderInfo.getPrice().multiply(BigDecimal.valueOf(orderInfo.getGoodNum())));
}
System.out.println(price);
goodsOrderInfoArrayList.get(0).setTotalPrice(price);
payUrl = zfbPayConfig.pay(goodsOrderInfoArrayList.get(0));
goodsOrderInfoArrayList.get(0).setCodeUrl(payUrl);
goodsOrderInfoArrayList.get(0).setOrderIds(id);
service.saveOrUpdate(goodsOrderInfoArrayList.get(0));
}
return Result.ok(payUrl);
}
2.3.4 支付回调处理接口(用于支付成功后,支付宝通知我们支付信息)
@PostMapping("/zfb-pay/notify") // 注意这里必须是POST接口
public String payNotify(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
Map<String, String[]> requestParams = request.getParameterMap();
System.out.println("=========支付宝异步回调========");
System.out.println(JSON.toJSONString(requestParams));
for (String name : requestParams.keySet()) {
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);
}
try {
boolean flag = AlipaySignature.rsaCheckV1(params, alipayConfig.getAlipayPublicKey(), alipayConfig.getCharset(), alipayConfig.getSignType());
if (flag) {
System.out.println("支付回调信息:" + params);
// TODO 验签成功
System.out.println("支付成功,异步验签成功!");
// 验证订单是否为当前订单
String orderNumber = params.get("out_trade_no");
LambdaQueryWrapper<GoodsOrderInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(GoodsOrderInfo::getOrderNo, orderNumber);
// .eq(ShopOrderInfo::getIsDeleted, 0)
GoodsOrderInfo orderInfo = service.getOne(queryWrapper);
if (orderInfo == null) {
throw new LeaseException(ResultCodeEnum.SHOP_ORDER_NOT_FIND_ERROR);
}
// 验证订单金额是否正确 TODO 测试环境不验证金额都是 0.01
// BigDecimal totalPrice = new BigDecimal(params.get("total_amount"));
// BigDecimal totalPriceParam = orderInfo.getTotalPrice();
// if (!Objects.equals(totalPriceParam, totalPrice)) {
// throw new LeaseException(ResultCodeEnum.SHOP_ORDER_TOTAL_PRICE_ERROR);
// }
// 验证商家ID是否一致 防止付给别人了
String sellerId = config.getProperty("alipay.sellerId");
String sellerIdParams = params.get("seller_id");
if (!Objects.equals(sellerId, sellerIdParams)) {
throw new LeaseException(ResultCodeEnum.SHOP_ORDER_BUSINESS_PID_ERROR);
}
// 验证APPID是否一致
String appId = config.getProperty("alipay.app_id");
String appIdParams = params.get("app_id");
if (!Objects.equals(appIdParams, appId)) {
throw new LeaseException(ResultCodeEnum.SHOP_ORDER_BUSINESS_APPID_ERROR);
}
// 检查交易状态
String tradeStatus = params.get("trade_status");
if (!"TRADE_SUCCESS".equals(tradeStatus) && !"TRADE_CLOSED".equals(tradeStatus)) {
throw new LeaseException(ResultCodeEnum.SHOP_ORDER_PAY_ERROR);
}
// 支付成功
if ("TRADE_SUCCESS".equals(tradeStatus)) {
// TODO 处理订单业务 修改订单状态 记录支付日志
service.processOrder(params);
}
// 退款操作
if ("TRADE_CLOSED".equals(tradeStatus)) {
String orderNo = params.get("out_biz_no");
service.refund(orderNo);
}
return "success";
} else {
// TODO 支付失败标记为未付款
String orderNumber = params.get("out_trade_no");
service.noPay(orderNumber);
return "error";
}
} catch (AlipayApiException e) {
System.out.println("支付宝错误回调:" + e.getErrMsg());
e.printStackTrace();
return "error";
}
}
2.3.4 退款接口
ApplyRefund 退款参数
@Data
public class ApplyRefund {
private String id;
private String refundInfo;
private String remarks;
}
退款接口
@Operation(summary = "申请退款")
@PostMapping("apply/refund")
public Result applyRefund(@RequestBody ApplyRefund applyRefund) {
service.applyRefund(applyRefund);
return Result.ok();
}
3 前端代码
3.1 支付接口调用
// 支付订单
export const payShopOrderApi = (id) => {
return http.get(`/app/shop/order/pay/zfb?id=${id}`)
}
3.2 支付方法
const toPayFun = async () => {
// 调起支付
let res = await payShopOrderApi(ids.value);
console.log(res.data);
uni.requestPayment({
//服务提供商 通过uni.getProvider获取
provider: 'alipay',
//后台返回的订单数据
orderInfo: res.data,
//调用成功的回调
success(res) {
uni.showToast({
title: "支付成功"
})
setTimeout(() => {
uni.redirectTo({
url: "/pages/src/user/user-order/user-order"
})
}, 500)
},
//调用失败的回调
fail(err) {
uni.showToast({
title: "支付取消",
icon: "none"
})
setTimeout(() => {
uni.redirectTo({
url: "/pages/src/user/user-order/user-order"
})
}, 500)
}
})
}
3.2 退款接口调用
这里我的退款接口放在商家平台,app只负责申请退款,需要商家审核,使用的是TS代码。
// 退款
export interface PostInterfacesRefundRes {
orderIds: number,
tradeNo: number
}
const openDialog = async (
rowData: PostInterfacesRefundRes = {} as PostInterfacesRefundRes,
) => {
console.log(rowData);
const params = {
orderIds: rowData.orderIds,
tradeNo: rowData.tradeNo
}
let res = await postAgreementRefundApi(params);
ElMessage.success({ message: `操作成功!` })
proTable.value?.getTableList()
}