一、前期准备:
1.注册商家账号:支付宝
2.登录进入,在产品中心选择自己所需要对接的功能:
3.进入对接功能,选择立即开通
4.填写商户信息:
5.开通成功之后,产品中心>>开发设置>>创建应用并关联,进行应用创建
6.应用创建成功之后步骤:申请应用上线>>进行产品绑定(需要先进行通)
7.在开发设置中配置密钥(涉及隐私就不付截图),有证书模式、公钥模式两种,私钥需通过其他平台或支付宝提供的支付宝密钥生成工具生成,生成之后的商家私钥个公钥请妥善保管;
8.开发时需要用到的相关数据(普通公钥模式):
AppId:前面申请的应用所附带的应用ID;
PrivateKey:商家私钥;
Domain:外网地址,用来接收支付宝异步通知;
ServiceUrl:支付宝网关(固定为沙箱或者正式环境);
二、代码
1.商户信息:
@Getter
@Setter
@Builder
@AllArgsConstructor
public class Merchant {
/**
* 应用appID
*/
private String appId;
/**
* 应用密钥
*/
private String appKey;
/**
* 商户ID
*/
private String mchId;
/**
* 商户密钥
*/
private String mchKey;
/**
* 支付宝公钥
*/
private String aliPayPublicKey;
/**
* 支付宝私钥
*/
private String privateKey;
/**
* 外网访问项目的域名,支付通知中会使用
*/
private String domain;
/**
* 支付宝环境
* 正式:https://openapi.alipay.com/gateway.do
* 沙箱:https://openapi.alipaydev.com/gateway.do
*/
private String serviceUrl;
}
2.依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-WxPay</artifactId>
<version>${ijapy.version}</version>
</dependency>
<dependency>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-AliPay</artifactId>
<version>${ijapy.version}</version>
</dependency>
<dependency>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-QQ</artifactId>
<version>${ijapy.version}</version>
</dependency>
<dependency>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-UnionPay</artifactId>
<version>${ijapy.version}</version>
</dependency>
<dependency>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-JDPay</artifactId>
<version>${ijapy.version}</version>
</dependency>
<dependency>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-PayPal</artifactId>
<version>${ijapy.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>enjoy</artifactId>
<version>${enjoy.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
3.支付客户端:
/**
* 预支付
* @param request 请求体
* @param merchant 商户配置
* @return 预支付响应
*/
public PrePayResponse prePay(PrePayRequest request, Merchant merchant){
//如果订单号为空,自动生成订单号
if (StrUtil.isBlank(request.getOrderNo())){
request.setOrderNo(getOrderNo());
}
Method method = getPayMethod(this.superService.getClass(),request.getPayMethod());
try {
PrePayResponse response = (PrePayResponse) method.invoke(this.superService,request,merchant);
if (response.getPayStatus() == null) response.setPayStatus(PayStatus.PAYING);
return response;
} catch (IllegalAccessException | InvocationTargetException e) {
log.error("支付发起失败", e);
throw new InternalRuntimeException();
}
}
public PrePayResponse webPay(HttpServletResponse httpServletResponse,PrePayRequest request,Merchant merchant) throws NoSuchMethodException {
//如果订单号为空,自动生成订单号
if (StrUtil.isBlank(request.getOrderNo())){
request.setOrderNo(getOrderNo());
}
Method method = this.superService.getClass()
.getMethod(StrUtil.toCamelCase("zfb_web"),
HttpServletResponse.class,
PrePayRequest.class,
Merchant.class);
try {
PrePayResponse response = (PrePayResponse)method.invoke(this.superService,httpServletResponse, request, merchant);
if (response.getPayStatus() == null) response.setPayStatus(PayStatus.PAYING);
return response;
} catch (IllegalAccessException | InvocationTargetException e) {
throw new InternalRuntimeException();
}
}
/**
* 解析支付通知
* @param request http请求
* @return 支付通知解析结果
*/
public PayNotify payNotify(HttpServletRequest request){
try {
Method method = this.superService.getClass().getMethod("payNotify", HttpServletRequest.class);
return (PayNotify) method.invoke(this.superService,request);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
log.error("通知处理失败", e);
throw new InternalRuntimeException();
}
}
/**
* 验证支付通知
* @param payNotify 解析后的支付通知
* @param merchant 商户配置
* @return true 验证通过, false 验证失败
*/
public boolean verifyPayNotify(PayNotify payNotify, Merchant merchant){
try {
Method method = this.superService.getClass().getMethod("verifyPayNotify", PayNotify.class, Merchant.class);
return (boolean) method.invoke(this.superService,payNotify,merchant);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
log.error("通知验证失败", e);
throw new InternalRuntimeException();
}
}
/**
* 响应上游支付通知
* @param response http响应
*/
public void payNotifyResponse(HttpServletResponse response){
try {
Method method = this.superService.getClass().getMethod("payNotifyResponse", HttpServletResponse.class);
method.invoke(this.superService,response);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
log.error("响应上游支付通知失败", e);
throw new InternalRuntimeException();
}
}
/**
* 支付查询
* @param orderNo 内部订单编号
* @param merchant 商户配置
* @return 查询结果
*/
public PayResult payOrderQuery(String orderNo, Merchant merchant){
try {
Method method = this.superService.getClass().getMethod("payOrderQuery", String.class, Merchant.class);
return (PayResult) method.invoke(this.superService,orderNo, merchant);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
log.error("支付查询失败", e);
throw new InternalRuntimeException();
}
}
/**
* 实例化支付服务
* @param paySuper 上游通道
* @return 支付服务
*/
private SuperService getService(PaySuper paySuper){
try {
Class<?> adapter = Class.forName(ClassUtil.getPackage(this.getClass()) + ".services." + StrUtil.upperFirst(paySuper.getAdapterIndex()) + "Service");
return (SuperService) adapter.newInstance();
} catch (IllegalAccessException | InstantiationException | ClassNotFoundException e) {
log.error("支付服务实例化错误", e);
throw new InternalRuntimeException();
}
}
/**
* 实例化支付方法
* @param serviceClass 服务类
* @param payMethod 支付方式
* @return 服务方法
*/
private Method getPayMethod(Class<?> serviceClass, PayMethod payMethod){
try {
return serviceClass.getMethod(StrUtil.toCamelCase(payMethod.getValue()), PrePayRequest.class, Merchant.class);
} catch (NoSuchMethodException e) {
log.error("支付服务方法调用错误", e);
throw new InternalRuntimeException();
}
}
/*private Method getPayMethod(HttpServletResponse response,Class<?> serviceClass, PayMethod payMethod) {
try {
return serviceClass.getMethod(StrUtil.toCamelCase(payMethod.getValue()),HttpServletResponse.class, PrePayRequest.class, Merchant.class);
} catch (NoSuchMethodException var4) {
log.error("支付服务方法调用错误", var4);
throw new InternalRuntimeException();
}
}*/
private String getOrderNo(){
return DateUtil.format(new Date(),"yyMMddhhmmssSSS") + RandomUtil.randomNumbers(7);
}
4.通道服务接口:
/**
* 支付宝二维码支付
* @param request
* @param merchant
* @return
*/
default PrePayResponse zfbQr(PrePayRequest request, Merchant merchant) throws AlipayApiException {
throw new NotSupportedMethodException();
}
/**
* 支付宝手机网页支付
* @param response
* @param merchant
* @param request
* @return
* @throws AlipayApiException
*/
default void zfbWeb(HttpServletResponse response,PrePayRequest request,Merchant merchant)throws AlipayApiException{
throw new NotSupportedMethodException();
}
/**
* 处理支付通知
* @param request
* @return
*/
default PayNotify payNotify(HttpServletRequest request){
throw new NotSupportedMethodException("支付通知不支持");
}
/**
* 验证支付通知签名或者加密
* @param notify
* @param merchant
* @return
*/
default boolean verifyPayNotify(PayNotify notify, Merchant merchant) throws AlipayApiException {
throw new NotSupportedMethodException("通知验证不支持");
};
/**
* 收到上游通知,返回响应
* @param response
*/
default void payNotifyResponse(HttpServletResponse response) {}
/**
* 订单查询
* @param orderNo 内部订单编号
* @param merchant 商户信息
* @return
*/
default PayResult payOrderQuery(String orderNo, Merchant merchant) throws AlipayApiException {
throw new NotSupportedMethodException("订单查询不支持");
}
5.支付宝官方对接:
package com.yuheng.payment.services;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradePrecreateModel;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.ijpay.alipay.AliPayApi;
import com.ijpay.alipay.AliPayApiConfig;
import com.ijpay.alipay.AliPayApiConfigKit;
import com.ijpay.core.kit.HttpKit;
import com.ijpay.core.kit.WxPayKit;
import com.yuheng.payment.constants.PayStatus;
import com.yuheng.payment.exceptions.InternalRuntimeException;
import com.yuheng.payment.exceptions.PrePayResponseException;
import com.yuheng.payment.models.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Map;
/**
* 支付宝官方
* @author michael
* @since 2022/11/15
*/
public class ZhifubaoService implements SuperService {
private static final Logger log = LoggerFactory.getLogger(ZhifubaoService.class);
/**
* 初始化商户、应用信息
* @param merchant
* @return
* @throws AlipayApiException
*/
public AliPayApiConfig getConfig(Merchant merchant) throws AlipayApiException {
AliPayApiConfig aliPayApiConfig;
try {
aliPayApiConfig = AliPayApiConfigKit.getApiConfig(merchant.getAppId());
} catch (Exception e) {
aliPayApiConfig = AliPayApiConfig
.builder()
.setAppId(merchant.getAppId())
.setPrivateKey(merchant.getPrivateKey())
/*.setAliPayCertPath(merchant.getAliPayCertPath())
.setAppCertPath(merchant.getAppCertPath())
.setAliPayRootCertPath(merchant.getAliPayRootCertPath())*/
.setAliPayPublicKey(merchant.getAliPayPublicKey())
.setCharset("UTF-8")
.setSignType("RSA2")
.setServiceUrl(merchant.getServiceUrl())
// 普通公钥方式
.build();
// 证书模式
// .buildByCert();
}
return aliPayApiConfig;
}
/**
* 支付宝扫码支付
* @param request
* @param merchant
* @return
* @throws AlipayApiException
*/
@Override
public PrePayResponse zfbQr(PrePayRequest request, Merchant merchant) throws AlipayApiException {
// AliPayApiConfigKit.putApiConfig(aliPayApiConfig);
AliPayApiConfigKit.setThreadLocalAliPayApiConfig(getConfig(merchant));
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
model.setSubject(request.getBody());
// 传入金额单位为 分,换成支付宝单位 元
model.setTotalAmount(Double.toString(request.getAmount() / 100.00));
model.setOutTradeNo(request.getOrderNo());
String resultStr = AliPayApi.tradePrecreatePayToResponse(model, request.getNotifyUrl()).getBody();
JSONObject jsonObject = JSONObject.parseObject(resultStr).getJSONObject("alipay_trade_precreate_response");
String code = jsonObject.getString("code");
if (!"10000".equals(code)){
log.error("支付发起失败{}", jsonObject.getString("sub_msg"));
}
String qrCode = jsonObject.getString("qr_code");
String outTradeNo = jsonObject.getString("out_trade_no");
// return jsonObject.getJSONObject("alipay_trade_precreate_response").getString("qr_code");
return PrePayResponse
.builder()
.orderNo(outTradeNo)
.payText(qrCode)
.payStatus(PayStatus.PAYING)
.build();
}
/**
* 手机网页支付
* @param response
* @param merchant
* @param request
* @return
* @throws AlipayApiException
*/
@Override
public void zfbWeb(HttpServletResponse response, PrePayRequest request, Merchant merchant) throws AlipayApiException{
AliPayApiConfigKit.setThreadLocalAliPayApiConfig(getConfig(merchant));
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setBody(request.getBody());
model.setSubject(request.getBody());
// 传入金额单位为 分,换成支付宝单位 元
model.setTotalAmount(Double.toString(request.getAmount() / 100.00));
model.setOutTradeNo(request.getOrderNo());
// System.out.println("wap outTradeNo>" + outTradeNo);
// model.setOutTradeNo(outTradeNo);
model.setProductCode("QUICK_WAP_PAY");
try {
AliPayApi.wapPay(response, model, request.getReturnUrl(), request.getNotifyUrl());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 待测试
* @param request
* @return
*/
@Override
public PayNotify payNotify(HttpServletRequest request) {
// 获取支付宝POST过来反馈信息
String readData = HttpKit.readData(request);
log.debug("收到支付宝支付原始通知:"+readData);
Map<String, String> params = AliPayApi.toMap(request);
/* for (Map.Entry<String, String> entry : params.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}*/
if ("10000".equals(params.get("code"))) {
PayNotify notifyResponse = PayNotify.builder()
.orderNo(params.get("out_trade_no"))
.outOrderNo(params.get("transaction_id"))
.payStatus(PayStatus.PAYING)
.build();
if ("TRADE_SUCCESS".equals(params.get("trade_status"))) {
notifyResponse.setPayTime(timeFormat(params.get("gmt_create")));
notifyResponse.setPayStatus(PayStatus.PAID);
}
notifyResponse.setRawSignObject(params);
return notifyResponse;
} else
{
return null;
}
}
/**
* 签名验证
* @param response
* @param merchant
* @return
* @throws AlipayApiException
*/
@Override
public boolean verifyPayNotify(PayNotify response, Merchant merchant) throws AlipayApiException {
return AlipaySignature.rsaCheckV1((Map<String, String>) response.getRawSignObject(), merchant.getAliPayPublicKey(), "UTF-8", "RSA2");
}
/**
* 根据订单号进行查询
* @param orderNo 内部订单编号
* @param merchant 商户信息
* @return
* @throws AlipayApiException
*/
@Override
public PayResult payOrderQuery(String orderNo, Merchant merchant) throws AlipayApiException {
AliPayApiConfigKit.setThreadLocalAliPayApiConfig(getConfig(merchant));
AlipayTradeQueryModel model = new AlipayTradeQueryModel();
model.setOutTradeNo(orderNo);
String body = AliPayApi.tradeQueryToResponse(model).getBody();
JSONObject jsonObject = JSONObject.parseObject(body).getJSONObject("alipay_trade_query_response");
String code = jsonObject.getString("code");
if (!"10000".equals(code)){
throw new InternalRuntimeException("发起查询失败,原因["+code+":"+ jsonObject.getString("sub_msg")+"]");
}
String tradeStatus = jsonObject.getString("trade_status");
PayStatus payStatus;
switch (tradeStatus){
case "TRADE_FINISHED" :
payStatus = PayStatus.FINISHED;
break;
case "TRADE_SUCCESS" :
payStatus = PayStatus.PAID;
break;
case "TRADE_CLOSED" :
payStatus = PayStatus.CLOSE;
break;
default:
payStatus = PayStatus.PAYING;
}
return PayResult
.builder()
.orderNo(jsonObject.getString("trade_no"))
.outOrderNo(jsonObject.getString("out_trade_no"))
.payStatus(payStatus)
.build();
}
/**
* 时间戳转时间字符串
* @param timeStr 时间戳Long
* @return String 时间
*/
private Long timeFormat(String timeStr){
DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
try {
return dateFormat.parse(timeStr).getTime();
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
/**
* 时间戳转时间字符串
* @param time 时间戳Long
* @return String 时间
*/
private String timeFormat(Long time){
return new SimpleDateFormat("yyyyMMddHHmmss").format(time);
}
}
6.接口实现:
private static final Logger log = LoggerFactory.getLogger(TestController.class);
@RequestMapping("/qrPay")
@ResponseBody
public PrePayResponse testPay(){
Client client = Client.newInstance(PaySuper.ALIPAY);
PrePayRequest request = PrePayRequest.builder()
.amount(1)
.body("支付宝扫码支付测试")
.payMethod(PayMethod.ZFB_QR)
.notifyUrl(getMerchant().getDomain())
.build();
PrePayResponse response = client.prePay(request,getMerchant());
return PrePayResponse
.builder()
.orderNo(response.getOrderNo())
.payText(response.getPayText())
.payStatus(response.getPayStatus())
.build();
}
@RequestMapping("/query")
@ResponseBody
public PayResult testQuery(String orderNo){
Client client = Client.newInstance(PaySuper.ALIPAY);
PayResult result = client.payOrderQuery(orderNo,getMerchant());
System.out.println(JSONUtil.toJsonStr(result));
return PayResult
.builder()
.payStatus(result.getPayStatus())
.orderNo(result.getOrderNo())
.outOrderNo(result.getOutOrderNo())
.payTime(result.getPayTime())
.build();
}
@RequestMapping("/webPay")
public void wapPay(HttpServletResponse httpServletResponse) throws NoSuchMethodException {
Client client = Client.newInstance(PaySuper.ALIPAY);
PrePayRequest request = PrePayRequest.builder()
.amount(1)
.body("支付宝网页支付测试")
.payMethod(PayMethod.ZFB_WEB)
.returnUrl(getMerchant().getDomain())
.notifyUrl(getNotifyURL())
.build();
PrePayResponse prePayResponse = client.webPay(httpServletResponse, request, getMerchant());
System.out.println(JSONUtil.toJsonStr(prePayResponse));
}
private Merchant getMerchant(){
return Merchant.builder()
.appId("应用ID")
.privateKey("私钥")
.aliPayPublicKey("支付宝公钥")
.serviceUrl("https://openapi.alipay.com/gateway.do")
.build();
}
private String getNotifyURL(){
return RequestHolder.getAppUrl() + "/test/notify/notify/";
}
7.异步通知:
@PostMapping
@ResponseBody
public void payNotify(HttpServletRequest request) throws UnsupportedEncodingException {
Client client = Client.newInstance(PaySuper.ALIPAY);
//获取支付宝GET过来反馈信息
Map<String,String> params = new HashMap<String,String>();
Map<String, String> map = AliPayApi.toMap(request);
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
params.put(entry.getKey(), entry.getValue());
}
String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8");
PayNotify payNotify = PayNotify.builder().rawSignObject(params).build();
boolean result = client.verifyPayNotify(payNotify,getMerchant());
if(result){//验证成功
System.out.println("验证成功<br />");
}else{
System.out.println("验证失败<br />");
}
}
8.支付通知类:
@Getter
@Setter
@Builder
@AllArgsConstructor
public class PayNotify {
private String orderNo;
private String outOrderNo;
private PayStatus payStatus;
private Long payTime;
private Object rawSignObject;
}
9.支付结果类:
/**
* 支付结果
* @author michael
* @since 2022/11/15
*/
@Getter
@Setter
@Builder
@AllArgsConstructor
public class PayResult {
/**
* 内部订单号
*/
private String orderNo;
/**
* 外部订单号
*/
private String outOrderNo;
/**
* 订单状态
*/
private PayStatus payStatus;
/**
* 支付时间
*/
private Long payTime;
}
10.预支付响应:
@Getter
@Setter
@AllArgsConstructor
@Builder
public class PrePayResponse {
/**
* 内部订单号
*/
private String orderNo;
/**
* 外部订单号
*/
private String outOrderNo;
/**
* 是否为重定向连接
*/
private Boolean isRedirect;
/**
* 支付文本或者支付连接
*/
private String payText;
/**
* 支付状态, 只有当面付中的被扫才会有支付成功(向商家展示付款码,商家扫码枪扫码完成支付)
*/
private PayStatus payStatus;
}
11.预支付请求:
@Getter
@Setter
@Builder
public class PrePayRequest {
/**
* 内部订单号
*/
private String orderNo;
/**
* 支付方式
*/
private PayMethod payMethod;
/**
* 金额,单位分
*/
private Integer amount;
/**
* 支付body, 说明支付场景
*/
private String body;
/**
* 支付成功后跳转地址
*/
private String returnUrl;
/**
* 支付宝userId
*/
private String openId;
/**
* 支付成功后通知地址
*/
private String notifyUrl;
/**
* 只有用户(如微信用户)向商家展示付款码才会用到
*/
private String authCode;
/**
* 过期时间, 大于这个时间节点就过期
*/
private Long expireTime;
}