前言:
在一次项目开发中我的业务模块涉及微信扫码支付和支付宝扫码支付。在查阅现有技术性文章后发现,许多文章并不能走通,于是我便阅读了官方文档,形成了这篇总结性文章,希望对读者有所帮助。
支付宝平台
支付宝开发者中心
支付宝开放平台
API开发文档
官方demo-easySDK
支付宝平台信息配置
相信查询此刻你一定已经注册登陆过支付宝平台了,在此不多赘述。需在平台中获取的开发所需信息如下:
APPID
:应用APPID(小程序|小程序插件|小程序模板|网页&移动应用|生活号)
ALIPAY_PUBLIC_KEY
:支付宝公钥
MERCHANT_PRIVATE_KEY
:应用私钥
SIGN_TYPE
:签名类型,Alipay Easy SDK只推荐使用RSA2,估此处固定填写RSA2
GATEWAYHOST
:网关域名,线上为:openapi.alipay.com,沙箱为:openapi.alipaydev.com
关于沙箱环境说明
沙箱环境
和线上环境
在代码书写上还好,除了接口请求地址几乎没有差别,但是在平台配置上差距可是老大了。等于说,沙箱环境不论你是个人账号还是商户账号,只要你申请了沙箱测试环境以上所需的核心参数就直接给你提供了。沙箱工具位置:登录支付宝开发者中心,依次点击:控制台
→沙箱
,就到了沙箱环境的控制台。
进入沙箱环境后,上面列出的商户参数信息一应俱全
注意:
沙箱应用
页面中的沙箱环境
→沙箱应用
→开发信息
中的接口加签方式在此使用推荐的自定义密钥
→RSA2密钥
- 需要手动进入能力管理页面点击
获取更多能力
获取所需能力,在此
申请支付产品
- 登录支付宝平台(此时需要商户账号,或者需要管理权限),选择
我是商家用户
→我是 支付宝/口碑 商家
,选择要登录的账号(此时选择商户的账号) - 找到
产品中心
,选择当面付
,立即开通
,此时需要甲方提供:营业执照、网站链接、授权函等信息(缺啥要啥就对了)。 - 提交后等待审核。
获取应用AppID
- 登录支付宝开放平台(此时需要商户账号,或者需要管理权限),在控制台页面,查看
我的应用
,我本次开发甲方提供的是网页&移动应用
- 登录支付宝平台
- 进入后依次点击:
账号中心
→APPID绑定
→添加绑定
,输入要绑定的APPID,然后联系商户手机验证码验证即可。
补充:
以上步骤仅供参考,你的开发过程中可能有各种需要甲方配合的地方,这个要视情况要求甲方配合,比如我在开发过程中甲方信息不完善,甚至连营业执照、公司logo、公司类型等这些基本信息都没上传。
获取支付宝公钥&私钥
- 登录支付宝开放平台(此时需要商户账号,或者需要管理权限),在控制台页面,查看
我的应用
,点击进入应用 - 进入应用信息页找到
应用信息
,在此页面开发信息
中设置密钥,可以选择线上生成密钥,也可选择下载支付宝官方平台工具(本人选择的是下载工具)
代码中的配置
引入依赖
此处引入了easysdk
工具
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-easysdk</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.22.0.ALL</version>
</dependency>
配置请求参数实体类
@Data
public class AlipayNotifyParam implements Serializable {
private String appId;
/**
* 支付宝交易凭证号
*/
private String tradeNo;
/**
* 原支付请求的商户订单号
*/
private String outTradeNo;
/**
* 商户业务ID,主要是退款通知中返回退款申请的流水号
*/
private String outBizNo;
/**
* 买家支付宝账号对应的支付宝唯一用户号。以2088开头的纯16位数字
*/
private String buyerId;
/**
* 买家支付宝账号
*/
private String buyerLogonId;
/**
* 卖家支付宝用户号
*/
private String sellerId;
/**
* 卖家支付宝账号
*/
private String sellerEmail;
/**
* 交易目前所处的状态,见交易状态说明
*/
private String tradeStatus;
/**
* 本次交易支付的订单金额
*/
private BigDecimal totalAmount;
/**
* 商家在交易中实际收到的款项
*/
private BigDecimal receiptAmount;
/**
* 用户在交易中支付的金额
*/
private BigDecimal buyerPayAmount;
/**
* 退款通知中,返回总退款金额,单位为元,支持两位小数
*/
private BigDecimal refundFee;
/**
* 商品的标题/交易标题/订单标题/订单关键字等
*/
private String subject;
/**
* 该订单的备注、描述、明细等。对应请求时的body参数,原样通知回来
*/
private String body;
/**
* 该笔交易创建的时间。格式为yyyy-MM-dd HH:mm:ss
*/
private Date gmtCreate;
/**
* 该笔交易的买家付款时间。格式为yyyy-MM-dd HH:mm:ss
*/
private Date gmtPayment;
/**
* 该笔交易的退款时间。格式为yyyy-MM-dd HH:mm:ss.S
*/
private Date gmtRefund;
/**
* 该笔交易结束时间。格式为yyyy-MM-dd HH:mm:ss
*/
private Date gmtClose;
/**
* 支付成功的各个渠道金额信息,array
*/
private String fundBillList;
/**
* 公共回传参数,如果请求时传递了该参数,则返回给商户时会在异步通知时将该参数原样返回。
*/
private String passbackParams;
}
配置支付工具类
此工具类仅供参考,配置信息也可根据个人喜好放在yml文件中(为方便阅读此处放在类中作为常量)。
public class AlipayUtil {
public static final String APPID = "";
public static final String MERCHANT_PRIVATE_KEY = "私钥内容";
public static final String ALIPAY_PUBLIC_KEY = "公钥内容";
public static final String NOTIFY_URL = "回调地址";
// 签名类型,Alipay Easy SDK只推荐使用RSA2,估此处固定填写RSA2
public static final String SIGN_TYPE = "RSA2";
// 请求使用的编码格式,如utf-8,gbk,gb2312等
public static final String CHARSET = "utf-8";
// 主机域名
public static final String GATEWAYHOST = "openapi.alipay.com";
// 请求协议
public static final String PROTOCOL = "https";
/**
* @Author Spence_Dou
* @Description 配置请求配置
* @Date 16:49 2021/12/29
* @return com.alipay.8.kernel.Config
*/
public static Config getOptions() {
Config config = new Config();
config.protocol = PROTOCOL;
config.gatewayHost = GATEWAYHOST;
config.signType = SIGN_TYPE;
config.appId = APPID;
config.merchantPrivateKey = AlipayUtil.MERCHANT_PRIVATE_KEY;
// 如果采用非证书模式,则无需赋值三个证书路径,改为赋值如下的支付宝公钥字符串即可
config.alipayPublicKey = AlipayUtil.ALIPAY_PUBLIC_KEY;
//可设置异步通知接收服务地址(可选)
config.notifyUrl = NOTIFY_URL;
//可设置AES密钥,调用AES加解密相关接口时需要(可选)
// 32位,需要的话直接生成随机字符串,需要在商户平台配置,位置在证书那一块
config.encryptKey = "*********";
return config;
}
/**
* @Author Spence_Dou
* @Description 生成订单号
* @Date 22:05 2021/12/28
* @Param []
* @return java.lang.String
*/
public static String orderNo(){
return UUID.randomUUID().toString()
.replaceAll("-", "")
.substring(0, 32);
}
/**
* @Author Spence_Dou
* @Description 将request中的参数转为Map
* @Date 18:01 2021/12/29
* @Param request 回调请求
* @return java.util.Map<java.lang.String,java.lang.String>
*/
public static Map<String, String> convertRequestParamsToMap(HttpServletRequest request){
HashMap<String, String> retMap = new HashMap<>();
Set<Map.Entry<String, String[]>> entrySet = request.getParameterMap().entrySet();
for (Map.Entry<String, String[]> entry : entrySet){
String name = entry.getKey();
String[] values = entry.getValue();
int valLen = values.length;
if (valLen == 1){
retMap.put(name, values[0]);
}else if (valLen > 1){
StringBuilder sb = new StringBuilder();
for (String val : values){
sb.append(",").append(val);
}
retMap.put(name, sb.toString().substring(1));
}else {
retMap.put(name, "");
}
}
return retMap;
}
/**
* @Author Spence_Dou
* @Description 校验AppID
* @Date 20:32 2021/12/29
* @Param params 回调信息
* @return void
*/
public static void check(Map<String, String> params) throws AlipayApiException {
if (!params.get("app_id").equals(APPID)) {
throw new AlipayApiException("app_id不一致");
}
}
/**
* @Author Spence_Dou
* @Description 生成请求实体类
* @Date 17:51 2022/2/12
* @Param params 响应体参数转map
* @return marchsoft.modules.spiritdeerpush.common.utils.pay.alipay.AlipayNotifyParam
*/
public static AlipayNotifyParam buildAlipayNotifyParam(Map<String, String> params) {
String json = JSON.toJSONString(params);
return JSON.parseObject(json, AlipayNotifyParam.class);
}
}
支付请求
- 不同的产品功能应参考官方demo-easySDK,此处以面对面支付为例
// 生成系统订单号
String outTradeNo = AlipayUtil.orderNo();
// 1. 设置参数(全局只需设置一次)
Factory.setOptions(AlipayUtil.getOptions());
try {
// 2. 发起API调用,description: 商品描述; total: 金额(元)
// 响应的 response 都有哪些参数,自己去看文档,太多了不一一描述
AlipayTradePrecreateResponse response = Factory.Payment.FaceToFace()
.preCreate(description, outTradeNo, total.toString());
// 3. 处理响应或异常
if (ResponseChecker.success(response)) {
// 付款二维码
String qrCode = response.getQrCode();
/**
* 在此可进行订单信息入库等业务逻辑操作
*/
return;
} else {
System.err.println("调用失败,原因:" + response.msg + "," + response.subMsg);
// 此处自定义抛出异常为公司框架所封装
throw new BadRequestException(response.msg);
}
} catch (Exception e) {
System.err.println("调用遭遇异常,原因:" + e.getMessage());
// 手动关闭事务
throw new RuntimeException(e.getMessage(), e);
}
回调接口
此接口地址对应支付请求时的请求参数notify_url,具体功能介绍在此不做阐述
// 将request中的参数转为Map
Map<String, String> stringStringMap = AlipayUtil.convertRequestParamsToMap(request);
try {
// 调用SDK验证签名
boolean signVerified = AlipaySignature.rsaCheckV1(stringStringMap, AlipayUtil.ALIPAY_PUBLIC_KEY, AlipayUtil.CHARSET, AlipayUtil.SIGN_TYPE);
if (signVerified){
AlipayUtil.check(stringStringMap);
AlipayNotifyParam param = AlipayUtil.buildAlipayNotifyParam(stringStringMap);
// 订单状态
String trade_status = param.getTradeStatus();
// 支付成功
if (trade_status.equals("TRADE_SUCCESS")
|| trade_status.equals("TRADE_FINISHED")){
/**
* 在此可进行订单信息维护等业务逻辑操作
*/
}else {
// 此处自定义抛出异常为公司框架所封装
throw new BadRequestException("支付失败!");
}
return "success";
}else {
// 不明白 failure 啥意思的话去看文档
return "failure";
}
} catch (AlipayApiException e) {
e.printStackTrace();
//强制手动事务回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return "failure";
}