最近在做一个网站,后端采纳了 SpringBoot,须要集成支付宝进行线上领取,在这个过程中钻研了大量支付宝的集成材料,也走了一些弯路,当初总结进去,置信你读完也能轻松集成支付宝领取。
在开始集成支付宝领取之前,咱们须要筹备一个支付宝商家账户,如果是集体开发者,能够通过注册公司或者让有公司资质的单位进行受权,后续在集成相干 API 的时候须要提供这些信息。
上面我以电脑网页端在线领取为例,介绍整个从集成、测试到上线的具体流程。
1. 预期成果展现
在开始之前咱们先看下咱们要达到的最初成果,具体如下:
- 前端点击领取跳转到支付宝界面
- 支付宝界面展现付款二维码
- 用户手机端领取
- 实现领取,支付宝回调开发者指定的 url。
2. 开发流程
2.1 沙盒调试
支付宝为咱们筹备了欠缺的沙盒开发环境,咱们能够先在沙盒环境调试好程序,后续新建好利用并胜利上线后,把程序中对应的参数替换为线上参数即可。
1. 创立沙盒利用
间接进入 https://open.alipay.com/develop/sandbox/app 创立沙盒利用即可,
这里因为是测试环境,咱们就抉择零碎默认密钥就行了,上面抉择公钥模式,另外利用网关地址就是用户实现领取之后,支付宝会回调的 url。在开发环境中,咱们能够采纳内网穿透的形式,将咱们本机的端口裸露在某个公网地址上,这里举荐 https://natapp.cn/,能够收费注册应用。
2. SpringBoot 代码实现
在创立好沙盒利用,获取到密钥,APPID,商家账户 PID 等信息之后,就能够在测试环境开发集成对应的 API 了。这里我以电脑端领取 API 为例,介绍如何进行集成。
对于电脑网站领取的具体产品介绍和 API 接入文档能够参考:https://opendocs.alipay.com/open/repo-0038oa?ref=api 和 https://opendocs.alipay.com/open/270/01didh?ref=api
- 步骤 1,增加 alipay sdk 对应的 Maven 依赖。
<!-- alipay -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.35.132.ALL</version>
</dependency>
- 步骤 2,增加支付宝下单、领取胜利后同步调用和异步调用的接口。
这里须要留神,同步接口是用户实现领取后会主动跳转的地址,因而须要是 Get 申请。异步接口,是用户实现领取之后,支付宝会回调来告诉领取后果的地址,所以是 POST 申请。
@RestController
@RequestMapping("/alipay")
public class AliPayController {
@Autowired
AliPayService aliPayService;
@PostMapping("/order")
public GenericResponse<Object> placeOrderForPCWeb(@RequestBody AliPayRequest aliPayRequest) {
try {return aliPayService.placeOrderForPCWeb(aliPayRequest);
} catch (IOException e) {throw new RuntimeException(e);
}
}
@PostMapping("/callback/async")
public String asyncCallback(HttpServletRequest request) {return aliPayService.orderCallbackInAsync(request);
}
@GetMapping("/callback/sync")
public void syncCallback(HttpServletRequest request, HttpServletResponse response) {aliPayService.orderCallbackInSync(request, response);
}
}
- 步骤 3,实现 Service 层代码
这里针对下面 controller 中的三个接口,别离实现 service 层对应的办法。上面是整个领取的外围流程,其中有些中央须要依据你本人的理论状况进行保留订单到 DB 或者查看订单状态的操作,这个能够依据理论业务需要进行设计。
public class AliPayService {
@Autowired
AliPayHelper aliPayHelper;
@Resource
AlipayConfig alipayConfig;
@Transactional(rollbackFor = Exception.class)
public GenericResponse<Object> placeOrderForPCWeb(AliPayRequest aliPayRequest) throws IOException {log.info("【申请开始 - 在线购买 - 交易创立】********* 对立下单开始 *********");
String tradeNo = aliPayHelper.generateTradeNumber();
String subject = "购买套餐 1";
Map<String, Object> map = aliPayHelper.placeOrderAndPayForPCWeb(tradeNo, 100, subject);
if (Boolean.parseBoolean(String.valueOf(map.get("isSuccess")))) {log.info("【申请开始 - 在线购买 - 交易创立】对立下单胜利,开始保留订单数据");
// 保留订单信息
// 增加你本人的业务逻辑,次要是保留订单数据
log.info("【申请胜利 - 在线购买 - 交易创立】********* 对立下单完结 *********");
return new GenericResponse<>(ResponseCode.SUCCESS, map.get("body"));
}else{log.info("【失败:申请失败 - 在线购买 - 交易创立】********* 对立下单完结 *********");
return new GenericResponse<>(ResponseCode.INTERNAL_ERROR, String.valueOf(map.get("subMsg")));
}
}
// sync return page
public void orderCallbackInSync(HttpServletRequest request, HttpServletResponse response) {
try {OutputStream outputStream = response.getOutputStream();
// 通过设置响应头管制浏览器以 UTF- 8 的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
response.setHeader("content-type", "text/html;charset=UTF-8");
String outputData = "领取胜利,请返回网站并刷新页面。";
/**
* data.getBytes() 是一个将字符转换成字节数组的过程,这个过程中肯定会去查码表,* 如果是中文的操作系统环境,默认就是查找查 GB2312 的码表,*/
byte[] dataByteArr = outputData.getBytes("UTF-8");// 将字符转换成字节数组,指定以 UTF- 8 编码进行转换
outputStream.write(dataByteArr);// 应用 OutputStream 流向客户端输入字节数组
} catch (IOException e) {throw new RuntimeException(e);
}
}
public String orderCallbackInAsync(HttpServletRequest request) {
try {Map<String, String> map = aliPayHelper.paramstoMap(request);
String tradeNo = map.get("out_trade_no");
String sign = map.get("sign");
String content = AlipaySignature.getSignCheckContentV1(map);
boolean signVerified = aliPayHelper.CheckSignIn(sign, content);
// check order status
// 这里在 DB 中查看 order 的状态,如果曾经领取胜利,无需再次验证。if(从 DB 中拿到 order,并且判断 order 是否领取胜利过){log.info("订单:" + tradeNo + "曾经领取胜利,无需再次验证。");
return "success";
}
// 验证业务数据是否统一
if(!checkData(map, order)){log.error("返回业务数据验证失败,订单:" + tradeNo);
return "返回业务数据验证失败";
}
// 签名验证胜利
if(signVerified){log.info("支付宝签名验证胜利,订单:" + tradeNo);
// 验证领取状态
String tradeStatus = request.getParameter("trade_status");
if(tradeStatus.equals("TRADE_SUCCESS")){log.info("领取胜利,订单:"+tradeNo);
// 更新订单状态,执行一些业务逻辑
return "success";
}else{System.out.println("领取失败,订单:" + tradeNo);
return "领取失败";
}
}else{log.error("签名验证失败,订单:" + tradeNo);
return "签名验证失败.";
}
} catch (IOException e) {log.error("IO exception happened", e);
throw new RuntimeException(ResponseCode.INTERNAL_ERROR, e.getMessage());
}
}
public boolean checkData(Map<String, String> map, OrderInfo order) {log.info("【申请开始 - 交易回调 - 订单确认】********* 校验订单确认开始 *********");
// 验证订单号是否精确,并且订单状态为待领取
if(验证订单号是否精确,并且订单状态为待领取){float amount1 = Float.parseFloat(map.get("total_amount"));
float amount2 = (float) order.getOrderAmount();
// 判断金额是否相等
if(amount1 == amount2){
// 验证收款商户 id 是否统一
if(map.get("seller_id").equals(alipayConfig.getPid())){
// 判断 appid 是否统一
if(map.get("app_id").equals(alipayConfig.getAppid())){log.info("【胜利:申请开始 - 交易回调 - 订单确认】********* 校验订单确认胜利 *********");
return true; }
}
}
}
log.info("【失败:申请开始 - 交易回调 - 订单确认】********* 校验订单确认失败 *********");
return false; }
}
- 步骤 4,实现 alipayHelper 类。这个类外面对支付宝的接口进行封装。
public class AliPayHelper {
@Resource
private AlipayConfig alipayConfig;
// 返回数据格式
private static final String FORMAT = "json";
// 编码类型
private static final String CHART_TYPE = "utf-8";
// 签名类型
private static final String SIGN_TYPE = "RSA2";
/* 领取销售产品码, 目前支付宝只反对 FAST_INSTANT_TRADE_PAY*/
public static final String PRODUCT_CODE = "FAST_INSTANT_TRADE_PAY";
private static AlipayClient alipayClient = null;
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
private static final Random random = new Random();
@PostConstruct
public void init(){
alipayClient = new DefaultAlipayClient(alipayConfig.getGateway(),
alipayConfig.getAppid(),
alipayConfig.getPrivateKey(),
FORMAT,
CHART_TYPE,
alipayConfig.getPublicKey(),
SIGN_TYPE);
};
/*================PC 网页领取 ====================*/
/**
* 对立下单并调用领取页面接口
* @param outTradeNo
* @param totalAmount
* @param subject
* @return
*/
public Map<String, Object> placeOrderAndPayForPCWeb(String outTradeNo, float totalAmount, String subject){AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setNotifyUrl(alipayConfig.getNotifyUrl());
request.setReturnUrl(alipayConfig.getReturnUrl());
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", outTradeNo);
bizContent.put("total_amount", totalAmount);
bizContent.put("subject", subject);
bizContent.put("product_code", PRODUCT_CODE);
request.setBizContent(bizContent.toString());
AlipayTradePagePayResponse response = null;
try {response = alipayClient.pageExecute(request);
} catch (AlipayApiException e) {e.printStackTrace();
}
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("isSuccess", response.isSuccess());
if(response.isSuccess()){log.info("调用胜利");
log.info(JSON.toJSONString(response));
resultMap.put("body", response.getBody());
} else {log.error("调用失败");
log.error(response.getSubMsg());
resultMap.put("subMsg", response.getSubMsg());
}
return resultMap;
}
/**
* 交易订单查问
* @param out_trade_no
* @return
*/
public Map<String, Object> tradeQueryForPCWeb(String out_trade_no){AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
JSONObject bizContent = new JSONObject();
bizContent.put("trade_no", out_trade_no);
request.setBizContent(bizContent.toString());
AlipayTradeQueryResponse response = null;
try {response = alipayClient.execute(request);
} catch (AlipayApiException e) {e.printStackTrace();
}
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("isSuccess", response.isSuccess());
if(response.isSuccess()){System.out.println("调用胜利");
System.out.println(JSON.toJSONString(response));
resultMap.put("status", response.getTradeStatus());
} else {System.out.println("调用失败");
System.out.println(response.getSubMsg());
resultMap.put("subMsg", response.getSubMsg());
}
return resultMap;
}
/**
* 验证签名是否正确
* @param sign
* @param content
* @return
*/
public boolean CheckSignIn(String sign, String content){
try {return AlipaySignature.rsaCheck(content, sign, alipayConfig.getPublicKey(), CHART_TYPE, SIGN_TYPE);
} catch (AlipayApiException e) {e.printStackTrace();
}
return false;
}
/**
* 将异步告诉的参数转化为 Map
* @return
*/
public Map<String, String> paramstoMap(HttpServletRequest request) throws UnsupportedEncodingException {Map<String, String> params = new HashMap<String, String>();
Map<String, String[]> requestParams = request.getParameterMap();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
// 乱码解决,这段代码在呈现乱码时应用。// valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
return params;
}
}
- 步骤 5,封装 config 类,用于寄存所有的配置属性。
@Data
@Component
@ConfigurationProperties(prefix = "alipay")
public class AlipayConfig {
private String gateway;
private String appid;
private String pid;
private String privateKey;
private String publicKey;
private String returnUrl;
private String notifyUrl;
}
另外须要在 application.properties 中,筹备好上述对应的属性。
# alipay config
alipay.gateway=https://openapi.alipaydev.com/gateway.do
alipay.appid=your_appid
alipay.pid=your_pid
alipay.privatekey=your_private_key
alipay.publickey=your_public_key
alipay.returnurl= 实现领取后的同步跳转地址
alipay.notifyurl= 实现领取后,支付宝会异步回调的地址
3. 前端代码实现
前端代码只须要实现两个性能,
- 依据用户的申请向后端发动领取申请。
- 间接提交返回数据实现跳转。
上面的例子中,我用 typescript 实现了用户点击领取之后的性能,
async function onPositiveClick() {
paymentLoading.value = true
const {data} = await placeAlipayOrder<string>({// 你的一些申请参数,例如金额等等})
const div = document.createElement('divform')
div.innerHTML = data
document.body.appendChild(div)
document.forms[0].setAttribute('target', '_blank')
document.forms[0].submit()
showModal.value = false
paymentLoading.value = false
}
2.2 创立并上线 APP
实现沙盒调试没问题之后,咱们须要创立对应的支付宝网页利用并上线。
登录 https://open.alipay.com/develop/manage 并抉择创立网页利用,
填写利用相干信息:
创立好利用之后,首先在开发设置中,设置好接口加签形式以及利用网关。
留神密钥抉择 RSA2,其余依照下面的操作指南一步步走即可,留神保存好本人的私钥和公钥。
之后在产品绑定页,绑定对应的 API,比方咱们这里是 PC 网页端领取,找到对应的 API 绑定就能够了。如果第一次绑定,可能须要填写相干的信息进行审核,按需填写即可,个别审核一天就通过了。
最初如果所有就绪,咱们就能够把 APP 提交上线了,上线胜利之后,咱们须要把上面 SpringBoot 中的 properties 替换为线上 APP 的信息,而后就能够在生产环境调用支付宝的接口进行领取了。
# alipay config
alipay.gateway=https://openapi.alipaydev.com/gateway.do
alipay.appid=your_appid
alipay.pid=your_pid
alipay.privatekey=your_private_key
alipay.publickey=your_public_key
alipay.returnurl= 实现领取后的同步跳转地址
alipay.notifyurl= 实现领取后,支付宝会异步回调的地址
参考:
- https://blog.csdn.net/xqnode/article/details/124457790
- https://blog.51cto.com/u_15754099/5585676
- https://zhuanlan.zhihu.com/p/596771147
- https://segmentfault.com/a/1190000041974184
欢送关注公众号【码老思】,只讲最通俗易懂的原创技术干货。