Browse Source

新增支付宝支付

赵冬冬 4 years ago
parent
commit
ef92c1c6e2

+ 28 - 0
UtilsDemo-WX/src/main/java/com/example/config/ali/AlipayConfiguration.java

@@ -0,0 +1,28 @@
+package com.example.config.ali;
+
+import com.alipay.api.AlipayClient;
+import com.alipay.api.DefaultAlipayClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableConfigurationProperties(AlipayProperties.class)
+public class AlipayConfiguration {
+
+    @Autowired
+    private AlipayProperties properties;
+
+    @Bean
+    public AlipayClient alipayClient(){
+        return new DefaultAlipayClient(
+                properties.getGatewayUrl(),
+                properties.getAppid(),
+                properties.getAppPrivateKey(),
+                properties.getFormate(),
+                properties.getCharset(),
+                properties.getAlipayPublicKey(),
+                properties.getSignType());
+    }
+}

+ 75 - 0
UtilsDemo-WX/src/main/java/com/example/config/ali/AlipayProperties.java

@@ -0,0 +1,75 @@
+package com.example.config.ali;
+
+import lombok.Data;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.PropertySource;
+
+/**
+ * 支付宝支付的参数配置
+ */
+@Data
+@PropertySource("classpath:application.yml")
+//配置信息自动注入参数
+@ConfigurationProperties("pay.alipay")
+public class AlipayProperties {
+    private static final Logger logger = LoggerFactory.getLogger(AlipayProperties.class);
+    /**
+     * 支付宝gatewayUrl
+     */
+    private String gatewayUrl;
+    /**
+     * 商户应用id
+     */
+    private String appid;
+    /**
+     * RSA私钥,用于对商户请求报文加签
+     */
+    private String appPrivateKey;
+    /**
+     * 支付宝RSA公钥,用于验签支付宝应答
+     */
+    private String alipayPublicKey;
+    /**
+     * 签名类型
+     */
+    private String signType = "RSA2";
+    /**
+     * 格式
+     */
+    private String formate = "json";
+    /**
+     * 编码
+     */
+    private String charset = "UTF-8";
+    /**
+     * 支付成功后跳转到的地址
+     */
+    private String returnUrl;
+    /**
+     * 支付成功后异步回调地址
+     */
+    private String notifyUrl;
+    /**
+     * 最大查询次数
+     */
+    private static int maxQueryRetry = 5;
+    /**
+     * 查询间隔(毫秒)
+     */
+    private static long queryDuration = 5000;
+    /**
+     * 最大撤销次数
+     */
+    private static int maxCancelRetry = 3;
+    /**
+     * 撤销间隔(毫秒)
+     */
+    private static long cancelDuration = 3000;
+
+    private AlipayProperties() {
+    }
+    /*省略set和get方法*/
+    //setters and getters
+}

+ 41 - 0
UtilsDemo-WX/src/main/java/com/example/config/wx/WxMaConfiguration.java

@@ -0,0 +1,41 @@
+package com.example.config.wx;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
+import cn.binarywang.wx.miniapp.config.WxMaInMemoryConfig;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConditionalOnClass(WxMaService.class)
+public class WxMaConfiguration {
+
+    /**
+     * 设置微信小程序的appid
+     */
+    @Value("${wx.ma.appId}")
+    private String appId;
+
+    /**
+     * 设置微信小程序的app secret
+     */
+    @Value("${wx.ma.secret}")
+
+    private String secret;
+
+
+    @Bean
+    @ConditionalOnMissingBean
+    public WxMaService wxMaService() {
+        WxMaInMemoryConfig config = new WxMaInMemoryConfig();
+        config.setAppid(appId);
+        config.setSecret(secret);
+        WxMaService wxMaService = new WxMaServiceImpl();
+        wxMaService.setWxMaConfig(config);
+        return wxMaService;
+    }
+
+}

+ 24 - 0
UtilsDemo-WX/src/main/java/com/example/config/wx/WxMaProperties.java

@@ -0,0 +1,24 @@
+package com.example.config.wx;
+
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+
+/**
+ * @author xiezt
+ */
+@Data
+@ConfigurationProperties(prefix = "wx.ma")
+public class WxMaProperties {
+    /**
+     * 设置微信公众号的appid
+     */
+    private String appId;
+
+    /**
+     * 设置微信公众号的app secret
+     */
+    private String secret;
+
+}

+ 42 - 0
UtilsDemo-WX/src/main/java/com/example/config/wx/WxMpConfiguration.java

@@ -0,0 +1,42 @@
+package com.example.config.wx;
+
+
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Slf4j
+@Configuration
+@ConditionalOnClass(WxMpService.class)
+@EnableConfigurationProperties(WxMpProperties.class)
+public class WxMpConfiguration {
+
+    private WxMpProperties properties;
+
+    @Autowired
+    public WxMpConfiguration(WxMpProperties properties) {
+        this.properties = properties;
+    }
+
+    @Bean
+    @ConditionalOnMissingBean
+    public WxMpService wxMpService() {
+        WxMpInMemoryConfigStorage configStorage = new WxMpInMemoryConfigStorage();
+        configStorage.setAppId(StringUtils.trimToNull(this.properties.getAppId()));
+        configStorage.setSecret(StringUtils.trimToNull(this.properties.getSecret()));
+        configStorage.setToken(StringUtils.trimToNull(this.properties.getToken()));
+        configStorage.setAesKey(StringUtils.trimToNull(this.properties.getAesKey()));
+        WxMpService wxMpService = new WxMpServiceImpl();
+        wxMpService.setWxMpConfigStorage(configStorage);
+        return wxMpService;
+    }
+}
+

+ 34 - 0
UtilsDemo-WX/src/main/java/com/example/config/wx/WxMpProperties.java

@@ -0,0 +1,34 @@
+package com.example.config.wx;
+
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+
+/**
+ * @author xiezt
+ */
+@Data
+@ConfigurationProperties(prefix = "wx.mp")
+public class WxMpProperties {
+    /**
+     * 设置微信公众号的appid
+     */
+    private String appId;
+
+    /**
+     * 设置微信公众号的app secret
+     */
+    private String secret;
+
+    /**
+     * 设置微信公众号的token
+     */
+    private String token;
+
+    /**
+     * 设置微信公众号的EncodingAESKey
+     */
+    private String aesKey;
+
+}

+ 49 - 0
UtilsDemo-WX/src/main/java/com/example/config/wx/WxPayConfiguration.java

@@ -0,0 +1,49 @@
+package com.example.config.wx;
+
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Binary Wang
+ */
+@Configuration
+@ConditionalOnClass(WxPayService.class)
+@EnableConfigurationProperties(WxPayProperties.class)
+public class WxPayConfiguration {
+
+  private WxPayProperties properties;
+
+  @Autowired
+  public WxPayConfiguration(WxPayProperties properties) {
+    this.properties = properties;
+  }
+
+  @Bean
+  @ConditionalOnMissingBean
+  public WxPayService wxService() {
+    WxPayConfig payConfig = new WxPayConfig();
+    payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));
+    payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));
+    payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey()));
+    payConfig.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId()));
+    payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));
+    payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));
+
+
+    // 可以指定是否使用沙箱环境
+    payConfig.setUseSandboxEnv(false);
+
+    WxPayService wxPayService = new WxPayServiceImpl();
+    wxPayService.setConfig(payConfig);
+    return wxPayService;
+  }
+
+}

+ 97 - 0
UtilsDemo-WX/src/main/java/com/example/config/wx/WxPayProperties.java

@@ -0,0 +1,97 @@
+package com.example.config.wx;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * wxpay pay properties
+ *
+ * @author Binary Wang
+ */
+@ConfigurationProperties(prefix = "wx.pay")
+public class WxPayProperties {
+  /**
+   * 设置微信公众号或者小程序等的appid
+   */
+  private String appId;
+
+  /**
+   * 微信支付商户号
+   */
+  private String mchId;
+
+  /**
+   * 微信支付商户密钥
+   */
+  private String mchKey;
+
+  /**
+   * 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除
+   */
+  private String subAppId;
+
+  /**
+   * 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除
+   */
+  private String subMchId;
+
+  /**
+   * apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定
+   */
+  private String keyPath;
+
+  public String getAppId() {
+    return this.appId;
+  }
+
+  public void setAppId(String appId) {
+    this.appId = appId;
+  }
+
+  public String getMchId() {
+    return mchId;
+  }
+
+  public void setMchId(String mchId) {
+    this.mchId = mchId;
+  }
+
+  public String getMchKey() {
+    return mchKey;
+  }
+
+  public void setMchKey(String mchKey) {
+    this.mchKey = mchKey;
+  }
+
+  public String getSubAppId() {
+    return subAppId;
+  }
+
+  public void setSubAppId(String subAppId) {
+    this.subAppId = subAppId;
+  }
+
+  public String getSubMchId() {
+    return subMchId;
+  }
+
+  public void setSubMchId(String subMchId) {
+    this.subMchId = subMchId;
+  }
+
+  public String getKeyPath() {
+    return this.keyPath;
+  }
+
+  public void setKeyPath(String keyPath) {
+    this.keyPath = keyPath;
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this,
+        ToStringStyle.MULTI_LINE_STYLE);
+  }
+}

+ 406 - 0
UtilsDemo-WX/src/main/java/com/example/controller/AlipayPagePayController.java

@@ -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("申请退款成功"));
+    }
+
+
+}

+ 9 - 0
UtilsDemo-WX/src/main/java/com/example/service/AlipayRefundService.java

@@ -0,0 +1,9 @@
+package com.example.service;
+
+import java.util.Map;
+
+public interface AlipayRefundService {
+    boolean containsByOutTradeNo(String trade_no);
+
+    void saveToDb(Map<String, String[]> parameterMap);
+}

+ 9 - 0
UtilsDemo-WX/src/main/java/com/example/service/AlipayService.java

@@ -0,0 +1,9 @@
+package com.example.service;
+
+import java.util.Map;
+
+public interface AlipayService {
+    boolean containsByOutTradeNo(String trade_no);
+
+    void saveToDb(Map<String, String[]> parameterMap);
+}

+ 19 - 0
UtilsDemo-WX/src/main/java/com/example/service/impl/AlipayRefundServiceImpl.java

@@ -0,0 +1,19 @@
+package com.example.service.impl;
+
+import com.example.service.AlipayRefundService;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+
+@Service
+public class AlipayRefundServiceImpl implements AlipayRefundService {
+    @Override
+    public boolean containsByOutTradeNo(String trade_no) {
+        return false;
+    }
+
+    @Override
+    public void saveToDb(Map<String, String[]> parameterMap) {
+
+    }
+}

+ 19 - 0
UtilsDemo-WX/src/main/java/com/example/service/impl/AlipayServiceImpl.java

@@ -0,0 +1,19 @@
+package com.example.service.impl;
+
+import com.example.service.AlipayService;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+
+@Service
+public class AlipayServiceImpl implements AlipayService {
+    @Override
+    public boolean containsByOutTradeNo(String trade_no) {
+        return false;
+    }
+
+    @Override
+    public void saveToDb(Map<String, String[]> parameterMap) {
+
+    }
+}

+ 45 - 0
UtilsDemo-WX/src/main/java/com/example/util/QrCodeUtil.java

@@ -0,0 +1,45 @@
+package com.example.util;
+
+import com.google.zxing.*;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.util.Hashtable;
+
+public class QrCodeUtil {
+    /**
+     * 生成包含字符串信息的二维码图片
+     * @param outputStream 文件输出流路径
+     * @param content 二维码携带信息
+     */
+    public static boolean createQrCode(OutputStream outputStream, String content) throws WriterException, IOException{
+        //设置二维码纠错级别MAP
+        Hashtable<EncodeHintType, ErrorCorrectionLevel> hintMap = new Hashtable<EncodeHintType, ErrorCorrectionLevel>();
+        hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);  // 矫错级别
+        QRCodeWriter qrCodeWriter = new QRCodeWriter();
+        //创建比特矩阵(位矩阵)的QR码编码的字符串
+        BitMatrix byteMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, 900, 900, hintMap);
+        // 使BufferedImage勾画QRCode  (matrixWidth 是行二维码像素点)
+        int matrixWidth = byteMatrix.getWidth();
+        BufferedImage image = new BufferedImage(matrixWidth-200, matrixWidth-200, BufferedImage.TYPE_INT_RGB);
+        image.createGraphics();
+        Graphics2D graphics = (Graphics2D) image.getGraphics();
+        graphics.setColor(Color.WHITE);
+        graphics.fillRect(0, 0, matrixWidth, matrixWidth);
+        // 使用比特矩阵画并保存图像
+        graphics.setColor(Color.BLACK);
+        for (int i = 0; i < matrixWidth; i++){
+            for (int j = 0; j < matrixWidth; j++){
+                if (byteMatrix.get(i, j)){
+                    graphics.fillRect(i-100, j-100, 1, 1);
+                }
+            }
+        }
+        return ImageIO.write(image, "JPEG", outputStream);
+    }
+}

+ 133 - 0
UtilsDemo-WX/src/main/java/com/example/util/SnowFlake.java

@@ -0,0 +1,133 @@
+package com.example.util;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 描述: Twitter的分布式自增ID雪花算法snowflake (Java版)
+ *
+ *
+ * @author  zdd
+ * @create  2020-12-21
+ **/
+public class SnowFlake {
+
+    /**
+     * 起始的时间戳
+     */
+    private final static long START_STMP = 1480166465631L;
+
+    /**
+     * 每一部分占用的位数
+     */
+    private final static long SEQUENCE_BIT = 12; //序列号占用的位数
+    private final static long MACHINE_BIT = 5;   //机器标识占用的位数
+    private final static long DATACENTER_BIT = 5;//数据中心占用的位数
+
+    /**
+     * 每一部分的最大值
+     */
+    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
+    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
+    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
+
+    /**
+     * 每一部分向左的位移
+     */
+    private final static long MACHINE_LEFT = SEQUENCE_BIT;
+    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
+    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
+
+    private long datacenterId;  //数据中心
+    private long machineId;     //机器标识
+    private long sequence = 0L; //序列号
+    private long lastStmp = -1L;//上一次时间戳
+
+    public SnowFlake(long datacenterId, long machineId) {
+        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
+            throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
+        }
+        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
+            throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
+        }
+        this.datacenterId = datacenterId;
+        this.machineId = machineId;
+    }
+
+    /**
+     * 产生下一个ID
+     *
+     * @return
+     */
+    public synchronized long nextId() {
+        long currStmp = getNewstmp();
+        if (currStmp < lastStmp) {
+            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
+        }
+
+        if (currStmp == lastStmp) {
+            //相同毫秒内,序列号自增
+            sequence = (sequence + 1) & MAX_SEQUENCE;
+            //同一毫秒的序列数已经达到最大
+            if (sequence == 0L) {
+                currStmp = getNextMill();
+            }
+        } else {
+            //不同毫秒内,序列号置为0
+            sequence = 0L;
+        }
+
+        lastStmp = currStmp;
+
+        return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分
+                | datacenterId << DATACENTER_LEFT       //数据中心部分
+                | machineId << MACHINE_LEFT             //机器标识部分
+                | sequence;                             //序列号部分
+    }
+
+    private long getNextMill() {
+        long mill = getNewstmp();
+        while (mill <= lastStmp) {
+            mill = getNewstmp();
+        }
+        return mill;
+    }
+
+    private long getNewstmp() {
+        return System.currentTimeMillis();
+    }
+
+    public static void main(String[] args) {
+        SnowFlake snowFlake = new SnowFlake(2, 3);
+        Map<Long, Long> map = new HashMap<>();
+        long starttime = new Date().getTime();
+        for (int i = 0; i < 100; i++) {
+            long commonRandomCode = snowFlake.nextId();
+            Long count = map.get(commonRandomCode);
+            if (null != count) {
+                count = count + 1L;
+                map.put(commonRandomCode, count);
+            } else {
+                map.put(commonRandomCode, 1L);
+            }
+            System.err.println(commonRandomCode);
+
+        }
+
+        for (Long s : map.keySet()) {
+            Long count = map.get(s);
+            if (count > 1) {
+                System.err.println(s + "---" + count);
+            } else {
+                //System.err.println(s + "---" + count);
+            }
+        }
+        long endtime = new Date().getTime();
+        long diff = endtime - starttime;
+        long time = diff / 1000;
+        System.err.println(time + ":秒");
+
+
+    }
+}