pay: 接入支付宝 Wap 支付

This commit is contained in:
YunaiV 2023-02-18 22:40:56 +08:00
parent aff9886a4b
commit e6f414b918
7 changed files with 89 additions and 72 deletions

View File

@ -1,5 +1,10 @@
package cn.iocoder.yudao.framework.pay.core.client.impl; 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.AbstractPayCodeMapping;
import cn.iocoder.yudao.framework.pay.core.client.PayClient; import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
@ -11,6 +16,9 @@ import lombok.extern.slf4j.Slf4j;
import javax.validation.Validation; 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; 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(); this.init();
} }
// TODO 芋艿后续抽取到工具类里
protected Double calculateAmount(Integer amount) {
return amount / 100.0;
}
@Override @Override
public Long getId() { public Long getId() {
return channelId; return channelId;
@ -113,4 +116,14 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
protected abstract PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable; 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);
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -44,7 +44,7 @@
<!-- 展示形式二维码 URL --> <!-- 展示形式二维码 URL -->
<el-dialog :title="qrCode.title" :visible.sync="qrCode.visible" width="350px" append-to-body <el-dialog :title="qrCode.title" :visible.sync="qrCode.visible" width="350px" append-to-body
:close-on-press-escape="false"> :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> </el-dialog>
<!-- 展示形式IFrame --> <!-- 展示形式IFrame -->
@ -53,7 +53,7 @@
<iframe :src="iframe.url" width="100%" /> <iframe :src="iframe.url" width="100%" />
</el-dialog> </el-dialog>
<!-- 展示形式 --> <!-- 展示形式Form -->
<div ref="formRef" v-html="form.value" /> <div ref="formRef" v-html="form.value" />
</div> </div>
@ -162,20 +162,15 @@ export default {
...this.buildSubmitParam(channelCode) ...this.buildSubmitParam(channelCode)
}).then(response => { }).then(response => {
const data = response.data const data = response.data
if (data.displayMode === 'iframe') { if (data.displayMode === PayDisplayModeEnum.IFRAME.mode) {
this.displayIFrame(channelCode, data) this.displayIFrame(channelCode, data)
} else if (data.displayMode === 'url') { } else if (data.displayMode === PayDisplayModeEnum.URL.mode) {
this.displayUrl(channelCode, data) this.displayUrl(channelCode, data)
} else if (data.displayMode === 'form') { } else if (data.displayMode === PayDisplayModeEnum.FORM.mode) {
this.displayForm(channelCode, data) 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() this.createQueryInterval()
@ -183,7 +178,7 @@ export default {
}, },
/** 构建提交支付的额外参数 */ /** 构建提交支付的额外参数 */
buildSubmitParam(channelCode) { buildSubmitParam(channelCode) {
// // PC
if (channelCode === PayChannelEnum.ALIPAY_PC.code) { if (channelCode === PayChannelEnum.ALIPAY_PC.code) {
// iframe // iframe
// 0- iframe 600px 300px // 0- iframe 600px 300px
@ -221,16 +216,13 @@ export default {
// displayMode: PayDisplayModeEnum.FORM.mode // displayMode: PayDisplayModeEnum.FORM.mode
// } // }
} }
return {} // Wap
}, if (channelCode === PayChannelEnum.ALIPAY_WAP.code) {
/** 提交支付后(支付宝扫码支付) */ return {
submitAfterAlipayQr(invokeResponse) { displayMode: PayDisplayModeEnum.QR_CODE.mode
this.qrCode = {
title: '请使用支付宝“扫一扫”扫码支付',
url: invokeResponse.qrCode,
visible: true
} }
this.submitLoading = false }
return {}
}, },
/** 提交支付后IFrame 内置 URL 的展示形式 */ /** 提交支付后IFrame 内置 URL 的展示形式 */
displayIFrame(channelCode, data) { displayIFrame(channelCode, data) {
@ -262,9 +254,26 @@ export default {
}, 1000); }, 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() { createQueryInterval() {
if (!this.interval) { if (this.interval) {
return return
} }
this.interval = setInterval(() => { this.interval = setInterval(() => {