Commit 85856bd9 authored by wp's avatar wp

Merge branch 'dev' of https://gitlab.33.cn/yimu/mall-server into dev

parents 5d7bb839 db3f3bf9
......@@ -3,13 +3,13 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>fzm-mall</artifactId>
<artifactId>fzm-yimu</artifactId>
<groupId>com.fzm.mall</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mall-customer-service</artifactId>
<artifactId>yimu-customer-service</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
......
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>fzm-mall</artifactId>
<groupId>com.fzm.mall</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mall-midea-pay</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>16.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpmime -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.3.4</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package com.fzm.mall.midea.pay.component;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import com.fzm.mall.midea.pay.properties.MideaPayProperties;
import com.fzm.mall.midea.pay.enums.MideaPayEnum;
import com.fzm.mall.midea.pay.model.BaseResponse;
import com.fzm.mall.midea.pay.model.HttpResponse;
import com.fzm.mall.midea.pay.params.BaseParam;
import com.fzm.mall.midea.pay.utils.HttpClientUtils;
import com.fzm.mall.midea.pay.utils.SignUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
/**
* @author tangtuo
* @date 2021/5/24 16:44
*/
@Slf4j
@Component
public class MideaPayComponent {
private static final String SUCCESS_CODE = "1001";
@Resource
private MideaPayProperties mideaPayProperties;
@Resource
private SignUtil signUtil;
/**
* 发送美的支付请求
*
* @param param 请求参数具体类型根据service判断,具体参考枚举类MideaPayEnum
* @return 响应参数类型参考MideaPayEnum
*/
public BaseResponse sendRequest(BaseParam param) {
String service = param.getService();
MideaPayEnum mideaPayEnum = MideaPayEnum.getByService(service);
if (!mideaPayEnum.getParam().isInstance(param)) {
throw new RuntimeException("请求参数类型和接口名不对应");
}
Map<String, String> map = formatParam(param);
log.info("请求接口是:{},参数是:{}", service, map);
HttpResponse httpResponse = HttpClientUtils.doPost(mideaPayProperties.getUrl(), map);
if (StringUtils.isBlank(httpResponse.getEntity()) || HttpStatus.HTTP_OK != httpResponse.getHttpCode()) {
throw new RuntimeException(service + "接口调用失败");
}
log.info("{}接口返回参数: {}", service, httpResponse);
BaseResponse response = JSONUtil.toBean(httpResponse.getEntity(), mideaPayEnum.getResponse());
if (!SUCCESS_CODE.equals(response.getResult_code())) {
throw new RuntimeException(service + " 接口响应失败 , " + response.getResult_info());
}
return response;
}
private Map<String, String> formatParam(BaseParam param) {
Map<String, String> map = param.beanToMap(param);
// 获取签名
String sign = signUtil.getSign(SignUtil.getStringToSignOfStr(map));
map.put("sign", sign);
// 校验签名
signUtil.checkSign(map);
return map;
}
}
package com.fzm.mall.midea.pay.constant;
/**
* @author tangtuo
* @date 2021/6/4 10:20
* <p>
* 美的支付接口名称常量类
* </p>
*/
public class ServiceConstant {
/**
* 微信支付单笔交易下单
*/
public static final String TRADE_PAY_WECHATPAY ="trade_pay_wechatpay";
/**
*微信支付合单交易下单
*/
public static final String BATCH_TRADE_PAY_WECHATPAY ="batch_trade_pay_wechatpay";
/**
* 支付宝单笔交易下单
*/
public static final String TRADE_PAY_ALIPAY ="trade_pay_alipay";
/**
* 支付宝合单交易下单
*/
public static final String BATCH_TRADE_PAY_ALIPAY ="batch_trade_pay_alipay";
/**
* 单笔交易查询
*/
public static final String TRADE_QUERY ="trade_query";
/**
* 合单交易查询
*/
public static final String BATCH_TRADE_QUERY ="batch_trade_query";
/**
* 退款下单
*/
public static final String TRADE_REFUND ="trade_refund";
/**
* 退款交易查询
*/
public static final String TRADE_REFUND_QUERY ="trade_refund_query";
/**
* 确认收货下单
*/
public static final String TRADE_PAY_RECEIVE ="trade_pay_receive";
}
package com.fzm.mall.midea.pay.enums;
import lombok.Getter;
/**
* @author tangtuo
* @date 2021/5/26 13:52
*/
@Getter
public enum AlipayType {
SCAN_CODE("SCAN_CODE","扫码"),
OFFICIAL_ACCT("OFFICIAL_ACCT","公众号/服务窗"),
MINI_PROGRAM("MINI_PROGRAM","小程序"),
APP("SCAN_CODE","app支付"),
;
private String barCode;
private String desc;
AlipayType(String barCode, String desc) {
this.barCode = barCode;
this.desc = desc;
}
}
package com.fzm.mall.midea.pay.enums;
import com.fzm.mall.midea.pay.constant.ServiceConstant;
import com.fzm.mall.midea.pay.model.*;
import com.fzm.mall.midea.pay.params.*;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
/**
* @author tangtuo
* @date 2021/5/27 14:01
*/
@Getter
public enum MideaPayEnum {
TRADE_PAY_WECHATPAY(ServiceConstant.TRADE_PAY_WECHATPAY, WechatPayTradeParam.class, WechatPayTradeResponse.class),
BATCH_TRADE_PAY_WECHATPAY(ServiceConstant.BATCH_TRADE_PAY_WECHATPAY, WechatPayBatchTradeParam.class, WechatPayTradeResponse.class),
TRADE_PAY_ALIPAY(ServiceConstant.TRADE_PAY_ALIPAY, AliPayTradeParam.class, AlipayTradeResponse.class),
BATCH_TRADE_PAY_ALIPAY(ServiceConstant.BATCH_TRADE_PAY_ALIPAY, AlipayBatchTradeParam.class, AlipayTradeResponse.class),
TRADE_QUERY(ServiceConstant.TRADE_QUERY, TradeQueryParam.class, TradeQueryResponse.class),
BATCH_TRADE_QUERY(ServiceConstant.BATCH_TRADE_QUERY, TradeQueryParam.class, BatchTradeQueryResponse.class),
TRADE_REFUND(ServiceConstant.TRADE_REFUND, RefundParam.class, RefundResponse.class),
REFUND_QUERY(ServiceConstant.TRADE_REFUND_QUERY, RefundQueryParam.class, RefundQueryResponse.class),
TRADE_PAY_RECEIVE(ServiceConstant.TRADE_PAY_RECEIVE, TradePayReceiveParam.class, TradePayReceiveResponse.class),
;
@ApiModelProperty(value = "接口名称")
private String service;
@ApiModelProperty(value = "请求参数类型")
private Class<? extends BaseParam> param;
@ApiModelProperty(value = "响应参数类型")
private Class<? extends BaseResponse> response;
MideaPayEnum(String service, Class<? extends BaseParam> param, Class<? extends BaseResponse> response) {
this.service = service;
this.param = param;
this.response = response;
}
public static MideaPayEnum getByService(String service) {
for (MideaPayEnum payEnum : MideaPayEnum.values()) {
if (service.equals(payEnum.getService())) {
return payEnum;
}
}
throw new RuntimeException("unknown service");
}
}
package com.fzm.mall.midea.pay.enums;
import lombok.Getter;
/**
* @author tangtuo
* @date 2021/5/26 13:49
*/
@Getter
public enum WechatPayType {
SCAN_CODE("SCAN_CODE","扫码"),
OFFICIAL_ACCT("OFFICIAL_ACCT","公众号/服务窗"),
MINI_PROGRAM("MINI_PROGRAM","小程序"),
APP("SCAN_CODE","app支付"),
H5("SCAN_CODE","H5支付"),
;
private String barCode;
private String desc;
WechatPayType(String barCode, String desc) {
this.barCode = barCode;
this.desc = desc;
}
}
package com.fzm.mall.midea.pay.model;
import lombok.Data;
/**
* @author tangtuo
* @date 2021/5/26 10:43
*/
@Data
public class AlipayTradeResponse extends BaseResponse{
/**
*
* 扫码支付:返回二维码图片地址
*
* 服务窗支付:此参数返回json数据,内容为tokenNo参数值
*
* APP支付:返回支付宝相关信息
*/
private String pay_info;
}
package com.fzm.mall.midea.pay.model;
import lombok.Data;
/**
* @author tangtuo
* @date 2021/5/26 14:25
*/
@Data
public abstract class BaseResponse {
private String partner;
private String service;
private String input_charset;
private String result_info;
private String sign;
private String language;
private String result_code;
private String version;
private String sign_type;
}
package com.fzm.mall.midea.pay.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author tangtuo
* @date 2021/5/27 15:19
*/
@Data
@ApiModel(value = "合单交易查询响应参数")
public class BatchTradeQueryResponse extends BaseResponse {
@ApiModelProperty(value = "美的付合作商户网站唯一订单号(确保在合作伙伴系统中唯一)")
private String out_trade_no;
@ApiModelProperty("该交易在美的付中的交易流水号")
private String trade_no;
@ApiModelProperty("美的付系统接收订单的时间")
private String trade_accept_time;
@ApiModelProperty("支付金额的货币类型 CNY:人民币")
private String currency_type;
@ApiModelProperty("总订单金额(单位分)")
private long total_order_amount;
@ApiModelProperty("总支付金额(单位分)")
private long total_amount;
@ApiModelProperty("订单总数(最大50笔)")
private long total_count;
@ApiModelProperty("交易成功数(最大50笔)")
private String success_amount;
/**
* 交易完成状态码
* NOT_EXIST:订单不存在
* WAIT_PAY:订单已接收
* PAYING:支付中
* COMPLETE:支付完成
*/
@ApiModelProperty("交易完成状态")
private String complete_status;
@ApiModelProperty("订单状态的描述信息")
private String complete_status_info;
@ApiModelProperty("该笔交易的支付完成时间,格式为yyyyMMddHHmmss")
private String complete_time;
@ApiModelProperty("商户保留域,保留域信息用于扩展")
private String attach;
@ApiModelProperty("支付方式:三级业务类型,见附录支付方式")
private String business_types;
@ApiModelProperty("子订单的信息,格式为json字符串")
private String sub_orders;
@Data
public static class SubOrder{
@ApiModelProperty("签约商户号,由美的支付生成,由10位数字组成")
private String partner;
@ApiModelProperty("子B2C订单商户订单号")
private String sub_out_trade_no;
@ApiModelProperty("该交易在美的付中的交易流水号")
private String sub_trade_no;
@ApiModelProperty("营销商户号")
private String market_acc_partner;
@ApiModelProperty("营销金额")
private String market_amount;
@ApiModelProperty("子B2C订单金额,单位: 分")
private String pay_amount;
@ApiModelProperty("美的付系统支付订单的时间")
private String pay_time;
/**
* 订单的状态码
* NOT_EXIST:订单不存在
* WAIT_PAY:未支付
* PAYING:支付中
* SUCCESS:成功
* FAIL:失败
*/
@ApiModelProperty("订单状态")
private String trade_status;
@ApiModelProperty("订单状态的描述信息")
private String trade_status_info;
/**
* RECEIVED:已确认收货
* UNRECEIVED:未确认收货
*/
@ApiModelProperty("确认收货状态")
private String product_recv_status;
@ApiModelProperty("确认收货时间,格式为yyyyMMddHHmmss")
private String product_recv_time;
@ApiModelProperty("商户自定义信息,原值返回")
private String attach;
}
}
package com.fzm.mall.midea.pay.model;
import java.io.Serializable;
/**
* @author harvey
* @date 2020/9/1
* @email husy7@midea.com
*/
public class CheckSignResponse implements Serializable{
private static final long serialVersionUID = -1486935956906268260L;
private String code;
private String partner;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getPartner() {
return partner;
}
public void setPartner(String partner) {
this.partner = partner;
}
}
package com.fzm.mall.midea.pay.model;
import lombok.Data;
import org.apache.http.Header;
@Data
public class HttpResponse {
private int httpCode;
private String entity;
private Header[] contentType;
public int getHttpCode() {
return httpCode;
}
public void setHttpCode(int httpCode) {
this.httpCode = httpCode;
}
public String getEntity() {
return entity;
}
public void setEntity(String entity) {
this.entity = entity;
}
public Header[] getContentType() {
return contentType;
}
public void setContentType(Header[] contentType) {
this.contentType = contentType;
}
}
package com.fzm.mall.midea.pay.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author tangtuo
* @date 2021/5/27 16:14
*/
@Data
@ApiModel("退款交易查询响应参数")
public class RefundQueryResponse extends BaseResponse {
@ApiModelProperty("商户网站唯一的退款订单号(确保在合作伙伴系统中唯一)")
private String out_refund_no;
@ApiModelProperty("平台创建退款交易的订单号")
private String refund_no;
@ApiModelProperty("需退款的商户网站唯一订单号(原交易订单的订单号)")
private String out_trade_no;
@ApiModelProperty("美的支付交易订单创建时间,格式为yyyyMMddHHmmss")
private String refund_accept_time;
@ApiModelProperty("支付金额的货币类型。CNY:人民币")
private String currency_type;
@ApiModelProperty("退款的总金额,单位分")
private String refund_total_amount;
@ApiModelProperty("退款的金额,单位: 分")
private String refund_amount;
@ApiModelProperty("退款的营销金额,单位分")
private String refund_market_amount;
@ApiModelProperty("系统支付订单的时间")
private String pay_time;
/**
* NOT_EXIST:订单不存在
* WAIT_PAY:未退款
* PAYING:退款中
* SUCCESS:退款成功
* FAIL:退款失败
*/
@ApiModelProperty("订单状态")
private String refund_status;
@ApiModelProperty("订单状态的描述信息")
private String refund_status_info;
@ApiModelProperty("到账状态")
private String arrival_status;
@ApiModelProperty("到账时间 格式为yyyyMMddHHmmss")
private String arrival_time;
}
package com.fzm.mall.midea.pay.model;
import lombok.Data;
@Data
public class RefundResponse extends BaseResponse {
/**
* 商户网站唯一的退款订单号(确保在合作伙伴系统中唯一)
*/
private String out_refund_no;
/**
* 需退款的商户网站唯一订单号(原交易订单的订单号)
*/
private String out_trade_no;
/**
* 平台创建退款交易的订单号
*/
private String refund_no;
/**
* 美的支付交易订单创建时间,格式为yyyyMMddHHmmss
*/
private String refund_accept_time;
/**
* 支付金额的货币类型: CNY 人民币
*/
private String currency_type;
/**
* 退款的金额,单位分
*/
private String refund_amount;
/**
* NOT_EXIST:订单不存在
* WAIT_PAY:未退款
* PAYING:退款中
* SUCCESS:退款成功
* FAIL:退款失败
*/
private String refund_status;
/**
* 订单状态的描述信息
*/
private String refund_status_info;
/**
* 到账状态
* WAIT_PAY:未支付
* PAYMENT_PROCESSING: 到账中
* PAYMENT_RECEIVED: 已到账
*/
private String arrival_status;
/**
* 商户自定义的信息
*/
private String attach;
}
\ No newline at end of file
package com.fzm.mall.midea.pay.model;
import java.io.Serializable;
/**
* @author harvey
* @date 2020/9/1
* @email husy7@midea.com
*/
public class SignResponse implements Serializable{
private static final long serialVersionUID = 5895445282321871872L;
private String code;
private String sign;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
}
package com.fzm.mall.midea.pay.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author tangtuo
* @date 2021/5/27 16:28
*/
@Data
@ApiModel(value = "确认收货接口响应参数")
public class TradePayReceiveResponse extends BaseResponse {
@ApiModelProperty(value = "商户B2C交易订单号")
private String out_trade_no;
/**
* 确认收货状态:
* RECEIVED:已确认收货
* UNRECEIVED:未确认收货
*/
@ApiModelProperty(value = "确认收货状态")
private String product_recv_status;
@ApiModelProperty(value = "确认收货时间,格式为yyyyMMddHHmmss")
private String product_recv_time;
}
package com.fzm.mall.midea.pay.model;
import lombok.Data;
import java.util.Date;
@Data
public class TradeQueryResponse extends BaseResponse{
private String trade_accept_time;
private long pay_amount;
private String business_types;
private String profit_sharing;
private String trade_status_info;
private long market_amount;
private String out_trade_no;
private String currency_type;
private String trade_status;
private long order_amount;
private String trade_no;
}
\ No newline at end of file
package com.fzm.mall.midea.pay.model;
import lombok.Data;
/**
* @author tangtuo
* @date 2021/5/26 9:58
*/
@Data
public class WechatPayTradeResponse extends BaseResponse{
/**
* 扫码支付:返回二维码图片地址
* <p>
* 微信app支付:返回的json数据,包含支持的服务和token_id
* <p>
* 公众号/小程序支付:返回json传,使用微信原生方法调用
*/
private String weixin_url;
}
package com.fzm.mall.midea.pay.params;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author tangtuo
* @date 2021/5/25 13:43
*/
@Data
@Accessors(chain = true)
public class AliPayTradeParam extends BaseParam {
/**
* 担保交易标志:
* TRUE 担保交易
* FALSE 即时到账交易(默认)
* 不可为空
*/
private String is_guarantee;
/**
* 美的支付合作商户网站唯一订单号(确保在合作伙伴系统中唯一)
* 不可为空
*/
private String out_trade_no;
/**
* 商户发起交易,提交订单的时间,格式yyyyMMddHHmmss
* 不可为空
*/
private String out_trade_time;
/**
* 买家用户类型(自动登录时必填)
* B:商户
* C:用户(默认)
*/
private String payer_type;
/**
* 付款方登录名
*/
private String payer_login_name;
/**
* 收款方登录名
*/
private String payee_login_name;
/**
* 支付类型
* SCAN_CODE:扫码
* OFFICIAL_ACCT:公众号/服务窗
* MINI_PROGRAM:小程序
* APP:app
* 不可为空
*/
private String bar_code;
/**
* 公众号id
* 注:服务窗支付时,必填
*/
private String app_id;
/**
* 用户id
* 服务窗\小程序支付时,必填
*/
private String open_id;
/**
* 货币类型:
* CNY:人民币
*/
private String currency_type;
/**
* 订单总金额等于支付金额加营销金额,单位分
* (3.4.0不可空)
*/
private String order_amount;
/**
* 交易的支付金额,单位分
* 不可为空
*/
private String pay_amount;
/**
* 营销金额,单位分
*/
private String market_amount;
/**
* DC:借记卡
* DOC:借记卡和信用卡(双卡)
*/
private String limit_acct_type;
/**
* 交易超时时间
* <p>
* 单位:分,时间范围:1-30,不传默认为30
* 接口版本号大于等于3.3.0,表示订单关单时间,小于3.3.0版本无关单功能
*/
private String pay_expire_time;
/**
* 产品标识:
* TRUE:虚拟交易
* FALSE:实物交易
* 不可为空
*/
private String is_virtual_product;
/**
* 商品名称
* 不可为空
*/
private String product_name;
/**
* 商品的描述
* 不可为空
*/
private String product_info;
/**
* 商品数量
*/
private String product_count;
/**
* 商品单价,单位分
*/
private String product_price;
/**
* 交易意图
*/
private String trade_purpose;
/**
* 商户自定义的信息,回调通知的时候美的支付平台会原值返回给商户
*/
private String attach;
/**
* 风控参数
* 不可为空
*/
private String risk_params;
/**
* 分润参数
*/
private String profit_params;
/**
* 多级分账标志
*/
private String profit_sharing;
/**
* 分期仅支持传入3、6、12,其他期数暂不支持
* (小程序支付才支持花呗,需要花呗支付时,才传此参数)
*/
private String hb_fq_num;
/**
* 代表卖家承担收费比例,商家承担手续费传入100,用户承担手续费传入0,仅支持传入0
* (小程序支付才支持花呗,需要花呗支付时,才传此参数)
*/
private String hb_fq_seller_percent;
}
package com.fzm.mall.midea.pay.params;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotBlank;
/**
* @author tangtuo
* @date 2021/5/24 13:51
*/
@Data
@Accessors(chain = true)
public class AlipayBatchTradeParam extends BaseParam {
@NotBlank(message = "担保交易标志不能为空")
@ApiModelProperty("担保交易标志:TRUE-担保交易 FALSE-即时到账交易(默认)")
private String is_guarantee;
@NotBlank(message = "订单号不能为空")
@ApiModelProperty("美的支付合作商户网站唯一订单号(确保在合作伙伴系统中唯一)")
private String out_trade_no;
@NotBlank(message = "订单提交时间不能为空")
@ApiModelProperty("商户发起交易,提交订单的时间,格式yyyyMMddHHmmss")
private String out_trade_time;
/**
* SCAN_CODE:扫码
* OFFICIAL_ACCT:公众号/服务窗
* MINI_PROGRAM:小程序
* APP:app
* H5:H5支付
*/
@NotBlank(message = "支付方式不能为空")
@ApiModelProperty(value = "支付方式")
private String bar_code;
@ApiModelProperty("公众号id 注:服务窗支付时,必填")
private String app_id;
@ApiModelProperty("用户id 服务窗/小程序支付时,必填")
private String open_id;
@ApiModelProperty("货币类型:CNY-人民币")
private String currency_type;
@ApiModelProperty(value = "订单总金额等于支付金额加营销金额,单位分 (3.4.0不可空)")
private String total_order_amount;
@NotBlank(message = "交易支付金额不能为空")
@ApiModelProperty("交易的支付金额,单位分")
private String total_amount;
@NotBlank(message = "订单总数不能为空")
@ApiModelProperty(value = "订单总数(最大50笔)")
private String total_count;
/**
* DC:借记卡
* DOC:借记卡和信用卡(双卡)
*/
@ApiModelProperty("支付卡限定")
private String limit_acct_type;
/**
* 单位:分,时间范围:1-30,不传默认为30
* 接口版本号大于等于3.3.0,表示订单关单时间,小于3.3.0版本无关单功能
*/
@ApiModelProperty("交易超时时间")
private String pay_expire_time;
@NotBlank(message = "产品标识不能为空")
@ApiModelProperty("产品标识:TRUE-虚拟交易 FALSE-实物交易")
private String is_virtual_product;
@NotBlank(message = "商品名称不可为空")
@ApiModelProperty("商品名称")
private String product_name;
@NotBlank(message = "商品的描述不可为空")
@ApiModelProperty("商品的描述")
private String product_info;
@ApiModelProperty("商户自定义的信息,回调通知的时候美的支付平台会原值返回给商户")
private String attach;
@NotBlank(message = "风控参数不可为空")
@ApiModelProperty("风控参数")
private String risk_params;
@ApiModelProperty("花呗分期数分期仅支持传入3、6、12,其他期数暂不支持(小程序支付才支持花呗,需要花呗支付时,才传此参数)")
private String hb_fq_num;
@ApiModelProperty("卖家承担收费比例")
private String hb_fq_seller_percent;
@NotBlank(message = "子单列表不可为空")
@ApiModelProperty("子单列表,格式为json字符串")
private String sub_orders;
@Data
public static class SubOrder {
@ApiModelProperty("签约商户号,由美的支付生成,由10位数字组成")
@NotBlank(message = "签约商户号不可为空")
private String partner;
@ApiModelProperty("营销金额出款商户号")
private String market_acc_partner;
@ApiModelProperty("子B2C订单商户订单号")
@NotBlank(message = "子B2C订单商户订单号不能为空")
private String sub_out_trade_no;
@ApiModelProperty("子B2C订单金额,单位: 分")
@NotBlank(message = "子B2C订单金额不可为空")
private String pay_amount;
@ApiModelProperty("子单营销金额,单位分")
private String market_amount;
@ApiModelProperty("商品名称")
@NotBlank(message = "商品名称不可为空")
private String sub_product_name;
@ApiModelProperty("商品描述信息")
@NotBlank(message = "商品描述信息不可为空")
private String sub_product_info;
@ApiModelProperty("商品数量")
private String sub_product_count;
@ApiModelProperty("商品单价,单位:分")
private String sub_product_price;
@ApiModelProperty("交易意图")
private String sub_trade_purpose;
@ApiModelProperty("商户自定义信息")
private String sub_attach;
@ApiModelProperty("分润参数")
private String profit_params;
@ApiModelProperty("多级分账标志")
private String profit_sharing;
}
}
package com.fzm.mall.midea.pay.params;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Map;
/**
* @author tangtuo
* @date 2021/5/24 15:50
*/
@Data
@Accessors(chain = true)
public abstract class BaseParam {
/**
* 接口名称
* 不可为空
*/
private String service;
/**
* 接口版本号 目前取值3.4.
* 不可为空
*/
private String version = "3.4.0";
/**
* 请求唯一序列号
* 不可为空
*/
private String req_seq_no;
/**
* 签约商户号,由美的支付生成,由10位数字组成
* 不可为空
*/
private String partner;
/**
* 营销金额出款商户号
*/
private String market_acc_partner;
/**
* 商户网站使用的编码格式,取值UTF-8
* 不可为空
*/
private String input_charset;
/**
* 语言类型:
* <p>
* ZH-CN:简体中文
*/
private String language;
/**
* 终端类型:
* ITG:后台
* 不可为空
*/
private String terminal_type;
/**
* 签名的方式:
* MD5_RSA_TW:天威证书签名
* 不可为空
*/
private String sign_type;
/**
* 签名
* 不可为空
*/
private String sign;
/**
* 支付成功后,美的支付通过此地址通知业务方订单状态,支持多个,英文分号分隔’;’
* 不可为空
*/
private String notify_url;
/**
* bean转map
* @param baseParam
* @return
*/
public Map<String, String> beanToMap(BaseParam baseParam) {
return JSONUtil.toBean(JSONUtil.toJsonStr(baseParam), new TypeReference<Map<String, String>>() {
}, true);
}
}
package com.fzm.mall.midea.pay.params;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* @author tangtuo
* @date 2021/5/24 13:51
*/
@Data
@Accessors(chain = true)
public class BatchTradeNotifyParam extends BaseParam {
/**
* 美的支付合作商户网站唯一订单号(确保在合作伙伴系统中唯一)
* 不可为空
*/
private String out_trade_no;
/**
* 美的支付交易订单号
*/
private String trade_no;
/**
* 美的支付交易订单创建时间,格式yyyyMMddHHmmss
* 不可为空
*/
private String trade_accept_time;
/**
* 美的支付通知商户时间,格式为yyyyMMddHHmmss
*/
private String notify_time;
/**
* 货币类型:
* CNY:人民币
*/
private String currency_type;
/**
* 总金额等于总支付金额加总营销金额
*/
private String total_order_amount;
/**
* 支付总金额(单位分)
*/
private String total_amount;
/**
* 订单总数
*/
private String total_count;
/**
* 交易成功的金额,单位:分
*/
private String success_amount;
/**
* 交易成功的订单数(<=total_count)
*/
private String success_count;
/**
* NOT_EXIST:订单不存在
* WAIT_PAY:订单已接收
* PAYING:支付中
* COMPLETE:支付完成
*/
private String complete_status;
/**
* 支付结果描述
*/
private String complete_status_info;
/**
* 该笔交易的支付完成时间,格式为yyyyMMddHHmmss
*/
private String complete_time;
/**
* 商户自定义信息,原值返回给商户
*/
private String attach;
/**
* 支付方式:三级业务类型
* 注:B2C下单版本>=3.1.0才返回此参数
*/
private String business_types;
/**
* 子单列表,格式为json字符串
*/
private String sub_orders;
/**
* 由sub_orders转换而来
*/
private List<SubOrder> subOrderList;
@Data
public static class SubOrder {
/**
* 签约的美的支付唯一商户号,10位纯数字组成
*/
private String partner;
/**
* 子B2C订单商户订单号
*/
private String sub_out_trade_no;
/**
* 美的支付生成的交易单号
*/
private String sub_trade_no;
/**
* 营销商户号
*/
private String market_acc_partner;
/**
* 营销金额,单位:分
*/
private String market_amount;
/**
* 子B2C订单金额,单位: 分
*/
private String pay_amount;
/**
* 该笔交易的支付时间,格式为yyyyMMddHHmmss
*/
private String pay_time;
/**
* NOT_EXIST:订单不存在
* WAIT_PAY:未支付
* PAYING:支付中
* SUCCESS:成功
* FAIL:失败
*/
private String trade_status;
/**
* 支付结果描述
*/
private String trade_status_info;
/**
* 商户自定义的信息,原值返回给商户
*/
private String sub_attach;
}
}
package com.fzm.mall.midea.pay.params;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author tangtuo
* @date 2021/5/24 13:51
*/
@Data
@Accessors(chain = true)
public class RefundNotifyParam extends BaseParam {
/**
* 商户网站唯一的退款订单号
*/
private String out_refund_no;
/**
* 平台创建退款交易的订单号
*/
private String refund_no;
/**
* 需退款的商户网站唯一订单号(原交易订单的订单号)
*/
private String out_trade_no;
/**
* 退款单创建时间,格式为yyyyMMddHHmmss
*/
private String refund_accept_time;
/**
* 美的支付通知商户时间,格式为yyyyMMddHHmmss
*/
private String notify_time;
/**
* 货币类型:
* CNY:人民币
*/
private String currency_type;
/**
* 退款总金额,单位:分
*/
private String refund_total_amount;
/**
* 退款营销金额,单位:分
*/
private String refund_market_amount;
/**
* 退款的金额,单位:分
*/
private String refund_amount;
/**
* 退款交易执行的时间,格式为yyyyMMddHHmmss
*/
private String pay_time;
/**
* NOT_EXIST:订单不存在
* WAIT_PAY:未退款
* PAYING:退款中
* SUCCESS:退款成功
* FAIL:退款失败
*/
private String refund_status;
/**
* 退款结果描述
*/
private String refund_status_info;
/**
* 到账状态:
* WAIT_PAY:未支付
* PAYMENT_PROCESSING: 到账中
* PAYMENT_RECEIVED: 已到账
*/
private String arrival_status;
/**
* 到账时间,格式为 yyyyMMddHHmmss
*/
private String arrival_time;
/**
* 商户请求时传入的attach值
*/
private String attach;
}
package com.fzm.mall.midea.pay.params;
import lombok.Data;
/**
* @author tangtuo
* @date 2021/5/25 16:41
*/
@Data
public class RefundParam extends BaseParam {
/**
* 商户网站唯一的退款订单号(确保在合作伙伴系统中唯一)
* 不可为空
*/
private String out_refund_no;
/**
* 商户发起交易,提交订单的时间,格式yyyyMMddHHmmss
* 不可为空
*/
private String out_refund_time;
/**
* 需退款的商户网站唯一订单号(原交易订单的订单号
* 不可为空
*/
private String out_trade_no;
/**
* 支付金额的货币类型: CNY 人民币
*/
private String currency_type;
/**
* 退款总金额,单位分
*/
private String refund_total_amount;
/**
* 支付金额退款,单位分
* 不可为空
*/
private String refund_amount;
/**
* 营销金额退款,单位分
*/
private String refund_market_amount;
/**
* 商户自定义的信息,回调通知的时候平台会原值返回给商户
*/
private String attach;
/**
* 风控参数
* 不可为空
*/
private String risk_params;
}
package com.fzm.mall.midea.pay.params;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author tangtuo
* @date 2021/5/27 16:06
*/
@Data
@ApiModel(value = "退款交易查询请求参数")
public class RefundQueryParam extends BaseParam{
@NotBlank(message = "退款订单号不能为空")
@ApiModelProperty("商户网站唯一的退款订单号(确保在合作伙伴系统中唯一)")
private String out_refund_no;
}
package com.fzm.mall.midea.pay.params;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Map;
/**
* @author tangtuo
* @date 2021/5/24 13:51
*/
@Data
@Accessors(chain = true)
public class TradeNotifyParam extends BaseParam{
/**
* 担保交易标志:
* TRUE 担保交易
* FALSE 即时到账交易(默认)
* 不可为空
*/
private String is_guarantee;
/**
* 美的支付合作商户网站唯一订单号(确保在合作伙伴系统中唯一)
* 不可为空
*/
private String out_trade_no;
/**
* 美的支付交易订单号
*/
private String trade_no;
/**
* 美的支付交易订单创建时间,格式yyyyMMddHHmmss
* 不可为空
*/
private String trade_accept_time;
/**
* 美的支付通知商户时间,格式为yyyyMMddHHmmss
*/
private String notify_time;
/**
* 货币类型:
* CNY:人民币
*/
private String currency_type;
/**
* 营销商户号
*/
private String market_partner;
/**
* 订单总金额等于支付金额加营销金额,单位分
* (3.4.0不可空)
*/
private String order_amount;
/**
* 交易的支付金额,单位分
* 不可为空
*/
private String pay_amount;
/**
* 营销金额,单位分
*/
private String market_amount;
/**
* 该笔交易的支付时间,格式为yyyyMMddHHmmss
*/
private String pay_time;
/**
* NOT_EXIST:订单不存在
* WAIT_PAY:未支付
* PAYING:支付中
* SUCCESS:成功
* FAIL:失败
*/
private String trade_status;
/**
*
* 支付结果描述
*/
private String trade_status_info;
/**
* 商户请求时传入的attach值
*/
private String attach;
/**
* 支付方式:三级业务类型
* 注:B2C下单版本>=3.1.0才返回此参数
*/
private String business_types;
}
package com.fzm.mall.midea.pay.params;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author tangtuo
* @date 2021/5/27 16:25
*/
@Data
@ApiModel(value = "确认收货接口请求参数")
public class TradePayReceiveParam extends BaseParam{
@NotBlank(message = "商户B2C交易订单号不能为空")
@ApiModelProperty(value = "商户B2C交易订单号")
private String out_trade_no;
@ApiModelProperty(value = "分润参数")
private String profit_params;
}
package com.fzm.mall.midea.pay.params;
import lombok.Data;
/**
* @author tangtuo
* @date 2021/5/25 17:55
*/
@Data
public class TradeQueryParam extends BaseParam{
/**
* 合作商户网站唯一订单号(确保在合作伙伴系统中唯一)
* 不可为空
*/
private String out_trade_no;
/**
* 查询选项:
* trade_settle_info:结算信息
*/
private String query_options;
/**
* 收款方登录名
*/
private String payee_login_name;
}
package com.fzm.mall.midea.pay.params;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotBlank;
import java.util.Map;
/**
* @author tangtuo
* @date 2021/5/24 13:51
*/
@Data
@Accessors(chain = true)
public class WechatPayBatchTradeParam extends BaseParam {
@NotBlank(message = "担保交易标志不能为空")
@ApiModelProperty("担保交易标志:TRUE-担保交易 FALSE-即时到账交易(默认)")
private String is_guarantee;
@NotBlank(message = "订单号不能为空")
@ApiModelProperty("美的支付合作商户网站唯一订单号(确保在合作伙伴系统中唯一)")
private String out_trade_no;
@NotBlank(message = "订单提交时间不能为空")
@ApiModelProperty("商户发起交易,提交订单的时间,格式yyyyMMddHHmmss")
private String out_trade_time;
/**
* SCAN_CODE:扫码
* OFFICIAL_ACCT:公众号/服务窗
* MINI_PROGRAM:小程序
* APP:app
* H5:H5支付
*/
@NotBlank(message = "支付方式不能为空")
@ApiModelProperty(value = "支付方式")
private String bar_code;
@ApiModelProperty("公众号id 注:公众号、app、小程序支付时,必填")
private String app_id;
@ApiModelProperty("用户id 公众号或小程序支付时,必填")
private String open_id;
@ApiModelProperty("货币类型:CNY-人民币")
private String currency_type;
@ApiModelProperty(value = "订单总金额等于支付金额加营销金额,单位分 (3.4.0不可空)")
private String total_order_amount;
@NotBlank(message = "交易支付金额不能为空")
@ApiModelProperty("交易的支付金额,单位分")
private String total_amount;
@NotBlank(message = "订单总数不能为空")
@ApiModelProperty(value = "订单总数(最大50笔)")
private String total_count;
/**
* DC:借记卡
* DOC:借记卡和信用卡(双卡)
*/
@ApiModelProperty("支付卡限定")
private String limit_acct_type;
/**
* 单位:分,时间范围:1-30,不传默认为30
* 接口版本号大于等于3.3.0,表示订单关单时间,小于3.3.0版本无关单功能
*/
@ApiModelProperty("交易超时时间")
private String pay_expire_time;
@NotBlank(message = "产品标识不能为空")
@ApiModelProperty("产品标识:TRUE-虚拟交易 FALSE-实物交易")
private String is_virtual_product;
@NotBlank(message = "商品名称不可为空")
@ApiModelProperty("商品名称")
private String product_name;
@NotBlank(message = "商品的描述不可为空")
@ApiModelProperty("商品的描述")
private String product_info;
@ApiModelProperty("商户自定义的信息,回调通知的时候美的支付平台会原值返回给商户")
private String attach;
@NotBlank(message = "风控参数不可为空")
@ApiModelProperty("风控参数")
private String risk_params;
@ApiModelProperty("商户网站url(H5支付方式必传)")
private String wap_url;
@ApiModelProperty("商户网站名称(H5支付方式必传)")
private String wap_name;
@NotBlank(message = "子单列表不可为空")
@ApiModelProperty("子单列表,格式为json字符串")
private String sub_orders;
@Data
public static class SubOrder {
@ApiModelProperty("签约商户号,由美的支付生成,由10位数字组成")
@NotBlank(message = "签约商户号不可为空")
private String partner;
@ApiModelProperty("营销金额出款商户号")
private String market_acc_partner;
@ApiModelProperty("子B2C订单商户订单号")
@NotBlank(message = "子B2C订单商户订单号不能为空")
private String sub_out_trade_no;
@ApiModelProperty("子B2C订单金额,单位: 分")
@NotBlank(message = "子B2C订单金额不可为空")
private String pay_amount;
@ApiModelProperty("子单营销金额,单位分")
private String market_amount;
@ApiModelProperty("商品名称")
@NotBlank(message = "商品名称不可为空")
private String sub_product_name;
@ApiModelProperty("商品描述信息")
@NotBlank(message = "商品描述信息不可为空")
private String sub_product_info;
@ApiModelProperty("商品数量")
private String sub_product_count;
@ApiModelProperty("商品单价,单位:分")
private String sub_product_price;
@ApiModelProperty("交易意图")
private String sub_trade_purpose;
@ApiModelProperty("商户自定义信息")
private String sub_attach;
@ApiModelProperty("分润参数")
private String profit_params;
@ApiModelProperty("多级分账标志")
private String profit_sharing;
}
}
package com.fzm.mall.midea.pay.params;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Map;
/**
* @author tangtuo
* @date 2021/5/24 13:51
*/
@Data
@Accessors(chain = true)
public class WechatPayTradeParam extends BaseParam{
/**
* 担保交易标志:
* TRUE 担保交易
* FALSE 即时到账交易(默认)
* 不可为空
*/
private String is_guarantee;
/**
* 美的支付合作商户网站唯一订单号(确保在合作伙伴系统中唯一)
* 不可为空
*/
private String out_trade_no;
/**
* 商户发起交易,提交订单的时间,格式yyyyMMddHHmmss
* 不可为空
*/
private String out_trade_time;
/**
* 买家用户类型(自动登录时必填)
* B:商户
* C:用户(默认)
*/
private String payer_type;
/**
* 付款方登录名
*/
private String payer_login_name;
/**
* 收款方登录名
*/
private String payee_login_name;
/**
* SCAN_CODE:扫码
* OFFICIAL_ACCT:公众号/服务窗
* MINI_PROGRAM:小程序
* APP:app
* H5:H5支付
* 不可为空
*/
private String bar_code;
/**
* 公众号id
* 注:公众号、app、小程序支付时,必填
*/
private String app_id;
/**
* 用户id
* 公众号或小程序支付时,必填
*/
private String open_id;
/**
* 货币类型:
* CNY:人民币
*/
private String currency_type;
/**
* 订单总金额等于支付金额加营销金额,单位分
* (3.4.0不可空)
*/
private String order_amount;
/**
* 交易的支付金额,单位分
* 不可为空
*/
private String pay_amount;
/**
* 营销金额,单位分
*/
private String market_amount;
/**
* DC:借记卡
* DOC:借记卡和信用卡(双卡)
*/
private String limit_acct_type;
/**
* 交易超时时间
* <p>
* 单位:分,时间范围:1-30,不传默认为30
* 接口版本号大于等于3.3.0,表示订单关单时间,小于3.3.0版本无关单功能
*/
private String pay_expire_time;
/**
*
* 产品标识:
* TRUE:虚拟交易
* FALSE:实物交易
* 不可为空
*/
private String is_virtual_product;
/**
* 商品名称
* 不可为空
*/
private String product_name;
/**
* 商品的描述
* 不可为空
*/
private String product_info;
/**
* 商品数量
*/
private String product_count;
/**
* 商品单价,单位分
*/
private String product_price;
/**
* 交易意图
*/
private String trade_purpose;
/**
* 商户自定义的信息,回调通知的时候美的支付平台会原值返回给商户
*/
private String attach;
/**
* 风控参数
* 不可为空
*/
private String risk_params;
/**
*
* 分润参数
*/
private String profit_params;
/**
* 多级分账标志
*/
private String profit_sharing;
/**
* 商户网站url(H5支付方式必传)
*/
private String wap_url;
/**
* 商户网站名称(H5支付方式必传)
*/
private String wap_name;
public static void main(String[] args) {
WechatPayTradeParam wechatPayTradeParam = new WechatPayTradeParam();
wechatPayTradeParam.setApp_id("123").setAttach("attach").setService("wechat_trade");
Map<String, String> map = wechatPayTradeParam.beanToMap(wechatPayTradeParam);
System.out.println(map);
}
}
package com.fzm.mall.midea.pay.properties;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author tangtuo
* @date 2021/5/26 15:17
*/
@Data
@Component
@ConfigurationProperties(prefix = "midea.pay")
public class MideaPayProperties {
@ApiModelProperty(value = "美的支付网关地址")
private String url;
@ApiModelProperty(value = "商户号")
private String partner;
@ApiModelProperty(value = "单笔交易支付回调地址")
private String tradeNotifyUrl;
@ApiModelProperty(value = "合单交易支付回调地址")
private String batchTradeNotifyUrl;
@ApiModelProperty(value = "退款回调地址")
private String refundNotifyUrl;
@ApiModelProperty(value = "天威签名地址")
private String getSignUrl;
@ApiModelProperty(value = "天威验签地址")
private String checkSignUrl;
}
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fzm.mall.midea.pay.utils;
import org.apache.commons.lang3.StringUtils;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Miscellaneous methods for calculating digests.
* <p>Mainly for internal use within the framework; consider
* <a href="http://commons.apache.org/codec/">Apache Commons Codec</a> for a
* more comprehensive suite of digest utilities.
*
* @author Arjen Poutsma
* @see org.apache.commons.codec.digest.DigestUtils
* @since 3.0
*/
public abstract class DigestUtils{
private static final String MD5_ALGORITHM_NAME = "MD5";
private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
'f' };
/**
* Calculate the MD5 digest of the given bytes.
*
* @param bytes the bytes to calculate the digest over
* @return the digest
*/
public static byte[] md5Digest(byte[] bytes) {
return digest(MD5_ALGORITHM_NAME, bytes);
}
/**
* Return a hexadecimal string representation of the MD5 digest of the given
* bytes.
*
* @param bytes the bytes to calculate the digest over
* @return a hexadecimal digest string
*/
public static String md5DigestAsHex(byte[] bytes) {
return digestAsHexString(MD5_ALGORITHM_NAME, bytes);
}
public static String getMD5String(String param) {
if (StringUtils.isEmpty(param))
return param;
byte[] result = digest(MD5_ALGORITHM_NAME, param.getBytes(StandardCharsets.UTF_8));
String str = new String(encodeHex(result));
return str.toUpperCase();
}
/**
* Append a hexadecimal string representation of the MD5 digest of the given
* bytes to the given {@link StringBuilder}.
*
* @param bytes the bytes to calculate the digest over
* @param builder the string builder to append the digest to
* @return the given string builder
*/
public static StringBuilder appendMd5DigestAsHex(byte[] bytes, StringBuilder builder) {
return appendDigestAsHex(MD5_ALGORITHM_NAME, bytes, builder);
}
/**
* Creates a new {@link MessageDigest} with the given algorithm. Necessary
* because {@code MessageDigest} is not thread-safe.
*/
private static MessageDigest getDigest(String algorithm) {
try {
return MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException("Could not find MessageDigest with algorithm \"" + algorithm + "\"", ex);
}
}
private static byte[] digest(String algorithm, byte[] bytes) {
return getDigest(algorithm).digest(bytes);
}
private static String digestAsHexString(String algorithm, byte[] bytes) {
char[] hexDigest = digestAsHexChars(algorithm, bytes);
return new String(hexDigest);
}
private static StringBuilder appendDigestAsHex(String algorithm, byte[] bytes, StringBuilder builder) {
char[] hexDigest = digestAsHexChars(algorithm, bytes);
return builder.append(hexDigest);
}
public static String aesEncrypt(String plaintext, String key) {
byte[] aes = aes(plaintext.getBytes(StandardCharsets.UTF_8), key.getBytes(StandardCharsets.UTF_8),
Cipher.ENCRYPT_MODE);
return new String(encodeHex1(aes));
}
private static char[] digestAsHexChars(String algorithm, byte[] bytes) {
byte[] digest = digest(algorithm, bytes);
return encodeHex(digest);
}
public static char[] encodeHex(byte[] bytes) {
char chars[] = new char[32];
for (int i = 0; i < chars.length; i = i + 2) {
byte b = bytes[i / 2];
chars[i] = HEX_CHARS[(b >>> 0x4) & 0xf];
chars[i + 1] = HEX_CHARS[b & 0xf];
}
return chars;
}
protected static char[] encodeHex1(final byte[] data) {
final int l = data.length;
final char[] out = new char[l << 1];
// two characters form the hex value.
for (int i = 0, j = 0; i < l; i++) {
out[j++] = HEX_CHARS[(0xF0 & data[i]) >>> 4];
out[j++] = HEX_CHARS[0x0F & data[i]];
}
return out;
}
/**
* 字符串MD5加密
*
* @throws Exception
*/
public static String encryptMD5(String strInput) {
return encryptMD5(strInput, "UTF-8");
}
public static String encryptMD5(String strInput, String encoder) {
try {
StringBuffer buf = null;
MessageDigest md;
md = MessageDigest.getInstance("MD5");
md.update(strInput.getBytes(encoder));
byte b[] = md.digest();
buf = new StringBuffer(b.length * 2);
for (int i = 0; i < b.length; i++) {
if (((int) b[i] & 0xff) < 0x10) { // & 0xff转换无符号整型
buf.append("0");
}
// buf.append(Long.toString((int) b[i] & 0xff,
// 16));//转换16进制,下方法同
buf.append(Long.toHexString((int) b[i] & 0xff));
}
String result = buf.toString();
return result;
} catch (Exception e) {
return strInput;
}
}
public static byte[] aesEncrypt(byte[] input, byte[] key) {
return aes(input, key, Cipher.ENCRYPT_MODE);
}
private static byte[] aes(byte[] input, byte[] key, int mode) {
try {
SecretKey secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(mode, secretKey);
return cipher.doFinal(input);
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
return null;
}
}
package com.fzm.mall.midea.pay.utils;
import com.fzm.mall.midea.pay.model.HttpResponse;
import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 网络请求工具
*
* @author harvey
* @date 2020/8/31
* @email husy7@midea.com
*/
public class HttpClientUtils {
/**
* 带文件类型POST请求
*
* @param url
* @param params
* @param file
* @return
*/
public static HttpResponse doPostFile(String url, Map<String, String> params, File file) {
SSLContext sslcontext = null;
try {
sslcontext = SSLContexts.custom()
.build();
} catch (KeyManagementException e1) {
throw new RuntimeException(e1);
} catch (NoSuchAlgorithmException e1) {
throw new RuntimeException(e1);
}
// Allow TLSv1.2 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] { "TLSv1.2" },
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
HttpResponse resp = new HttpResponse();
BasicHttpClientConnectionManager connManager;
connManager = new BasicHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslsf)
.build(),
null,
null,
null
);
//构造MultipartEntityBuilder
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create()
.setCharset(StandardCharsets.UTF_8).setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
//设置普通请求参数,如<input type="text" name="name" value="value"/>
for (Map.Entry<String, String> param : params.entrySet()) {
if (StringUtils.isNotEmpty(param.getKey(), param.getValue())) {
multipartEntityBuilder.addTextBody(param.getKey(), param.getValue(), ContentType.create(
"text/plain", Consts.UTF_8));
}
}
//设置文件参数
multipartEntityBuilder.addPart("file", new FileBody(file));
//创建client和post
HttpPost httpPost = new HttpPost(url);
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(15000).setConnectTimeout(3000).build();
httpPost.setConfig(requestConfig);
httpPost.setEntity(multipartEntityBuilder.build());
CloseableHttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(connManager)
.build();
try {
//发起请求
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
if (httpResponse != null) {
resp.setHttpCode(httpResponse.getStatusLine().getStatusCode());
resp.setContentType(httpResponse.getHeaders("Content-Type"));
}
if(httpResponse.getEntity() != null) {
resp.setEntity(EntityUtils.toString(httpResponse.getEntity()));
}
System.out.println(String.format("http响应结果:code:%s,ContentType:%s,entity%s",
resp.getHttpCode(),getContentType(resp.getContentType()),resp.getEntity()));
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return resp;
}
/**
* 普通POST请求
*
* @param url
* @param params
* @return
*/
public static HttpResponse doPost(String url, Map<String, String> params) {
SSLContext sslcontext = null;
try {
sslcontext = SSLContexts.custom()
.build();
} catch (KeyManagementException e1) {
throw new RuntimeException(e1);
} catch (NoSuchAlgorithmException e1) {
throw new RuntimeException(e1);
}
// Allow TLSv1.2 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] { "TLSv1.2" },
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
HttpResponse resp = new HttpResponse();
BasicHttpClientConnectionManager connManager;
connManager = new BasicHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslsf)
.build(),
null,
null,
null
);
// HttpResponse resp = new HttpResponse();
// BasicHttpClientConnectionManager connManager;
// connManager = new BasicHttpClientConnectionManager(
// RegistryBuilder.<ConnectionSocketFactory>create()
// .register("http", PlainConnectionSocketFactory.getSocketFactory())
// .register("https", SSLConnectionSocketFactory.getSocketFactory())
// .build(),
// null,
// null,
// null
// );
CloseableHttpClient client = HttpClientBuilder.create()
.setConnectionManager(connManager)
.build();
try {
//参数转换
List<NameValuePair> nameValuePairs = new ArrayList<>();
for (Map.Entry<String, String> param : params.entrySet()) {
nameValuePairs.add(new BasicNameValuePair(param.getKey(), param.getValue()));
}
HttpPost post = new HttpPost(url);
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(15000).setConnectTimeout(3000).build();
post.setConfig(requestConfig);
post.setEntity(new UrlEncodedFormEntity(nameValuePairs, StandardCharsets.UTF_8));
//发起请求
CloseableHttpResponse httpResponse = client.execute(post);
if (httpResponse != null) {
resp.setHttpCode(httpResponse.getStatusLine().getStatusCode());
resp.setContentType(httpResponse.getHeaders("Content-Type"));
}
if(httpResponse.getEntity() != null) {
resp.setEntity(EntityUtils.toString(httpResponse.getEntity()));
}
System.out.println(String.format("http响应结果:code:%s,ContentType:%s,entity%s",
resp.getHttpCode(),getContentType(resp.getContentType()),resp.getEntity()));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
} catch (ClientProtocolException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (client != null) {
try {
client.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
return resp;
}
private static String getContentType(Header[] contentTypes) {
String contentType = "";
if(contentTypes != null) {
for(Header header : contentTypes) {
contentType = contentType + header.getValue();
}
}
return contentType;
}
}
package com.fzm.mall.midea.pay.utils;
import com.alibaba.fastjson.JSON;
import com.fzm.mall.midea.pay.params.BaseParam;
import com.fzm.mall.midea.pay.properties.MideaPayProperties;
import com.fzm.mall.midea.pay.model.CheckSignResponse;
import com.fzm.mall.midea.pay.model.HttpResponse;
import com.fzm.mall.midea.pay.model.SignResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
/**
* @author tangtuo
* @date 2021/5/19 18:02
*/
@Component
public class SignUtil {
@Resource
private MideaPayProperties mideaPayProperties;
private static final String SUCC_CODE = "1";
public static String partnerAesEncrypt(String text, String partner, String reqSeqNo, String key) {
String md5Source = new StringBuilder().append("partner=").append(partner).append("&req_seq_no=")
.append(reqSeqNo).append("&key=").append(key).toString();
String aesKey = DigestUtils.getMD5String(md5Source).substring(8, 24).toLowerCase();
return DigestUtils.aesEncrypt(text, aesKey);
}
public String getSign(String sourceSign) {
Map<String, String> params = new HashMap<>();
params.put("source", DigestUtils.encryptMD5(sourceSign));
//此处商户应该调用自己部署的tw-rsa-web程序
HttpResponse httpResp = HttpClientUtils.doPost(mideaPayProperties.getGetSignUrl(), params);
//WebUtils.checkJson(httpResp);
SignResponse signResponse = JSON.parseObject(httpResp.getEntity(), SignResponse.class);
if (signResponse != null && !SUCC_CODE.equals(signResponse.getCode())) {
throw new RuntimeException("get tw sign exception");
}
return signResponse.getSign();
}
public void checkSign(Map<String, String> resultMap) {
String sign = resultMap.get("sign");
String sourceSign = getStringToSignOfStr(resultMap);
Map<String, String> params = new HashMap<>();
params.put("source", DigestUtils.encryptMD5(sourceSign));
params.put("sign", sign);
HttpResponse resp = HttpClientUtils.doPost(mideaPayProperties.getCheckSignUrl(), params);
CheckSignResponse checkSignResponse = JSON.parseObject(resp.getEntity(), CheckSignResponse.class);
if (!SUCC_CODE.equals(checkSignResponse.getCode())) {
//验证签名成功&&返回的结果码为成功(如果是交易接口,还需要判断订单的状态),可做业务逻辑处理
throw new RuntimeException("签名验证失败");
}
}
public void checkSign(BaseParam param) {
checkSign(param.beanToMap(param));
}
public static String getStringToSignOfStr(Map<String, String> map) {
TreeMap<String, String> treeMap = new TreeMap<>();
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
treeMap.put(key, map.get(key));
}
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : treeMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (StringUtils.isBlank(value) || "sign".equals(key)) {
continue;
} else {
sb.append(key).append("=").append(value).append("&");
}
}
if (sb.length() > 1)
return sb.deleteCharAt(sb.length() - 1).toString();
else
return "";
}
}
package com.fzm.mall.midea.pay.utils;
/**
* 字符处理工具
*
* @author harvey
* @date 2020/8/31
* @email husy7@midea.com
*/
public class StringUtils {
public static boolean isNotEmpty(String... values) {
boolean result = true;
if (values == null || values.length == 0) {
result = false;
} else {
for (String value : values) {
result &= !isEmpty(value);
}
}
return result;
}
public static boolean isEmpty(String value) {
int strLen;
if (value == null || (strLen = value.length()) == 0) {
return true;
}
for (int i = 0; i < strLen; i++) {
if ((Character.isWhitespace(value.charAt(i)) == false)) {
return false;
}
}
return true;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.fzm.mall</groupId>
<artifactId>fzm-mall</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>mall-pay</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mall-pay</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 阿里pay -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-easysdk</artifactId>
<version>2.0.2</version>
</dependency>
<!-- 微信pay -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.2.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.68</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.49</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>central</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<layout>default</layout>
<!-- 是否开启发布版构件下载 -->
<releases>
<enabled>true</enabled>
</releases>
<!-- 是否开启快照版构件下载 -->
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>aliyun</id>
<name>aliyun repositories</name>
<layout>default</layout>
<url>http://146.56.197.85:12100/repository/maven-public/</url>
</repository>
</repositories>
</project>
package com.fzm.mall.pay;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MallPayApplication {
public static void main(String[] args) {
SpringApplication.run(MallPayApplication.class, args);
}
}
package com.fzm.mall.pay.config;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
/**
* 类名:AlipayConfig
* 功能:基础配置类
* 详细:设置帐户有关信息及返回路径
* 修改日期:2017-04-05
* 说明:
* 以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。
* 该代码仅供学习和研究支付宝接口使用,只是提供一个参考。
*
* @author wangp
* @date 2021/3/18 13:35
* @description 阿里pay配置
* @since JDK 1.8
*/
@Configuration
public class AlipayConfig {
// 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
@Value("${alipay.app-id}")
public String appId;
// 商户私钥,您的PKCS8格式RSA2私钥
@Value("${alipay.merchant-private-key}")
public String merchantPrivateKey;
// 应用公钥证书文件路径
@Value("${alipay.merchant-cert-path}")
public String merchantCertPath;
// 支付宝公钥证书文件路径
@Value("${alipay.alipay-cert-path}")
public String alipayCertPath;
// 支付宝根证书文件路径
@Value("${alipay.alipay-root-cert-path}")
public String alipayRootCertPath;
// 服务器异步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
@Value("${alipay.notify-url}")
public String notifyUrl;
// 签名方式
@Value("${alipay.sign-type}")
public String signType;
// 支付宝网关
@Value("${alipay.gateway-url}")
public String gatewayUrl;
// 协议类型
@Value("${alipay.protocol}")
private String protocol;
@Bean
public Config getOptions() {
Config config = new Config();
config.protocol = protocol;
config.gatewayHost = gatewayUrl;
config.signType = signType;
config.appId = appId;
config.merchantPrivateKey = merchantPrivateKey;
config.merchantCertPath = merchantCertPath;
config.alipayCertPath = alipayCertPath;
config.alipayRootCertPath = alipayRootCertPath;
config.notifyUrl = notifyUrl;
return config;
}
@PostConstruct
private void initOptions() {
//设置参数(全局只需设置一次)
Factory.setOptions(getOptions());
}
}
package com.fzm.mall.pay.config;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @author lyz
* @mail lyz@disanbo.com
* @create 2021/1/25 9:56
* @description
*/
@Configuration
@EnableSwagger2
@EnableKnife4j
public class Knife4jApiDoc {
@Bean
public Docket docketApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.groupName("商城支付接口")
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
@Bean
public ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("商城管理后台接口文档")
.version("1.0.0")
.description("复杂美自研商城管理后台服务端接口文档")
.termsOfServiceUrl("http://www.33.cn")
.build();
}
}
package com.fzm.mall.pay.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
public class WeChatPayConfig {
//应用ID
@Value("${wechatpay.app-id}")
private String appId;
//商户号
@Value("${wechatpay.mch-id}")
private String mchId;
//商户私钥
@Value("${wechatpay.private-key}")
private String privateKey;
//商户证书序列号
@Value("${wechatpay.mch-serial-no}")
private String mchSerialNo;
//API-V3秘钥
@Value("${wechatpay.api-V3-key}")
private String apiV3Key;
//支付结果通知地址
@Value("${wechatpay.notify_url}")
private String notifyUrl;
}
package com.fzm.mall.pay.config.i18n;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
import javax.validation.constraints.NotNull;
/**
* @author lyz
* @mail lyz@disanbo.com
* @create 2021/1/25 17:12
* @description
*/
@Component
public class MallMessage implements MessageSourceAware {
private static MessageSource messageSource;
@Override
public void setMessageSource(@NotNull MessageSource messageSource) {
MallMessage.messageSource = messageSource;
}
public static String getMsg(String msgKey){
return messageSource.getMessage(msgKey, null, LocaleContextHolder.getLocale());
}
}
package com.fzm.mall.pay.constant;
public class GlobalConfig {
//支付宝异步通知回复
public static final String ALIPAY_RES_SUCCESS = "success";
public static final String ALIPAY_RES_FAIL = "failed";
public static final String WXPAY_SUCCESS = "SUCCESS";
public static final String WXPAY_RES_SUCCESS = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[SUCCESS]]></return_msg>" + "</xml> ";
public static final String WXPAY_RES_FAIL = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[FAIL]]></return_msg>" + "</xml> ";
}
package com.fzm.mall.pay.constant;
/**
* 错误码分三大类
* 1.客户端 -- 以A0001开头
* 2.业务端 -- 以B0001开头
* 3.第三方 -- 以C0001开头
* <p>
* 每大类下分小类
* 小类范围A01** - A99**
* 先到先得
* <p>
* 小类下在细分具体错误
* 范围A0101 - A0199
*
* @author wangp
* @date 2021/2/26 16:06
* @description 错误码
* @since JDK 1.8
*/
public class MallResponseError {
/**
* 客户端错误
*/
public static final String CLIENT_ERROR = "A0001";
// 管理员相关
public static final String ADMIN_ERROR = "A0100";
public static final String ACCOUNT_OR_PASSWORD_ERROR = "A0101";
public static final String PASSWORD_FORMAT_ERROR = "A0102";
public static final String PASSWORDS_ENTERED_TWICE_ARE_INCONSISTENT = "A0103";
public static final String ADMIN_NOT_EXISTED = "A0104";
public static final String OLD_PASSWORD_ERROR = "A0105";
public static final String HAS_LOCKING_ERROR = "A0106";
public static final String PASSWORD_INIT_ERROR = "A0107";
// 参数相关
public static final String PARAM_ERROR = "A0200";
public static final String NULL_PARAM_ERROR = "A0201";
public static final String PHONE_FORMAT__ERROR = "A0202";
public static final String PHONE_CODE__ERROR = "A0203";
public static final String PHONE_EXIST__ERROR = "A0204";
public static final String PHONE_NOT_REGISTER__ERROR = "A0205";
public static final String VALUE_LT_0_ERROR = "A0206";
public static final String COIN_HAS_EXIST = "A0207";
public static final String COIN_NOT_RMB = "A0208";
public static final String GOODS_NAME_EXIST = "A0209";
public static final String ID_CARD_FORMAT_ERROR = "A0210";
// TOKEN相关
public static final String TOKEN_ERROR = "A0300";
public static final String NULL_TOKEN_ERROR = "A0301";
public static final String INVALID_TOKEN_ERROR = "A0302";
// 权限相关
public static final String AUTH_ERROR = "A0400";
public static final String NO_AUTH_ERROR = "A0401";
// 上传
public static final String UPLOAD_ERROR = "A0500";
public static final String UPLOAD_FORMAT_ERROR = "A0501";
/**
* 业务端错误
*/
public static final String UPDATE_FAIL = "B0100";
public static final String SAVE_DUPLICATE = "B0101";
public static final String DELETE_FAIL = "B0102";
public static final String MERCHANT_ERROR = "B0200";
public static final String CANCEL_ERROR = "B0201";
//快递模板相关
public static final String EXPRESS_TEMPLATE_ERROR = "B0300";
public static final String NULL_TEMPLATE = "B0301";
/**
* 第三方错误
*/
public static final String THIRD_ERROR = "C0001";
//mysql相关
public static final String MYSQL_ERROR = "C0100";
public static final String SAVE_ERROR = "C0101";
public static final String UPDATE_ERROR = "C0102";
public static final String DELETE_ERROR = "C0103";
//区块链
public static final String BLOCKCHAIN_ERROR = "C0200";
public static final String ADDRESS_ERROR = "C0201";
public static final String PRI_KEY_ERROR = "C0202";
}
package com.fzm.mall.pay.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.util.ResponseChecker;
import com.alipay.easysdk.payment.common.models.AlipayTradeQueryResponse;
import com.alipay.easysdk.payment.common.models.AlipayTradeRefundResponse;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.fzm.mall.pay.constant.GlobalConfig;
import com.fzm.mall.pay.entity.dto.AlipayDTO;
import com.fzm.mall.pay.enums.AlipayTradeStautsEnum;
import com.fzm.mall.pay.repo.ResponseFactory;
import com.fzm.mall.pay.repo.ResponseVO;
import com.fzm.mall.pay.util.UUIDUtil;
import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* @author wangp
* @date 2021/3/18 14:00
* @description 支付宝
* @since JDK 1.8
*/
@Api(tags = "支付宝支付")
@Slf4j
@RestController
@RequestMapping("/alipay")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AlipayController {
private final ResponseFactory resFac;
@Value("${alipay.pay-notify-url}")
private String payNotifyUrl;
@Value("${alipay.return-url}")
private String returnUrl;
@PostMapping("/pagePay")
public ResponseVO pay(@RequestBody @Valid AlipayDTO alipayDTO) {
String outTradeNo = alipayDTO.getOutTradeNo();
String subject = alipayDTO.getSubject();
String totalAmount = alipayDTO.getTotalAmount();
JSONObject jsonObj = new JSONObject();
try {
//发起API调用
AlipayTradePagePayResponse res = Factory.Payment.Page()
// 调用asyncNotify扩展方法,可以为每此API调用,设置独立的异步通知地址
// 此处设置的异步通知地址的优先级高于全局Config中配置的异步通知地址
.asyncNotify(payNotifyUrl)
.pay(
subject,
outTradeNo,
totalAmount,
returnUrl
);
//处理响应或异常
if (ResponseChecker.success(res)) {
System.err.println(res.body);
jsonObj.put("form", res.body);
return resFac.getSuccessResponseWithData(jsonObj);
}
} catch (Exception e) {
e.printStackTrace();
}
return resFac.getSimpleFailureResponse();
}
// 支付回调
@PostMapping("/payNotify")
public String payNotify(HttpServletRequest req) {
try {
Map<String, String> params = genParams(req);
System.err.println("收到支付宝的支付回调");
log.info("收到支付宝的支付回调:");
log.info(JSON.toJSONString(params));
// 验签
if (Factory.Payment.Common().verifyNotify(params)) {
return GlobalConfig.ALIPAY_RES_SUCCESS;
}
} catch (Exception e) {
e.printStackTrace();
}
return GlobalConfig.ALIPAY_RES_FAIL;
}
@PostMapping("/mainNotify")
public String mainNotify(HttpServletRequest req) {
log.info("收到支付宝回调通知: ");
try {
Map<String, String> params = genParams(req);
log.info(JSON.toJSONString(params));
// 验签
if (Factory.Payment.Common().verifyNotify(params)) {
// 该交易在支付宝系统中的交易流水号。最长64位。
String tradeNo = params.get("trade_no");
// 交易状态
String tradeStatus = params.get("trade_status");
// 商户业务ID,主要是退款通知中返回退款申请的流水号
String outBizNo = params.get("out_biz_no");
String refundFee = params.get("refund_fee");
AlipayTradeStautsEnum statusEnum = AlipayTradeStautsEnum.getEnum(tradeStatus);
switch (Objects.requireNonNull(statusEnum)) {
case SUCCESS:
// 支付成功,或部分退款 退款成功
break;
case CLOSED:
// 未付款交易超时关闭,或支付完成后全额退款
break;
case FINISHED:
// 交易完结
break;
case BUYER_PAY:
// 交易创建
break;
default:
break;
}
// 如果商户反馈给支付宝的字符不是success这7个字符,支付宝服务器会不断重发通知,直到超过24小时22分钟。一般情况下,25小时以内完成8次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h)
return GlobalConfig.ALIPAY_RES_SUCCESS;
}
} catch (Exception e) {
e.printStackTrace();
}
return GlobalConfig.ALIPAY_RES_FAIL;
}
@PostMapping("/refund")
public String refund(@RequestParam String outTradeNo, @RequestParam String amount) {
log.info("收到退款请求:" + outTradeNo + "--" + amount);
try {
// 可设置异步通知地址
// String notifyUrl = "xxxx";
// Factory.Payment.Common().asyncNotify(notifyUrl).refund(outTradeNo, amount);
String outRequestNo = UUIDUtil.getUUID();
AlipayTradeRefundResponse res = Factory.Payment.Common()
.optional("out_request_no", outRequestNo)
.refund(outTradeNo, amount);
if (ResponseChecker.success(res)) {
log.info("退款成功~");
return "success";
} else {
return "error";
}
} catch (Exception e) {
e.printStackTrace();
}
return "error";
}
@PostMapping("/query")
public String query(@RequestParam String outTradeNo, @RequestParam String outRequestNo) {
try {
// 查询交易
AlipayTradeQueryResponse res1 = Factory.Payment.Common().query(outTradeNo);
System.err.println(res1.body);
/*// 查询退款
AlipayTradeFastpayRefundQueryResponse res2 = Factory.Payment.Common().queryRefund(outTradeNo, outRequestNo);
// 关闭交易
AlipayTradeCloseResponse res3 = Factory.Payment.Common().close(outTradeNo);
// 撤销交易
AlipayTradeCancelResponse res4 = Factory.Payment.Common().cancel(outTradeNo);*/
} catch (Exception e) {
e.printStackTrace();
}
return "success";
}
private Map<String, String> genParams(HttpServletRequest req) {
Map<String, String[]> requestParams = req.getParameterMap();
Map<String, String> params = new HashMap<>();
for (Map.Entry<String, String[]> entry : requestParams.entrySet()) {
String key = entry.getKey();
String[] values = entry.getValue();
StringBuilder valStr = new StringBuilder();
for (int i = 0; i < values.length; i++) {
if (i != values.length - 1) {
valStr.append(values[i]).append(",");
} else {
valStr.append(values[i]);
}
}
params.put(key, valStr.toString());
}
return params;
}
}
package com.fzm.mall.pay.controller;
import com.fzm.mall.pay.repo.ResponseFactory;
import com.fzm.mall.pay.repo.ResponseVO;
import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.HttpPost;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author wangp
* @date 2021/3/19 15:50
* @description 微信支付APIv3
* @since JDK 1.8
*/
@Api(tags = "微信支付APIv3")
@Slf4j
@RestController
@RequestMapping("/wechatpay")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class WeChatPayController {
private final ResponseFactory resFac;
public ResponseVO nativePay() {
return null;
}
}
package com.fzm.mall.pay.entity.dto;
import com.fzm.mall.pay.constant.MallResponseError;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author wangp
* @date 2021/3/18 16:18
* @description 阿里pay数据传输
* @since JDK 1.8
*/
@Data
@ApiModel(value = "AlipayDTO", description = "阿里支付数据")
public class AlipayDTO {
@ApiModelProperty("商品标题/交易标题/订单标题/订单关键字等。注意:不可使用特殊字符,如 /,=,& 等。")
@NotBlank(message = MallResponseError.NULL_PARAM_ERROR)
private String subject;
@ApiModelProperty("商户订单号。64 个字符以内的大小,仅支持字母、数字、下划线。需保证该参数在商户端不重复。")
@NotBlank(message = MallResponseError.NULL_PARAM_ERROR)
private String outTradeNo;
@ApiModelProperty("订单总金额,单位为人民币(元),取值范围为 0.01~100000000.00,精确到小数点后两位。")
@NotBlank(message = MallResponseError.NULL_PARAM_ERROR)
private String totalAmount;
}
package com.fzm.mall.pay.entity.dto;
import com.fzm.mall.pay.constant.MallResponseError;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author wangp
* @date 2021/4/15 15:02
* @description 微信退款信息
* @since JDK 1.8
*/
@Data
@ApiModel(value = "WxRefundDTO", description = "微信退款信息")
public class WxRefundDTO {
@ApiModelProperty("微信支付订单号/二选一")
private String transactionId;
@ApiModelProperty("商户订单号/二选一")
private String outTradeNo;
@ApiModelProperty("商户退款单号")
@NotBlank(message = MallResponseError.NULL_PARAM_ERROR)
private String outRefundNo;
@ApiModelProperty("退款金额,币种的最小单位")
@NotBlank(message = MallResponseError.NULL_PARAM_ERROR)
private Integer refund;
@ApiModelProperty("原支付交易的订单总金额")
@NotBlank(message = MallResponseError.NULL_PARAM_ERROR)
private Integer total;
}
package com.fzm.mall.pay.entity.dto;
import com.fzm.mall.pay.constant.MallResponseError;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author wangp
* @date 2021/4/14 16:59
* @description 微信统一下单信息
* @since JDK 1.8
*/
@Data
@ApiModel(value = "WxUnifiedOrderDTO", description = "微信统一下单信息")
public class WxUnifiedOrderDTO {
@ApiModelProperty("商品描述")
@NotBlank(message = MallResponseError.NULL_PARAM_ERROR)
private String description;
@ApiModelProperty("商户订单号")
@NotBlank(message = MallResponseError.NULL_PARAM_ERROR)
private String outTradeNo;
@ApiModelProperty("订单总金额/分")
@NotBlank(message = MallResponseError.NULL_PARAM_ERROR)
private Integer total;
}
package com.fzm.mall.pay.enums;
import org.springframework.util.StringUtils;
public enum AlipayTradeStautsEnum {
SUCCESS("TRADE_SUCCESS"),
FINISHED("TRADE_FINISHED"),
CLOSED("TRADE_CLOSED"),
BUYER_PAY("WAIT_BUYER_PAY");
private String status;
AlipayTradeStautsEnum(String status){
this.status = status;
}
public String getVal() {
return status;
}
public static AlipayTradeStautsEnum getEnum(String val) {
if (StringUtils.isEmpty(val)) {
for (AlipayTradeStautsEnum statusEnum : AlipayTradeStautsEnum.values()) {
if (statusEnum.getVal().equals(val)) {
return statusEnum;
}
}
}
return null;
}
}
package com.fzm.mall.pay.enums.response;
/**
* @author lyz
* @mail lyz@disanbo.com
* @create 2021/1/25 14:32
* @description
*/
public enum MallResponseEnum {
SUCCESS("00000", "SUCCESS"),
FAIL("11111", "FAIL");
private String code;
private String msgKey;
MallResponseEnum(String code, String msgKey) {
this.code = code;
this.msgKey = msgKey;
}
public String code() {
return this.code;
}
public String msgKey() {
return this.msgKey;
}
}
package com.fzm.mall.pay.repo;
import com.fzm.mall.pay.enums.response.MallResponseEnum;
import org.springframework.stereotype.Component;
/**
* @author wangp
* @date 2021/3/1 15:18
* @description 返回工厂
* @since JDK 1.8
*/
@Component
public class ResponseFactory {
public ResponseVO<String> getSimpleSuccessResponse() {
return new ResponseVO<>(MallResponseEnum.SUCCESS);
}
public ResponseVO<String> getSimpleFailureResponse() {
return new ResponseVO<>(MallResponseEnum.FAIL);
}
public ResponseVO<String> getResponse(String code) {
return new ResponseVO<>(code);
}
public <T> ResponseVO<T> getSuccessResponseWithData(T data) {
return new ResponseVO<>(MallResponseEnum.SUCCESS, data);
}
public <T> ResponseVO<T> getFailResponseWithData(T data) {
return new ResponseVO<>(MallResponseEnum.FAIL, data);
}
public <T> ResponseVO<T> getResponseWithData(String code, T data) {
return new ResponseVO<>(code, data);
}
}
package com.fzm.mall.pay.repo;
import com.fzm.mall.pay.config.i18n.MallMessage;
import com.fzm.mall.pay.enums.response.MallResponseEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author lyz
* @mail lyz@disanbo.com
* @create 2021/1/25 10:24
* @description
*/
@Data
@ApiModel("返回对象")
public class ResponseVO<T> {
@ApiModelProperty("应答码,00000:成功,其它:失败")
private String code;
@ApiModelProperty("应答消息")
private String msg;
@ApiModelProperty("应答数据")
private T data;
public ResponseVO(MallResponseEnum responseEnum) {
this.code = responseEnum.code();
this.msg = responseEnum.msgKey();
}
public ResponseVO(MallResponseEnum responseEnum, T data) {
this.code = responseEnum.code();
this.msg = responseEnum.msgKey();
this.data = data;
}
public ResponseVO(String code) {
this.code = code;
this.msg = MallMessage.getMsg(code);
}
public ResponseVO(String code, T data) {
this.code = code;
this.msg = MallMessage.getMsg(code);
this.data = data;
}
}
package com.fzm.mall.pay.service;
import com.fzm.mall.pay.entity.dto.WxRefundDTO;
import com.fzm.mall.pay.entity.dto.WxUnifiedOrderDTO;
import java.io.IOException;
/**
* @author wangp
* @date 2021/4/14 16:28
* @description 微信Native支付
* @since JDK 1.8
*/
public interface IWxNativePayService {
/**
* 统一下单
*
* @param dto 微信统一下单信息
* @return
*/
String CreateOrder(WxUnifiedOrderDTO dto) throws IOException;
/**
* 微信支付订单号查询
*
* @param transactionId 微信支付订单号
* @return
*/
String QueryOrderByTransactionId(String transactionId);
/**
* 商户订单号查询
*
* @param outTradeNo 商户订单号
* @return
*/
String QueryOrderByOutTradeNo(String outTradeNo);
/**
* 关闭订单
*
* @param outTradeNo 商户订单号
* @return
*/
String CloseOrder(String outTradeNo);
/**
* 申请退款
*
* @param dto 微信退款信息
* @return
*/
String ApplyRefund(WxRefundDTO dto);
/**
* 查询单笔退款
*
* @param outRefundNo 商户退款单号
* @return
*/
String QueryRefund(String outRefundNo);
/**
* 申请交易账单
*
* @param billDate 账单日期
* @return
*/
String applyTradeBill(String billDate);
/**
* 申请资金账单
*
* @param billDate
* @return
*/
String applyFundFlowBill(String billDate);
}
package com.fzm.mall.pay.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.fzm.mall.pay.config.WeChatPayConfig;
import com.fzm.mall.pay.entity.dto.WxRefundDTO;
import com.fzm.mall.pay.entity.dto.WxUnifiedOrderDTO;
import com.fzm.mall.pay.service.IWxNativePayService;
import com.fzm.mall.pay.util.WxPayHttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
/**
* @author wangp
* @date 2021/4/14 16:32
* @description 微信Native支付
* @since JDK 1.8
*/
@Slf4j
@Service
public class WxNativePayServiceImpl implements IWxNativePayService {
@Autowired
private WeChatPayConfig config;
@Override
public String CreateOrder(WxUnifiedOrderDTO dto) {
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
// 请求body参数
JSONObject reqdata = new JSONObject();
reqdata.put("appid", config.getAppId());
reqdata.put("mchid", config.getMchId());
reqdata.put("description", dto.getDescription());
reqdata.put("out_trade_no", dto.getOutTradeNo());
reqdata.put("notify_url", config.getNotifyUrl());
reqdata.put("amount", new JSONObject().put("total", dto.getTotal()));
StringEntity entity = null;
try {
entity = new StringEntity(reqdata.toJSONString());
entity.setContentType("application/json");
} catch (UnsupportedEncodingException e) {
log.info(e.toString());
}
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
return WxPayHttpUtil.weChatPayHttpRequest(httpPost);
}
@Override
public String QueryOrderByTransactionId(String transactionId) {
URI build = null;
//请求URL
try {
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/id/" + transactionId);
uriBuilder.setParameter("mchid", config.getMchId());
build = uriBuilder.build();
} catch (URISyntaxException e) {
e.printStackTrace();
}
//完成签名并执行请求
HttpGet httpGet = new HttpGet(build);
httpGet.addHeader("Accept", "application/json");
return WxPayHttpUtil.weChatPayHttpRequest(httpGet);
}
public String QueryOrderByOutTradeNo(String outTradeNo) {
URI build = null;
//请求URL
try {
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + outTradeNo);
uriBuilder.setParameter("mchid", config.getMchId());
build = uriBuilder.build();
} catch (URISyntaxException e) {
e.printStackTrace();
}
//完成签名并执行请求
HttpGet httpGet = new HttpGet(build);
httpGet.addHeader("Accept", "application/json");
return WxPayHttpUtil.weChatPayHttpRequest(httpGet);
}
public String CloseOrder(String outTradeNo) {
//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + outTradeNo + "/close");
//请求body参数
String reqdata = "{\"mchid\": \"" + config.getMchId() + "\"}";
StringEntity entity = null;
try {
entity = new StringEntity(reqdata);
entity.setContentType("application/json");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
return WxPayHttpUtil.weChatPayHttpRequest(httpPost);
}
public String ApplyRefund(WxRefundDTO dto) {
//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");
// 请求body参数
JSONObject reqdata = new JSONObject();
reqdata.put("transaction_id", dto.getTransactionId());
reqdata.put("out_trade_no", dto.getOutTradeNo());
reqdata.put("out_refund_no", dto.getOutRefundNo());
JSONObject amountJson = new JSONObject();
amountJson.put("refund", dto.getRefund());
amountJson.put("total", dto.getTotal());
amountJson.put("currency", "CNY");
reqdata.put("amount", amountJson);
StringEntity entity = null;
try {
entity = new StringEntity(reqdata.toJSONString());
entity.setContentType("application/json");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
return WxPayHttpUtil.weChatPayHttpRequest(httpPost);
}
public String QueryRefund(String outRefundNo) {
URI build = null;
//请求URL
try {
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/" + outRefundNo);
uriBuilder.setParameter("mchid", config.getMchId());
build = uriBuilder.build();
} catch (URISyntaxException e) {
e.printStackTrace();
}
//完成签名并执行请求
HttpGet httpGet = new HttpGet(build);
httpGet.addHeader("Accept", "application/json");
return WxPayHttpUtil.weChatPayHttpRequest(httpGet);
}
public String applyTradeBill(String billDate) {
URI build = null;
//请求URL
try {
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/bill/tradebill");
uriBuilder.setParameter("bill_date", billDate);
build = uriBuilder.build();
} catch (URISyntaxException e) {
e.printStackTrace();
}
//完成签名并执行请求
HttpGet httpGet = new HttpGet(build);
httpGet.addHeader("Accept", "application/json");
return WxPayHttpUtil.weChatPayHttpRequest(httpGet);
}
public String applyFundFlowBill(String billDate) {
URI build = null;
//请求URL
try {
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/bill/fundflowbill");
uriBuilder.setParameter("bill_date", billDate);
build = uriBuilder.build();
} catch (URISyntaxException e) {
e.printStackTrace();
}
//完成签名并执行请求
HttpGet httpGet = new HttpGet(build);
httpGet.addHeader("Accept", "application/json");
return WxPayHttpUtil.weChatPayHttpRequest(httpGet);
}
}
package com.fzm.mall.pay.util;
import java.util.UUID;
/**
* @author wangp
* @description UUID工具类
* @date 2020/8/10 10:27
*/
public class UUIDUtil {
/**
* 获取UUID
*
* @return UUID
*/
public static String getUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
}
package com.fzm.mall.pay.util;//package com.fzm.mall.server.admin.pay.util;
//
//import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
//import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
//import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
//import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
//import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
//import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
//import org.apache.http.impl.client.CloseableHttpClient;
//import org.junit.After;
//import org.junit.Before;
//
//import java.io.ByteArrayInputStream;
//import java.io.IOException;
//import java.security.PrivateKey;
//
///**
// * @author wangp
// * @date 2021/3/19 18:20
// * @description
// * @since JDK 1.8
// */
//public class WechatPayUtil {
//
// public static CloseableHttpClient httpClient;
//
// @Before
// public void setup() throws IOException {
// // 加载商户私钥(privateKey:私钥字符串)
// PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
//
// // 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3秘钥)
// AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(new WechatPay2Credentials(mchId,
// new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes("utf-8"));
//
// // 初始化httpClient
// httpClient = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, merchantPrivateKey)
// .withValidator(new WechatPay2Validator(verifier)).build();
// }
//
// @After
// public void after() throws IOException {
// httpClient.close();
// }
//
//
//}
package com.fzm.mall.pay.util;
import com.fzm.mall.pay.config.WeChatPayConfig;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
/**
* @author wangp
* @date 2021/4/15 15:37
* @description 微信支付http工具类
* @since JDK 1.8
*/
@Slf4j
@Component
public class WxPayHttpUtil {
@Autowired
private WeChatPayConfig config;
private static CloseableHttpClient httpClient = null;
private static RequestConfig requestConfig = null;
/*@PostConstruct
public void setup() {
// 加载商户私钥(privateKey:私钥字符串)
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(config.getPrivateKey().getBytes(StandardCharsets.UTF_8)));
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3秘钥)
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(config.getMchId(), new PrivateKeySigner(config.getMchSerialNo(), merchantPrivateKey)),
config.getApiV3Key().getBytes(StandardCharsets.UTF_8));
// 初始化httpClient,通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(config.getMchId(), config.getMchSerialNo(), merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
requestConfig = RequestConfig.custom()
//设置连接超时
.setConnectTimeout(35000)
//设置从连接池获取连接实例的超时
.setConnectionRequestTimeout(35000)
//设置读取超时
.setSocketTimeout(60000)
.build();
}*/
public static String weChatPayHttpRequest(HttpRequestBase base) {
String result = null;
base.setConfig(requestConfig);
try (CloseableHttpResponse response = httpClient.execute(base)) {
result = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) { //处理成功
log.info("success,return body = " + result);
} else if (statusCode == 204) { //处理成功,无返回Body
log.info("success");
return "204";
} else {
log.info("failed,resp code = " + statusCode + ",return body = " + result);
throw new IOException("request failed");
}
} catch (IOException e) {
log.info(e.getMessage());
}
return result;
}
}
//package com.fzm.mall.pay.util;
//
//import okhttp3.HttpUrl;
//
//import java.security.NoSuchAlgorithmException;
//import java.security.Signature;
//import java.security.SignatureException;
//import java.util.Base64;
//
///**
// * @author wangp
// * @date 2021/4/15 18:10
// * @description 微信签名
// * @since JDK 1.8
// */
//public class WxpaySignatureUtil {
//
// // Authorization:
//// GET - getToken("GET", httpurl, "")
//// POST - getToken("POST", httpurl, json)
// String schema = "WECHATPAY2-SHA256-RSA2048";
// HttpUrl httpurl = HttpUrl.parse(url);
//
// String getToken(String method, HttpUrl url, String body) {
// String nonceStr = "your nonce string";
// long timestamp = System.currentTimeMillis() / 1000;
// String message = buildMessage(method, url, timestamp, nonceStr, body);
// String signature = sign(message.getBytes("utf-8"));
//
// return "mchid=\"" + yourMerchantId + "\","
// + "nonce_str=\"" + nonceStr + "\","
// + "timestamp=\"" + timestamp + "\","
// + "serial_no=\"" + yourCertificateSerialNo + "\","
// + "signature=\"" + signature + "\"";
// }
//
// String sign(byte[] message) throws NoSuchAlgorithmException, SignatureException {
// Signature sign = Signature.getInstance("SHA256withRSA");
// sign.initSign(yourPrivateKey);
// sign.update(message);
//
// return Base64.getEncoder().encodeToString(sign.sign());
// }
//
// String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
// String canonicalUrl = url.encodedPath();
// if (url.encodedQuery() != null) {
// canonicalUrl += "?" + url.encodedQuery();
// }
//
// return method + "\n"
// + canonicalUrl + "\n"
// + timestamp + "\n"
// + nonceStr + "\n"
// + body + "\n";
// }
//
//}
server:
port: 8089
tomcat:
max-swallow-size: -1
servlet:
context-path: /mallpay
max-http-header-size: 8999
spring:
application:
name: mall-pay
messages:
basename: i18n/messages
servlet:
multipart:
max-file-size: 50MB
max-request-size: 50MB
#支付宝支付配置
alipay:
app-id: 2016102400749958
merchant-private-key: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDF5b27H3MoKz7sQbWVd7O54NdwBpokSMlQbzP56u9gqEPKJKMQLrTjF1XkAgurQh8ZYRMsVHUogml+GerBhbXZh3RIVp8gk8/Fp7iAtT68kzJ5YSMfTUrwV8zQGF+GqGmaZHqtStIckmZ7fdj9qaCiyDrEOqRjCWqO9hYjnJ6+f9D5AdwA/KPgTXzKFjcbqIHCJBJY8mZ67gFRW9uGfOi+OHFiG95/4ShgyFz99jSOaS6WVsPcU1h+IBh4xPqx60Pe0AhMuHB1xZppW56HTUwNZvP3zp0ySdh36Itc5C+XyjgI7qu8gEERVvh+QwuPLI+IweHeZ4BSAZtczb5XQH8pAgMBAAECggEAMeoaiuIJ163gtxJzzHleuZl+fWEM6+336PPDdlvNeOG4B3pqLqC5OzYdkd76Nn38/hfAEERVMJyEIyxCTAF/tpg1ujEu/baKh14gaUZNxp6R8o+PnkuOhUSAAlTDc2FzEWAamS7wBAjhFHDPlFt+itBaEjiAUgKD//8N4nmUiJOeaZ3ruymV+5u7/higeuZandYfmTyZdZvjxcuuquTp+LeR3/YpP8UjmvXbSE02E8mhQkRXelU2SbWZiAlsMgCysCerRDX2iuc/xxb78SUSYg9Lc3tv/qZoZsCl121gsyCMCkgakt9iRBjOBaGC8oGmjDl7ZFQXen7wXDv6utw7oQKBgQD5jvMdK7RwPopoyUEnbjiJ0MBs4CFeOnq1MwaZ4mVoxpszucE9zwDw/5fY+azZ3GBU/e4rb4ajcqnMDZbL6KgbCEXlqvYul8xxyVJqjOCAsmF1e4Lf86IbcmrAg3/8okqFsMBxXJnVhs8sR2TEftol6jxlZxKCXLrelOEEoStdBQKBgQDLAWwcc2JDmJEvavh0oQjs8bfvcp5hBR1C3LUlCwZbnAf/v3XF7DdLO4CkQhpcYjArtX/UcS/wMNl1P34HtLZ3CL4uN88JsqTWQpNcrRE2KtS3tqMYDyKIsDwWXNN8k4otw1E/EUnr/5CH0SgsLZVE4tGQcoIP6fbfi+w+8sbS1QKBgQCA2gwwrZY2tjPksrUPVNugXMZd8MOJ0Yv17uijn79Yf+M2q9xYu37y8CVJBHD4885RU+MbTjQoRFXjY8jOc8wIWRct5D3EBOCsh5QkK6TnDjM/44vJchPrNAJmb/8vQ+RSbtaOMY/ELhzd8ebXhd/A39ZbTQLDiHl730bniwfGuQKBgBMHRBn6mshZQaqfLUDJY2vKUIJqTPwtHYwJ4hThzqvBmWJYkPKHbLtpjWpYI95q+bbvIVdJcMPj4E5EME0KnFvV/vWYF5kCHL9UqDl0o3tfUmcSAIGd0Y/cbRVviAdSUQ5vE2uE9Ffhsat5aHmToD0/wYL3qMdTF+fmVrOj0A0tAoGAMyzBkJID/yinRfqiB27UZbE4fUv0tDftE8F1wQDRXWUerPIHb8N7YFRP8ax9whwzMnyGHaTd92JMHZPAhV/tSGLHCVXFyWqpMWK7eO//NUQEuV3zvtAyiMzqEQ7krawDriBfO/9xooik34XcMx86GtesAYestHL6uPvLgruxxPo=
# notify-url: http://177.22.17.80:8089/alipay/mainNotify
notify-url: https://www.baidu.com
merchant-cert-path: D:\download\appCertPublicKey_2016102400749958.crt
alipay-cert-path: D:\download\alipayCertPublicKey_RSA2.crt
alipay-root-cert-path: D:\download\alipayRootCert.crt
quit-url: https://www.baidu.com?id=
return-url: https://www.baidu.com?id=
pay-notify-url: http://47.99.152.140:8092/alipay/payNotify
refund-notify-url: http://47.99.152.140:8092/alipay/refundNotify
sign-type: RSA2
gateway-url: openapi.alipaydev.com
protocol: https
#微信支付配置
wechatpay:
app-id: wx396fe76aad87adff
mch-id: 1503745861
private-key: 5bb9d7cbc7207f363e118bb73f075d66
mch-serial-no: yilingou2018wexinappsecretkeykey
api-V3-key: 8a1eb649426d072a54e14e68493ffd5a
notify_url: http://47.99.152.140:8092/wxpay/payNotify
spring:
# 数据源配置 10.206.0.38
datasource:
url: jdbc:mysql://146.56.197.42:3306/mall?useUnicode=true&characterEncoding=utf8&useSSL=false&tinyInt1isBit=false
username: root
password: fzm_db_01@TEST
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
hikari:
maximum-pool-size: 200
minimum-idle: 10
auto-commit: true
idle-timeout: 60000
connection-timeout: 30000
connection-test-query: select 1
max-lifetime: 6000000
\ No newline at end of file
spring:
profiles:
active:
- common
- dev
-----BEGIN CERTIFICATE-----
MIIDqDCCApCgAwIBAgIQICAGBSAF7CQIrRjll3XfMDANBgkqhkiG9w0BAQsFADCBkTELMAkGA1UE
BhMCQ04xGzAZBgNVBAoMEkFudCBGaW5hbmNpYWwgdGVzdDElMCMGA1UECwwcQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkgdGVzdDE+MDwGA1UEAww1QW50IEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eSBDbGFzcyAyIFIxIHRlc3QwHhcNMjAwNjA1MDMwODE2WhcNMjMwNjA0MDMwODE2WjB6
MQswCQYDVQQGEwJDTjEVMBMGA1UECgwM5rKZ566x546v5aKDMQ8wDQYDVQQLDAZBbGlwYXkxQzBB
BgNVBAMMOuaUr+S7mOWunSjkuK3lm70p572R57uc5oqA5pyv5pyJ6ZmQ5YWs5Y+4LTIwODgxMDIx
ODA4NjEyMTQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCEOMiLnTX2xlPdU4+/GFTl
ZaYrG+aCssal3ORJ2hHhUkLcDFLPkD6UfYCH0UonT163/LApbuOfRcB6Bdk1jFqucJW1UvZ5y4gq
cRQZrcxSST8T58PFlxqhiP2WoQUGHwfawuo+NveAU+g8k/J5vDGqJ4MOIkPtopPPMs7pUsxOF3LP
fstIkj847kkcvVl/tvKoRucsWD6VF+fJKRjNF8lsY6Y/RQCEJLgUd+fb1ifIsyFLeO6xb42rNTfS
jdKS2v2f7cW6facl2cWlERD9xbXLBFMfblT7+JgPu689C3M3YyKOhtOXwoJJpQQjNGmgEDl7AR9c
+cvGIxMfCeHmUmbVAgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIE8DANBgkqhkiG9w0BAQsFAAOCAQEA
MS5GuEG5BrH94Jz+k5tdGiB54xgEvYLrQLLL9bOmsuJ4i21TakOAwnhWdfM3s+g0+O9G8lcKTRU4
y42kXM3Fn9tpO/vzGldMo4SDXEwqx8xXmSBmcSrUbtEe1oMj4OT+OWsKEndw67L3PBKoNl2Hkqtu
cWJmKCkINHHbn2r0LqAVd/MrHkVAEugBTLSe578N/KKR6e7Tcxw6FC62u3Mu+vZQI0Yt+ocz3ofA
iKtLJfEYuutW+FVnrD+W+vqSlazaBCwgGqa2CKVQTlfIvm271OHHK9LLGUhVKF+ohLpnETCPB287
FAy4TV9901jSoItfJQnXc0jmtqdk0O8IAMyjkQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBszCCAVegAwIBAgIIaeL+wBcKxnswDAYIKoEcz1UBg3UFADAuMQswCQYDVQQG
EwJDTjEOMAwGA1UECgwFTlJDQUMxDzANBgNVBAMMBlJPT1RDQTAeFw0xMjA3MTQw
MzExNTlaFw00MjA3MDcwMzExNTlaMC4xCzAJBgNVBAYTAkNOMQ4wDAYDVQQKDAVO
UkNBQzEPMA0GA1UEAwwGUk9PVENBMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE
MPCca6pmgcchsTf2UnBeL9rtp4nw+itk1Kzrmbnqo05lUwkwlWK+4OIrtFdAqnRT
V7Q9v1htkv42TsIutzd126NdMFswHwYDVR0jBBgwFoAUTDKxl9kzG8SmBcHG5Yti
W/CXdlgwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFEwysZfZ
MxvEpgXBxuWLYlvwl3ZYMAwGCCqBHM9VAYN1BQADSAAwRQIgG1bSLeOXp3oB8H7b
53W+CKOPl2PknmWEq/lMhtn25HkCIQDaHDgWxWFtnCrBjH16/W3Ezn7/U/Vjo5xI
pDoiVhsLwg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF0zCCA7ugAwIBAgIIH8+hjWpIDREwDQYJKoZIhvcNAQELBQAwejELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmlj
YXRpb24gQXV0aG9yaXR5MTEwLwYDVQQDDChBbnQgRmluYW5jaWFsIENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5IFIxMB4XDTE4MDMyMTEzNDg0MFoXDTM4MDIyODEzNDg0
MFowejELMAkGA1UEBhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNV
BAsMF0NlcnRpZmljYXRpb24gQXV0aG9yaXR5MTEwLwYDVQQDDChBbnQgRmluYW5j
aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFIxMIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEAtytTRcBNuur5h8xuxnlKJetT65cHGemGi8oD+beHFPTk
rUTlFt9Xn7fAVGo6QSsPb9uGLpUFGEdGmbsQ2q9cV4P89qkH04VzIPwT7AywJdt2
xAvMs+MgHFJzOYfL1QkdOOVO7NwKxH8IvlQgFabWomWk2Ei9WfUyxFjVO1LVh0Bp
dRBeWLMkdudx0tl3+21t1apnReFNQ5nfX29xeSxIhesaMHDZFViO/DXDNW2BcTs6
vSWKyJ4YIIIzStumD8K1xMsoaZBMDxg4itjWFaKRgNuPiIn4kjDY3kC66Sl/6yTl
YUz8AybbEsICZzssdZh7jcNb1VRfk79lgAprm/Ktl+mgrU1gaMGP1OE25JCbqli1
Pbw/BpPynyP9+XulE+2mxFwTYhKAwpDIDKuYsFUXuo8t261pCovI1CXFzAQM2w7H
DtA2nOXSW6q0jGDJ5+WauH+K8ZSvA6x4sFo4u0KNCx0ROTBpLif6GTngqo3sj+98
SZiMNLFMQoQkjkdN5Q5g9N6CFZPVZ6QpO0JcIc7S1le/g9z5iBKnifrKxy0TQjtG
PsDwc8ubPnRm/F82RReCoyNyx63indpgFfhN7+KxUIQ9cOwwTvemmor0A+ZQamRe
9LMuiEfEaWUDK+6O0Gl8lO571uI5onYdN1VIgOmwFbe+D8TcuzVjIZ/zvHrAGUcC
AwEAAaNdMFswCwYDVR0PBAQDAgEGMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFF90
tATATwda6uWx2yKjh0GynOEBMB8GA1UdIwQYMBaAFF90tATATwda6uWx2yKjh0Gy
nOEBMA0GCSqGSIb3DQEBCwUAA4ICAQCVYaOtqOLIpsrEikE5lb+UARNSFJg6tpkf
tJ2U8QF/DejemEHx5IClQu6ajxjtu0Aie4/3UnIXop8nH/Q57l+Wyt9T7N2WPiNq
JSlYKYbJpPF8LXbuKYG3BTFTdOVFIeRe2NUyYh/xs6bXGr4WKTXb3qBmzR02FSy3
IODQw5Q6zpXj8prYqFHYsOvGCEc1CwJaSaYwRhTkFedJUxiyhyB5GQwoFfExCVHW
05ZFCAVYFldCJvUzfzrWubN6wX0DD2dwultgmldOn/W/n8at52mpPNvIdbZb2F41
T0YZeoWnCJrYXjq/32oc1cmifIHqySnyMnavi75DxPCdZsCOpSAT4j4lAQRGsfgI
kkLPGQieMfNNkMCKh7qjwdXAVtdqhf0RVtFILH3OyEodlk1HYXqX5iE5wlaKzDop
PKwf2Q3BErq1xChYGGVS+dEvyXc/2nIBlt7uLWKp4XFjqekKbaGaLJdjYP5b2s7N
1dM0MXQ/f8XoXKBkJNzEiM3hfsU6DOREgMc1DIsFKxfuMwX3EkVQM1If8ghb6x5Y
jXayv+NLbidOSzk4vl5QwngO/JYFMkoc6i9LNwEaEtR9PhnrdubxmrtM+RjfBm02
77q3dSWFESFQ4QxYWew4pHE0DpWbWy/iMIKQ6UZ5RLvB8GEcgt8ON7BBJeMc+Dyi
kT9qhqn+lw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICiDCCAgygAwIBAgIIQX76UsB/30owDAYIKoZIzj0EAwMFADB6MQswCQYDVQQG
EwJDTjEWMBQGA1UECgwNQW50IEZpbmFuY2lhbDEgMB4GA1UECwwXQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkxMTAvBgNVBAMMKEFudCBGaW5hbmNpYWwgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkgRTEwHhcNMTkwNDI4MTYyMDQ0WhcNNDkwNDIwMTYyMDQ0
WjB6MQswCQYDVQQGEwJDTjEWMBQGA1UECgwNQW50IEZpbmFuY2lhbDEgMB4GA1UE
CwwXQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxMTAvBgNVBAMMKEFudCBGaW5hbmNp
YWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRTEwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAASCCRa94QI0vR5Up9Yr9HEupz6hSoyjySYqo7v837KnmjveUIUNiuC9pWAU
WP3jwLX3HkzeiNdeg22a0IZPoSUCpasufiLAnfXh6NInLiWBrjLJXDSGaY7vaokt
rpZvAdmjXTBbMAsGA1UdDwQEAwIBBjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBRZ
4ZTgDpksHL2qcpkFkxD2zVd16TAfBgNVHSMEGDAWgBRZ4ZTgDpksHL2qcpkFkxD2
zVd16TAMBggqhkjOPQQDAwUAA2gAMGUCMQD4IoqT2hTUn0jt7oXLdMJ8q4vLp6sg
wHfPiOr9gxreb+e6Oidwd2LDnC4OUqCWiF8CMAzwKs4SnDJYcMLf2vpkbuVE4dTH
Rglz+HGcTLWsFs4KxLsq7MuU+vJTBUeDJeDjdA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIUEMdk6dVgOEIS2cCP0Q43P90Ps5YwDQYJKoZIhvcNAQEF
BQAwajELMAkGA1UEBhMCQ04xEzARBgNVBAoMCmlUcnVzQ2hpbmExHDAaBgNVBAsM
E0NoaW5hIFRydXN0IE5ldHdvcmsxKDAmBgNVBAMMH2lUcnVzQ2hpbmEgQ2xhc3Mg
MiBSb290IENBIC0gRzMwHhcNMTMwNDE4MDkzNjU2WhcNMzMwNDE4MDkzNjU2WjBq
MQswCQYDVQQGEwJDTjETMBEGA1UECgwKaVRydXNDaGluYTEcMBoGA1UECwwTQ2hp
bmEgVHJ1c3QgTmV0d29yazEoMCYGA1UEAwwfaVRydXNDaGluYSBDbGFzcyAyIFJv
b3QgQ0EgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOPPShpV
nJbMqqCw6Bz1kehnoPst9pkr0V9idOwU2oyS47/HjJXk9Rd5a9xfwkPO88trUpz5
4GmmwspDXjVFu9L0eFaRuH3KMha1Ak01citbF7cQLJlS7XI+tpkTGHEY5pt3EsQg
wykfZl/A1jrnSkspMS997r2Gim54cwz+mTMgDRhZsKK/lbOeBPpWtcFizjXYCqhw
WktvQfZBYi6o4sHCshnOswi4yV1p+LuFcQ2ciYdWvULh1eZhLxHbGXyznYHi0dGN
z+I9H8aXxqAQfHVhbdHNzi77hCxFjOy+hHrGsyzjrd2swVQ2iUWP8BfEQqGLqM1g
KgWKYfcTGdbPB1MCAwEAAaNjMGEwHQYDVR0OBBYEFG/oAMxTVe7y0+408CTAK8hA
uTyRMB8GA1UdIwQYMBaAFG/oAMxTVe7y0+408CTAK8hAuTyRMA8GA1UdEwEB/wQF
MAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBLnUTfW7hp
emMbuUGCk7RBswzOT83bDM6824EkUnf+X0iKS95SUNGeeSWK2o/3ALJo5hi7GZr3
U8eLaWAcYizfO99UXMRBPw5PRR+gXGEronGUugLpxsjuynoLQu8GQAeysSXKbN1I
UugDo9u8igJORYA+5ms0s5sCUySqbQ2R5z/GoceyI9LdxIVa1RjVX8pYOj8JFwtn
DJN3ftSFvNMYwRuILKuqUYSHc2GPYiHVflDh5nDymCMOQFcFG3WsEuB+EYQPFgIU
1DHmdZcz7Llx8UOZXX2JupWCYzK1XhJb+r4hK5ncf/w8qGtYlmyJpxk3hr1TfUJX
Yf4Zr0fJsGuv
-----END CERTIFICATE-----
\ No newline at end of file
-----BEGIN CERTIFICATE-----
MIIDjzCCAnegAwIBAgIQICEFF/HtyxaCO2oCQWkPmzANBgkqhkiG9w0BAQsFADCBkTELMAkGA1UE
BhMCQ04xGzAZBgNVBAoMEkFudCBGaW5hbmNpYWwgdGVzdDElMCMGA1UECwwcQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkgdGVzdDE+MDwGA1UEAww1QW50IEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eSBDbGFzcyAyIFIxIHRlc3QwHhcNMjEwNTE3MDY1MjIwWhcNMjQwNTE1MDY1MjIwWjBh
MQswCQYDVQQGEwJDTjEVMBMGA1UECgwM5rKZ566x546v5aKDMQ8wDQYDVQQLDAZBbGlwYXkxKjAo
BgNVBAMMITIwODgxMDIxODA4NjEyMTQtMjAxNjEwMjQwMDc0OTk1ODCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAMXlvbsfcygrPuxBtZV3s7ng13AGmiRIyVBvM/nq72CoQ8okoxAutOMX
VeQCC6tCHxlhEyxUdSiCaX4Z6sGFtdmHdEhWnyCTz8WnuIC1PryTMnlhIx9NSvBXzNAYX4aoaZpk
eq1K0hySZnt92P2poKLIOsQ6pGMJao72FiOcnr5/0PkB3AD8o+BNfMoWNxuogcIkEljyZnruAVFb
24Z86L44cWIb3n/hKGDIXP32NI5pLpZWw9xTWH4gGHjE+rHrQ97QCEy4cHXFmmlbnodNTA1m8/fO
nTJJ2Hfoi1zkL5fKOAjuq7yAQRFW+H5DC48sj4jB4d5ngFIBm1zNvldAfykCAwEAAaMSMBAwDgYD
VR0PAQH/BAQDAgTwMA0GCSqGSIb3DQEBCwUAA4IBAQDA2xbDvXVJpX0LyosOtIq5CLiJaaI4VarO
HimM962qn4peNk/rXLGU0bigqW93TinnazfxFrCgovPrEaM8G7THGfhZcUDJ0l6QGtVqpARIrolx
B5FeXuvV+LlxzBYVWsma9HGRiNbVb/EFF6nAJrAQia7GfLSS7Jj7kfAz7hckpRUaMxdN95Xrzzxq
BQ+sGe2R59uSsbh6+y3rZzrgMtyhrezZHZI7S5cK4XI+RFRngaQdQjlF4lSA09GwJLXPP3f0tuXV
ixFQVZwnABfzpqqP3VtqO12hW4sJQqmVBrT7BpEn+19I8rPQVbWgNq2iAxUQxByO3VaHaogs1vkP
lJuY
-----END CERTIFICATE-----
\ No newline at end of file
00000=SUCCESS
SUCCESS=SUCCESS
A0001=\u5ba2\u6237\u7aef\u9519\u8bef
A0100=\u767b\u9646\u9519\u8bef
A0101=\u8d26\u53f7\u6216\u5bc6\u7801\u9519\u8bef
00000=SUCCESS
11111=FAIL
A0001=\u5ba2\u6237\u7aef\u9519\u8bef
A0100=\u767b\u9646\u9519\u8bef
A0101=\u8d26\u53f7\u6216\u5bc6\u7801\u9519\u8bef
A0102=\u5bc6\u7801\u683c\u5f0f\u9519\u8bef
A0103=\u5bc6\u7801\u4e0d\u4e00\u81f4
A0104=\u8d26\u53f7\u4e0d\u5b58\u5728
A0105=\u65e7\u5bc6\u7801\u9519\u8bef
A0106=\u60a8\u5df2\u88ab\u9501\u5b9a
A0107=\u65b0\u5bc6\u7801\u4e0d\u80fd\u4e3a\u0031\u0032\u0033\u0034\u0035\u0036
A0200=\u53c2\u6570\u9519\u8bef
A0201=\u53c2\u6570\u4e0d\u80fd\u4e3a\u7a7a
A0202=\u624b\u673a\u53f7\u7801\u683c\u5f0f\u4e0d\u6b63\u786e
A0203=\u9a8c\u8bc1\u7801\u9519\u8bef
A0204=\u624b\u673a\u53f7\u5df2\u6ce8\u518c
A0205=\u8be5\u624b\u673a\u5c1a\u672a\u6ce8\u518c
A0206=\u53c2\u6570\u503c\u4e0d\u80fd\u5c0f\u4e8e\u0030
A0207=\u5546\u54c1\u5e01\u5df2\u5b58\u5728
A0208=\u5546\u54c1\u5e01\u540d\u79f0\u4e0d\u80fd\u4e3a\u0052\u004d\u0042
A0209=\u5546\u54c1\u5df2\u5b58\u5728
A0210=\u8eab\u4efd\u8bc1\u683c\u5f0f\u9519\u8bef
A0211=\u4f60\u8f93\u5165\u7684\u9a8c\u8bc1\u7801\u6709\u8bef\uff0c\u8bf7\u91cd\u65b0\u8f93\u5165
A0300=\u0074\u006f\u006b\u0065\u006e\u9519\u8bef
A0301=\u7a7a\u0074\u006f\u006b\u0065\u006e\u9519\u8bef
A0302=\u975e\u6cd5\u0074\u006f\u006b\u0065\u006e\u9519\u8bef
A0400=\u6743\u9650\u9519\u8bef
A0401=\u60a8\u6ca1\u6709\u8be5\u6743\u9650
A0500=\u4e0a\u4f20\u9519\u8bef
A0501=\u4e0a\u4f20\u683c\u5f0f\u9519\u8bef
B0100=\u5f53\u524d\u4fe1\u606f\u5df2\u88ab\u4fee\u6539\uff0c\u66f4\u65b0\u5931\u8d25
B0101=\u53c2\u6570\u91cd\u590d
B0102=\u8d44\u6e90\u5df2\u88ab\u4f7f\u7528\uff0c\u4e0d\u53ef\u88ab\u5220\u9664
B0200=\u5546\u6237\u9519\u8bef
B0201=\u5546\u6237\u5df2\u53d6\u6d88\u5546\u54c1\u5ba1\u6838
B0300=\u5feb\u9012\u6a21\u677f\u9519\u8bef
B0301=\u8bf7\u5148\u521b\u5efa\u5feb\u9012\u6a21\u677f
C0001=\u7b2c\u4e09\u65b9\u9519\u8bef
C0100=\u006d\u0079\u0073\u0071\u006c\u9519\u8bef
C0101=\u4fdd\u5b58\u9519\u8bef
C0102=\u66f4\u65b0\u9519\u8bef
C0103=\u5220\u9664\u9519\u8bef
C0200=\u533A\u5757\u94FE\u9519\u8BEF
C0201=TOKEN\u5730\u5740\u521B\u5EFA\u5931\u8D25
C0202=\u5BFC\u51FA\u79C1\u94A5\u5931\u8D25
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<property name="LOG_HOME" value="./log"/>
<property name="LOG_FILE" value="mall-pay"/>
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/${LOG_FILE}.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>20MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
\ No newline at end of file
package com.fzm.mall.pay;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class MallPayApplicationTests {
@Test
void contextLoads() {
}
}
......@@ -4,12 +4,12 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.fzm.mall</groupId>
<artifactId>fzm-mall</artifactId>
<artifactId>fzm-yimu</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>mall-server-admin</artifactId>
<name>mall-server-admin</name>
<artifactId>yimu-server-admin</artifactId>
<name>yimu-server-admin</name>
<description>商城管理后台服务端</description>
<dependencies>
......
......@@ -51,6 +51,11 @@ public class Spu implements Serializable {
public static final Integer NFT_SALES_TYPE_COPY = 2;
/**
* 盲盒销售
*/
public static final Integer BLIND_BOX_SALES = 2;
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
......@@ -151,6 +156,9 @@ public class Spu implements Serializable {
@ApiModelProperty(value = "延迟提货(0--否 1--是)")
private Integer delayDelivery;
@ApiModelProperty(value = "销售方式 1.普通 2.盲盒")
private Integer salesType;
@ApiModelProperty(value = "ntf文件")
private String nftFile;
......
......@@ -6,9 +6,11 @@ import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.SystemClock;
import com.fzm.mall.server.admin.constant.MallResponseError;
import com.fzm.mall.server.admin.exception.MyException;
import com.fzm.mall.server.admin.goods_center.mapper.SkuMapper;
import com.fzm.mall.server.admin.goods_center.mapper.SpuMapper;
import com.fzm.mall.server.admin.goods_center.model.Category;
import com.fzm.mall.server.admin.goods_center.model.Sku;
import com.fzm.mall.server.admin.goods_center.model.Spu;
import com.fzm.mall.server.admin.goods_center.service.ICategoryService;
import com.fzm.mall.server.admin.marketing.service.ICouponCategoryService;
import com.fzm.mall.server.admin.marketing.service.ICouponSkuService;
......@@ -23,6 +25,7 @@ import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
......@@ -141,20 +144,48 @@ public class Coupon implements Serializable {
setAmt(circulation);
}
public void saveCouponSku(ICouponSkuService couponSkuService) {
List<CouponSku> skuList = getCouponSkuList();
if (CollectionUtils.isNotEmpty(skuList)) {
skuList.forEach(couponSku -> {
couponSku.setMerchantId(this.getMerchantId());
couponSku.setCouponId(this.getCouponId());
couponSku.setStatus(CouponSku.COUPON_SKU_STATUS_EFFECTIVE);
});
couponSkuService.saveBatch(skuList);
public void saveCouponSku(ICouponSkuService couponSkuService, SpuMapper spuMapper, SkuMapper skuMapper) {
batchSaveCouponSku(this.couponSkuList, couponSkuService, spuMapper, skuMapper);
}
public void batchSaveCouponSku(List<CouponSku> paramList, ICouponSkuService couponSkuService, SpuMapper spuMapper, SkuMapper skuMapper) {
if (CollectionUtils.isEmpty(paramList)) {
return;
}
/**
* 如果商品销售类型为盲盒,默认勾选盲盒的所有规格(即使前台勾选不完全)
*/
List<String> goodsIdList = paramList.stream().map(CouponSku::getGoodsId).collect(Collectors.toList());
QueryWrapper<Spu> queryWrapper = new QueryWrapper<>();
queryWrapper.in("goods_id", goodsIdList);
List<Spu> spuList = spuMapper.selectList(queryWrapper);
List<String> blindBoxSpuIdList = spuList.stream().filter(spu -> spu.getSalesType().equals(Spu.BLIND_BOX_SALES)).map(Spu::getGoodsId).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(blindBoxSpuIdList)) {
List<String> paramSkuIdList = paramList.stream().map(CouponSku::getSkuId).collect(Collectors.toList());
QueryWrapper<Sku> skuQueryWrapper = new QueryWrapper<>();
skuQueryWrapper.in("goods_id", goodsIdList);
List<Sku> dataSkuList = skuMapper.selectList(skuQueryWrapper);
List<Sku> skuToAddList = dataSkuList.stream().filter(sku -> !paramSkuIdList.contains(sku.getSkuId())).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(skuToAddList)) {
skuToAddList.forEach(sku -> {
CouponSku couponSku = new CouponSku();
couponSku.setSkuId(sku.getSkuId());
couponSku.setGoodsId(sku.getGoodsId());
paramList.add(couponSku);
});
}
}
paramList.forEach(couponSku -> {
couponSku.setMerchantId(this.getMerchantId());
couponSku.setCouponId(this.getCouponId());
couponSku.setStatus(CouponSku.COUPON_SKU_STATUS_EFFECTIVE);
});
couponSkuService.saveBatch(paramList);
}
public void updateCouponSku(ICouponSkuService couponSkuService) {
List<CouponSku> paramSkuList = getCouponSkuList();
public void updateCouponSku(ICouponSkuService couponSkuService, SpuMapper spuMapper, SkuMapper skuMapper) {
List<CouponSku> paramSkuList = this.couponSkuList;
List<CouponSku> dataSkuList = couponSkuService.list(new QueryWrapper<CouponSku>().eq("coupon_id", this.getCouponId())
.eq("merchant_id", this.getMerchantId())
.eq("status", CouponSku.COUPON_SKU_STATUS_EFFECTIVE));
......@@ -163,13 +194,7 @@ public class Coupon implements Serializable {
List<CouponSku> reaminList = paramSkuList;
reaminList.removeAll(toAddList);
toAddList.forEach(couponSku -> {
couponSku.setCouponId(this.getCouponId());
couponSku.setMerchantId(this.getMerchantId());
couponSku.setStatus(CouponSku.COUPON_SKU_STATUS_EFFECTIVE);
});
couponSkuService.saveBatch(toAddList);
batchSaveCouponSku(toAddList, couponSkuService, spuMapper, skuMapper);
List<String> reaminCouponIdList = reaminList.stream().map(CouponSku::getGoodsId).collect(Collectors.toList());
......
......@@ -8,6 +8,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fzm.mall.server.admin.constant.MallResponseError;
import com.fzm.mall.server.admin.exception.MyException;
import com.fzm.mall.server.admin.goods_center.mapper.SkuMapper;
import com.fzm.mall.server.admin.goods_center.mapper.SpuMapper;
import com.fzm.mall.server.admin.goods_center.service.ICategoryService;
import com.fzm.mall.server.admin.marketing.entity.dto.CouponDTO;
import com.fzm.mall.server.admin.marketing.entity.dto.CouponSaveDTO;
......@@ -44,6 +46,8 @@ public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> impleme
private final ICouponSkuService couponSkuService;
private final ICouponCategoryService couponCategoryService;
private final ICategoryService categoryService;
private final SpuMapper spuMapper;
private final SkuMapper skuMapper;
@Override
public List<Coupon> listByOid(String oid, String uid, List<String> goodsIdList) {
......@@ -58,7 +62,7 @@ public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> impleme
coupon.init();
coupon.saveCouponSku(couponSkuService);
coupon.saveCouponSku(couponSkuService, spuMapper, skuMapper);
coupon.saveCategory(couponCategoryService);
couponMapper.insert(coupon);
......@@ -86,7 +90,7 @@ public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> impleme
throw new MyException(MallResponseError.UPDATE_FAIL);
}
coupon.updateCouponSku(couponSkuService);
coupon.updateCouponSku(couponSkuService, spuMapper, skuMapper);
coupon.updateCategory(couponCategoryService);
}
......
......@@ -26,6 +26,7 @@
<result column="delay_delivery" property="delayDelivery"/>
<result column="nft_file" property="nftFile"/>
<result column="hash" property="hash"/>
<result column="sales_type" property="salesType"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<result column="sales_type" property="salesType"/>
......
......@@ -4,12 +4,12 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.fzm.mall</groupId>
<artifactId>fzm-mall</artifactId>
<artifactId>fzm-yimu</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>mall-server-front</artifactId>
<name>mall-server-front</name>
<artifactId>yimu-server-front</artifactId>
<name>yimu-server-front</name>
<description>商城前台服务端</description>
<dependencies>
......
......@@ -95,13 +95,3 @@ alipay:
pay-notify-url: http://testwxc.huowulian365.com/root/xls/alipay/without/payNotify
pay-postfee-url: http://testwxc.huowulian365.com/root/xls/alipay/without/payPostFee
refund-notify-url: http://testwxc.huowulian365.com/root/pay/refundNotify
midea:
pay:
url: https://in.mideaepayuat.com/gateway.htm
partner: 1010088401
trade-notify-url:
batch-trade-notify-url:
refund-notify-url:
get-sign-url: http://172.22.20.54:8005/rsa/sign.htm
check-sign-url: http://172.22.20.54:8005/rsa/checkSign.htm
\ No newline at end of file
......@@ -95,13 +95,3 @@ express:
key: key
# 阿里云调用: appcode
appcode: d97dd0febda34a64a751f3ff6ea6d242
midea:
pay:
url: https://in.mideaepayuat.com/gateway.htm
partner: 1010088401
trade-notify-url:
batch-trade-notify-url:
refund-notify-url:
get-sign-url: http://172.22.20.54:8005/rsa/sign.htm
check-sign-url: http://172.22.20.54:8005/rsa/checkSign.htm
......@@ -75,14 +75,4 @@ alipay:
notify-url: http://47.99.152.140:8092/pay/mainNotify
pay-notify-url: http://testwxc.huowulian365.com/root/xls/alipay/without/payNotify
pay-postfee-url: http://testwxc.huowulian365.com/root/xls/alipay/without/payPostFee
refund-notify-url: http://testwxc.huowulian365.com/root/pay/refundNotify
midea:
pay:
url: https://in.mideaepayuat.com/gateway.htm
partner: 1010088401
trade-notify-url:
batch-trade-notify-url:
refund-notify-url:
get-sign-url: http://172.22.20.54:8005/rsa/sign.htm
check-sign-url: http://172.22.20.54:8005/rsa/checkSign.htm
\ No newline at end of file
refund-notify-url: http://testwxc.huowulian365.com/root/pay/refundNotify
\ No newline at end of file
......@@ -12,7 +12,7 @@
</parent>
<groupId>com.fzm.mall</groupId>
<artifactId>fzm-mall</artifactId>
<artifactId>fzm-yimu</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
......@@ -23,9 +23,6 @@
<module>mall-server-admin</module>
<!-- 商城客服中心 -->
<module>mall-customer-service</module>
<!-- 商城支付 -->
<module>mall-pay</module>
<module>mall-midea-pay</module>
</modules>
<properties>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment