文章目录
- 一、介绍
- 1. 支付
- 2. 支付结果
- 二、前提准备
- 1. 支付宝开放平台
- 2. 内网穿透
- 3. 局域网
- 三、order微服务
- 1. 依赖、配置
- 2. 工具类
- 1. 二维码生成
- 2. AlipayConfig
- 3. 端点代码
- 1. /generatepaycode
- 2. /requestpay
- 3. /payresult
- 4. /receivenotify
环境如下
Version | |
---|---|
手机 | 安卓 |
支付平台 | 支付宝 |
SpringBoot | 3.2.1 |
alipay-sdk-java | 4.38.200.ALL |
一、介绍
系统处于开发阶段时,无需营业执照,无需任何费用,沙箱模拟网站在线完整支付流程。
参考资料如下:
- 手机网站支付快速接入
- alipay.trade.query(统一收单交易查询)
- 异步通知说明
1. 支付
有一个在线网站,可以为商品生成支付二维码,手机支付宝扫码,支付。
支付流程大体如下:
2. 支付结果
获取支付结果有两种方法
- 一种为
主动查询
。在顾客支付后再查询方可得到正确的结果,然而这个时机是无法确定的。 - 一种为
被动接收
。顾客支付后,支付宝服务器向微服务发送消息通知。
二、前提准备
1. 支付宝开放平台
-
注册
支付宝开放平台
https://openhome.alipay.com/ -
来到
控制台
下滑找到沙箱
https://openhome.alipay.com/develop/manage
或者点这里进入沙箱环境
https://openhome.alipay.com/develop/sandbox/app
-
下载
支付宝沙箱版
到手机
2. 内网穿透
-
下载软件
https://hsk.oray.com/download
本文选择的是贝锐花生壳
,会赠送一个域名。 -
添加映射
- 映射类型:
HTTPS
- 外网端口:貌似改不了
- 内网
ip:port
:order微服务的地址端口。
- 映射类型:
这样之后,谁往https://5m34y83626.vicp.fun/orders/receivenotify
发送请求,就相当于往order微服务的/orders/receivenotify
这个端点发送请求。
3. 局域网
参考这篇文章
同一Wifi下允许手机访问电脑(win10)
主要目的就是要知道,手机通过什么ip
可以访问到电脑。本文是192.168.0.102
,所以访问192.168.0.102:63030
就相当于访问到了order微服务
。
三、order微服务
1. 依赖、配置
<!-- 支付宝SDK --><dependency><groupId>com.alipay.sdk</groupId><artifactId>alipay-sdk-java</artifactId><version>4.38.200.ALL</version></dependency><!--生成二维码--><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.3.3</version></dependency><dependency><groupId>com.google.zxing</groupId><artifactId>javase</artifactId><version>3.3.3</version></dependency>
server:servlet:context-path: /ordersport: 63030pay:#扫描二维码得到urlqrcodeurl: http://???:63030/orders/requestpay?payNo=%salipay:APP_ID: ???APP_PRIVATE_KEY: ???ALIPAY_PUBLIC_KEY: ???
???
填充分别为
-
在同一局域网中手机访问电脑的ip
-
沙箱环境->沙箱应用->应用信息->基本信息
-
沙箱环境->沙箱应用->应用信息->开发信息->应用私钥
-
沙箱环境->沙箱应用->应用信息->开发信息->支付宝公钥
2. 工具类
1. 二维码生成
package com.xuecheng.orders.config;import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.xuecheng.base.utils.EncryptUtil;
import jakarta.servlet.ServletOutputStream;
import org.apache.commons.lang3.StringUtils;import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;/*** @author mumu* @version 1.0* @description 二维码生成工具* @date 2024/02/16 14:56*/
public class QRCodeUtil {/*** 生成二维码** @param content 二维码对应的URL* @param width 二维码图片宽度* @param height 二维码图片高度* @return*/public String createQRCode(String content, int width, int height) throws IOException {String resultImage = "";//除了尺寸,传入内容不能为空if (!StringUtils.isEmpty(content)) {ServletOutputStream stream = null;ByteArrayOutputStream os = new ByteArrayOutputStream();//二维码参数@SuppressWarnings("rawtypes")HashMap<EncodeHintType, Comparable> hints = new HashMap<>();//指定字符编码为“utf-8”hints.put(EncodeHintType.CHARACTER_SET, "utf-8");//L M Q H四个纠错等级从低到高,指定二维码的纠错等级为M//纠错级别越高,可以修正的错误就越多,需要的纠错码的数量也变多,相应的二维吗可储存的数据就会减少hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);//设置图片的边距hints.put(EncodeHintType.MARGIN, 1);try {//zxing生成二维码核心类QRCodeWriter writer = new QRCodeWriter();//把输入文本按照指定规则转成二维吗BitMatrix bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, width, height, hints);//生成二维码图片流BufferedImage bufferedImage = MatrixToImageWriter.toBufferedImage(bitMatrix);//输出流ImageIO.write(bufferedImage, "png", os);/*** 原生转码前面没有 data:image/png;base64 这些字段,返回给前端是无法被解析,所以加上前缀*/resultImage = "data:image/png;base64," + EncryptUtil.encodeBase64(os.toByteArray());return resultImage;} catch (Exception e) {e.printStackTrace();throw new RuntimeException("生成二维码出错");} finally {if (stream != null) {stream.flush();stream.close();}}}return null;}public static void main(String[] args) throws IOException {QRCodeUtil qrCodeUtil = new QRCodeUtil();//String qrCode = qrCodeUtil.createQRCode("http://10.0.2.2:63030/orders/alipaytest", 200, 200);String qrCode = qrCodeUtil.createQRCode("http://192.168.0.102:63030/orders/alipaytest", 200, 200);System.out.println(qrCode);}
}
这里的content
参数就是指二维码扫出来指向的url
,在本文中,这个url
指的是order
微服务orders/requestpay
端点,请求支付。
2. AlipayConfig
package com.xuecheng.orders.config;/*** @author mumu* @version 1.0* @description 支付宝配置参数* @date 2024/02/16 14:56*/
public class AlipayConfig {// 服务器异步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问public static String notify_url = "https://5m34y83626.vicp.fun/orders/receivenotify";// 页面跳转同步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问 商户可以自定义同步跳转地址//public static String return_url = "http://商户网关地址/alipay.trade.wap.pay-JAVA-UTF-8/return_url.jsp";// 请求网关地址//public static String URL = "https://openapi.alipaydev.com/gateway.do";public static String URL = "https://openapi-sandbox.dl.alipaydev.com/gateway.do";// 编码public static String CHARSET = "UTF-8";// 返回格式public static String FORMAT = "json";// 日志记录目录public static String log_path = "/log";// RSA2public static String SIGNTYPE = "RSA2";
}
这里的notify_url
指的是贝锐花生壳给的域名+/orders/receivenotify
,用于支付宝服务器通知我们支付结果。
这里的URL
指的是支付宝网关地址,目前沙箱环境用的是固定的https://openapi-sandbox.dl.alipaydev.com/gateway.do
,如有变更可以去 沙箱环境->沙箱应用->开发信息 校验。
3. 端点代码
1. /generatepaycode
前端点击“前往支付”按钮后,携带商品信息,向后端发送一个生成支付二维码的请求。后端收到商品信息,结合用户信息,为他们创建一张订单到数据库,包含金额等信息,订单号为payNo
。
于是我们把这个payNo
拼接到二维码所表示的url
中,当用户扫码后,携带订单号前往支付端点。
参考代码如下
@Value("${pay.qrcodeurl}")private String qrcodeurl;
//生成二维码QRCodeUtil qrCodeUtil = new QRCodeUtil();String url = String.format(qrcodeurl, payNo);String qrCode = null;try {qrCode = qrCodeUtil.createQRCode(url, 200, 200);} catch (IOException e) {XueChengPlusException.cast("生成二维码失败");}
2. /requestpay
后端返回二维码到前端,拿起手机,打开支付宝沙箱版
登录。
账号密码在 沙箱环境->沙箱账号->买家信息
登录后,拿起扫一扫二维码,会请求形如http://192.168.0.102:63030/orders/requestpay?payNo=1758388376771436544
,也就是请求到/requestpay
端点。后端从数据库取出订单信息,比如金额等,填入bizContent
,作为支付宝api
必要数据。
参考代码如下
@Value("${pay.alipay.APP_ID}")String APP_ID;@Value("${pay.alipay.APP_PRIVATE_KEY}")String APP_PRIVATE_KEY;@Value("${pay.alipay.ALIPAY_PUBLIC_KEY}")String ALIPAY_PUBLIC_KEY;
@RequestMapping("/requestpay")public void requestpay(String payNo, HttpServletResponse httpResponse) throws AlipayApiException, IOException {//约束XcPayRecord payRecord = orderService.getPayRecordByPayNo(payNo);if (payRecord == null){XueChengPlusException.cast("支付记录不存在,异常!");}if ("601002".equals(payRecord.getStatus())){XueChengPlusException.cast("已支付,无需重复支付");}//获得初始化的AlipayClientAlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.URL, APP_ID, APP_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET, ALIPAY_PUBLIC_KEY,AlipayConfig.SIGNTYPE);//创建API对应的requestAlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();//在公共参数中设置回跳和通知地址alipayRequest.setNotifyUrl(AlipayConfig.notify_url);//alipayRequest.setReturnUrl(AlipayConfig.return_url);JSONObject bizContent = new JSONObject();//商户订单号,商家自定义,保持唯一性bizContent.put("out_trade_no", payNo);//支付金额,最小值0.01元bizContent.put("total_amount", payRecord.getTotalPrice());//订单标题,不可使用特殊符号bizContent.put("subject", payRecord.getOrderName());bizContent.put("product_code", "QUICK_WAP_WAY");alipayRequest.setBizContent(bizContent.toString());//填充业务参数String form = alipayClient.pageExecute(alipayRequest).getBody(); //调用SDK生成表单httpResponse.setContentType("text/html;charset=" + AlipayConfig.CHARSET);httpResponse.getWriter().write(form);//直接将完整的表单html输出到页面httpResponse.getWriter().flush();}
后端把form
返回到手机,这个form
会唤起支付功能,手机上完成支付,支付宝服务端就会收到请求。
3. /payresult
现在订单支付完了,怎么知道支付结果呢?一种是主动查询,需要当时向支付宝服务器发送支付请求时填入的out_trade_no
,也就是payNo
。可以设计一个前端按钮“查询支付结果”,发送请求到后端/payresult
端点,后端主动携带payNo
去支付宝服务器请求结果。
参考代码如下
@Value("${pay.alipay.APP_ID}")String APP_ID;@Value("${pay.alipay.APP_PRIVATE_KEY}")String APP_PRIVATE_KEY;@Value("${pay.alipay.ALIPAY_PUBLIC_KEY}")String ALIPAY_PUBLIC_KEY;
//查询支付状态//获得初始化的AlipayClientAlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.URL, APP_ID, APP_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET, ALIPAY_PUBLIC_KEY,AlipayConfig.SIGNTYPE);//创建API对应的requestAlipayTradeQueryRequest alipayRequest = new AlipayTradeQueryRequest();JSONObject bizContent = new JSONObject();//商户订单号,商家自定义,保持唯一性bizContent.put("out_trade_no", payNo);alipayRequest.setBizContent(bizContent.toString());//填充业务参数AlipayTradeQueryResponse response = null;try {response = alipayClient.execute(alipayRequest);} catch (AlipayApiException e) {XueChengPlusException.cast("查询支付宝支付状态失败");}//解析支付结果PayStatusDto payStatusDto = new PayStatusDto();payStatusDto.setOut_trade_no(payNo);payStatusDto.setTrade_no(response.getTradeNo());payStatusDto.setTrade_status(response.getTradeStatus());payStatusDto.setApp_id(APP_ID);payStatusDto.setTotal_amount(response.getTotalAmount());return payStatusDto;
然后可以根据response.getTradeStatus()
的值校验是否支付成功,然后编写后续业务代码。
4. /receivenotify
除了主动查询支付结果之外,还可以定义端点,让支付宝服务端异步通知我们。支付宝服务器在公网,所以我们也需要一个公网来接收它的请求,利用内网穿透技术,把我们的/receivenotify
端点暴露到公网,并指定notify_url
来告知支付宝服务端该向哪里发起异步通知,指定notify_url
在/requestpay
那一步完成。
参考代码
@Value("${pay.alipay.APP_ID}")String APP_ID;@Value("${pay.alipay.APP_PRIVATE_KEY}")String APP_PRIVATE_KEY;@Value("${pay.alipay.ALIPAY_PUBLIC_KEY}")String ALIPAY_PUBLIC_KEY;
@PostMapping("/receivenotify")public void receivenotify(HttpServletRequest request,HttpServletResponse response) throws AlipayApiException, IOException {Map<String,String> params = new HashMap<String,String>();Map requestParams = request.getParameterMap();for (Iterator 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] + ",";}//乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "gbk");params.put(name, valueStr);}//获取支付宝的通知返回参数,可参考技术文档中页面跳转同步通知参数列表(以上仅供参考)////计算得出通知验证结果//boolean AlipaySignature.rsaCheckV1(Map<String, String> params, String publicKey, String charset, String sign_type)boolean verify_result = AlipaySignature.rsaCheckV1(params, ALIPAY_PUBLIC_KEY, AlipayConfig.CHARSET, AlipayConfig.SIGNTYPE);if(verify_result) {//验证成功////请在这里加上商户的业务逻辑程序代码//商户订单号String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8");//支付宝交易号String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8");//交易状态String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"),"UTF-8");//交易金额String total_amount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"), "UTF-8");//——请根据您的业务逻辑来编写程序(以下代码仅作参考)——if (trade_status.equals("TRADE_SUCCESS")) {PayStatusDto payStatusDto = new PayStatusDto();payStatusDto.setOut_trade_no(out_trade_no);payStatusDto.setTrade_no(trade_no);payStatusDto.setTrade_status(trade_status);//app_id和total_amount可以不填payStatusDto.setApp_id(APP_ID);payStatusDto.setTotal_amount(total_amount);System.out.println(payStatusDto);orderService.saveAliPayStatus(payStatusDto);}System.out.println("success");response.getWriter().write("success");}else{response.getWriter().write("支付情况校验失败");}}
完。