# 支付宝支付在网页应用中简单实现
> 前提:您已经在支付宝开放平台注册,并仔细阅读过API文档,有外网可以访问到回调地址。如果您没有,可以使用内网穿透工具。(申请教程自行百度,禁止用于违法途径)
> - 支付宝开放平台 https://open.alipay.com/platform/home.htm
> - 支付宝沙箱模式 https://open.alipay.com/platform/appDaily.htm?tab=info
> - 支付宝支付Demo下载 https://gw.alipayobjects.com/os/bmw-prod/a526522f-37a6-4a8e-9abc-d22cf240cbbd.zip
> - natapp内网穿透工具官网:https://natapp.cn
> - 支付API: https://opendocs.alipay.com/apis/api_1/alipay.trade.precreate?scene=19
## 摘要
支付宝的支付场景有很多,分为当面付、App支付、电脑网站支付以及刷脸支付。本文仅介绍当面付(通俗点,就是你去线下超市买东西时这种场景)。
准确点说,当面付包括付款码支付和扫码支付两种收款方式。适用于线下实体店支付、面对面支付、自助售货机等场景。
- 付款码支付:商家使用扫码枪或其他扫码机具扫描用户出示的付款码,来实现收款。
- 扫码支付:商家提供收款二维码,由用户通过支付宝扫码支付,来实现收款。
![image-20220505183214350](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220505183214350.png)
本文的主要内容是介绍当面付中的扫码支付在网页应用的简单实现。
## 准备工作
在支付宝开发平台创建沙箱应用。如若创建的是网页应用,就勾选对应的网页应用。
![image-20220503221353929](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220503221353929.png)
同时找到你的沙箱账号。如下图所示。
![image-20220503221803232](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220503221803232.png)
这样在沙箱环境下,你既是买家(消费端)又是买家。你可在买家端可账户充值,随意设置金额。(哇!99999¥感觉人生一下实现暴富,当然又不是真钱)
同时,你需要下载一个沙箱版的支付宝,然后用**买家账号登录**。(不要用你生活中使用支付宝账号登录),这样你就用沙箱版支付宝扫描二维码支付。当
然这个二维码当然也你的沙箱的。相当于沙箱环境给我们一个模拟的支付过程环境。
![image-20220503222231727](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220503222231727.png)
应用截图
<img src="https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220503222410696.png" alt="image-20220503222410696" style="zoom: 50%;" />
最后,就是获取到你的`支付宝公钥`和`应用私钥`。建议复制保存到本地。(别搞错!不要复制成`应用公钥`)
![image-20220503223048066](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220503223048066.png)
还有最后的准备,阅读相关的API文档。例如,在扫码支付中要实现`alipay.trade.precreate(统一收单线下交易预创建)`,你需要了解相关的请求参数和响应参数以及请求示例。
![image-20220505180609298](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220505180609298.png)
## 原理
例如,实现扫码支付最简单案例,完成最基本功能:实现交易。(不需要退款,也不要撤销,查询以及下载账单等)。扫码支付流程如下图所示。
![image-20220505185207646](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220505185207646.png)
接口调用流程
![img](https://gw.alipayobjects.com/zos/skylark-tools/public/files/0dd5f9ae0c3da01ce2b9d6019efde979.png)
在这里展示只实现最简单支付案例(暂时不实现轮询)流程如下所示。
![image-20220505185439773](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220505185439773.png)
第一步,商户系统(也就是我们开发的应用)调用`alipay.trade.precreate`接口向支付宝系统请求生成二维码连接, 然后支付宝系统同步返回二维码连接 。(这就是需要回调地址的原因)
第二步,在商户系统中将二维码连接`生成二维码`(需要实现的地方,二维码在网页应用中需要展示给前端)
第三步,用户(比如我)在手机中打开我的`支付宝APP`,扫一扫二维码,输入支付密码,点击支付,完成支付。支付成功:返回支付成功信息给到我们手机应用。(人就会在手机中看到支付成功的页面)。
同时,也将支付成功信息返回到商户系统。商户系统在得知支付成功后,会返回success信息到支付宝系统。
(这样`用户`、`商家`、`支付宝`都了知道支付成功的消息,那么这样一笔交易就成立了)
## 实现
### 请求参数 (共计10个必须参数)
![image-20220505192527516](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220505192527516.png)
### 响应参数
![image-20220505192709737](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220505192709737.png)
`alipay-sdk-java`依赖
```xml
<!-- alipay-sdk-java -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.16.2.ALL</version>
</dependency>
```
这是业务层的实现
```java
/**
* @author : zhoujiankang
* @Desc: 支付宝支付实现
* @since : 2022/5/3 23:16
*/
@Slf4j
@Service
public class AlipayServiceImpl implements AlipayService
{
Logger logger = LoggerFactory.getLogger(AlipayServiceImpl.class);
@Autowired private AlipayConfig alipayConfig;
@Resource private CoursesMapper coursesMapper;
@Resource private PayCommonService payCommonService;
@Resource private OrderTradeService orderTradeService;
/**
* 将请求得到二维码连接生成二维码 返回到前端
*/
@Override
public byte[] alipay(PayVo payVo ) throws Exception
{
//获取响应二维码信息
QRCodeResponse QRCodeResponse = qrcodePay(payModel(payVo));
//制作二维码并且返回给前端
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
String logopath = "";
logopath = ResourceUtils.getFile("classpath:favicon.jpg").getAbsolutePath();
logger.info("二维码的图片路径为===>" + logopath);
BufferedImage encode = QRCodeUtil.encode(QRCodeResponse.getQr_code(), logopath, false);
ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(byteArrayOutputStream);
ImageIO.write(encode, "JPEG", imageOutputStream);
imageOutputStream.close();
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
return FileCopyUtils.copyToByteArray(byteArrayInputStream);
}
/**
* 支付宝客户端发送支付请求获取支付二维码信息
*/
public QRCodeResponse qrcodePay(AlipayTradePrecreateModel model) throws AlipayApiException
{
//1.获取请求客户端
AlipayClient alipayClient = getAlipayClient();
//2. 获取请求对象
AlipayTradePrecreateRequest alipayRequest = new AlipayTradePrecreateRequest();
//3.设置请求参数
alipayRequest.setBizModel(model);
alipayRequest.setNotifyUrl(alipayConfig.getNotify_url());
alipayRequest.setReturnUrl(alipayConfig.getReturn_url());
AlipayTradePrecreateResponse alipayResponse = alipayClient.execute(alipayRequest);
String body = alipayResponse.getBody();
logger.info("请求的响应二维码信息====>" + body);
QRResponse QRResponse = JSON.parseObject(body, QRResponse.class);
return QRResponse.getAlipay_trade_precreate_response();
}
/**
* 获取阿里客户端
*
* @return
*/
public AlipayClient getAlipayClient()
{
DefaultAlipayClient defaultAlipayClient = new DefaultAlipayClient(alipayConfig.getURL(), alipayConfig.getAPPID(), alipayConfig.getRSA_PRIVATE_KEY(), alipayConfig.getFORMAT(), alipayConfig.getCHARSET(), alipayConfig.getALIPAY_PUBLIC_KEY(), alipayConfig.getSIGNTYPE());
return defaultAlipayClient;
}
/**
* 设置支付参数
* @param payVo
* @return
*/
public AlipayTradePrecreateModel payModel(PayVo payVo)
{
//获取课程商品
Course courses = coursesMapper.selectById(payVo.getCourseId());
if (courses == null) {
throw new PayException(ResultCodeEnum.SYSTEM_EXCEPTION);
}
String orderNumber = GenerateNum.generateOrder();
//设置支付回调时可以在request中获取的参数(msg)
JSONObject jsonObject = new JSONObject();
jsonObject.put("courseId", courses.getCourseid());
jsonObject.put("courseTitle", courses.getTitle());
jsonObject.put("courseImg", courses.getImg());
jsonObject.put("orderNumber", orderNumber);
jsonObject.put("payType", payVo.getPayMethod());
jsonObject.put("price", courses.getPrice());
String params = jsonObject.toString();
//设置支付参数
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
model.setBody(params);
model.setTotalAmount(courses.getPrice().toString());
model.setOutTradeNo(orderNumber);
model.setSubject(courses.getTitle());
return model;
}
/**
* 支付回调
*/
@Override
public Boolean alipayCallback(HttpServletRequest request)
{
try {
Map<String, String> params = ParamsUtil.ParamstoMap(request);
logger.info("回调参数=========>" + params);
String trade_no = params.get("trade_no");
String body1 = params.get("body");
logger.info("交易的流水号和交易信息===========>", trade_no, body1);
JSONObject body = JSONObject.parseObject(body1);
String ptype = body.getString("payType");
String orderNumber = body.getString("orderNumber");
if (ptype != null && ptype.equals("1")) {
payCommonService.payproductcourse(body, "1", orderNumber, trade_no, "1");
}
} catch (Exception e) {
e.printStackTrace();
logger.info("异常====>", e.toString());
return false;
}
return true;
}
/**
* 申请退款
*
* @param orderTrade 退款VO
* @return
* @throws AlipayApiException 支付宝Api异常
*/
@Override
public boolean refund(OrderTrade orderTrade)
{
//1.获取请求客户端
AlipayClient alipayClient = getAlipayClient();
//建立退款请求
AlipayTradeRefundRequest alipayRequest = new AlipayTradeRefundRequest();
JSONObject bizContent = new JSONObject();
//建立请求参数集合
bizContent.put("out_trade_no", orderTrade.getOutTradeNO());
bizContent.put("trade_no", orderTrade.getTradeNo());
bizContent.put("refund_amount", orderTrade.getTotalAmount());
//返回参数选项
JSONArray queryOptions = new JSONArray();
queryOptions.add("refund_detail_item_list");
bizContent.put("query_options", queryOptions);
try {
alipayRequest.setBizContent(bizContent.toString());
AlipayTradeRefundResponse alipayResponse = alipayClient.execute(alipayRequest);
if (alipayResponse.isSuccess()) {
logger.info("退款成功");
return true;
} else {
logger.info("退款失败");
return false;
}
} catch (AlipayApiException e) {
e.printStackTrace();
logger.info("出现异常,退款失败!");
return false;
}
}
/**
* 通过商户订单号获取支付宝交易号
*
* @param out_trade_no
* @return
*/
@Override
public String query(String out_trade_no)
{
//1.获取请求客户端
AlipayClient alipayClient = getAlipayClient();
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", out_trade_no);
request.setBizContent(bizContent.toString());
try {
AlipayTradeQueryResponse response = alipayClient.execute(request);
if (response.isSuccess()) {
return response.getTradeNo();
}
} catch (AlipayApiException e) {
e.printStackTrace();
logger.info("查询失败");
}
return "";
}
}
```
> PS:代码写得很乱,凑合看!有时间再优化!
在前端得到二维码,扫描二维码,进行支付。
![](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220505220356602.png)
手机端支付界面
<img src="https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220505220813450.png" alt="image-20220505220813450" style="zoom: 50%;" />
订单记录
<img src="https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220505220939466.png" alt="image-20220505220939466" style="zoom:50%;" />
# 附录1:二维码生成
值得一说的是!将二维码连接生成二维码。那么二维码是如何生成的呢?主要做法是通过Google的Zxing
来生成二维码图片。
`ZXing` 是一个开源 Java 类库用于解析多种格式的 1D/2D 条形码。目标是能够对QR编码、Data Matrix、UPC的1D条形码进行解码。其提供了多种平台下的客户端包括:J2ME、J2SE和Android。
链接:ZXing[官网地址](https://zxing.org/) 、 ZXing [Github地址](https://github.com/zxing/zxing)
![image-20220505213756110](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220505213756110.png)
`com.google.zxing`依赖
```xml
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.3.0</version>
</dependency>
```
代码实现
```java
import com.google.zxing.*;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.OutputStream;
import java.util.Hashtable;
public class QRCodeUtil {
private static final String CHARSET = "utf-8";
private static final String FORMAT_NAME = "JPG";
// 二维码尺寸
private static final int QRCODE_SIZE = 300;
// LOGO宽度
private static final int WIDTH = 90;
// LOGO高度
private static final int HEIGHT = 90;
/**
* @Description 二维码生成的方法
* @Param [content, imgPath, needCompress]
* @return java.awt.image.BufferedImage
**/
private static BufferedImage createImage(String content, String imgPath, boolean needCompress) throws Exception {
Hashtable hints = new Hashtable();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
hints.put(EncodeHintType.MARGIN, 1);
BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE,
hints);
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
// 绘制图片用色 0xFFFFFFFF表示纯白 0xFF000000表示纯黑
image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
}
}
if (imgPath == null || "".equals(imgPath)) {
return image;
}
// 插入图片
QRCodeUtil.insertImage(image, imgPath, needCompress);
return image;
}
private static void insertImage(BufferedImage source, String imgPath, boolean needCompress) throws Exception {
File file = new File(imgPath);
if (!file.exists()) {
System.err.println("" + imgPath + " 该文件不存在!");
return;
}
Image src = ImageIO.read(new File(imgPath));
int width = src.getWidth(null);
int height = src.getHeight(null);
if (needCompress) { // 压缩LOGO
if (width > WIDTH) {
width = WIDTH;
}
if (height > HEIGHT) {
height = HEIGHT;
}
Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH);
BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = tag.getGraphics();
g.drawImage(image, 0, 0, null); // 绘制缩小后的图
g.dispose();
src = image;
}
// 插入LOGO
Graphics2D graph = source.createGraphics();
int x = (QRCODE_SIZE - width) / 2;
int y = (QRCODE_SIZE - height) / 2;
graph.drawImage(src, x, y, width, height, null);
Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
graph.setStroke(new BasicStroke(3f));
graph.draw(shape);
graph.dispose();
}
```
这是生成的二维码图片(logo有一点小)
![image-20220505220356602](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220505220356602.png)
# 附录2
项目推荐:PayCloud,其支付服务:支付宝,微信,银联详细 **代码案例**
![image-20220505221412583](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220505221412583.png)
以下所有支付Demo,测试通过,真实有效。
#### 支付宝
扫码支付、电脑支付、WAP支付、APP支付服务端
#### 微信
扫码支付(模式一二)、公众号H5支付、WAP支付
#### 银联
电脑支付、WAP支付
## 参考:
#### 支付宝
- 电脑支付:[https://docs.open.alipay.com/270](https://gitee.com/link?target=https%3A%2F%2Fdocs.open.alipay.com%2F270)
- 扫码支付:[https://docs.open.alipay.com/194](https://gitee.com/link?target=https%3A%2F%2Fdocs.open.alipay.com%2F194)
- 手机支付:[https://docs.open.alipay.com/203](https://gitee.com/link?target=https%3A%2F%2Fdocs.open.alipay.com%2F203)
- APP支付 : [https://docs.open.alipay.com/54/106370/](https://gitee.com/link?target=https%3A%2F%2Fdocs.open.alipay.com%2F54%2F106370%2F)
- 沙箱环境:[https://docs.open.alipay.com/200/105311/](https://gitee.com/link?target=https%3A%2F%2Fdocs.open.alipay.com%2F200%2F105311%2F)
- 支付宝公钥参数:[https://openclub.alipay.com/read.php?tid=2190&fid=69](https://gitee.com/link?target=https%3A%2F%2Fopenclub.alipay.com%2Fread.php%3Ftid%3D2190%26fid%3D69)
- RSA(SHA1)升级为RSA(SHA256):[https://opensupport.alipay.com/support/knowledge/20069/201602242782](https://gitee.com/link?target=https%3A%2F%2Fopensupport.alipay.com%2Fsupport%2Fknowledge%2F20069%2F201602242782)
#### 微信
- H5支付:[https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_1](https://gitee.com/link?target=https%3A%2F%2Fpay.weixin.qq.com%2Fwiki%2Fdoc%2Fapi%2FH5.php%3Fchapter%3D15_1)
- 公众号支付:[https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1](https://gitee.com/link?target=https%3A%2F%2Fpay.weixin.qq.com%2Fwiki%2Fdoc%2Fapi%2Fjsapi.php%3Fchapter%3D7_1)
- 扫码支付模式一:[https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4](https://gitee.com/link?target=https%3A%2F%2Fpay.weixin.qq.com%2Fwiki%2Fdoc%2Fapi%2Fnative.php%3Fchapter%3D6_4)
- 扫码支付模式二:[https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5](https://gitee.com/link?target=https%3A%2F%2Fpay.weixin.qq.com%2Fwiki%2Fdoc%2Fapi%2Fnative.php%3Fchapter%3D6_5)
- 微信退款说明:[https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3](https://gitee.com/link?target=https%3A%2F%2Fpay.weixin.qq.com%2Fwiki%2Fdoc%2Fapi%2Fnative.php%3Fchapter%3D4_3)
- 网络设置指引:[https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=23_2](https://gitee.com/link?target=https%3A%2F%2Fpay.weixin.qq.com%2Fwiki%2Fdoc%2Fapi%2Fnative.php%3Fchapter%3D23_2)
- HTTPS服务器配置:[https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=10_4](https://gitee.com/link?target=https%3A%2F%2Fpay.weixin.qq.com%2Fwiki%2Fdoc%2Fapi%2Fwxa%2Fwxa_api.php%3Fchapter%3D10_4)
- 参数wxinfo.properties
- 微信网页授权部分,向微信申请测试号:[http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421137522](https://gitee.com/link?target=http%3A%2F%2Fmp.weixin.qq.com%2Fwiki%3Ft%3Dresource%2Fres_main%26id%3Dmp1421137522)
#### 银联
- 开放平台:[https://open.unionpay.com/](https://gitee.com/link?target=https%3A%2F%2Fopen.unionpay.com%2F)
- 商家中心:[https://merchant.unionpay.com/join/](https://gitee.com/link?target=https%3A%2F%2Fmerchant.unionpay.com%2Fjoin%2F)
- 测试账号:[https://blog.52itstyle.vip/archives/326/](https://gitee.com/link?target=https%3A%2F%2Fblog.52itstyle.vip%2Farchives%2F326%2F)
- 证书问题(QA):[https://open.unionpay.com/ajweb/help/faq/list?id=174&level=0&from=0](https://gitee.com/link?target=https%3A%2F%2Fopen.unionpay.com%2Fajweb%2Fhelp%2Ffaq%2Flist%3Fid%3D174%26level%3D0%26from%3D0)
支付宝支付在SpringBoot项目的实现