Bladeren bron

pay: 接入支付宝 Wap 支付

YunaiV 2 jaren geleden
bovenliggende
commit
e6f414b918

+ 18 - 5
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java

@@ -1,5 +1,10 @@
 package cn.iocoder.yudao.framework.pay.core.client.impl;
 
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.iocoder.yudao.framework.common.util.date.DateUtils;
+import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
 import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
@@ -11,6 +16,9 @@ import lombok.extern.slf4j.Slf4j;
 
 import javax.validation.Validation;
 
+import java.time.LocalDateTime;
+
+import static cn.hutool.core.date.DatePattern.NORM_DATETIME_MS_FORMATTER;
 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 
 /**
@@ -69,11 +77,6 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
         this.init();
     }
 
-    // TODO 芋艿:后续抽取到工具类里
-    protected Double calculateAmount(Integer amount) {
-        return amount / 100.0;
-    }
-
     @Override
     public Long getId() {
         return channelId;
@@ -113,4 +116,14 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
 
     protected abstract PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
 
+
+    protected String formatAmount(Integer amount) {
+        return String.valueOf(amount / 100.0);
+    }
+
+    protected String formatTime(LocalDateTime time) {
+        // "yyyy-MM-dd HH:mm:ss"
+        return LocalDateTimeUtil.format(time, NORM_DATETIME_MS_FORMATTER);
+    }
+
 }

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java

@@ -112,7 +112,7 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
         model.setOutTradeNo(reqDTO.getPayTradeNo());
 
         model.setOutRequestNo(reqDTO.getMerchantRefundId());
-        model.setRefundAmount(calculateAmount(reqDTO.getAmount() / 2).toString());
+        model.setRefundAmount(formatAmount(reqDTO.getAmount() / 2).toString());
         model.setRefundReason(reqDTO.getReason());
 
         AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest();

+ 8 - 11
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java

@@ -1,11 +1,8 @@
 package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
 
-import cn.hutool.core.lang.Pair;
 import cn.hutool.core.map.MapUtil;
-import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.http.Method;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
@@ -18,12 +15,11 @@ import com.alipay.api.request.AlipayTradePagePayRequest;
 import com.alipay.api.response.AlipayTradePagePayResponse;
 import lombok.extern.slf4j.Slf4j;
 
-import java.util.HashMap;
 import java.util.Objects;
 
-
 /**
- * 支付宝【PC网站支付】的 PayClient 实现类
+ * 支付宝【PC 网站支付】的 PayClient 实现类
+ *
  * 文档:https://opendocs.alipay.com/open/270/105898
  *
  * @author XGD
@@ -32,7 +28,8 @@ import java.util.Objects;
 public class AlipayPcPayClient extends AbstractAlipayClient {
 
     public AlipayPcPayClient(Long channelId, AlipayPayClientConfig config) {
-        super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config, new AlipayPayCodeMapping());
+        super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config,
+                new AlipayPayCodeMapping());
     }
 
     @Override
@@ -42,8 +39,10 @@ public class AlipayPcPayClient extends AbstractAlipayClient {
         // ① 通用的参数
         model.setOutTradeNo(reqDTO.getMerchantOrderId());
         model.setSubject(reqDTO.getSubject());
-        model.setTotalAmount(String.valueOf(calculateAmount(reqDTO.getAmount())));
-        model.setProductCode("FAST_INSTANT_TRADE_PAY"); // 销售产品码. 目前电脑支付场景下仅支持 FAST_INSTANT_TRADE_PAY
+        model.setBody(reqDTO.getBody());
+        model.setTotalAmount(formatAmount(reqDTO.getAmount()));
+        model.setTimeExpire(formatTime(reqDTO.getExpireTime()));
+        model.setProductCode("FAST_INSTANT_TRADE_PAY"); // 销售产品码. 目前 PC 支付场景下仅支持 FAST_INSTANT_TRADE_PAY
         // ② 个性化的参数
         // 参考 https://www.pingxx.com/api/支付渠道 extra 参数说明.html 的 alipay_pc_direct 部分
         model.setQrPayMode(MapUtil.getStr(reqDTO.getChannelExtras(), "qr_pay_mode"));
@@ -71,10 +70,8 @@ public class AlipayPcPayClient extends AbstractAlipayClient {
         }
 
         // 2.2 处理结果
-        System.out.println(response.getBody());
         PayOrderUnifiedRespDTO respDTO = new PayOrderUnifiedRespDTO()
                 .setDisplayMode(displayMode).setDisplayContent(response.getBody());
-        // 响应为表单格式,前端可嵌入响应的页面或关闭当前支付窗口
         return PayCommonResult.build(StrUtil.blankToDefault(response.getCode(),"10000"),
                 response.getMsg(), respDTO, codeMapping);
     }

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java

@@ -31,7 +31,7 @@ public class AlipayQrPayClient extends AbstractAlipayClient {
         model.setOutTradeNo(reqDTO.getMerchantOrderId());
         model.setSubject(reqDTO.getSubject());
         model.setBody(reqDTO.getBody());
-        model.setTotalAmount(calculateAmount(reqDTO.getAmount()).toString()); // 单位:元
+        model.setTotalAmount(formatAmount(reqDTO.getAmount()).toString()); // 单位:元
         // TODO 芋艿:userIp + expireTime
         // 构建 AlipayTradePrecreateRequest
         AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();

+ 26 - 31
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java

@@ -1,10 +1,13 @@
 package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
 
-import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.hutool.http.Method;
 import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
 import com.alipay.api.AlipayApiException;
 import com.alipay.api.domain.AlipayTradeWapPayModel;
 import com.alipay.api.request.AlipayTradeWapPayRequest;
@@ -14,7 +17,8 @@ import lombok.extern.slf4j.Slf4j;
 import java.util.Objects;
 
 /**
- * 支付宝【手机网站】的 PayClient 实现类
+ * 支付宝【手机 网站】的 PayClient 实现类
+ *
  * 文档:https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay
  *
  * @author 芋道源码
@@ -22,31 +26,32 @@ import java.util.Objects;
 @Slf4j
 public class AlipayWapPayClient extends AbstractAlipayClient {
 
-
     public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) {
-        super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config, new AlipayPayCodeMapping());
+        super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config,
+                new AlipayPayCodeMapping());
     }
 
     @Override
-    public PayCommonResult<AlipayTradeWapPayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
-        // 构建 AlipayTradeWapPayModel 请求
+    public PayCommonResult<PayOrderUnifiedRespDTO> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
+        // 1.1 构建 AlipayTradeWapPayModel 请求
         AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
+        // ① 通用的参数
         model.setOutTradeNo(reqDTO.getMerchantOrderId());
         model.setSubject(reqDTO.getSubject());
         model.setBody(reqDTO.getBody());
-        model.setTotalAmount(calculateAmount(reqDTO.getAmount()).toString());
-        model.setProductCode("QUICK_WAP_PAY"); // TODO 芋艿:这里咋整
-        //TODO 芋艿:这里咋整  jason @芋艿 可以去掉吧,
-        // TODO 芋艿 似乎这里不用传sellerId
-        // https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay
-        //model.setSellerId("2088102147948060");
-        model.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(),"yyyy-MM-dd HH:mm:ss"));
-        // TODO 芋艿:userIp
-        // 构建 AlipayTradeWapPayRequest
+        model.setTotalAmount(formatAmount(reqDTO.getAmount()));
+        model.setProductCode("QUICK_WAP_PAY"); // 销售产品码. 目前 Wap 支付场景下仅支持 QUICK_WAP_PAY
+        // ② 个性化的参数【无】
+        // ③ 支付宝 Wap 支付只有一种展示,考虑到前端可能希望二维码扫描后,手机打开
+        String displayMode = ObjectUtil.defaultIfNull(reqDTO.getDisplayMode(),
+                PayDisplayModeEnum.URL.getMode());
+
+        // 1.2 构建 AlipayTradeWapPayRequest 请求
         AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
         request.setBizModel(model);
         request.setNotifyUrl(reqDTO.getNotifyUrl());
-        request.setReturnUrl(reqDTO.getReturnUrl());
+        request.setReturnUrl(reqDTO.getReturnUrl()); // TODO 芋艿,待搞
+        model.setQuitUrl(reqDTO.getReturnUrl()); // TODO 芋艿,待搞
 
         // 执行请求
         AlipayTradeWapPayResponse response;
@@ -57,21 +62,11 @@ public class AlipayWapPayClient extends AbstractAlipayClient {
         }
         System.out.println(response.getBody());
 
-        // TODO 芋艿:sub Code
-        if(response.isSuccess() && Objects.isNull(response.getCode()) && Objects.nonNull(response.getBody())){
-            //成功alipay wap 成功 code 为 null , body 为form 表单
-            return PayCommonResult.build("-9999", "Success", response, codeMapping);
-        }else {
-            return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping);
-        }
+        // 2.2 处理结果
+        PayOrderUnifiedRespDTO respDTO = new PayOrderUnifiedRespDTO()
+                .setDisplayMode(displayMode).setDisplayContent(response.getBody());
+        return PayCommonResult.build(StrUtil.blankToDefault(response.getCode(),"10000"),
+                response.getMsg(), respDTO, codeMapping);
     }
 
-
-
-
-
-
-
-
-
 }

+ 3 - 0
yudao-ui-admin/src/utils/constants.js

@@ -162,6 +162,9 @@ export const PayDisplayModeEnum = {
   },
   FORM: {
     "mode": "form"
+  },
+  QR_CODE: {
+    "mode": "qr_code"
   }
 }
 

+ 32 - 23
yudao-ui-admin/src/views/pay/order/submit.vue

@@ -44,7 +44,7 @@
     <!-- 展示形式:二维码 URL -->
     <el-dialog :title="qrCode.title" :visible.sync="qrCode.visible" width="350px" append-to-body
                :close-on-press-escape="false">
-      <qrcode-vue :value="qrCode.url" size="310" level="H" />
+      <qrcode-vue :value="qrCode.url" size="310" level="L" />
     </el-dialog>
 
     <!-- 展示形式:IFrame -->
@@ -53,7 +53,7 @@
       <iframe :src="iframe.url" width="100%" />
     </el-dialog>
 
-    <!-- 展示形式: -->
+    <!-- 展示形式:Form -->
     <div ref="formRef" v-html="form.value" />
 
   </div>
@@ -162,20 +162,15 @@ export default {
         ...this.buildSubmitParam(channelCode)
       }).then(response => {
         const data = response.data
-        if (data.displayMode === 'iframe') {
+        if (data.displayMode === PayDisplayModeEnum.IFRAME.mode) {
           this.displayIFrame(channelCode, data)
-        } else if (data.displayMode === 'url') {
+        } else if (data.displayMode === PayDisplayModeEnum.URL.mode) {
           this.displayUrl(channelCode, data)
-        } else if (data.displayMode === 'form') {
+        } else if (data.displayMode === PayDisplayModeEnum.FORM.mode) {
           this.displayForm(channelCode, data)
+        } else if (data.displayMode === PayDisplayModeEnum.QR_CODE.mode) {
+          this.displayQrCode(channelCode, data)
         }
-        // 不同的支付,调用不同的策略
-        // if (channelCode === PayChannelEnum.ALIPAY_QR.code) {
-        //   this.submitAfterAlipayQr(invokeResponse)
-        // } else if (channelCode === PayChannelEnum.ALIPAY_PC.code
-        //   || channelCode === PayChannelEnum.ALIPAY_WAP.code) {
-        //   this.submitAfterAlipayPc(invokeResponse)
-        // }
 
         // 打开轮询任务
         this.createQueryInterval()
@@ -183,7 +178,7 @@ export default {
     },
     /** 构建提交支付的额外参数 */
     buildSubmitParam(channelCode) {
-      // 支付宝网页支付时,有多种展示形态
+      // ① 支付宝 PC 支付时,有多种展示形态
       if (channelCode === PayChannelEnum.ALIPAY_PC.code) {
         // 情况【前置模式】:将二维码前置到商户的订单确认页的模式。需要商户在自己的页面中以 iframe 方式请求支付宝页面。具体支持的枚举值有以下几种:
         // 0:订单码-简约前置模式,对应 iframe 宽度不能小于 600px,高度不能小于 300px
@@ -221,16 +216,13 @@ export default {
         //   displayMode: PayDisplayModeEnum.FORM.mode
         // }
       }
-      return {}
-    },
-    /** 提交支付后(支付宝扫码支付) */
-    submitAfterAlipayQr(invokeResponse) {
-      this.qrCode = {
-        title: '请使用支付宝“扫一扫”扫码支付',
-        url: invokeResponse.qrCode,
-        visible: true
+      // ② 支付宝 Wap 支付时,引导手机扫码支付
+      if (channelCode === PayChannelEnum.ALIPAY_WAP.code) {
+        return {
+          displayMode: PayDisplayModeEnum.QR_CODE.mode
+        }
       }
-      this.submitLoading = false
+      return {}
     },
     /** 提交支付后,IFrame 内置 URL 的展示形式 */
     displayIFrame(channelCode, data) {
@@ -262,9 +254,26 @@ export default {
         }, 1000);
       });
     },
+    /** 提交支付后(支付宝扫码支付) */
+    displayQrCode(channelCode, data) {
+      let title = '请使用手机浏览器“扫一扫”';
+      if (channelCode === PayChannelEnum.ALIPAY_WAP.code) {
+        // 考虑到 WAP 测试,所以引导手机浏览器搞
+      } else if (channelCode.indexOf('alipay_') === 0) {
+        title = '请使用支付宝“扫一扫”扫码支付';
+      } else if (channelCode.indexOf('wx_') === 0) {
+        title = '请使用微信“扫一扫”扫码支付';
+      }
+      this.qrCode = {
+        title: title,
+        url: data.displayContent,
+        visible: true
+      }
+      this.submitLoading = false
+    },
     /** 轮询查询任务 */
     createQueryInterval() {
-      if (!this.interval) {
+      if (this.interval) {
         return
       }
       this.interval = setInterval(() => {