alipay 当面付扫码支付实战开发 | 您所在的位置:网站首页 › 当面付v2 › alipay 当面付扫码支付实战开发 |
参考官网地址:https://opendocs.alipay.com/open/194/105072 1、当面付介绍:当面付包括付款码支付和扫码支付两种收款方式。适用于线下实体店支付、面对面支付、自助售货机等场景。 付款码支付:商家使用扫码枪或其他扫码机具扫描用户出示的付款码,来实现收款。 扫码支付:商家提供收款二维码,由用户通过支付宝扫码支付,来实现收款。 2、参数准备• APPID • 商家私钥 • 支付宝公钥 • 支付回调地址 • 网关地址 • 加密签名算法RSA2 3、JAVA 代码实战,项目基础功能准备 3.1 创建 springBoot 工程类似雪花只试用单体项目 package com.wyj.alipay.util; import java.text.SimpleDateFormat; import java.util.Date; /** * @author: yijun.wen * @date: 2022/3/14 10:23 上午 * @description: */ public class GenerateNum { /** * 全局自增数 */ private static int count = 0; /** * 每毫秒秒最多生成多少订单(最好是像9999这种准备进位的值) */ private static final int total = 99; /** * 格式化的时间字符串 */ private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); /** * 获取当前时间年月日时分秒毫秒字符串 * * @return */ private static String getNowDateStr() { return sdf.format(new Date()); } /** * 记录上一次的时间,用来判断是否需要递增全局数 */ private static String now = null; /** * 生成一个订单号 */ public static String generateOrder() { String dataStr = getNowDateStr(); if (dataStr.equals(now)) { count++; } else { count = 1; now = dataStr; } // 算补位 int countInteger = String.valueOf(total).length() - String.valueOf(count).length(); //补字符串 String bu = ""; for (int i = 0; i < countInteger; i++) { bu += "0"; } bu += String.valueOf(count); if (count >= total) { count = 0; } return dataStr + bu; } } 3.7 项目VO与DTO导入 package com.wyj.alipay.model; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * @author: yijun.wen * @date: 2022/3/11 5:52 下午 * @description: */ @Data @NoArgsConstructor public class ViewData implements Serializable { protected int code; protected V data; protected Object error; } package com.wyj.alipay.model; import lombok.Data; import java.io.Serializable; /** * @author: yijun.wen * @date: 2022/3/11 5:50 下午 * @description: */ @Data public class PayDto implements Serializable { private Long userId; private String totalAmount; private int payType; } package com.wyj.alipay.model; import lombok.Data; import java.io.Serializable; /** * @author: yijun.wen * @date: 2022/3/11 5:52 下午 * @description: */ @Data public class PayCallbackDto implements Serializable { private Long userId; private String payNumber; } package com.wyj.alipay.model; import lombok.Data; /** * @author: yijun.wen * @date: 2022/3/14 10:21 上午 * @description: */ @Data public class QrCodeVo { private Long UserId; private String payNumber; private String qrCode; } 上述为准备工作,接下来开始核心代码 4、 创建alipay业务接口及实现类 4.1 整体业务接口 package com.wyj.alipay.service; import com.alipay.api.AlipayApiException; import com.wyj.alipay.model.PayCallbackDto; import com.wyj.alipay.model.PayDto; import com.wyj.alipay.model.ViewData; import javax.servlet.http.HttpServletRequest; /** * @author: yijun.wen * @date: 2022/3/11 5:44 下午 * @description: */ public interface AlipayService { /** * 生成支付二维码 * * @param payInfo * @return */ ViewData alipay(PayDto payInfo); /** * 支付宝回调接口 * * @param request * @return */ boolean alipayCallback(HttpServletRequest request); /** * alipay 监听支付状态的接口 * * @param payCallbackInfo * @return * @throws AlipayApiException */ ViewData alipayCallback(PayCallbackDto payCallbackInfo) throws AlipayApiException; } 4.2 二维码返回接口实现 package com.wyj.alipay.service.impl; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayClient; import com.alipay.api.DefaultAlipayClient; import com.alipay.api.domain.AlipayTradePrecreateModel; import com.alipay.api.request.AlipayTradePrecreateRequest; import com.alipay.api.response.AlipayTradePrecreateResponse; import com.wyj.alipay.config.AlipayConfig; import com.wyj.alipay.model.PayCallbackDto; import com.wyj.alipay.model.PayDto; import com.wyj.alipay.model.QrCodeVo; import com.wyj.alipay.model.ViewData; import com.wyj.alipay.service.IAlipayService; import com.wyj.alipay.util.GenerateNum; import com.wyj.alipay.util.QrCodeResponse; import com.wyj.alipay.util.QrCodeUtil; import com.wyj.alipay.util.QrResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.FileCopyUtils; import javax.imageio.ImageIO; import javax.imageio.stream.ImageOutputStream; import javax.servlet.http.HttpServletRequest; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.Base64; /** * @author: yijun.wen * @date: 2022/3/14 10:19 上午 * @description: */ @Service @Slf4j public class AlipayServiceImpl implements IAlipayService { @Autowired private AlipayConfig alipayConfig; @Override public ViewData alipay(PayDto payInfo) { ViewData viewData = new ViewData(); // 1:支付的用户 Long userId = payInfo.getUserId(); // 2: 支付金额 String totalAmount = payInfo.getTotalAmount(); // 3: 支付的产品名称 String productName = "Alipay test"; // 4: 支付的订单编号 String payNumber = GenerateNum.generateOrder(); // 5: 支付方式 int payType = payInfo.getPayType(); // 6:支付宝携带的参数在回调中可以通过request获取 参数 JSONObject json = JSONUtil.createObj(); json.set("memberId", userId); json.set("totalAmount", totalAmount); json.set("productName", productName); json.set("payNumber", payNumber); json.set("payType", payType); // 7:设置支付相关的信息 AlipayTradePrecreateModel model = new AlipayTradePrecreateModel(); // 自定义订单号 model.setOutTradeNo(payNumber); // 支付金额 model.setTotalAmount(totalAmount); // 支付的产品名称 model.setSubject(productName); // 支付的请求体参数 model.setBody(json.toString()); // 支付的超时时间 model.setTimeoutExpress("5m"); // 支付的库存 id(根据 cloudPKI 业务,这里我们用用户id ) model.setStoreId(userId + ""); // 调用 alipay 获取二维码参数 QrCodeResponse qrCodeResponse = qrcodePay(model); try { ByteArrayOutputStream output = new ByteArrayOutputStream(); // 自定义二维码logo todo: 可以在二维码中间可以加上公司 logo //String logoPath = ResourceUtils.getFile("classpath:favicon.png").getAbsolutePath(); String logoPath = ""; // 生成二维码 BufferedImage buffImg = QrCodeUtil.encode(qrCodeResponse.getQr_code(), logoPath, false); ImageOutputStream imageOut = ImageIO.createImageOutputStream(output); ImageIO.write(buffImg, "JPEG", imageOut); imageOut.close(); ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray()); byte[] data = FileCopyUtils.copyToByteArray(input); QrCodeVo qrCodeVo = new QrCodeVo(); qrCodeVo.setQrCode(Base64.getEncoder().encodeToString(data)); qrCodeVo.setPayNumber(payNumber); qrCodeVo.setUserId(userId); viewData.setData(qrCodeVo); return viewData; } catch (Exception ex) { ex.printStackTrace(); return viewData; } } @Override public boolean alipayCallback(HttpServletRequest request) { return false; } @Override public ViewData alipayCallback(PayCallbackDto payCallbackInfo) throws AlipayApiException { return null; } /** * 扫码运行代码 * 验签通过返回QrResponse * 失败打印日志信息 * 参考地址:https://opendocs.alipay.com/apis/api_1/alipay.trade.app.pay * * @param model * @return */ public QrCodeResponse qrcodePay(AlipayTradePrecreateModel model) { // 1: 获取阿里请求客户端 AlipayClient alipayClient = getAlipayClient(); // 2: 获取阿里请求对象 AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest(); // 3:设置请求参数的集合,最大长度不限 request.setBizModel(model); // 设置异步回调地址 request.setNotifyUrl(alipayConfig.getNotify_url()); // 设置同步回调地址 request.setReturnUrl(alipayConfig.getReturn_url()); AlipayTradePrecreateResponse alipayTradePrecreateResponse = null; try { alipayTradePrecreateResponse = alipayClient.execute(request); } catch (AlipayApiException e) { e.printStackTrace(); } QrResponse qrResponse = JSON.parseObject(alipayTradePrecreateResponse.getBody(), QrResponse.class); return qrResponse.getAlipay_trade_precreate_response(); } /** * 获取AlipayClient对象 * * @return */ private AlipayClient getAlipayClient() { //获得初始化的AlipayClient AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig.getGatewayUrl(), alipayConfig.getApp_id(), alipayConfig.getMerchant_private_key(), "JSON", alipayConfig.getCharset(), alipayConfig.getAlipay_public_key(), alipayConfig.getSign_type()); return alipayClient; } } 4.3 创建 web 接口 package com.wyj.alipay.controller; import com.wyj.alipay.model.PayDto; import com.wyj.alipay.model.ViewData; import com.wyj.alipay.service.IAlipayService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author: yijun.wen * @date: 2022/3/14 10:57 上午 * @description: */ @RestController @RequestMapping("/api/pay/alipay/") public class AlipayController { @Autowired private IAlipayService alipayService; /** * 生成支付宝支付二维码 * * @param payInfo * @return */ @PostMapping("/qr_code") public ViewData alipay(@RequestBody PayDto payInfo) { return alipayService.alipay(payInfo); } } 4.4 使用 Postman 进行接口测试qr_Code响应值为 Base64 编码 我们可以简单写个 html 页面来测试 My alipay qr_code重写 AlipayServiceImpl alipayCallback方法 /** * 支付宝回调 * * @return * @throws Exception */ @Override public boolean alipayCallback(HttpServletRequest request) { // 获取支付宝GET过来反馈信息 Map params = new LinkedHashMap(); 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] + ","; } try { params.put(name, new String(valueStr.getBytes("ISO-8859-1"), "UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } // 计算得出通知验证结果 log.info("1:获取支付宝回传的参数" + params); try { // 验签 //RSA2密钥验签 boolean checkV1 = AlipaySignature.rsaCheckV1(params, alipayConfig.alipay_public_key, alipayConfig.charset, alipayConfig.sign_type); log.info("验签成功"); if (!checkV1) { log.info("验签失败接口参数:{}", params); return false; } } catch (AlipayApiException e) { e.printStackTrace(); } // 返回公共参数 String extparamString = request.getParameter("extra_common_param"); log.info("2:支付宝交易返回的公共参数:{}", extparamString); String tradeNo = params.get("trade_no"); //交易完成 String body = params.get("body"); log.info("3:【支付宝】交易的参数信息是:{},流水号是:{}", body, tradeNo); try { JSONObject bodyJson = new JSONObject(body); Long userId = bodyJson.getLong("userId"); String payType = bodyJson.getStr("payType"); String payNumber = bodyJson.getStr("payNumber"); log.info("4:【支付宝】交易的参数信息是:payType:{},payNumber:{},userId:{}", payType, payNumber, userId); // todo 入库充值记录 修改库存等一系列 DB 操作 } catch (Exception ex) { log.error("支付宝支付出现了异常,流水号是:{}", tradeNo); ex.printStackTrace(); return false; } return true; } 5.2 创建 web 接口下面代码拷贝到 AlipayController 检查 alipay 回调地址是否为当前接口地址 /** * alipay 异步通知 * 参考地址:https://opendocs.alipay.com/support/01ravg */ @ResponseBody @PostMapping("/notifyUrl") public String notify_url(HttpServletRequest request) { boolean result = alipayService.alipayCallback(request); if (result) { // alipay 规范,请不要修改或删除 return "success"; } else { // 验证失败 return "fail"; } } 5.3 查看测试效果注意:回调接口为 alipay 调用,必须外网能够访问 我这里打包部署到服务器上,给大家看下日志效果 重新按照步骤4生成二维码,支付宝扫码支付成功后,可见日志: 这个接口主要是给前端轮询调用获取支付状态使用 当然,还有一种解决方案,使用websocket,在步骤5中直接发送消息通知前端 6.1 监听支付状态的接口实现重写 AlipayServiceImpl alipayCallback 方法 @Override public ViewData alipayCallback(PayCallbackDto payCallbackInfo) throws AlipayApiException { ViewData viewData = new ViewData(); // 1: 获取阿里请求客户端 AlipayClient alipayClient = getAlipayClient(); // 2: 获取阿里请求对象 AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); // 3: 设置业务参数 request.setBizContent(JSONUtil.toJsonStr(JSONUtil.createObj().set("out_trade_no", payCallbackInfo.getPayNumber()))); //通过alipayClient调用API,获得对应的response类 AlipayTradeQueryResponse response = alipayClient.execute(request); String body = response.getBody(); JSONObject json = new JSONObject(new JSONObject(body).getStr("alipay_trade_query_response")); if ("10000".equals(json.getStr("code")) && "TRADE_SUCCESS".equals(json.getStr("trade_status"))) { viewData.setData("success"); } else { viewData.setData("fail"); } return viewData; } 6.2 创建 web 接口 /** * alipay 监听支付状态的接口 * * @param PayCallbackInfo * @return */ @PostMapping("/alipaycallback") public ViewData alipayCallback(@RequestBody PayCallbackDto PayCallbackInfo) throws AlipayApiException { return alipayService.alipayCallback(PayCallbackInfo); } 6.3 测试这里前端轮询逻辑,展示二维码 5 秒后,每3秒调用一次支付查询接口,得到响应success或5分钟后结束调用 |
CopyRight 2018-2019 实验室设备网 版权所有 |