《Java接入支付宝沙箱支付全流程详解》
一、引言
二、环境准备
1. 注册支付宝开放平台账号
2. 创建沙箱应用
3. 生成密钥
4. Natapp内网穿透8080端口
三、Java集成支付宝SDK
pom.xml
各种实体类
AliPay配置类
服务接口类
四、实现效果
五、结语
一、引言
支付宝沙箱环境为开发者提供了安全的测试环境,本文将详细介绍如何通过Java语言集成支付宝沙箱支付功能,涵盖环境配置、密钥生成、SDK集成及支付流程实现。
在沙箱环境中,开发者可以:
模拟真实的支付场景
测试各种支付结果(成功、失败、退款等)
调试支付回调通知
验证签名机制
使用沙箱环境的主要优势在于可以在不影响真实业务的情况下,充分测试支付系统的各种边界条件和异常情况。
二、环境准备
1. 注册支付宝开放平台账号
支付宝|开放平台 地址:登录 - 支付宝
完成注册与实名认证。
2. 创建沙箱应用
登录后进入控制台,选择「沙箱环境」,系统自动生成应用并获取APPID。
沙箱应用与正式应用功能相同,但所有交易都是模拟的。每个开发者账号默认有一个沙箱应用,无需额外申请。
3. 生成密钥
开启沙箱应用以后,还需要下载支付宝秘钥工具。秘钥工具创建的秘钥,需要填写到查看中。
【沙箱账号】,里面提供了后续在网页上支付时,输入的账号、密码和支付密码。
【沙箱工具】,里面提供了安卓版测试软件,可以在手机扫码支付。
文档中心 | 开放平台 地址:小程序文档 - 支付宝文档中心 - 下载支付宝开放平台秘钥工具 在文档的介绍中,也有很详细的说明
下载生成应用私钥(merchantPrivateKey)和应用公钥。
打开密钥生成工具
选择"生成密钥"→"RSA2(2048位)"
工具会生成应用私钥(merchantPrivateKey)和应用公钥
妥善保存生成的私钥文件(后续代码中需要使用)
上传应用公钥至支付宝开放平台,获取支付宝公钥(alipayPublicKey)。
点击"设置公钥",粘贴上一步生成的应用公钥
保存后,系统会返回支付宝公钥(alipayPublicKey)
记录支付宝公钥,后续验证回调签名时需要
4. Natapp内网穿透8080端口
请参考:《使用Natapp实现内网穿透:轻松打通内外网连接》_natapp怎么样-CSDN博客
Powered By NATAPP Please visit https://natapp.cn(Ctrl+C to Quit)Tunnel Status OnlineVersion 2.4.0Forwarding http://xxxxxxx.natappfree.cc -> 127.0.0.1:8080Web Interface DisabledTotal Connections 0
配置要点:
隧道协议选择HTTP
本地端口与Spring Boot应用端口一致
免费隧道每24小时更换域名,付费隧道可固定域名
主要关注外部地址:
Forwarding http://xxxxxxx.natappfree.cc
三、Java集成支付宝SDK
支付配置:
app_id:应用ID - 您的APPID,收款账号既是你的APPID对应支付宝账号。
merchant_private_key:商户私钥,【通过支付宝开放平台秘钥工具】创建出来的私钥。公钥填写到网页上,私钥程序里使用。
alipay_public_key:支付宝公钥,在网页上填写公钥后,会给你一个支付宝的公钥。
notify_url:服务器异步通知回调地址,也就是你支付完成后,支付宝调用你的地址。因为我们是在本地做测试,外网是访问不到的。所以为了能做这样的测试,可以回调到我们。那么这里需要使用 natapp做一个内网穿透。
return_url:支付完成后跳转的地址,回调的地址需要公网IP地址或者走内网穿透。
gatewayUrl:支付宝沙箱环境的地址,固定的。
sign_type:签名方式固定的
charset:字符编码固定的
调用配置
out_trade_no:你的单号,用你的单号类生成支付单信息。每次创建订单的时候,单号保持唯一。
total_amount:支付金额
subject:商品名称
product_code:固定值;FAST_INSTANT_TRADE_PAY
pom.xml
添加支付宝SDK依赖,支付宝SDK封装了与支付宝网关的交互逻辑,包括签名生成、请求发送和响应处理等。
<!-- 支付宝支付SDK -->
<!-- 官方文档:https://opendocs.alipay.com/common/02kkv7 -->
<dependency><groupId>com.alipay.sdk</groupId><artifactId>alipay-sdk-java</artifactId><version>4.38.157.ALL</version>
</dependency>
各种实体类
Response:通用响应对象,包含状态码、提示信息和数据体,用于统一接口返回格式。
Constants:系统常量定义,包括响应码枚举和订单状态枚举,提高代码可读性和可维护性。
PayOrder:支付订单实体类,记录订单基本信息、状态及支付相关数据。
PayOrderRes:支付订单响应对象,包含订单ID和支付链接等信息。
ShopCartReq:购物车请求对象,用于传递用户和商品信息。
ProductVO:商品值对象,封装商品的基本属性和价格信息。
Response
通用响应对象,包含code、info和data字段,用于统一接口返回格式
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;/**
* @Description: 通用响应结果
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Response<T> implements Serializable {private static final long serialVersionUID = 7000723935764546321L;private String code;private String info;private T data;}
Constants
包含系统常量定义,如响应码、订单状态枚举等
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;/*** @description 通用的枚举值*/
public class Constants {public final static String SPLIT = ",";@AllArgsConstructor@NoArgsConstructor@Getterpublic enum ResponseCode {SUCCESS("0000", "调用成功"),UN_ERROR("0001", "调用失败"),ILLEGAL_PARAMETER("0002", "非法参数"),NO_LOGIN("0003", "未登录"),;private String code;private String info;}@Getter@AllArgsConstructorpublic enum OrderStatusEnum {CREATE("CREATE", "创建完成 - 如果调单了,也会从创建记录重新发起创建支付单"),PAY_WAIT("PAY_WAIT", "等待支付 - 订单创建完成后,创建支付单"),PAY_SUCCESS("PAY_SUCCESS", "支付成功 - 接收到支付回调消息"),DEAL_DONE("DEAL_DONE", "交易完成 - 商品发货完成"),CLOSE("CLOSE", "超时关单 - 超市未支付"),;private final String code;private final String desc;}}
PayOrder
支付订单实体,记录订单基本信息、状态和支付信息
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import java.math.BigDecimal;
import java.util.Date;@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PayOrder {private Long id; // 主键private String userId; // 用户idprivate String productId; // 商品idprivate String productName; // 商品名称private String orderId; // 订单idprivate Date orderTime; // 下单时间private BigDecimal totalAmount; // 总金额private String status; // 订单状态private String payUrl; // 支付链接private Date payTime; // 支付时间private Date createTime; // 创建时间private Date updateTime; // 修改时间}
PayOrderRes
支付订单响应对象,包含订单ID和支付链接
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PayOrderRes {// 用户IDprivate String userId;// 订单IDprivate String orderId;// 支付链接private String payUrl;// 订单状态private Constants.OrderStatusEnum orderStatusEnum;}
ShopCartReq
购物车请求对象,包含用户ID和商品ID
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ShopCartReq {// 用户idprivate String userId;// 商品idprivate String productId;}
ProductVO
商品值对象,包含商品基本信息
import lombok.Data;import java.math.BigDecimal;@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ProductVO {/** 商品ID */private String productId;/** 商品名称 */private String productName;/** 商品描述 */private String productDesc;/** 商品价格 */private BigDecimal price;}
AliPay配置类
application.yml
在配置文件中设置支付宝相关参数,包括应用ID、公私钥、回调地址和网关地址等。
# ===================== 支付宝支付配置 =====================
# 沙箱环境文档:https://opendocs.alipay.com/common/02kkv7
alipay:enabled: true # 是否启用app_id: # <你的沙箱应用ID>merchant_private_key: # <你的商户私钥>alipay_public_key: # <支付宝公钥>notify_url: http://<可外网访问的域名或内网穿透>/api/v1/alipay/alipay_notify_url # 异步通知地址return_url: # 同步跳转地址(支付成功页面)gatewayUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.do # 沙箱网关地址
AliPayConfigProperties
使用@ConfigurationProperties注解将配置映射到Java类:
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;@Data
@ConfigurationProperties(prefix = "alipay", ignoreInvalidFields = true) // 指定配置前缀,忽略无效字段
public class AliPayConfigProperties {// 「沙箱环境」应用ID - 您的APPID,收款账号既是你的APPID对应支付宝账号。获取地址;https://open.alipay.com/develop/sandbox/appprivate String app_id;// 「沙箱环境」商户私钥,你的PKCS8格式RSA2私钥private String merchant_private_key;// 「沙箱环境」支付宝公钥private String alipay_public_key;// 「沙箱环境」服务器异步通知页面路径private String notify_url;// 「沙箱环境」页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问private String return_url;// 「沙箱环境」private String gatewayUrl;// 签名方式private String sign_type = "RSA2";// 字符编码格式private String charset = "utf-8";// 传输格式private String format = "json";}
AliPayConfig
配置支付宝客户端Bean,用于初始化支付宝客户端实例,封装SDK的调用逻辑。
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
@EnableConfigurationProperties(AliPayConfigProperties.class)
public class AliPayConfig {@Bean("alipayClient")public AlipayClient alipayClient(AliPayConfigProperties properties) {return new DefaultAlipayClient(properties.getGatewayUrl(),properties.getApp_id(),properties.getMerchant_private_key(),properties.getFormat(),properties.getCharset(),properties.getAlipay_public_key(),properties.getSign_type());}}
服务接口类
AliPayController
提供创建支付订单和处理支付宝回调的接口,包括订单生成、签名验证和状态更新等功能。
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;@Slf4j
@RestController()
@CrossOrigin("*")
@RequestMapping("/api/v1/alipay/")
public class AliPayController {@Value("${alipay.alipay_public_key}")private String alipayPublicKey;@Resourceprivate IOrderService orderService;/*** http://localhost:8080/api/v1/alipay/create_pay_order* 创建支付单接口,返回支付链接url* {* "userId": "10001", // 用户ID【实际产生中会通过登录模块获取,不需要透彻,这里只是方便】* "productId": "100001"* }*/@RequestMapping(value = "create_pay_order", method = RequestMethod.POST)public Response<String> createPayOrder(@RequestBody CreatePayRequestDTO createPayRequestDTO) {try {log.info("商品下单,根据商品ID创建支付单开始 userId:{} productId:{}", createPayRequestDTO.getUserId(), createPayRequestDTO.getUserId());String userId = createPayRequestDTO.getUserId();String productId = createPayRequestDTO.getProductId();// 购物车商品对象ShopCartReq shopCartReq = ShopCartReq.builder().userId(userId).productId(productId).build();// 下单从,创建支付单PayOrderRes payOrderRes = orderService.createOrder(shopCartReq);log.info("商品下单,根据商品ID创建支付单完成 userId:{} productId:{} orderId:{}", userId, productId, payOrderRes.getOrderId());return Response.<String>builder().code(Constants.ResponseCode.SUCCESS.getCode()).info(Constants.ResponseCode.SUCCESS.getInfo()).data(payOrderRes.getPayUrl()) // 返回支付链接.build();} catch (Exception e) {log.error("商品下单,根据商品ID创建支付单失败 userId:{} productId:{}", createPayRequestDTO.getUserId(), createPayRequestDTO.getUserId(), e);return Response.<String>builder().code(Constants.ResponseCode.UN_ERROR.getCode()).info(Constants.ResponseCode.UN_ERROR.getInfo()).build();}}/*** http://xxxx.natapp1.cc/api/v1/alipay/alipay_notify_url* 支付宝支付结果异步通知回调接口** @param request HTTP请求对象,包含支付宝回调参数* @return 处理结果:"success"表示处理成功,"false"表示处理失败* @throws AlipayApiException 支付宝API异常*/@RequestMapping(value = "alipay_notify_url", method = RequestMethod.POST)public String payNotify(HttpServletRequest request) throws AlipayApiException {try {// 1. 打印回调日志(建议记录原始回调参数,方便排查问题)log.info("支付回调,消息接收 {}", request.getParameter("trade_status"));// 2. 校验交易状态(只处理支付成功的回调)if (!"TRADE_SUCCESS".equals(request.getParameter("trade_status"))) {log.warn("非成功交易状态,忽略处理. 状态: {}", request.getParameter("trade_status"));return "false";}// 3. 转换请求参数格式(Map<String,String[]> -> Map<String,String>)Map<String, String> params = new HashMap<>();Map<String, String[]> requestParams = request.getParameterMap();for (String name : requestParams.keySet()) {params.put(name, request.getParameter(name)); // 只取第一个值}// 4. 获取关键业务参数String tradeNo = params.get("out_trade_no"); // 商户订单号String gmtPayment = params.get("gmt_payment"); // 支付时间String alipayTradeNo = params.get("trade_no"); // 支付宝交易号// 5. 支付宝签名验证校验(重要安全措施)String sign = params.get("sign");String content = AlipaySignature.getSignCheckContentV1(params);boolean checkSignature = AlipaySignature.rsa256CheckContent(content,sign,alipayPublicKey,"UTF-8");if (!checkSignature) {log.error("支付宝签名验证失败,疑似非法请求. 订单号: {}", tradeNo);return "false";}// 6. 验签通过后记录回调详情(建议持久化存储)log.info("支付回调详情 => 交易名称: {}", params.get("subject"));log.info("支付回调详情 => 交易状态: {}", params.get("trade_status"));log.info("支付回调详情 => 支付宝交易号: {}", alipayTradeNo);log.info("支付回调详情 => 商户订单号: {}", tradeNo);log.info("支付回调详情 => 交易金额: {}", params.get("total_amount"));log.info("支付回调详情 => 买家ID: {}", params.get("buyer_id"));log.info("支付回调详情 => 付款时间: {}", gmtPayment);log.info("支付回调详情 => 实付金额: {}", params.get("buyer_pay_amount"));// 7. 更新订单状态(建议增加幂等处理)log.info("支付回调,开始更新订单状态 {}", tradeNo);orderService.changeOrderPaySuccess(tradeNo);// 8. 返回成功响应(必须返回"success"字符串)return "success";} catch (Exception e) {log.error("支付回调,处理失败", e);return "false";}}}
IOrderService
定义订单服务的接口方法,包括创建订单和更新订单状态等。
import java.util.List;/*** @description 订单服务接口*/
public interface IOrderService {/*** 创建订单* @param shopCartReq* @return*/PayOrderRes createOrder(ShopCartReq shopCartReq) throws Exception;/*** 修改订单支付成功* @param orderId*/void changeOrderPaySuccess(String orderId);
}
OrderServiceImpl
实现订单服务的具体逻辑,包括订单查询、支付单创建和状态更新等,确保支付流程的完整性和可靠性。
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.google.common.eventbus.EventBus;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;/*** @description 订单服务*/
@Slf4j
@Service
public class OrderServiceImpl implements IOrderService {@Value("${alipay.notify_url}")private String notifyUrl;@Value("${alipay.return_url}")private String returnUrl;@Resourceprivate AlipayClient alipayClient; // 支付宝客户端@Overridepublic PayOrderRes createOrder(ShopCartReq shopCartReq) throws Exception {// 1. 查询当前用户是否存在未支付订单或掉单订单PayOrder payOrderReq = new PayOrder();payOrderReq.setUserId(shopCartReq.getUserId());payOrderReq.setProductId(shopCartReq.getProductId());//PayOrder unpaidOrder = orderDao.queryUnPayOrder(payOrderReq); // 数据库中进行查询if (null != unpaidOrder && Constants.OrderStatusEnum.PAY_WAIT.getCode().equals(unpaidOrder.getStatus())) {log.info("创建订单-存在,已存在未支付订单。userId:{} productId:{} orderId:{}", shopCartReq.getUserId(), shopCartReq.getProductId(), unpaidOrder.getOrderId());return PayOrderRes.builder().orderId(unpaidOrder.getOrderId()).payUrl(unpaidOrder.getPayUrl()).build();} else if (null != unpaidOrder && Constants.OrderStatusEnum.CREATE.getCode().equals(unpaidOrder.getStatus())) {// 网络等其他问题,导致订单状态未更新成等待支付,则重新创建支付单log.info("创建订单-存在,存在未创建支付单订单,创建支付单开始 userId:{} productId:{} orderId:{}", shopCartReq.getUserId(), shopCartReq.getProductId(), unpaidOrder.getOrderId());PayOrder payOrder = doPrepayOrder(unpaidOrder.getProductId(), unpaidOrder.getProductName(), unpaidOrder.getOrderId(), unpaidOrder.getTotalAmount());return PayOrderRes.builder().orderId(payOrder.getOrderId()).payUrl(payOrder.getPayUrl()).build();} else {log.info("创建订单-不存在,存在未创建支付单订单,创建支付单开始 userId:{} productId:{}", shopCartReq.getUserId(), shopCartReq.getProductId());}// 2. 查询商品 & 创建订单ProductVO productVO = new ProductVO();productVO.setProductId(productId);productVO.setProductName("测试商品");productVO.setProductDesc("这是一个测试商品");productVO.setPrice(new BigDecimal("13.14"));String orderId = RandomStringUtils.randomNumeric(16); // 生成16位随机订单号(正式环境建议使用分布式ID生成器(如雪花算法)代替)orderDao.insert(PayOrder.builder().userId(shopCartReq.getUserId()).productId(shopCartReq.getProductId()).productName(productVO.getProductName()).orderId(orderId).totalAmount(productVO.getPrice()).orderTime(new Date()).status(Constants.OrderStatusEnum.CREATE.getCode()).build());// 3. 创建支付单PayOrder payOrder = doPrepayOrder(productVO.getProductId(), productVO.getProductName(), orderId, productVO.getPrice());return PayOrderRes.builder().orderId(orderId).payUrl(payOrder.getPayUrl()).build();}@Overridepublic void changeOrderPaySuccess(String orderId) {// 检查此订单是否已支付过PayOrder payOrder = orderDao.queryByOrderIdAndStatus(orderId, Constants.OrderStatusEnum.PAY_WAIT.getCode());if (payOrder == null) return;// 更新支付单为支付成功PayOrder updatePayOrder = new PayOrder();updatePayOrder.setOrderId(orderId);updatePayOrder.setStatus(Constants.OrderStatusEnum.PAY_SUCCESS.getCode());orderDao.changeOrderPaySuccess(updatePayOrder);// 消息队列发送支付成功消息......}/*** 创建支付宝预支付订单** @param productId 商品ID* @param productName 商品名称(将作为支付显示的标题)* @param orderId 商户订单号(唯一标识)* @param totalAmount 订单总金额(单位:元)* @return PayOrder 支付订单信息* @throws AlipayApiException 支付宝API调用异常*/private PayOrder doPrepayOrder(String productId, String productName, String orderId, BigDecimal totalAmount)throws AlipayApiException {// 1. 创建支付宝页面支付请求对象AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();// 设置异步通知回调地址(支付成功后支付宝服务器主动通知的地址),用于支付宝服务器在支付成功后主动通知商户服务器,触发订单状态更新等业务逻辑request.setNotifyUrl(notifyUrl);// 设置同步跳转地址(支付成功后用户浏览器跳转的地址),支付成功后,支付宝通过浏览器重定向跳转到该地址,用于向用户展示支付结果,需要公网IP地址或者走内网穿透request.setReturnUrl(returnUrl);// 2. 构建业务请求参数JSONObject bizContent = new JSONObject();bizContent.put("out_trade_no", orderId); // 商户订单号bizContent.put("total_amount", totalAmount.toString()); // 订单金额(字符串格式)bizContent.put("subject", productName); // 订单标题/商品名称bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY"); // 销售产品码(固定值)// 将业务参数设置到请求中request.setBizContent(bizContent.toString());// 3. 调用支付宝接口生成支付表单String form = alipayClient.pageExecute(request).getBody();// 4. 构建支付订单信息PayOrder payOrder = new PayOrder();payOrder.setOrderId(orderId); // 设置订单IDpayOrder.setPayUrl(form); // 设置支付宝返回的表单HTMLpayOrder.setStatus(Constants.OrderStatusEnum.PAY_WAIT.getCode()); // 设置订单状态为"等待支付"// 5. 更新订单支付信息到数据库//orderDao.updateOrderPayInfo(payOrder);return payOrder;}}
四、实现效果
通过调用接口生成支付链接,将链接粘贴至HTML文件并在浏览器中打开,即可跳转至支付宝沙箱支付页面。使用沙箱提供的买家账号完成支付操作后,系统会异步通知本地服务并更新订单状态,实现完整的支付流程测试。
Apifox调用接口得到支付链接
将它负责粘贴到html文件,删除不必要的\与\n,并且打开到浏览器
根据平台的买家账号进行购买即可实现支付订单。
五、结语
通过本文的详细指导,您已经完成了从零开始集成支付宝沙箱支付功能的完整流程。让我们回顾一下关键要点:
安全测试环境:支付宝沙箱环境为开发者提供了零风险的测试平台,让您可以在不影响真实业务的情况下全面验证支付流程。
全流程覆盖:从账号注册、密钥配置到SDK集成和回调处理,本文提供了端到端的解决方案,确保每个环节都有据可依。
希望本指南能帮助您顺利实现支付宝支付功能的集成。如果在实施过程中遇到任何问题,建议:
查阅支付宝官方文档
检查应用日志获取详细错误信息
在开发者社区寻求帮助
本文详细介绍了Java接入支付宝沙箱支付的全流程,从环境准备到代码实现,逐步引导开发者完成支付功能的集成。支付宝沙箱环境为开发者提供了安全、便捷的测试平台,帮助其在真实业务上线前充分验证系统功能。希望通过本文的指导,开发者能够顺利完成支付功能的接入,为项目的正式上线奠定坚实基础。
如果对此篇文章有什么疑问或者建议欢迎评论区留言讨论!