|
@@ -0,0 +1,406 @@
|
|
|
+package com.example.controller;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.alipay.api.AlipayApiException;
|
|
|
+import com.alipay.api.AlipayClient;
|
|
|
+import com.alipay.api.domain.AlipayTradePagePayModel;
|
|
|
+import com.alipay.api.domain.AlipayTradePrecreateModel;
|
|
|
+import com.alipay.api.domain.AlipayTradeRefundModel;
|
|
|
+import com.alipay.api.domain.AlipayTradeWapPayModel;
|
|
|
+import com.alipay.api.internal.util.AlipaySignature;
|
|
|
+import com.alipay.api.request.AlipayTradePagePayRequest;
|
|
|
+import com.alipay.api.request.AlipayTradePrecreateRequest;
|
|
|
+import com.alipay.api.request.AlipayTradeRefundRequest;
|
|
|
+import com.alipay.api.request.AlipayTradeWapPayRequest;
|
|
|
+import com.alipay.api.response.AlipayTradePrecreateResponse;
|
|
|
+import com.alipay.api.response.AlipayTradeRefundResponse;
|
|
|
+import com.example.base.BaseController;
|
|
|
+import com.example.base.ResponseBase;
|
|
|
+import com.example.config.ali.AlipayProperties;
|
|
|
+import com.example.service.AlipayRefundService;
|
|
|
+import com.example.service.AlipayService;
|
|
|
+import com.example.util.SnowFlake;
|
|
|
+import io.swagger.annotations.ApiOperation;
|
|
|
+import io.swagger.annotations.ApiParam;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.stereotype.Controller;
|
|
|
+import org.springframework.web.bind.annotation.*;
|
|
|
+import springfox.documentation.annotations.ApiIgnore;
|
|
|
+
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.UnsupportedEncodingException;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Objects;
|
|
|
+
|
|
|
+@Controller
|
|
|
+@RequestMapping("/alipay")
|
|
|
+public class AlipayPagePayController extends BaseController {
|
|
|
+
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(AlipayPagePayController.class);
|
|
|
+
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private AlipayProperties alipayProperties;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private AlipayClient alipayClient;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 商品定价
|
|
|
+ */
|
|
|
+ @Value("${pay.alipay.totalAmount}")
|
|
|
+ private String totalAmount;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 商品名
|
|
|
+ */
|
|
|
+ @Value("${pay.subjectName}")
|
|
|
+ public String subjectName;
|
|
|
+
|
|
|
+
|
|
|
+ @GetMapping("/test")
|
|
|
+ @ResponseBody
|
|
|
+ public String test() {
|
|
|
+ return "suucess";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 扫码付款成功后跳转的url
|
|
|
+ */
|
|
|
+ @Value("${pay.alipay.returnUrlForScan}")
|
|
|
+ private String returnUrlForScan;
|
|
|
+
|
|
|
+ @GetMapping("/getPayimg")
|
|
|
+ @ResponseBody
|
|
|
+ public ResponseBase getPayimg(String outTradeNo) {
|
|
|
+ AlipayTradePrecreateRequest req = new AlipayTradePrecreateRequest();
|
|
|
+
|
|
|
+ AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
|
|
|
+ model.setOutTradeNo(outTradeNo);
|
|
|
+ model.setSubject("测试二维码");
|
|
|
+ String amount = String.valueOf(0.01);
|
|
|
+ model.setTotalAmount(amount);
|
|
|
+ req.setBizModel(model);
|
|
|
+ req.setNotifyUrl(alipayProperties.getNotifyUrl());
|
|
|
+
|
|
|
+ logger.info("发起AliPay下单请求");
|
|
|
+
|
|
|
+ AlipayTradePrecreateResponse execute = null;
|
|
|
+ String img = null;
|
|
|
+ try {
|
|
|
+ execute = alipayClient.execute(req);
|
|
|
+ String result = execute.getBody();
|
|
|
+ JSONObject res = JSONObject.parseObject(result);
|
|
|
+ res = res.getJSONObject("alipay_trade_precreate_response");
|
|
|
+ String code = res.getString("code");
|
|
|
+ String qr_code = res.getString("qr_code");
|
|
|
+ if (code.equals("10000") && qr_code != null) {
|
|
|
+ img = qr_code;
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ return responseSuccess(error("构建二维码失败!"));
|
|
|
+
|
|
|
+ }
|
|
|
+ return responseSuccess(success(img));
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * h5 页面支付
|
|
|
+ *
|
|
|
+ * @param outTradeNo
|
|
|
+ * @return
|
|
|
+ * @throws AlipayApiException
|
|
|
+ */
|
|
|
+ @GetMapping("/alipage")
|
|
|
+ @ResponseBody
|
|
|
+ public ResponseBase gotoPayPage(@RequestParam String outTradeNo) throws AlipayApiException {
|
|
|
+ logger.info("支付订单号:{}", outTradeNo);
|
|
|
+
|
|
|
+ // 订单模型
|
|
|
+ String productCode = "QUICK_WAP_WAY";
|
|
|
+ AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
|
|
|
+ //防止传空参数
|
|
|
+ if (outTradeNo == null || outTradeNo == "" || outTradeNo.length() < 4) {
|
|
|
+ outTradeNo = String.valueOf(System.nanoTime());
|
|
|
+ }
|
|
|
+ // String outTradeNo = UUID.randomUUID().toString();
|
|
|
+ model.setOutTradeNo(outTradeNo);
|
|
|
+ model.setSubject(subjectName);
|
|
|
+ model.setTotalAmount(totalAmount);
|
|
|
+ model.setBody(subjectName + ",共0.01元");
|
|
|
+ model.setTimeoutExpress("2m");
|
|
|
+ model.setProductCode(productCode);
|
|
|
+
|
|
|
+ AlipayTradeWapPayRequest wapPayRequest = new AlipayTradeWapPayRequest();
|
|
|
+ wapPayRequest.setReturnUrl(alipayProperties.getReturnUrl());
|
|
|
+ wapPayRequest.setNotifyUrl(alipayProperties.getNotifyUrl());
|
|
|
+ wapPayRequest.setBizModel(model);
|
|
|
+
|
|
|
+ // 调用SDK生成表单, 并直接将完整的表单html输出到页面
|
|
|
+ String form = alipayClient.pageExecute(wapPayRequest).getBody();
|
|
|
+ return responseSuccess(success(form));
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @ApiOperation(value = "网页扫码支付", notes = "跳转到支付宝扫码支付页面,用户可通过扫码进行支付,成功则跳转到成功页面")
|
|
|
+ @GetMapping("/gotoPayPage")
|
|
|
+ public void gotoPayPage(HttpServletResponse response) throws AlipayApiException, IOException {
|
|
|
+ // 订单模型
|
|
|
+ String productCode = "FAST_INSTANT_TRADE_PAY";
|
|
|
+ AlipayTradePagePayModel model = new AlipayTradePagePayModel();
|
|
|
+ SnowFlake snowFlake = new SnowFlake(2, 3);
|
|
|
+
|
|
|
+ model.setOutTradeNo(snowFlake.nextId() + "");
|
|
|
+ model.setSubject(subjectName);
|
|
|
+ model.setTotalAmount(totalAmount);
|
|
|
+ model.setBody(subjectName + ",共0.01元");
|
|
|
+ model.setProductCode(productCode);
|
|
|
+
|
|
|
+ AlipayTradePagePayRequest pagePayRequest = new AlipayTradePagePayRequest();
|
|
|
+ pagePayRequest.setReturnUrl(returnUrlForScan);
|
|
|
+ pagePayRequest.setNotifyUrl(alipayProperties.getNotifyUrl());
|
|
|
+ pagePayRequest.setBizModel(model);
|
|
|
+
|
|
|
+ // 调用SDK生成表单, 并直接将完整的表单html输出到页面
|
|
|
+ String form = alipayClient.pageExecute(pagePayRequest).getBody();
|
|
|
+ response.setContentType("text/html;charset=" + alipayProperties.getCharset());
|
|
|
+ response.getWriter().write(form);
|
|
|
+ response.getWriter().flush();
|
|
|
+ response.getWriter().close();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 支付宝页面跳转同步通知页面
|
|
|
+ */
|
|
|
+ @ApiIgnore
|
|
|
+ @GetMapping("/returnUrl")
|
|
|
+ public String returnUrl(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException, AlipayApiException {
|
|
|
+ response.setContentType("text/html;charset=" + alipayProperties.getCharset());
|
|
|
+
|
|
|
+ //获取支付宝GET过来反馈信息
|
|
|
+ Map<String, String> params = new HashMap<>();
|
|
|
+ Map requestParams = request.getParameterMap();
|
|
|
+ for (Object iter : requestParams.keySet()) {
|
|
|
+ String name = (String) iter;
|
|
|
+ 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"), "utf-8");
|
|
|
+ params.put(name, valueStr);
|
|
|
+ }
|
|
|
+ logger.info("params is " + params.toString());
|
|
|
+ //验签
|
|
|
+ boolean verifyResult = AlipaySignature.rsaCheckV1(params, alipayProperties.getAlipayPublicKey(),
|
|
|
+ alipayProperties.getCharset(), "RSA2");
|
|
|
+ if (verifyResult) {
|
|
|
+ //验证成功
|
|
|
+ //请在这里加上商户的业务逻辑程序代码,如保存支付宝交易号
|
|
|
+ //商户订单号
|
|
|
+ String outTradeNo = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"), "UTF-8");
|
|
|
+ //支付宝交易号
|
|
|
+ String tradeNo = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"), "UTF-8");
|
|
|
+
|
|
|
+ return "success";
|
|
|
+ } else {
|
|
|
+ return "error";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private AlipayService alipayService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private AlipayRefundService alipayRefundService;
|
|
|
+
|
|
|
+ static boolean rsaCheckV1(HttpServletRequest request) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 支付异步通知
|
|
|
+ * 接收到异步通知并验签通过后,一定要检查通知内容,包括通知中的app_id、out_trade_no、total_amount是否与请求中的一致,
|
|
|
+ * 并根据trade_status进行后续业务处理
|
|
|
+ * https://docs.open.alipay.com/194/103296
|
|
|
+ */
|
|
|
+
|
|
|
+ @ApiIgnore
|
|
|
+ @RequestMapping("/notify")
|
|
|
+ @ResponseBody
|
|
|
+ public String notify(HttpServletRequest request) throws AlipayApiException, UnsupportedEncodingException {
|
|
|
+ // 一定要验签,防止黑客篡改参数
|
|
|
+ Map<String, String[]> parameterMap = request.getParameterMap();
|
|
|
+ StringBuilder notifyBuild = new StringBuilder("/****************************** alipay notify **********" +
|
|
|
+ "********************/\n");
|
|
|
+ parameterMap.forEach((key, value) -> notifyBuild.append(key + "=" + value[0] + "\n"));
|
|
|
+ //订单信息
|
|
|
+ logger.info("notifyBuild" + notifyBuild.toString());
|
|
|
+
|
|
|
+ //校验签名
|
|
|
+ boolean flag = this.rsaCheckV1(request);
|
|
|
+ if (flag) {
|
|
|
+ /**
|
|
|
+ * 需要严格按照如下描述校验通知数据的正确性
|
|
|
+ * 商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
|
|
|
+ * 并判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
|
|
|
+ * 同时需要校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
|
|
|
+ * 上述有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。
|
|
|
+ * 在上述验证通过后商户必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。
|
|
|
+ * 在支付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功。
|
|
|
+ */
|
|
|
+ //交易状态
|
|
|
+ String tradeStatus = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"), "UTF-8");
|
|
|
+ //支付宝交易号
|
|
|
+ String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"), "UTF-8");
|
|
|
+ String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"), "UTF-8");
|
|
|
+
|
|
|
+ //商户id
|
|
|
+ String app_id = new String(request.getParameter("app_id").getBytes("ISO-8859-1"), "UTF-8");
|
|
|
+ //退款费用 用于判断是否是退款
|
|
|
+ String refund_fee = null;
|
|
|
+ try {
|
|
|
+ refund_fee = new String(request.getParameter("refund_fee").getBytes("ISO-8859-1"), "UTF-8");
|
|
|
+ } catch (Exception e) {
|
|
|
+ refund_fee = "";
|
|
|
+ }
|
|
|
+ /*判断是否是支付异步通知*/
|
|
|
+ if (refund_fee == null || refund_fee == "") {
|
|
|
+ //付款金额
|
|
|
+ String total_amount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"), "UTF-8");
|
|
|
+ //判断订单号是否已处理
|
|
|
+ if (alipayService.containsByOutTradeNo(trade_no)) {
|
|
|
+ logger.warn("订单已处理,支付宝重复调用");
|
|
|
+ return "success";
|
|
|
+ }
|
|
|
+ //判断订单金额是否一致 防止被篡改
|
|
|
+ if (!total_amount.equals(totalAmount)) {
|
|
|
+ logger.warn("订单金额不一致,请防止黑客恶意篡改信息");
|
|
|
+ return "fail";
|
|
|
+ }
|
|
|
+ //判断商户id是否一致,防止被篡改
|
|
|
+ if (!app_id.equals(alipayProperties.getAppid())) {
|
|
|
+ logger.warn("操作的商户id不一致,请防止黑客恶意篡改信息");
|
|
|
+ return "fail";
|
|
|
+ }
|
|
|
+
|
|
|
+ // TRADE_FINISHED(表示交易已经成功结束,并不能再对该交易做后续操作);
|
|
|
+ // TRADE_SUCCESS(表示交易已经成功结束,可以对该交易做后续操作,如:分润、退款等);
|
|
|
+ if ("TRADE_FINISHED".equals(tradeStatus)) {
|
|
|
+ //判断该笔订单是否在商户网站中已经做过处理
|
|
|
+ //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,
|
|
|
+ // 并判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),并执行商户的业务程序
|
|
|
+ //请务必判断请求时的total_fee、seller_id与通知时获取的total_fee、seller_id为一致的
|
|
|
+ //如果有做过处理,不执行商户的业务程序
|
|
|
+ //注意:
|
|
|
+ //如果签约的是可退款协议,退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
|
|
|
+ //如果没有签约可退款协议,那么付款完成后,支付宝系统发送该交易状态通知。
|
|
|
+ } else if ("TRADE_SUCCESS".equals(tradeStatus)) {
|
|
|
+ //判断该笔订单是否在商户网站中已经做过处理
|
|
|
+ //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,
|
|
|
+ // 并判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),并执行商户的业务程序
|
|
|
+ //请务必判断请求时的total_fee、seller_id与通知时获取的total_fee、seller_id为一致的
|
|
|
+ //如果有做过处理,不执行商户的业务程序
|
|
|
+ logger.info("签约可退款协议");
|
|
|
+ alipayService.saveToDb(parameterMap);
|
|
|
+ //注意:
|
|
|
+ //如果签约的是可退款协议,那么付款完成后,支付宝系统发送该交易状态通知。
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /*判断是否是退款异步通知*/
|
|
|
+ else {
|
|
|
+ if (alipayRefundService.containsByOutTradeNo(trade_no)) {
|
|
|
+ logger.warn("退款已处理,用户可能重复提交");
|
|
|
+ return "have refunded";
|
|
|
+ }
|
|
|
+ //判断订单金额是否一致 防止被篡改
|
|
|
+ if (!refund_fee.equals(totalAmount)) {
|
|
|
+ logger.warn("订单金额不一致,请防止黑客恶意篡改信息");
|
|
|
+ return "fail";
|
|
|
+ }
|
|
|
+ //判断商户id是否一致,防止被篡改
|
|
|
+ if (!app_id.equals(alipayProperties.getAppid())) {
|
|
|
+ logger.warn("操作的商户id不一致,请防止黑客恶意篡改信息");
|
|
|
+ return "fail";
|
|
|
+ }
|
|
|
+ if ("TRADE_FINISHED".equals(tradeStatus)) {
|
|
|
+ //判断该笔订单是否在商户网站中已经做过处理
|
|
|
+ //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,
|
|
|
+ // 并判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),并执行商户的业务程序
|
|
|
+ //请务必判断请求时的total_fee、seller_id与通知时获取的total_fee、seller_id为一致的
|
|
|
+ //如果有做过处理,不执行商户的业务程序
|
|
|
+ //注意:
|
|
|
+ //如果签约的是可退款协议,退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
|
|
|
+ //如果没有签约可退款协议,那么付款完成后,支付宝系统发送该交易状态通知。
|
|
|
+ } else if ("TRADE_SUCCESS".equals(tradeStatus)) {
|
|
|
+ //判断该笔订单是否在商户网站中已经做过处理
|
|
|
+ //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,
|
|
|
+ // 并判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),并执行商户的业务程序
|
|
|
+ //请务必判断请求时的total_fee、seller_id与通知时获取的total_fee、seller_id为一致的
|
|
|
+ //如果有做过处理,不执行商户的业务程序
|
|
|
+ alipayRefundService.saveToDb(parameterMap);
|
|
|
+ //注意:
|
|
|
+ //如果签约的是可退款协议,那么付款完成后,支付宝系统发送该交易状态通知。
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return "success";
|
|
|
+ }
|
|
|
+ return "fail";
|
|
|
+ }
|
|
|
+
|
|
|
+ @ApiOperation(value = "退款", notes = "通过商户订单号退款")
|
|
|
+ @ApiParam(name = "out_trade_no", value = "商户订单号", required = true)
|
|
|
+ @GetMapping("/refund")
|
|
|
+ @ResponseBody
|
|
|
+ public ResponseBase refund(@RequestParam String outTradeNo) {
|
|
|
+ //若订单已处理,直接返回
|
|
|
+ if (alipayRefundService.containsByOutTradeNo(outTradeNo)) {
|
|
|
+ logger.warn("退款已处理,用户可能重复提交");
|
|
|
+ responseSuccess(error("退款已处理,用户可能重复提交"));
|
|
|
+ }
|
|
|
+ AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
|
|
|
+ AlipayTradeRefundModel refundModel = new AlipayTradeRefundModel();
|
|
|
+ //refundModel.setTradeNo(outTradeNo);
|
|
|
+ refundModel.setOutTradeNo(outTradeNo);
|
|
|
+ refundModel.setRefundAmount(totalAmount);
|
|
|
+ refundModel.setRefundReason("用户归还胶片,退还费用");
|
|
|
+ request.setBizModel(refundModel);
|
|
|
+ AlipayTradeRefundResponse response = null;
|
|
|
+ try {
|
|
|
+ response = alipayClient.execute(request);
|
|
|
+ logger.info(response.getBody());
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("申请退款出错");
|
|
|
+ e.printStackTrace();
|
|
|
+ responseSuccess(error("申请退款出错"));
|
|
|
+ }
|
|
|
+ if (Objects.requireNonNull(response).isSuccess()) {
|
|
|
+ logger.info("申请退款成功");
|
|
|
+ Map<String, String> responseMap = new HashMap<>(2);
|
|
|
+ responseMap.put("return_code", "200");
|
|
|
+ responseMap.put("return_msg", "refund success");
|
|
|
+ responseSuccess(error("申请退款成功"));
|
|
|
+ } else {
|
|
|
+ logger.info("申请退款失败");
|
|
|
+ Map<String, String> responseMap = new HashMap<>(2);
|
|
|
+ responseMap.put("return_code", "200");
|
|
|
+ responseMap.put("return_msg", "refund error");
|
|
|
+ return responseSuccess(error("申请退款失败"));
|
|
|
+ }
|
|
|
+ return responseSuccess(success("申请退款成功"));
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+}
|