[fix]修改视频相关

This commit is contained in:
wangqx 2025-08-21 15:47:21 +08:00
parent 99b07e32fb
commit b8702f827c
100 changed files with 8871 additions and 298 deletions

View File

@ -19,6 +19,13 @@ import com.wzj.soopin.member.service.IAccountBillService;
import com.wzj.soopin.member.service.IMemberAccountService; import com.wzj.soopin.member.service.IMemberAccountService;
import com.wzj.soopin.member.service.IMemberBankService; import com.wzj.soopin.member.service.IMemberBankService;
import com.wzj.soopin.member.service.IMemberService; import com.wzj.soopin.member.service.IMemberService;
import com.wzj.soopin.transaction.convert.ChargeConvert;
import com.wzj.soopin.transaction.convert.WithdrawConvert;
import com.wzj.soopin.transaction.domain.bo.ChargeBO;
import com.wzj.soopin.transaction.domain.bo.WithdrawBO;
import com.wzj.soopin.transaction.enums.WithdrawType;
import com.wzj.soopin.transaction.service.IChargeService;
import com.wzj.soopin.transaction.service.IWithdrawService;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import me.zhyd.oauth.model.AuthResponse; import me.zhyd.oauth.model.AuthResponse;
@ -30,6 +37,8 @@ import org.dromara.common.core.domain.model.SocialLoginBody;
import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.ValidatorUtils; import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.social.config.properties.SocialProperties; import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.utils.SocialUtils; import org.dromara.common.social.utils.SocialUtils;
@ -56,7 +65,11 @@ public class AppMemberController {
private final IMemberBankService memberBankService; private final IMemberBankService memberBankService;
private final MemberBankConvert memberBankConvert; private final MemberBankConvert memberBankConvert;
private final IChargeService chargeService;
private final ChargeConvert chargeConvert;
private final IWithdrawService withdrawService;
private final WithdrawConvert withdrawConvert;
@Tag(name = "获取会员账户信息详细信息") @Tag(name = "获取会员账户信息详细信息")
@GetMapping(value = "/info") @GetMapping(value = "/info")
@ -145,4 +158,35 @@ public class AppMemberController {
return R.ok(authUserData.getToken()); return R.ok(authUserData.getToken());
} }
@Tag(name = "充值")
@Log(title = "新增 ", businessType = BusinessType.INSERT)
@PostMapping("/charge")
public R charge(@RequestBody ChargeBO bo) {
//获取用户信息
LoginUser loginUser = LoginHelper.getLoginUser();
if (loginUser == null) {
throw new ServiceException("用户未登录");
}
Long memberId = loginUser.getUserId();
bo.setMemberId(memberId);
return R.ok(chargeService.charge(chargeConvert.toPo(bo)));
}
@Tag(name = "提现")
@Log(title = "提现 ", businessType = BusinessType.INSERT)
@PostMapping("/withdraw")
public R withdraw(@RequestBody WithdrawBO bo) {
//获取用户信息
LoginUser loginUser = LoginHelper.getLoginUser();
if (loginUser == null) {
throw new ServiceException("用户未登录");
}
Long memberId = loginUser.getUserId();
bo.setMemberId(memberId);
bo.setType(WithdrawType.WALLET.getCode());
return R.ok(withdrawService.withdrawWallet(withdrawConvert.toPo(bo)));
}
} }

View File

@ -0,0 +1,76 @@
package org.dromara.app;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wzj.soopin.content.domain.bo.IndexListBO;
import com.wzj.soopin.content.domain.bo.MyListBO;
import com.wzj.soopin.content.domain.bo.SimpleListBO;
import com.wzj.soopin.content.domain.bo.VlogBO;
import com.wzj.soopin.content.service.VlogService;
import com.wzj.soopin.content.service.VlogUploadService;
import com.wzj.soopin.content.utils.PagedGridResult;
import com.wzj.soopin.content.utils.QcCloud;
import com.wzj.soopin.content.utils.RedisOperator;
import com.wzj.soopin.order.domain.bo.OrderBo;
import com.wzj.soopin.order.domain.entity.Order;
import com.wzj.soopin.order.domain.vo.OrderVO;
import com.wzj.soopin.order.service.OrderItemService;
import com.wzj.soopin.order.service.impl.OrderServiceImpl;
import io.swagger.annotations.Api;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static com.wzj.soopin.content.domain.base.BaseInfoProperties.*;
@Slf4j
@Api(tags = "订单相关接口")
@RequestMapping("/app/order")
@RestController
@RequiredArgsConstructor
public class AppOrderController {
private final OrderServiceImpl orderService;
private final OrderItemService orderItemService;
public RedisOperator redis;
@Tag(name ="查询订单列表")
@PostMapping("/page")
public R<IPage<OrderVO>> list(@RequestBody OrderBo query, Page<Order> page){
return R.ok(orderService.getlist(page,query));
}
@Tag(name ="新增订单表")
@Log(title = "订单表", businessType = BusinessType.INSERT)
@PostMapping("/add")
public R add(@RequestBody Order order) {
R<Order> result = orderService.insert(order);
if (result != null) {
// 订单创建成功发送消息
orderService.sendMessage(order);
log.info("订单创建成功消息已发送订单ID: {}", order.getId());
} else {
log.warn("订单创建失败: {}", "返回结果为null");
}
return result;
}
}

View File

@ -0,0 +1,105 @@
package org.dromara.app;
import com.wzj.soopin.transaction.enums.PaymentClientEnum;
import com.wzj.soopin.transaction.enums.PaymentMethodEnum;
import com.wzj.soopin.transaction.kit.CashierSupport;
import com.wzj.soopin.transaction.kit.dto.PayParam;
import com.wzj.soopin.transaction.kit.params.dto.CashierParam;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.ResultCode;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 买家端,收银台接口
*
* @author Chopper
* @since 2020-12-18 16:59
*/
@Slf4j
@RestController
@Api(tags = "买家端,收银台接口")
@RequestMapping("/app/payment")
@RequiredArgsConstructor
public class AppPayController {
private final CashierSupport cashierSupport;
// @ApiImplicitParams({
// @ApiImplicitParam(name = "client", value = "客户端类型", paramType = "path", allowableValues = "PC,H5,WECHAT_MP,APP")
// })
@PostMapping(value = "/tradeDetail")
@ApiOperation(value = "获取支付详情")
public R paymentParams(@RequestBody @Validated PayParam payParam) {
CashierParam cashierParam = cashierSupport.cashierParam(payParam);
return R.ok( cashierParam);
}
@ApiImplicitParams({
@ApiImplicitParam(name = "paymentMethod", value = "支付方式", paramType = "path", allowableValues = "WECHAT,ALIPAY"),
@ApiImplicitParam(name = "paymentClient", value = "调起方式", paramType = "path", allowableValues = "APP,NATIVE,JSAPI,H5,MP")
})
@PostMapping(value = "/pay")
@ApiOperation(value = "支付")
public R payment(
HttpServletRequest request,
HttpServletResponse response,
@RequestBody @Validated PayParam payParam) {
PaymentMethodEnum paymentMethodEnum = PaymentMethodEnum.valueOf(payParam.getPaymentMethod());
PaymentClientEnum paymentClientEnum = PaymentClientEnum.valueOf(payParam.getPaymentClient());
try {
return cashierSupport.payment(paymentMethodEnum, paymentClientEnum, request, response, payParam);
} catch (ServiceException se) {
log.info("支付异常", se);
throw se;
} catch (Exception e) {
log.error("收银台支付错误", e);
}
return null;
}
@ApiOperation(value = "支付回调")
@RequestMapping(value = "/callback/{paymentMethod}", method = {RequestMethod.GET, RequestMethod.POST})
public R<Object> callback(HttpServletRequest request, @PathVariable String paymentMethod) {
PaymentMethodEnum paymentMethodEnum = PaymentMethodEnum.valueOf(paymentMethod);
cashierSupport.callback(paymentMethodEnum, request);
return R.ok(ResultCode.PAY_SUCCESS);
}
@ApiOperation(value = "支付异步通知")
@RequestMapping(value = "/notify/{paymentMethod}", method = {RequestMethod.GET, RequestMethod.POST})
public void notify(HttpServletRequest request, @PathVariable String paymentMethod) {
PaymentMethodEnum paymentMethodEnum = PaymentMethodEnum.valueOf(paymentMethod);
cashierSupport.notify(paymentMethodEnum, request);
}
@ApiOperation(value = "查询支付结果")
@GetMapping(value = "/result")
public R<Boolean> paymentResult(PayParam payParam) {
return R.ok(cashierSupport.paymentResult(payParam));
}
}

View File

@ -14,9 +14,11 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@Tag(name ="商品分类接口列表") @Tag(name ="商品分类接口列表")
@RestController @RestController
@RequestMapping("/app/productCategory") @RequestMapping("/app/product/category")
@RequiredArgsConstructor @RequiredArgsConstructor
public class AppProductCategoryController { public class AppProductCategoryController {
@ -30,5 +32,11 @@ public class AppProductCategoryController {
Page<ProductCategory> productCategoryPage = service.page(page,query.toWrapper()); Page<ProductCategory> productCategoryPage = service.page(page,query.toWrapper());
return R.ok(convert.toVO(productCategoryPage)); return R.ok(convert.toVO(productCategoryPage));
} }
@Tag(name = "查询列表")
@PostMapping("/tree")
public R<List<ProductCategoryVO>> tree(@RequestBody ProductCategoryBo bo ) {
List<ProductCategoryVO> articleList = service.tree( bo.toWrapper());
return R.ok( articleList);
}
} }

View File

@ -0,0 +1,44 @@
package org.dromara.app;
import com.wzj.soopin.transaction.convert.WithdrawConvert;
import com.wzj.soopin.transaction.domain.bo.WithdrawBO;
import com.wzj.soopin.transaction.enums.WithdrawType;
import com.wzj.soopin.transaction.service.IWithdrawService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "租户")
@RestController
@RequestMapping("/app/tenant")
@RequiredArgsConstructor
public class AppTenantController {
private final IWithdrawService withdrawService;
private final WithdrawConvert withdrawConvert;
@Tag(name = "提现")
@Log(title = "提现 ", businessType = BusinessType.INSERT)
@PostMapping("/withdraw")
public R withdraw(@RequestBody WithdrawBO bo) {
//获取用户信息
LoginUser loginUser = LoginHelper.getLoginUser();
if (loginUser == null) {
throw new ServiceException("用户未登录");
}
Long memberId = loginUser.getUserId();
bo.setMemberId(memberId);
bo.setType(WithdrawType.REVENUE.getCode());
return R.ok(withdrawService.withdrawRevenue(withdrawConvert.toPo(bo)));
}
}

View File

@ -2,10 +2,9 @@ package org.dromara.app;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.wzj.soopin.content.domain.bo.IndexListBO; import com.wzj.soopin.content.domain.bo.*;
import com.wzj.soopin.content.domain.bo.MyListBO; import com.wzj.soopin.content.domain.po.MyLikedVlog;
import com.wzj.soopin.content.domain.bo.SimpleListBO; import com.wzj.soopin.content.domain.vo.IndexVlogVO;
import com.wzj.soopin.content.domain.bo.VlogBO;
import com.wzj.soopin.content.service.VlogService; import com.wzj.soopin.content.service.VlogService;
import com.wzj.soopin.content.service.VlogUploadService; import com.wzj.soopin.content.service.VlogUploadService;
import com.wzj.soopin.content.utils.PagedGridResult; import com.wzj.soopin.content.utils.PagedGridResult;
@ -45,60 +44,58 @@ public class AppVlogController {
public RedisOperator redis; public RedisOperator redis;
@Tag(name = "首页视频列表") @Tag(name = "首页视频列表")
@PostMapping("/indexList") @PostMapping("/indexList")
public R<PagedGridResult> indexList(@RequestBody IndexListBO bo, @RequestBody Page page) { public R<Page<IndexVlogVO>> indexList(@RequestBody IndexListBO bo, @RequestBody Page page) {
PagedGridResult pages = vlogService.getIndexVlogList(bo, page); Page<IndexVlogVO> pages = vlogService.getIndexVlogList(bo, page);
return R.ok(pages); return R.ok(pages);
} }
@GetMapping("/detail") @GetMapping("/detail/{vlogId}")
public R<Object> detail(@RequestParam(defaultValue = "") String userId, public R<Object> detail( @PathVariable String vlogId) {
@RequestParam String vlogId) {
return R.ok(vlogService.getVlogDetailById(userId, vlogId)); return R.ok(vlogService.getVlogDetailById(vlogId));
} }
@Tag(name = "我的私密视频列表") @Tag(name = "我的私密视频列表")
@PostMapping("/myPrivateList") @PostMapping("/myPrivateList")
public R<PagedGridResult> myPrivateList(@RequestBody MyListBO bo, @RequestBody Page page) { public R<Page<IndexVlogVO>> myPrivateList(@RequestBody MyListBO bo, @RequestBody Page page) {
LoginUser loginUser = LoginHelper.getLoginUser(); LoginUser loginUser = LoginHelper.getLoginUser();
if (loginUser == null) { if (loginUser == null) {
throw new ServiceException("用户未登录"); throw new ServiceException("用户未登录");
} }
bo.setUserId(String.valueOf(loginUser.getUserId())); bo.setUserId(String.valueOf(loginUser.getUserId()));
PagedGridResult pages = vlogService.queryMyVlogList(bo, page); Page<IndexVlogVO> pages = vlogService.queryMyVlogList(bo, page);
return R.ok(pages); return R.ok(pages);
} }
@Tag(name = "我点赞的视频列表") @Tag(name = "我点赞的视频列表")
@PostMapping("/myLikedList") @PostMapping("/myLikedList")
public R<PagedGridResult> myLikedList(@RequestBody MyListBO bo, @RequestBody Page page) { public R<Page<IndexVlogVO>> myLikedList(@RequestBody MyLikedVlogBO bo, @RequestBody Page page) {
LoginUser loginUser = LoginHelper.getLoginUser();
if (loginUser == null) { Page<IndexVlogVO> pages = vlogService.getMyLikedVlogList(page);
throw new ServiceException("用户未登录");
}
bo.setUserId(String.valueOf(loginUser.getUserId()));
PagedGridResult pages = vlogService.getMyLikedVlogList(bo, page);
return R.ok(pages); return R.ok(pages);
} }
@Tag(name = "我关注的视频列表") @Tag(name = "我关注的人的视频列表")
@PostMapping("/followList") @PostMapping("/followList")
public R<PagedGridResult> followList(@RequestBody SimpleListBO bo, @RequestBody Page page) { public R<Page<IndexVlogVO>> followList(@RequestBody SimpleListBO bo, @RequestBody Page page) {
LoginUser loginUser = LoginHelper.getLoginUser(); LoginUser loginUser = LoginHelper.getLoginUser();
if (loginUser == null) { if (loginUser == null) {
throw new ServiceException("用户未登录"); throw new ServiceException("用户未登录");
} }
bo.setMyId(String.valueOf(loginUser.getUserId())); bo.setMyId(String.valueOf(loginUser.getUserId()));
PagedGridResult pages = vlogService.getMyFollowVlogList(bo, page); Page<IndexVlogVO> pages = vlogService.getMyFollowVlogList( page);
return R.ok(pages); return R.ok(pages);
} }
@Tag(name = "好友视频列表") @Tag(name = "好友视频列表")
@PostMapping("/friendList") @PostMapping("/friendList")
public R<PagedGridResult> friendList(@RequestBody SimpleListBO bo, @RequestBody Page page) { public R<Page<IndexVlogVO>> friendList(@RequestBody SimpleListBO bo, @RequestBody Page page) {
LoginUser loginUser = LoginHelper.getLoginUser(); LoginUser loginUser = LoginHelper.getLoginUser();
if (loginUser == null) { if (loginUser == null) {
throw new ServiceException("用户未登录"); throw new ServiceException("用户未登录");
} }
bo.setMyId(String.valueOf(loginUser.getUserId())); bo.setMyId(String.valueOf(loginUser.getUserId()));
PagedGridResult pages = vlogService.getMyFriendVlogList(bo, page); Page<IndexVlogVO> pages = vlogService.getMyFriendVlogList( page);
return R.ok(pages); return R.ok(pages);
} }
@PostMapping("publish") @PostMapping("publish")
@ -122,8 +119,8 @@ public class AppVlogController {
} }
@Tag(name = "我的公开视频列表") @Tag(name = "我的公开视频列表")
@PostMapping("/myPublicList") @PostMapping("/myPublicList")
public R<PagedGridResult> myPublicList(@RequestBody MyListBO bo, @RequestBody Page page) { public R<Page<IndexVlogVO>> myPublicList(@RequestBody MyListBO bo, @RequestBody Page page) {
PagedGridResult pages = vlogService.queryMyVlogList(bo, page); Page<IndexVlogVO> pages = vlogService.queryMyVlogList(bo, page);
return R.ok(pages); return R.ok(pages);
} }
@ -138,11 +135,11 @@ public class AppVlogController {
if (loginUser == null) { if (loginUser == null) {
throw new ServiceException("用户未登录"); throw new ServiceException("用户未登录");
} }
String userId = String.valueOf(loginUser.getUserId()); String userId = String.valueOf(loginUser.getUserId());
String vlogerId = params.get("vlogerId"); String vlogerId = params.get("vlogerId");
String vlogId = params.get("vlogId"); String vlogId = params.get("vlogId");
if (StringUtils.isBlank(vlogerId) || StringUtils.isBlank(vlogId)) { if (StringUtils.isBlank(vlogerId) || StringUtils.isBlank(vlogId)) {
throw new ServiceException("参数不完整"); throw new ServiceException("参数不完整");
} }
@ -180,11 +177,11 @@ public class AppVlogController {
if (loginUser == null) { if (loginUser == null) {
throw new ServiceException("用户未登录"); throw new ServiceException("用户未登录");
} }
String userId = String.valueOf(loginUser.getUserId()); String userId = String.valueOf(loginUser.getUserId());
String vlogerId = params.get("vlogerId"); String vlogerId = params.get("vlogerId");
String vlogId = params.get("vlogId"); String vlogId = params.get("vlogId");
if (StringUtils.isBlank(vlogerId) || StringUtils.isBlank(vlogId)) { if (StringUtils.isBlank(vlogerId) || StringUtils.isBlank(vlogId)) {
throw new ServiceException("参数不完整"); throw new ServiceException("参数不完整");
} }

View File

@ -0,0 +1,30 @@
package org.dromara.app;
import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 买家端,退款回调
*
* @author Chopper
* @since 2020-12-18 16:59
*/
@Api(tags = "买家端,退款回调")
@RestController
@RequestMapping("/buyer/payment/cashierRefund")
@RequiredArgsConstructor
public class CashierRefundController {
// private final RefundSupport refundSupport;
//
//
// @ApiOperation(value = "退款通知")
// @RequestMapping(value = "/notify/{paymentMethod}", method = {RequestMethod.GET, RequestMethod.POST})
// public void notify(HttpServletRequest request, @PathVariable String paymentMethod) {
// PaymentMethodEnum paymentMethodEnum = PaymentMethodEnum.valueOf(paymentMethod);
// refundSupport.notify(paymentMethodEnum, request);
// }
}

View File

@ -99,7 +99,7 @@ sa-token:
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true is-concurrent: true
# 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) # 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: false is-share: true
# jwt秘钥 # jwt秘钥
jwt-secret-key: abcdefghijklmnopqrstuvwxyz jwt-secret-key: abcdefghijklmnopqrstuvwxyz
@ -293,6 +293,8 @@ xss:
# 排除链接(多个用逗号分隔) # 排除链接(多个用逗号分隔)
excludeUrls: excludeUrls:
- /system/notice - /system/notice
- /pms/product/add
- /pms/product/update
# 全局线程池相关配置 # 全局线程池相关配置
# 如使用JDK21请直接使用虚拟线程 不要开启此配置 # 如使用JDK21请直接使用虚拟线程 不要开启此配置

View File

@ -46,7 +46,7 @@ public class SaTokenExceptionHandler {
public R<Void> handleNotLoginException(NotLoginException e, HttpServletRequest request) { public R<Void> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
String requestURI = request.getRequestURI(); String requestURI = request.getRequestURI();
log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage()); log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "认证失败,无法访问系统资源"); return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "认证失败,无法访问系统资源:" + e.getMessage());
} }
} }

View File

@ -115,6 +115,7 @@ public class TenantSaTokenDao extends PlusSaTokenDao {
*/ */
@Override @Override
public long getObjectTimeout(String key) { public long getObjectTimeout(String key) {
return super.getObjectTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key); return super.getObjectTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key);
} }

View File

@ -12,6 +12,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
public class SnailJobServerApplication { public class SnailJobServerApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(com.aizuda.snailjob.server.SnailJobServerApplication.class, args); SpringApplication.run(com.aizuda.snailjob.server.SnailJobServerApplication.class, args);
} }

View File

@ -0,0 +1,37 @@
package com.wzj.soopin.content.domain.bo;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.wzj.soopin.content.domain.po.MyLikedVlog;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.domain.BaseBO;
import org.dromara.common.core.domain.model.BaseAudit;
@TableName("cont_my_liked_vlog")
@Data
@EqualsAndHashCode(callSuper = true)
public class MyLikedVlogBO extends BaseBO<MyLikedVlog> {
@TableId
private String id;
/**
* 用户id
*/
private String userId;
/**
* 喜欢的短视频id
*/
@TableField("vlog_id")
private String vlogId;
@Override
public LambdaQueryWrapper<MyLikedVlog> toWrapper() {
return super.toWrapper().eq(userId!=null,MyLikedVlog::getUserId,userId)
.eq(vlogId!=null,MyLikedVlog::getVlogId,vlogId);
}
}

View File

@ -1,34 +1,26 @@
package com.wzj.soopin.content.domain.bo; package com.wzj.soopin.content.domain.bo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.wzj.soopin.content.domain.po.Vlog;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.dromara.common.core.domain.BaseBO;
public class MyListBO { @Data
public class MyListBO extends BaseBO<Vlog> {
@Schema(description = "用户ID") @Schema(description = "用户ID")
private String userId; private String userId;
@Schema(description = "我的ID") @Schema(description = "我的ID")
private String myId; private String myId;
@Schema(description = "是否公开1公开0私密") @Schema(description = "是否公开1公开0私密")
private Integer yesOrNo; private Integer privateFlag;
@Schema(description = "页码", defaultValue = "1") @Schema(description = "页码", defaultValue = "1")
private Long pageNum = 1L; private Long pageNum = 1L;
@Schema(description = "每页大小", defaultValue = "10") @Schema(description = "每页大小", defaultValue = "10")
private Long pageSize = 10L; private Long pageSize = 10L;
// getter/setter
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
public String getMyId() { return myId; }
public void setMyId(String myId) { this.myId = myId; }
public Integer getYesOrNo() { return yesOrNo; }
public void setYesOrNo(Integer yesOrNo) { this.yesOrNo = yesOrNo; }
public Long getPageNum() { return pageNum; }
public void setPageNum(Long pageNum) { this.pageNum = pageNum; }
public Long getPageSize() { return pageSize; }
public void setPageSize(Long pageSize) { this.pageSize = pageSize; }
} }

View File

@ -1,11 +1,11 @@
package com.wzj.soopin.content.domain.bo; package com.wzj.soopin.content.domain.bo;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
public class SimpleListBO { @Data
public class SimpleListBO {
@Schema(description = "我的ID") @Schema(description = "我的ID")
private String myId; private String myId;
// getter/setter
public String getMyId() { return myId; } }
public void setMyId(String myId) { this.myId = myId; }
}

View File

@ -1,6 +1,7 @@
package com.wzj.soopin.content.mapper; package com.wzj.soopin.content.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore; import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wzj.soopin.content.domain.po.Vlog; import com.wzj.soopin.content.domain.po.Vlog;
import com.wzj.soopin.content.domain.vo.IndexVlogVO; import com.wzj.soopin.content.domain.vo.IndexVlogVO;
import com.wzj.soopin.content.domain.vo.VlogerVO; import com.wzj.soopin.content.domain.vo.VlogerVO;
@ -13,16 +14,25 @@ import java.util.Map;
@Repository @Repository
@InterceptorIgnore(tenantLine = "true") @InterceptorIgnore(tenantLine = "true")
public interface VlogMapperCustom extends BaseMapperPlus<Vlog, VlogerVO> { public interface VlogMapperCustom extends BaseMapperPlus<Vlog, VlogerVO> {
public List<IndexVlogVO> getIndexVlogList(@Param("paramMap")Map<String, Object> map); List<IndexVlogVO> getIndexVlogList(@Param("paramMap") Map<String, Object> map);
public List<IndexVlogVO> getVlogDetailById(@Param("paramMap")Map<String, Object> map); Page<IndexVlogVO> getIndexVlogList(@Param("paramMap") Map<String, Object> map, Page<IndexVlogVO> page);
public List<IndexVlogVO> getMyLikedVlogList(@Param("paramMap")Map<String, Object> map); List<IndexVlogVO> getVlogDetailById(@Param("paramMap") Map<String, Object> map);
public List<IndexVlogVO> getMyFollowVlogList(@Param("paramMap")Map<String, Object> map); List<IndexVlogVO> getMyLikedVlogList(@Param("paramMap") Map<String, Object> map);
public List<IndexVlogVO> getMyFriendVlogList(@Param("paramMap")Map<String, Object> map); Page<IndexVlogVO> getMyLikedVlogList(@Param("paramMap") Map<String, Object> map, Page<IndexVlogVO> page);
List<IndexVlogVO> getMyFollowVlogList(@Param("paramMap") Map<String, Object> map);
Page<IndexVlogVO> getMyFollowVlogList(@Param("paramMap") Map<String, Object> map, Page<IndexVlogVO> page);
List<IndexVlogVO> getMyFriendVlogList(@Param("paramMap") Map<String, Object> map);
Page<IndexVlogVO> getMyFriendVlogList(@Param("paramMap") Map<String, Object> map, Page<IndexVlogVO> page);
} }

View File

@ -2,13 +2,10 @@ package com.wzj.soopin.content.service;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wzj.soopin.content.domain.bo.VlogBO; import com.wzj.soopin.content.domain.bo.*;
import com.wzj.soopin.content.domain.po.Vlog; import com.wzj.soopin.content.domain.po.Vlog;
import com.wzj.soopin.content.domain.vo.IndexVlogVO; import com.wzj.soopin.content.domain.vo.IndexVlogVO;
import com.wzj.soopin.content.utils.PagedGridResult; import com.wzj.soopin.content.utils.PagedGridResult;
import com.wzj.soopin.content.domain.bo.SimpleListBO;
import com.wzj.soopin.content.domain.bo.MyListBO;
import com.wzj.soopin.content.domain.bo.IndexListBO;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -31,12 +28,12 @@ public interface VlogService {
/** /**
* 查询首页/搜索的vlog列表 * 查询首页/搜索的vlog列表
*/ */
public PagedGridResult getIndexVlogList(IndexListBO bo, Page page); public Page<IndexVlogVO> getIndexVlogList(IndexListBO bo, Page page);
/** /**
* 根据视频主键查询vlog * 根据视频主键查询vlog
*/ */
public IndexVlogVO getVlogDetailById(String userId, String vlogId); public IndexVlogVO getVlogDetailById( String vlogId);
/** /**
* 用户把视频改为公开/私密的视频 * 用户把视频改为公开/私密的视频
@ -51,7 +48,7 @@ public interface VlogService {
/** /**
* 查询用的公开/私密的视频列表 * 查询用的公开/私密的视频列表
*/ */
public PagedGridResult queryMyVlogList(MyListBO bo, Page page); public Page<IndexVlogVO> queryMyVlogList(MyListBO bo, Page page);
/** /**
* 用户点赞/喜欢视频 * 用户点赞/喜欢视频
@ -71,17 +68,17 @@ public interface VlogService {
/** /**
* 查询用户点赞过的短视频 * 查询用户点赞过的短视频
*/ */
public PagedGridResult getMyLikedVlogList(MyListBO bo, Page page); public Page<IndexVlogVO> getMyLikedVlogList( Page page);
/** /**
* 查询用户关注的博主发布的短视频列表 * 查询用户关注的博主发布的短视频列表
*/ */
public PagedGridResult getMyFollowVlogList(SimpleListBO bo, Page page); public Page<IndexVlogVO> getMyFollowVlogList( Page page);
/** /**
* 查询朋友发布的短视频列表 * 查询朋友发布的短视频列表
*/ */
public PagedGridResult getMyFriendVlogList(SimpleListBO bo, Page page); public Page<IndexVlogVO> getMyFriendVlogList( Page page);
/** /**
* 根据主键查询vlog * 根据主键查询vlog

View File

@ -9,10 +9,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.wzj.soopin.content.domain.base.BaseInfoProperties; import com.wzj.soopin.content.domain.base.BaseInfoProperties;
import com.wzj.soopin.content.domain.bo.VlogBO; import com.wzj.soopin.content.domain.bo.*;
import com.wzj.soopin.content.domain.bo.SimpleListBO;
import com.wzj.soopin.content.domain.bo.MyListBO;
import com.wzj.soopin.content.domain.bo.IndexListBO;
import com.wzj.soopin.content.domain.po.MyLikedVlog; import com.wzj.soopin.content.domain.po.MyLikedVlog;
import com.wzj.soopin.content.domain.po.Vlog; import com.wzj.soopin.content.domain.po.Vlog;
import com.wzj.soopin.content.domain.po.Users; import com.wzj.soopin.content.domain.po.Users;
@ -35,6 +32,7 @@ import com.wzj.soopin.member.service.IFansService;
import com.wzj.soopin.content.convert.VlogConvert; import com.wzj.soopin.content.convert.VlogConvert;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.dromara.common.core.domain.model.LoginUser; import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -146,7 +144,7 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService {
} }
@Override @Override
public PagedGridResult getIndexVlogList(IndexListBO bo, Page page) { public Page<IndexVlogVO> getIndexVlogList(IndexListBO bo, Page page) {
String userId = bo.getMemberId(); String userId = bo.getMemberId();
String search = bo.getSearch(); String search = bo.getSearch();
String cityCode = bo.getCityCode(); String cityCode = bo.getCityCode();
@ -240,11 +238,11 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService {
} }
// 封装分页结果 // 封装分页结果
PagedGridResult gridResult = new PagedGridResult(); Page<IndexVlogVO> gridResult = new Page<IndexVlogVO>();
gridResult.setRows(voList); // 当前页数据列表 gridResult.setRecords(voList); // 当前页数据列表
gridResult.setPage(current); // 当前页码 gridResult.setCurrent(current); // 当前页码
gridResult.setRecords(vlogPage.getTotal()); // 总记录数 gridResult.setTotal(vlogPage.getTotal()); // 总记录数
gridResult.setTotal(vlogPage.getPages()); // 总页数 gridResult.setSize(vlogPage.getPages()); // 总页数
return gridResult; return gridResult;
@ -268,23 +266,27 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService {
return Integer.valueOf(countsStr); return Integer.valueOf(countsStr);
} }
private boolean doILikeVlog(String myId, String vlogId) { private boolean doILikeVlog(String myId, String vlogId) {
String doILike = redis.get(REDIS_USER_LIKE_VLOG + ":" + myId + ":" + vlogId); String doILike = redis.get(REDIS_USER_LIKE_VLOG + ":" + myId + ":" + vlogId);
return StringUtils.isNotBlank(doILike) && doILike.equalsIgnoreCase("1"); return StringUtils.isNotBlank(doILike) && doILike.equalsIgnoreCase("1");
} }
@Override @Override
public IndexVlogVO getVlogDetailById(String userId, String vlogId) { public IndexVlogVO getVlogDetailById( String vlogId) {
Vlog vlog = vlogMapper.selectById(vlogId); Vlog vlog = vlogMapper.selectById(vlogId);
if (vlog == null) { if (vlog == null) {
return null; return null;
} }
IndexVlogVO vo = MapstructUtils.convert(vlog, IndexVlogVO.class); IndexVlogVO vo = MapstructUtils.convert(vlog, IndexVlogVO.class);
//获取用户信息
if (StringUtils.isNotBlank(userId)) { try{
vo.setDoIFollowVloger(fansService.queryDoIFollowVloger(userId, vlog.getMemberId())); LoginUser loginUser = LoginHelper.getLoginUser();
vo.setDoILikeThisVlog(doILikeVlog(userId, vlogId)); if (loginUser != null) {
vo.setDoIFollowVloger(fansService.queryDoIFollowVloger(loginUser.getUserId()+"", vlog.getMemberId()));
vo.setDoILikeThisVlog(doILikeVlog(loginUser.getUserId()+"", vlogId));
}
}catch (Exception e){
log.error(e.getMessage());
} }
vo.setLikeCounts(getVlogBeLikedCounts(vlogId)); vo.setLikeCounts(getVlogBeLikedCounts(vlogId));
@ -321,7 +323,7 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService {
} }
@Override @Override
public PagedGridResult queryMyVlogList(MyListBO bo, Page page) { public Page<IndexVlogVO> queryMyVlogList(MyListBO bo, Page page) {
int current = (int) page.getCurrent(); int current = (int) page.getCurrent();
int size = (int) page.getSize(); int size = (int) page.getSize();
Page<Vlog> pageParam = new Page<>(current, size); Page<Vlog> pageParam = new Page<>(current, size);
@ -329,12 +331,15 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService {
if(bo.getUserId()==null){ if(bo.getUserId()==null){
LoginUser user= LoginHelper.getLoginUser(); LoginUser user= LoginHelper.getLoginUser();
if(user==null){
throw new ServiceException("用户未登录");
}
queryWrapper.eq(Vlog::getMemberId, user.getUserId()); queryWrapper.eq(Vlog::getMemberId, user.getUserId());
}else{ }else{
queryWrapper.eq(Vlog::getMemberId, bo.getUserId()); queryWrapper.eq(Vlog::getMemberId, bo.getUserId());
} }
queryWrapper.eq(Vlog::getIsPrivate, bo.getYesOrNo()); queryWrapper.eq(bo.getPrivateFlag()!=null,Vlog::getIsPrivate, bo.getPrivateFlag());
Page<Vlog> vlogPage = vlogMapper.selectPage(pageParam, queryWrapper); Page<Vlog> vlogPage = vlogMapper.selectPage(pageParam, queryWrapper);
List<Vlog> vlogList = vlogPage.getRecords(); List<Vlog> vlogList = vlogPage.getRecords();
@ -351,22 +356,22 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService {
} }
vo.setLikeCounts(getVlogBeLikedCounts(vlog.getId())); vo.setLikeCounts(getVlogBeLikedCounts(vlog.getId()));
vo.setCommentsCounts(getVlogComment(vlog.getId())); vo.setCommentsCounts(getVlogComment(vlog.getId()));
// 补充用户信息头像/昵称 // 补充用户信息头像/昵称
Member author = memberMapper.selectById(vlog.getMemberId()); Member author = memberMapper.selectById(vlog.getMemberId());
if (author != null) { if (author != null) {
vo.setAvatar(author.getAvatar()); vo.setAvatar(author.getAvatar());
vo.setNickname(author.getNickname()); vo.setNickname(author.getNickname());
} }
return vo; return vo;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
PagedGridResult gridResult = new PagedGridResult(); Page<IndexVlogVO> gridResult = new Page<IndexVlogVO>();
gridResult.setRows(voList); gridResult.setRecords(voList);
gridResult.setPage(current); gridResult.setSize(vlogPage.getSize());
gridResult.setRecords(vlogPage.getTotal()); gridResult.setCurrent(current);
gridResult.setTotal(vlogPage.getPages()); gridResult.setTotal(vlogPage.getTotal());
return gridResult; return gridResult;
} }
@ -444,118 +449,48 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService {
} }
@Override @Override
public PagedGridResult getMyLikedVlogList(MyListBO bo, Page page) { public Page<IndexVlogVO> getMyLikedVlogList( Page page) {
int current = (int) page.getCurrent();
int size = (int) page.getSize(); LoginUser user= LoginHelper.getLoginUser();
Page<MyLikedVlog> pageParam = new Page<>(current, size); if(user==null){
LambdaQueryWrapper<MyLikedVlog> queryWrapper = new LambdaQueryWrapper<>(); throw new ServiceException("用户未登录");
queryWrapper.eq(MyLikedVlog::getUserId, bo.getUserId());
Page<MyLikedVlog> likedPage = myLikedVlogMapper.selectPage(pageParam, queryWrapper);
List<MyLikedVlog> likedList = likedPage.getRecords();
// 组装返回的 VO 列表
String uid = bo.getUserId();
if (StringUtils.isBlank(uid)) {
LoginUser user = LoginHelper.getLoginUser();
uid = user != null ? String.valueOf(user.getUserId()) : null;
} }
final String finalUid = uid; Map<String, Object> map = new HashMap<>();
map.put("myId", user.getUserId());
Page<IndexVlogVO> likedPage = vlogMapperCustom.getMyLikedVlogList(map,page);
List<IndexVlogVO> voList = likedList.stream() likedPage.getRecords().stream().forEach(
.map(liked -> { liked -> {
Vlog vlog = vlogMapper.selectById(liked.getVlogId()); liked.setDoIFollowVloger(true);
if (vlog == null) { liked.setDoILikeThisVlog(true);
return null; });
} return likedPage;
IndexVlogVO vo = MapstructUtils.convert(vlog, IndexVlogVO.class);
if (StringUtils.isNotBlank(finalUid)) {
vo.setDoIFollowVloger(fansService.queryDoIFollowVloger(finalUid, vlog.getMemberId()));
vo.setDoILikeThisVlog(true);
}
vo.setLikeCounts(getVlogBeLikedCounts(vlog.getId()));
vo.setCommentsCounts(getVlogComment(vlog.getId()));
// 补充用户信息头像/昵称
Member author = memberMapper.selectById(vlog.getMemberId());
if (author != null) {
vo.setAvatar(author.getAvatar());
vo.setNickname(author.getNickname());
}
return vo;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
PagedGridResult gridResult = new PagedGridResult();
gridResult.setRows(voList);
gridResult.setPage(current);
gridResult.setRecords(likedPage.getTotal());
gridResult.setTotal(likedPage.getPages());
return gridResult;
} }
@Override @Override
public PagedGridResult getMyFollowVlogList(SimpleListBO bo, Page page) { public Page<IndexVlogVO> getMyFollowVlogList(Page page) {
int current = (int) page.getCurrent(); LoginUser user= LoginHelper.getLoginUser();
int size = (int) page.getSize(); if(user==null){
throw new ServiceException("用户未登录");
}
Map<String, Object> map = new HashMap<>(); Map<String, Object> map = new HashMap<>();
map.put("myId", bo.getMyId()); map.put("myId", user.getUserId());
List<IndexVlogVO> voList = vlogMapperCustom.getMyFollowVlogList(map); Page<IndexVlogVO> voPage = vlogMapperCustom.getMyFollowVlogList(map,page);
// 补充用户信息头像/昵称 return voPage;
voList.forEach(vo -> {
if (StringUtils.isNotBlank(vo.getMemberId())) {
Member author = memberMapper.selectById(vo.getMemberId());
if (author != null) {
vo.setAvatar(author.getAvatar());
vo.setNickname(author.getNickname());
}
}
});
// 手动分页
int startIndex = (current - 1) * size;
int endIndex = Math.min(startIndex + size, voList.size());
List<IndexVlogVO> pagedList = voList.subList(startIndex, endIndex);
PagedGridResult gridResult = new PagedGridResult();
gridResult.setRows(pagedList);
gridResult.setPage(current);
gridResult.setRecords(voList.size());
gridResult.setTotal((int) Math.ceil((double) voList.size() / size));
return gridResult;
} }
@Override @Override
public PagedGridResult getMyFriendVlogList(SimpleListBO bo, Page page) { public Page<IndexVlogVO> getMyFriendVlogList( Page page) {
int current = (int) page.getCurrent(); LoginUser user= LoginHelper.getLoginUser();
int size = (int) page.getSize(); if(user==null){
throw new ServiceException("用户未登录");
}
Map<String, Object> map = new HashMap<>(); Map<String, Object> map = new HashMap<>();
map.put("myId", bo.getMyId()); map.put("myId", user.getUserId());
List<IndexVlogVO> voList = vlogMapperCustom.getMyFriendVlogList(map); Page<IndexVlogVO> voPage = vlogMapperCustom.getMyFriendVlogList(map,page);
// 补充用户信息头像/昵称 return voPage;
voList.forEach(vo -> {
if (StringUtils.isNotBlank(vo.getMemberId())) {
Member author = memberMapper.selectById(vo.getMemberId());
if (author != null) {
vo.setAvatar(author.getAvatar());
vo.setNickname(author.getNickname());
}
}
});
// 手动分页
int startIndex = (current - 1) * size;
int endIndex = Math.min(startIndex + size, voList.size());
List<IndexVlogVO> pagedList = voList.subList(startIndex, endIndex);
PagedGridResult gridResult = new PagedGridResult();
gridResult.setRows(pagedList);
gridResult.setPage(current);
gridResult.setRecords(voList.size());
gridResult.setTotal((int) Math.ceil((double) voList.size() / size));
return gridResult;
} }
@Override @Override

View File

@ -117,6 +117,7 @@
v.id as Id, v.id as Id,
m.avatar as avatar, m.avatar as avatar,
m.nickname as nickname m.nickname as nickname
FROM FROM
cont_vlog v cont_vlog v
LEFT JOIN LEFT JOIN
@ -128,7 +129,7 @@
ON ON
v.member_id = m.id v.member_id = m.id
WHERE WHERE
mlv.user_id = #{paramMap.userId} mlv.user_id = #{paramMap.myId}
AND v.status = 1 AND v.status = 1
AND v.first_frame_img IS NOT NULL AND v.first_frame_img IS NOT NULL
AND v.is_private = 0 AND v.is_private = 0
@ -151,35 +152,29 @@
<select id="getMyFollowVlogList" parameterType="map" resultType="com.wzj.soopin.content.domain.vo.IndexVlogVO"> <select id="getMyFollowVlogList" parameterType="map" resultType="com.wzj.soopin.content.domain.vo.IndexVlogVO">
SELECT SELECT
v.id as vlogId, v.id AS vlogId,
v.member_id as memberId, v.member_id AS memberId,
v.title as title, v.title AS title,
v.title as content, v.title AS content,
v.url as url, v.url AS url,
v.cover as cover, v.cover AS cover,
v.width as width, v.width AS width,
v.height as height, v.height AS height,
v.like_counts as likeCounts, v.like_counts AS likeCounts,
v.comments_counts as commentsCounts, v.comments_counts AS commentsCounts,
v.is_private as isPrivate, v.is_private AS isPrivate,
v.city_code as cityCode, v.city_code AS cityCode,
v.reason as reason, v.reason AS reason,
v.status as status, v.STATUS AS STATUS,
v.file_id as fileId, v.file_id AS fileId,
v.first_frame_img as firstFrameImg, v.first_frame_img AS firstFrameImg,
v.id as Id, v.id AS Id,
m.avatar as avatar, m.avatar AS avatar,
m.nickname as nickname m.nickname AS nickname
FROM FROM
cont_vlog v cont_vlog v
LEFT JOIN LEFT JOIN ums_fans f ON v.member_id = f.vlogger_id
cont_fans f LEFT JOIN ums_member m ON v.member_id = m.id
ON
v.member_id = f.vloger_id
LEFT JOIN
ums_member m
ON
v.member_id = m.id
WHERE WHERE
v.is_private = 0 v.is_private = 0
AND v.status = 1 AND v.status = 1
@ -227,9 +222,9 @@
AND v.status = 1 AND v.status = 1
AND v.first_frame_img IS NOT NULL AND v.first_frame_img IS NOT NULL
AND AND
f.vloger_id = #{paramMap.myId} f.fan_id = #{paramMap.myId}
AND AND
f.is_fan_friend_of_mine = 1 f.friend_flag = 1
ORDER BY ORDER BY
v.create_time v.create_time
DESC DESC

View File

@ -1,5 +1,6 @@
package com.wzj.soopin.goods.controller; package com.wzj.soopin.goods.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wzj.soopin.goods.convert.ProductCategoryConvert; import com.wzj.soopin.goods.convert.ProductCategoryConvert;
import com.wzj.soopin.goods.domain.bo.ProductCategoryBo; import com.wzj.soopin.goods.domain.bo.ProductCategoryBo;
@ -25,7 +26,7 @@ import java.util.List;
*/ */
@Tag(name ="商品分类接口列表") @Tag(name ="商品分类接口列表")
@RestController @RestController
@RequestMapping("/pms/productCategory") @RequestMapping("/pms/product/category")
@RequiredArgsConstructor @RequiredArgsConstructor
public class ProductCategoryController extends BaseController { public class ProductCategoryController extends BaseController {
@ -34,6 +35,18 @@ public class ProductCategoryController extends BaseController {
private final ProductCategoryServiceImpl service; private final ProductCategoryServiceImpl service;
private final ProductCategoryConvert convert; private final ProductCategoryConvert convert;
@Tag(name = "查询列表")
@PostMapping("/tree")
public R< List<ProductCategoryVO>> tree(@RequestBody ProductCategoryBo bo ) {
List<ProductCategoryVO> articleList = service.tree( bo.toWrapper());
return R.ok( articleList);
}
@Tag(name = "查询列表")
@PostMapping("/page")
public R<IPage<ProductCategoryVO>> page(@RequestBody ProductCategoryBo bo, @RequestBody Page page) {
Page<ProductCategory> pages = service.page(page, bo.toWrapper());
return R.ok(convert.toVO(pages));
}
@Tag(name ="查询商品分类列表") @Tag(name ="查询商品分类列表")
@PostMapping("list") @PostMapping("list")
public R<List<ProductCategoryVO>> list(@RequestBody ProductCategoryBo query) { public R<List<ProductCategoryVO>> list(@RequestBody ProductCategoryBo query) {
@ -67,4 +80,6 @@ public class ProductCategoryController extends BaseController {
public R remove(@PathVariable Long id) { public R remove(@PathVariable Long id) {
return R.ok(service.removeById(id)); return R.ok(service.removeById(id));
} }
} }

View File

@ -58,8 +58,6 @@ public class SkuController extends BaseController {
@Log(title = "sku信息", businessType = BusinessType.INSERT) @Log(title = "sku信息", businessType = BusinessType.INSERT)
@PostMapping("/add") @PostMapping("/add")
public R add(@RequestBody Sku sku) { public R add(@RequestBody Sku sku) {
Long tenantId = Long.valueOf(LoginHelper.getTenantId());
sku.setTenantId(tenantId);
return R.ok(service.save(sku)); return R.ok(service.save(sku));
} }

View File

@ -11,11 +11,11 @@ import java.util.List;
@Data @Data
public class ProductDTO { public class ProductDTO {
@Schema(description = "BRAND_ID") @Schema(description = "品牌")
@Excel(name = "BRAND_ID") @Excel(name = "BRAND_ID")
private Long brandId; private Long brandId;
@Schema(description = "CATEGORY_ID") @Schema(description = "分类")
@Excel(name = "CATEGORY_ID") @Excel(name = "CATEGORY_ID")
private Long categoryId; private Long categoryId;
@ -43,8 +43,8 @@ public class ProductDTO {
@Excel(name = "排序") @Excel(name = "排序")
private Integer sort; private Integer sort;
@Schema(description = "PRICE") @Schema(description = "价格")
@Excel(name = "PRICE") @Excel(name = "价格")
private BigDecimal price; private BigDecimal price;
@Schema(description = "单位") @Schema(description = "单位")
@ -81,7 +81,7 @@ public class ProductDTO {
@Schema(description = "店铺id") @Schema(description = "店铺id")
@Excel(name = "店铺id") @Excel(name = "店铺id")
private Long tenantId; private String tenantId;
@Schema(description = "审核状态 1.待审核;->2.审核通过;3->审核驳回") @Schema(description = "审核状态 1.待审核;->2.审核通过;3->审核驳回")
@Excel(name = "审核状态 1.待审核;->2.审核通过;3->审核驳回") @Excel(name = "审核状态 1.待审核;->2.审核通过;3->审核驳回")

View File

@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import org.dromara.common.core.domain.model.BaseAudit; import org.dromara.common.core.domain.model.BaseAudit;
import org.dromara.common.excel.annotation.Excel; import org.dromara.common.excel.annotation.Excel;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.math.BigDecimal; import java.math.BigDecimal;
@ -16,7 +17,7 @@ import java.math.BigDecimal;
@Schema(description = "商品信息对象") @Schema(description = "商品信息对象")
@Data @Data
@TableName("pms_product") @TableName("pms_product")
public class Product extends BaseAudit { public class Product extends BaseEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Schema(description = "ID") @Schema(description = "ID")
@ -92,7 +93,7 @@ public class Product extends BaseAudit {
@Schema(description = "店铺id") @Schema(description = "店铺id")
@Excel(name = "店铺id") @Excel(name = "店铺id")
private Long tenantId; private String tenantId;
@Schema(description = "审核状态 1.待审核;->2.审核通过;3->审核驳回") @Schema(description = "审核状态 1.待审核;->2.审核通过;3->审核驳回")
@Excel(name = "审核状态 1.待审核;->2.审核通过;3->审核驳回") @Excel(name = "审核状态 1.待审核;->2.审核通过;3->审核驳回")

View File

@ -50,5 +50,5 @@ public class Sku extends BaseAudit {
@Schema(description = "租户id") @Schema(description = "租户id")
@Excel(name = "租户id") @Excel(name = "租户id")
private Long tenantId; private String tenantId;
} }

View File

@ -113,7 +113,7 @@ public class ProductVO extends BaseAudit {
private String contactPhone; private String contactPhone;
@Schema(description = "店铺id") @Schema(description = "店铺id")
private Long tenantId; private String tenantId;
@Schema(description = "审核状态 1.待审核、2.审核通过、3.审核驳回") @Schema(description = "审核状态 1.待审核、2.审核通过、3.审核驳回")
private String authFlag; private String authFlag;

View File

@ -1,7 +1,12 @@
package com.wzj.soopin.goods.service; package com.wzj.soopin.goods.service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.wzj.soopin.goods.domain.entity.ProductCategory; import com.wzj.soopin.goods.domain.entity.ProductCategory;
import com.wzj.soopin.goods.domain.vo.ProductCategoryVO;
import java.util.List;
public interface ProductCategoryService extends IService<ProductCategory> { public interface ProductCategoryService extends IService<ProductCategory> {
List<ProductCategoryVO> tree(Wrapper<ProductCategory> wrapper);
} }

View File

@ -1,10 +1,19 @@
package com.wzj.soopin.goods.service.impl; package com.wzj.soopin.goods.service.impl;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wzj.soopin.goods.convert.ProductCategoryConvert;
import com.wzj.soopin.goods.domain.entity.ProductCategory; import com.wzj.soopin.goods.domain.entity.ProductCategory;
import com.wzj.soopin.goods.domain.vo.ProductCategoryVO;
import com.wzj.soopin.goods.mapper.ProductCategoryMapper; import com.wzj.soopin.goods.mapper.ProductCategoryMapper;
import com.wzj.soopin.goods.service.ProductCategoryService; import com.wzj.soopin.goods.service.ProductCategoryService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/** /**
@ -13,6 +22,26 @@ import org.springframework.stereotype.Service;
* @author zcc * @author zcc
*/ */
@Service @Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor
public class ProductCategoryServiceImpl extends ServiceImpl<ProductCategoryMapper, ProductCategory> implements ProductCategoryService { public class ProductCategoryServiceImpl extends ServiceImpl<ProductCategoryMapper, ProductCategory> implements ProductCategoryService {
private final ProductCategoryConvert convert;
@Override
public List<ProductCategoryVO> tree(Wrapper<ProductCategory> wrapper) {
List<ProductCategory> list = this.list(wrapper);
return list.stream().filter(item -> item.getParentId() == 0).map(item -> {
ProductCategoryVO productCategoryVO = convert.toVO(item);
productCategoryVO.setChildren(
list.stream().filter(child -> Objects.equals(child.getParentId(), item.getId()))
.map(convert::toVO)
.collect(Collectors.toList())
);
return productCategoryVO;
}).toList();
}
} }

View File

@ -20,6 +20,7 @@ import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginUser; import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List; import java.util.List;
@ -126,17 +127,12 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
product.setProductAttr(dto.getProductAttr()); product.setProductAttr(dto.getProductAttr());
product.setDetailHtml(dto.getDetailHtml()); product.setDetailHtml(dto.getDetailHtml());
product.setDetailMobileHtml(dto.getDetailMobileHtml()); product.setDetailMobileHtml(dto.getDetailMobileHtml());
if (loginUser != null) {
product.setTenantId(Long.valueOf(loginUser.getTenantId()));
log.info("登录租户id{}", loginUser.getTenantId());
}
product.setAuthFlag(1); product.setAuthFlag(1);
product.setPublishStatus(dto.getPublishStatus()); product.setPublishStatus(dto.getPublishStatus());
product.setBrandName(dto.getBrandName()); product.setBrandName(dto.getBrandName());
product.setProductCategoryName(dto.getProductCategoryName()); product.setProductCategoryName(dto.getProductCategoryName());
product.setType(1); product.setType(1);
product.setSales("0"); product.setSales("0");
product.setTenantId(dto.getTenantId());
return product; return product;
} }
@ -162,7 +158,13 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
return productMapper.deleteById(id) > 0; return productMapper.deleteById(id) > 0;
} }
/**
* 不带租户的获取商品信息
* @param page
* @param query
* @return
*/
public IPage<ProductVO> getProduct(Page<Product> page, ProductBo query) { public IPage<ProductVO> getProduct(Page<Product> page, ProductBo query) {
return productMapper.getProduct(page, query); return TenantHelper.ignore(() -> productMapper.getProduct(page, query));
} }
} }

View File

@ -3,12 +3,13 @@ package com.wzj.soopin.member.enums;
public enum AccountBillSourceEnum { public enum AccountBillSourceEnum {
WITHDRAW(1, "提现"), WITHDRAW(1, "提现"),
CHARGE(2, "充值"), WITHDRAW_LOCK(2, "提现锁定"),
RECHARGE(3, "充值"), RECHARGE(3, "充值"),
RECHARGE_REFUND(4, "充值退款"), RECHARGE_REFUND(4, "充值退款"),
WITHDRAW_REFUND(5, "提现退款"), WITHDRAW_REFUND(5, "提现退款"),
RECHARGE_REFUND_REFUND(6, "充值退款退款"), RECHARGE_REFUND_REFUND(6, "充值退款退款"),
WITHDRAW_REFUND_REFUND(7, "提现退款退款"), WITHDRAW_REFUND_REFUND(7, "提现退款退款"),
PAYMENT(8, "支付"),
RECHARGE_REFUND_REFUND_REFUND(9, "充值退款退款退款"), RECHARGE_REFUND_REFUND_REFUND(9, "充值退款退款退款"),
RED_PACKAGE_RECEIVE(10, "红包接收"), RED_PACKAGE_RECEIVE(10, "红包接收"),
RED_PACKAGE_SEND(11, "红包发送"), RED_PACKAGE_SEND(11, "红包发送"),

View File

@ -6,8 +6,11 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.wzj.soopin.member.domain.bo.MemberAccountBO; import com.wzj.soopin.member.domain.bo.MemberAccountBO;
import com.wzj.soopin.member.domain.po.MemberAccount; import com.wzj.soopin.member.domain.po.MemberAccount;
import com.wzj.soopin.member.domain.vo.MemberAccountVO; import com.wzj.soopin.member.domain.vo.MemberAccountVO;
import com.wzj.soopin.member.enums.AccountBillSourceEnum;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
import java.math.BigDecimal;
public interface IMemberAccountService extends IService<MemberAccount> { public interface IMemberAccountService extends IService<MemberAccount> {
MemberAccount getMemberAccount(Long memberId); MemberAccount getMemberAccount(Long memberId);
@ -15,4 +18,8 @@ public interface IMemberAccountService extends IService<MemberAccount> {
IPage<MemberAccountVO> pageWithMember(Page<?> page, MemberAccountBO bo); IPage<MemberAccountVO> pageWithMember(Page<?> page, MemberAccountBO bo);
Object getCount(); Object getCount();
boolean addMoney(BigDecimal money, Long memberId, AccountBillSourceEnum type, String remark);
boolean reduceMoney(BigDecimal money, Long memberId, AccountBillSourceEnum type, String remark);
} }

View File

@ -4,11 +4,16 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wzj.soopin.member.domain.po.AccountBill;
import com.wzj.soopin.member.domain.po.MemberAccount; import com.wzj.soopin.member.domain.po.MemberAccount;
import com.wzj.soopin.member.domain.bo.MemberAccountBO; import com.wzj.soopin.member.domain.bo.MemberAccountBO;
import com.wzj.soopin.member.domain.vo.MemberAccountVO; import com.wzj.soopin.member.domain.vo.MemberAccountVO;
import com.wzj.soopin.member.enums.AccountBillChangeTypeEnum;
import com.wzj.soopin.member.enums.AccountBillSourceEnum;
import com.wzj.soopin.member.mapper.AccountBillMapper;
import com.wzj.soopin.member.mapper.MemberAccountMapper; import com.wzj.soopin.member.mapper.MemberAccountMapper;
import com.wzj.soopin.member.service.IMemberAccountService; import com.wzj.soopin.member.service.IMemberAccountService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.math.BigDecimal; import java.math.BigDecimal;
@ -20,8 +25,10 @@ import java.math.BigDecimal;
* @author zcc * @author zcc
*/ */
@Service @Service
@RequiredArgsConstructor
public class MemberAccountServiceImpl extends ServiceImpl<MemberAccountMapper,MemberAccount> implements IMemberAccountService { public class MemberAccountServiceImpl extends ServiceImpl<MemberAccountMapper,MemberAccount> implements IMemberAccountService {
private final AccountBillMapper accountBillMapper;
@Override @Override
public MemberAccount getMemberAccount(Long memberId) { public MemberAccount getMemberAccount(Long memberId) {
@ -50,4 +57,64 @@ public class MemberAccountServiceImpl extends ServiceImpl<MemberAccountMapper,Me
} }
@Override
public boolean addMoney(BigDecimal money, Long memberId, AccountBillSourceEnum source, String remark) {
MemberAccount memberAccount = this.getMemberAccount(memberId);
if (memberAccount == null) {
throw new RuntimeException("用户不存在");
}
//检查当前用于的账户余额是否充足
BigDecimal balance = memberAccount.getWallet();
BigDecimal newBalance = balance.add(money);
//锁定用户余额
this.updateById(memberAccount.toBuilder().wallet(newBalance).build());
//生成账单
AccountBill memberAccountChangeRecord = AccountBill.builder()
.accountId(memberAccount.getId())
.changeAmount(money)
.moneyBalance(newBalance)
.beforeBalance(balance)
.afterBalance(newBalance)
.changeType(AccountBillChangeTypeEnum.IN.getCode())
.changeDesc(remark)
.source(source.getCode())
.build();
accountBillMapper.insert(memberAccountChangeRecord);
return false;
}
@Override
public boolean reduceMoney(BigDecimal money, Long memberId, AccountBillSourceEnum source, String remark) {
MemberAccount memberAccount = this.getMemberAccount(memberId);
if (memberAccount == null) {
throw new RuntimeException("用户不存在");
}
//检查当前用于的账户余额是否充足
BigDecimal balance = memberAccount.getWallet();
if (balance.compareTo(money) < 0) {
throw new RuntimeException("用户余额不足");
}
BigDecimal newBalance = balance.subtract(money);
//锁定用户余额
this.updateById(memberAccount.toBuilder().wallet(newBalance).build());
//生成账单
AccountBill memberAccountChangeRecord = AccountBill.builder()
.accountId(memberAccount.getId())
.changeAmount(money)
.moneyBalance(newBalance)
.beforeBalance(balance)
.afterBalance(newBalance)
.changeType(AccountBillChangeTypeEnum.OUT.getCode())
.changeDesc(remark)
.source(source.getCode())
.build();
accountBillMapper.insert(memberAccountChangeRecord);
return true;
}
} }

View File

@ -15,7 +15,7 @@ import java.util.Map;
* 红包功能控制器 * 红包功能控制器
*/ */
@RestController @RestController
@RequestMapping("/api/red-packet") @RequestMapping("/api/packet")
@Api(tags = "红包功能接口") @Api(tags = "红包功能接口")
public class RedPacketController { public class RedPacketController {

View File

@ -0,0 +1,26 @@
package com.wzj.soopin.order.emum;
/**
* 支付方式0->未支付1->支付宝2->微信 精确匹配
*/
public enum OrderPayTypeEnum {
UNPAID(0, "未支付"),
ALIPAY(1, "支付宝"),
WECHAT(2, "微信");
private final Integer value;
private final String info;
OrderPayTypeEnum(Integer value, String info) {
this.value = value;
this.info = info;
}
public Integer getValue() {
return value;
}
public String getInfo() {
return info;
}
}

View File

@ -0,0 +1,29 @@
package com.wzj.soopin.order.emum;
/**
* 订单状态0->待付款1->待发货2->已发货3->已完成4->已关闭5->无效订单
*/
public enum OrderStatusEnum {
UNPAID(0, "待付款"),
WAITING_FOR_DELIVERY(1, "待发货"),
DELIVERED(2, "已发货"),
COMPLETED(3, "已完成"),
CLOSED(4, "已关闭"),
INVALID(5, "无效订单");
private final Integer value;
private final String info;
OrderStatusEnum(Integer value, String info) {
this.value = value;
this.info = info;
}
public Integer getValue() {
return value;
}
public String getInfo() {
return info;
}
}

View File

@ -0,0 +1,48 @@
package com.wzj.soopin.order.service.impl;
import com.wzj.soopin.order.service.OrderService;
import com.wzj.soopin.order.task.OrderCancelTask;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.DelayQueue;
/**
* 订单延迟取消服务
*/
@Service
public class OrderDelayCancelService {
private final DelayQueue<OrderCancelTask> delayQueue = new DelayQueue<>();
@Autowired
private OrderService orderService; // 订单服务用于修改订单状态
public OrderDelayCancelService() {
// 启动一个守护线程处理延迟任务
Thread daemonThread = new Thread(this::processDelayQueue);
daemonThread.setDaemon(true);
daemonThread.start();
}
// 添加订单到延迟队列
public void addOrderToCancelQueue(String orderSn, long delayTime) {
delayQueue.put(new OrderCancelTask(orderSn, delayTime));
}
// 处理延迟队列中的任务
private void processDelayQueue() {
while (true) {
try {
// 取出到期的任务
OrderCancelTask task = delayQueue.take();
// 执行订单取消逻辑
// orderService.updateOrderStatus(task.getOrderSn(), "CANCELLED");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
// 记录异常继续处理下一个任务
e.printStackTrace();
}
}
}
}

View File

@ -18,6 +18,7 @@ import com.wzj.soopin.order.domain.form.DeliverProductForm;
import com.wzj.soopin.order.domain.form.ManagerOrderQueryForm; import com.wzj.soopin.order.domain.form.ManagerOrderQueryForm;
import com.wzj.soopin.order.domain.query.OrderH5Query; import com.wzj.soopin.order.domain.query.OrderH5Query;
import com.wzj.soopin.order.domain.vo.*; import com.wzj.soopin.order.domain.vo.*;
import com.wzj.soopin.order.emum.OrderStatusEnum;
import com.wzj.soopin.order.mapper.*; import com.wzj.soopin.order.mapper.*;
import com.wzj.soopin.order.service.OrderService; import com.wzj.soopin.order.service.OrderService;
import com.wzj.soopin.order.service.VerificationCodeService; import com.wzj.soopin.order.service.VerificationCodeService;
@ -199,7 +200,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
order.setTenantId(tenantId); order.setTenantId(tenantId);
order.setCreateTime(LocalDateTime.now()); order.setCreateTime(LocalDateTime.now());
order.setWithdrawStatus(1); order.setWithdrawStatus(1);
order.setStatus(0); order.setStatus(OrderStatusEnum.UNPAID.getValue());
int insert = orderMapper.insert(order); int insert = orderMapper.insert(order);
if (insert>1){ if (insert>1){
return R.fail("订单创建失败"); return R.fail("订单创建失败");

View File

@ -0,0 +1,31 @@
package com.wzj.soopin.order.task;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
/**
* 订单延迟取消任务
*/
public class OrderCancelTask implements Delayed {
private final String orderSn; // 订单编号
private final long executeTime; // 执行时间毫秒
public OrderCancelTask(String orderSn, long delayTime) {
this.orderSn = orderSn;
this.executeTime = System.currentTimeMillis() + delayTime;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(executeTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed other) {
return Long.compare(this.executeTime, ((OrderCancelTask) other).executeTime);
}
public String getOrderSn() {
return orderSn;
}
}

View File

@ -1,5 +1,6 @@
package org.dromara.system.domain; package org.dromara.system.domain;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.dromara.common.core.domain.model.BaseAudit; import org.dromara.common.core.domain.model.BaseAudit;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;

View File

@ -18,4 +18,6 @@ public interface ISysTenantAccountService extends IService<SysTenantAccount> {
boolean save(SysTenantAccount po); boolean save(SysTenantAccount po);
boolean updateById(SysTenantAccount po); boolean updateById(SysTenantAccount po);
boolean removeById(Long id); boolean removeById(Long id);
SysTenantAccount getByTenantId(Long tenantId);
} }

View File

@ -64,4 +64,9 @@ public class SysTenantAccountServiceImpl extends ServiceImpl<SysTenantAccountMa
// 可根据实际业务补充更多条件 // 可根据实际业务补充更多条件
return wrapper; return wrapper;
} }
@Override
public SysTenantAccount getByTenantId(Long tenantId) {
return mapper.selectOne(new LambdaQueryWrapper<SysTenantAccount>().eq(SysTenantAccount::getTenantId, tenantId));
}
} }

View File

@ -69,6 +69,12 @@ public class WithdrawBO extends BaseBO<Withdraw> {
*/ */
@Schema(description ="类型") @Schema(description ="类型")
private Integer type; private Integer type;
/**
* 银行名称
*/
@Schema(description ="银行名称")
private String bankId;
/** /**
* 审核人 * 审核人
*/ */

View File

@ -0,0 +1,73 @@
package com.wzj.soopin.transaction.domain.po;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.common.core.domain.model.BaseAudit;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.Date;
/**
* 退款日志
*
* @author Chopper
* @since 2021/1/28 09:21
*/
@Data
@TableName("li_refund_log")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = "退款日志")
public class RefundLog extends BaseAudit {
@ApiModelProperty(value = "会员ID")
private String memberId;
@ApiModelProperty(value = "退单编号")
private String afterSaleNo;
@ApiModelProperty(value = "订单编号")
private String orderSn;
@ApiModelProperty(value = "金额")
private Double totalAmount;
@ApiModelProperty(value = "改笔交易支付金额")
private Double payPrice;
@ApiModelProperty(value = "是否已退款")
private Boolean isRefund;
@ApiModelProperty(value = "退款方式")
private String paymentName;
@ApiModelProperty(value = "支付第三方付款流水")
private String paymentReceivableNo;
@ApiModelProperty(value = "退款请求流水")
private String outOrderNo;
@ApiModelProperty(value = "第三方退款流水号")
private String receivableNo;
@ApiModelProperty(value = "退款理由")
private String refundReason;
@ApiModelProperty(value = "退款失败原因")
private String errorMessage;
}

View File

@ -69,6 +69,13 @@ public class Withdraw extends BaseAudit {
*/ */
private Long auditBy; private Long auditBy;
/**
* 银行名称
*/
@Schema(description ="银行名称")
private String bankId;
/** /**
* 审核时间 * 审核时间
*/ */

View File

@ -0,0 +1,23 @@
package com.wzj.soopin.transaction.enums;
/**
* 订单类型
*
* @author Chopper
* @since 2020-12-19 11:50
*/
public enum CashierEnum {
/**
* 订单
*/
ORDER,
/**
* 交易
*/
TRADE,
/**
* 充值
*/
RECHARGE
}

View File

@ -0,0 +1,18 @@
package com.wzj.soopin.transaction.enums;
import lombok.Getter;
@Getter
public enum ChargeStatus {
WAITING(0, "待验证"),
PENDING(1, "充值成功"),
SUCCESS(2, "充值失败");
private Integer code;
private String message;
ChargeStatus(Integer code, String message) {
this.code = code;
this.message = message;
}
}

View File

@ -0,0 +1,47 @@
package com.wzj.soopin.transaction.enums;
/**
* 客户端类型
*
* @author Chopper
* @since 2020/12/8 9:46
*/
public enum ClientTypeEnum {
/**
* "移动端"
*/
H5("移动端"),
/**
* "PC端"
*/
PC("PC端"),
/**
* "小程序端"
*/
WECHAT_MP("小程序端"),
/**
* "移动应用端"
*/
APP("移动应用端"),
/**
* "未知"
*/
UNKNOWN("未知");
private final String clientName;
ClientTypeEnum(String des) {
this.clientName = des;
}
public String clientName() {
return this.clientName;
}
public String value() {
return this.name();
}
}

View File

@ -0,0 +1,31 @@
package com.wzj.soopin.transaction.enums;
/**
* 支付方式枚举
*
* @author Chopper
* @since 2020/12/18 18:08
*/
public enum PaymentClientEnum {
/**
* app支付
**/
APP,
/**
* 展示二维码扫描支付
*/
NATIVE,
/**
* 公众号内部调用支付
*/
JSAPI,
/**
* 普通移动网页调用支付app
*/
H5,
/**
* 小程序通常指微信小程序
*/
MP
}

View File

@ -0,0 +1,69 @@
package com.wzj.soopin.transaction.enums;
/**
* 支付方式枚举
*
* @author Chopper
* @since 2020/12/18 18:08
*/
public enum PaymentMethodEnum {
/**
* 微信
*/
WECHAT("wechatPlugin", "微信"),
/**
* 支付宝
*/
ALIPAY("aliPayPlugin", "支付宝"),
/**
* 余额支付
*/
WALLET("walletPlugin", "余额支付"),
/**
* 线下转账
*/
BANK_TRANSFER("bankTransferPlugin", "线下转账"),
/**
* 易生支付
*/
EASY_PAY("easyPayPlugin", "易生支付");
/**
* 插件id 调用对象需要实现payment接口
*/
private final String plugin;
/**
* 支付名称
*/
private final String paymentName;
public String getPlugin() {
return plugin;
}
public String paymentName() {
return paymentName;
}
/**
* 根据支付方式名称返回对象
*
* @param name
* @return
*/
public static PaymentMethodEnum paymentNameOf(String name) {
for (PaymentMethodEnum value : PaymentMethodEnum.values()) {
if (value.name().equals(name)) {
return value;
}
}
return null;
}
PaymentMethodEnum(String plugin, String paymentName) {
this.plugin = plugin;
this.paymentName = paymentName;
}
}

View File

@ -0,0 +1,21 @@
package com.wzj.soopin.transaction.enums;
public enum SystemConfigEnum {
ORDER_AUTO_CANCEL("order_auto_cancel", "订单自动取消时间"),
ORDER_AUTO_CANCEL_MINUTE("order_auto_cancel_minute", "订单自动取消时间(分钟)") ;
private final String key;
private final String name;
SystemConfigEnum(String key, String name) {
this.key = key;
this.name = name;
}
public String getKey() {
return key;
}
public String getName() {
return name;
}
}

View File

@ -0,0 +1,17 @@
package com.wzj.soopin.transaction.enums;
import lombok.Getter;
@Getter
public enum WithdrawType {
WALLET(1, "钱包"),
REVENUE(2, "营收");
private Integer code;
private String message;
WithdrawType(Integer code, String message) {
this.code = code;
this.message = message;
}
}

View File

@ -0,0 +1,200 @@
package com.wzj.soopin.transaction.kit;
import com.wzj.soopin.member.service.IMemberAccountService;
import com.wzj.soopin.order.service.impl.OrderDelayCancelService;
import com.wzj.soopin.transaction.enums.ClientTypeEnum;
import com.wzj.soopin.transaction.enums.PaymentClientEnum;
import com.wzj.soopin.transaction.enums.PaymentMethodEnum;
import com.wzj.soopin.transaction.kit.dto.PayParam;
import com.wzj.soopin.transaction.kit.params.CashierExecute;
import com.wzj.soopin.transaction.kit.params.dto.CashierParam;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.ResultCode;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.system.service.ISysConfigService;
import org.springframework.stereotype.Component;
import java.time.ZoneId;
import java.time.temporal.TemporalField;
import java.util.Arrays;
import java.util.List;
/**
* 收银台工具
*
* @author Chopper
* @since 2020-12-19 09:25
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class CashierSupport {
/**
* 收银台
*/
private final List<CashierExecute> cashierExecuteList;
/**
* 预存款
*/
private final IMemberAccountService memberWalletService;
/**
* 配置
*/
private final ISysConfigService sysConfigService;
private final OrderDelayCancelService orderDelayCancelService;
public final
/**
* 支付
*
* @param paymentMethodEnum 支付渠道枚举
* @param paymentClientEnum 支付方式枚举
* @return 支付消息
*/
R<Object> payment(PaymentMethodEnum paymentMethodEnum, PaymentClientEnum paymentClientEnum,
HttpServletRequest request, HttpServletResponse response,
PayParam payParam) {
if (paymentClientEnum == null || paymentMethodEnum == null) {
throw new ServiceException(ResultCode.PAY_NOT_SUPPORT);
}
//获取支付插件
Payment payment = (Payment) SpringUtils.getBean( paymentMethodEnum.getPlugin());
log.info("支付请求:客户端:{},支付类型:{},请求:{}", paymentClientEnum.name(), paymentMethodEnum.name(), payParam.toString());
//支付方式调用
switch (paymentClientEnum) {
case H5:
return payment.h5pay(request, response, payParam);
case APP:
return payment.appPay(request, payParam);
case JSAPI:
return payment.jsApiPay(request, payParam);
case NATIVE:
return payment.nativePay(request, payParam);
case MP:
return payment.mpPay(request, payParam);
default:
return null;
}
}
/**
* 支付 支持的支付方式
*
* @param client 客户端类型
* @return 支持的支付方式
*/
public List<String> support(String client) {
ClientTypeEnum clientTypeEnum;
try {
clientTypeEnum = ClientTypeEnum.valueOf(client);
} catch (IllegalArgumentException e) {
throw new ServiceException(ResultCode.PAY_CLIENT_TYPE_ERROR);
}
//支付方式 循环获取
// Setting setting = settingService.get(SettingEnum.PAYMENT_SUPPORT.name());
// PaymentSupportSetting paymentSupportSetting = JSONUtil.toBean(setting.getSettingValue(), PaymentSupportSetting.class);
// for (PaymentSupportItem paymentSupportItem : paymentSupportSetting.getPaymentSupportItems()) {
// if (paymentSupportItem.getClient().equals(clientTypeEnum.name())) {
// return paymentSupportItem.getSupports();
// }
// }
//从系统配置中获取支付方式 默认为易生支付
return Arrays.asList(PaymentMethodEnum.EASY_PAY.name());
}
/**
* 支付回调
*
* @param paymentMethodEnum 支付渠道枚举
* @return 回调消息
*/
public void callback(PaymentMethodEnum paymentMethodEnum,
HttpServletRequest request) {
log.info("支付回调:支付类型:{}", paymentMethodEnum.name());
//获取支付插件
Payment payment = (Payment) SpringUtils.getBean((paymentMethodEnum.getPlugin()));
payment.callBack(request);
}
/**
* 支付通知
*
* @param paymentMethodEnum 支付渠道
*/
public void notify(PaymentMethodEnum paymentMethodEnum,
HttpServletRequest request) {
log.info("支付异步通知:支付类型:{}", paymentMethodEnum.name());
//获取支付插件
Payment payment = (Payment) SpringUtils.getBean((paymentMethodEnum.getPlugin()));
payment.notify(request);
}
/**
* 获取收银台参数
*
* @param payParam 支付请求参数
* @return 收银台参数
*/
public CashierParam cashierParam(PayParam payParam) {
for (CashierExecute paramInterface : cashierExecuteList) {
CashierParam cashierParam = paramInterface.getPaymentParams(payParam);
log.info("订单编号", payParam.getSn());
//如果为空则表示收银台参数初始化不匹配继续匹配下一条
if (cashierParam == null) {
continue;
}
//如果订单不需要付款则抛出异常直接返回
if (cashierParam.getPrice() ==null) {
throw new ServiceException(ResultCode.PAY_UN_WANTED);
}
cashierParam.setSupport(support(payParam.getClientType()));
//获取订单的memberid
//获取当前余额
// cashierParam.setWalletValue(memberWalletService.getMemberWallet(UserContext.getCurrentUser().getId()).getMemberWallet());
// 设置自动取消时间戳
long cancelTime = cashierParam.getCreateTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() + 30 * 1000; // 30秒
cashierParam.setAutoCancel(30l);
// 添加到延迟队列
long delayMillis = cancelTime - System.currentTimeMillis();
orderDelayCancelService.addOrderToCancelQueue(payParam.getSn(), delayMillis);
return cashierParam;
}
log.error("错误的支付请求:{}", payParam.toString());
throw new ServiceException(ResultCode.PAY_CASHIER_ERROR);
}
/**
* 支付结果
*
* @param payParam
* @return
*/
public Boolean paymentResult(PayParam payParam) {
for (CashierExecute cashierExecute : cashierExecuteList) {
if (cashierExecute.cashierEnum().name().equals(payParam.getOrderType())) {
return cashierExecute.paymentResult(payParam);
}
}
return false;
}
}

View File

@ -0,0 +1,156 @@
package com.wzj.soopin.transaction.kit;
import com.wzj.soopin.transaction.domain.po.RefundLog;
import com.wzj.soopin.transaction.enums.PaymentMethodEnum;
import com.wzj.soopin.transaction.kit.dto.PayParam;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.common.core.constant.ResultCode;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
/**
* 支付接口
*
* @author Chopper
* @since 2020-12-21 09:32
*/
public interface Payment {
/**
* 普通移动网页调用支付app
*
* @param request HttpServletRequest
* @param response HttpServletResponse
* @param payParam api参数
* @return 移动支付所需参数
*/
default R<Object> h5pay(HttpServletRequest request, HttpServletResponse response, PayParam payParam) {
throw new ServiceException(ResultCode.PAY_ERROR);
}
/**
* 公众号内部调用支付
*
* @param request HttpServletRequest
* @param payParam api参数
* @return 公众号内部支付参数
*/
default R<Object> jsApiPay(HttpServletRequest request, PayParam payParam) {
throw new ServiceException(ResultCode.PAY_ERROR);
}
/**
* app支付
*
* @param request HttpServletRequest
* @param payParam 支付参数
* @return app支付所需参数
*/
default R<Object> appPay(HttpServletRequest request, PayParam payParam) {
throw new ServiceException(ResultCode.PAY_ERROR);
}
/**
* 展示二维码扫描支付
*
* @param request HttpServletRequest
* @param payParam 支付参数
* @return 二维码内容
*/
default R<Object> nativePay(HttpServletRequest request, PayParam payParam) {
throw new ServiceException(ResultCode.PAY_ERROR);
}
/**
* 小程序支付
*
* @param request HttpServletRequest
* @param payParam 支付参数
* @return 二维码内容
*/
default R<Object> mpPay(HttpServletRequest request, PayParam payParam) {
throw new ServiceException(ResultCode.PAY_ERROR);
}
/**
* 退款
*
* @param refundLog 退款请求参数
*/
default void refund(RefundLog refundLog) {
throw new ServiceException(ResultCode.PAY_ERROR);
}
/**
* 取消支付订单
*
* @param refundLog 支付参数
*/
default void cancel(RefundLog refundLog) {
throw new ServiceException(ResultCode.PAY_ERROR);
}
/**
* 回调
*
* @param request HttpServletRequest
*/
default void callBack(HttpServletRequest request) {
throw new ServiceException(ResultCode.PAY_ERROR);
}
/**
* 异步通知
*
* @param request HttpServletRequest
*/
default void notify(HttpServletRequest request) {
throw new ServiceException(ResultCode.PAY_ERROR);
}
/**
* 退款异步通知
*
* @param request HttpServletRequest
*/
default void refundNotify(HttpServletRequest request) {
throw new ServiceException(ResultCode.PAY_ERROR);
}
/**
* 支付回调地址
*
* @param api api地址
* @param paymentMethodEnum 支付类型
* @return 回调地址
*/
default String callbackUrl(String api, PaymentMethodEnum paymentMethodEnum) {
return api + "/buyer/payment/cashier/callback/" + paymentMethodEnum.name();
}
/**
* 支付异步通知地址
*
* @param api api地址
* @param paymentMethodEnum 支付类型
* @return 异步通知地址
*/
default String notifyUrl(String api, PaymentMethodEnum paymentMethodEnum) {
return api + "/buyer/payment/cashier/notify/" + paymentMethodEnum.name();
}
/**
* 退款支付异步通知地址
*
* @param api api地址
* @param paymentMethodEnum 支付类型
* @return 异步通知地址
*/
default String refundNotifyUrl(String api, PaymentMethodEnum paymentMethodEnum) {
return api + "/buyer/payment/cashierRefund/notify/" + paymentMethodEnum.name();
}
}

View File

@ -0,0 +1,118 @@
package com.wzj.soopin.transaction.kit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 退款支持
*
* @author Chopper
* @since 2020-12-19 09:25
*/
@Component
@Slf4j
public class RefundSupport {
// /**
// * 店铺流水
// */
// @Autowired
// private StoreFlowService storeFlowService;
// /**
// * 订单
// */
// @Autowired
// private OrderService orderService;
// /**
// * 子订单
// */
// @Autowired
// private OrderItemService orderItemService;
//
// /**
// * 售后退款
// *
// * @param afterSale
// */
// public void refund(AfterSale afterSale) {
// Order order = orderService.getBySn(afterSale.getOrderSn());
// RefundLog refundLog = RefundLog.builder()
// .isRefund(false)
// .totalAmount(afterSale.getActualRefundPrice())
// .payPrice(afterSale.getActualRefundPrice())
// .memberId(afterSale.getMemberId())
// .paymentName(order.getPaymentMethod())
// .afterSaleNo(afterSale.getSn())
// .paymentReceivableNo(order.getReceivableNo())
// .outOrderNo("AF" + SnowFlake.getIdStr())
// .orderSn(afterSale.getOrderSn())
// .refundReason(afterSale.getReason())
// .build();
// PaymentMethodEnum paymentMethodEnum = PaymentMethodEnum.paymentNameOf(order.getPaymentMethod());
// Payment payment = (Payment) SpringContextUtil.getBean(paymentMethodEnum.getPlugin());
// payment.refund(refundLog);
//
// this.updateReturnGoodsNumber(afterSale);
//
// //记录退款流水
// storeFlowService.refundOrder(afterSale);
// }
//
// /**
// * 功能描述: 修改子订单中已售后退款商品数量
// *
// * @return void
// * @Author ftyy
// * @Description //TODO
// * @Date 17:33 2021/11/18
// * @Param [afterSale]
// **/
// private void updateReturnGoodsNumber(AfterSale afterSale) {
// //根据商品id及订单sn获取子订单
// OrderItem orderItem = orderItemService.getByOrderSnAndSkuId(afterSale.getOrderSn(), afterSale.getSkuId());
//
// orderItem.setReturnGoodsNumber(afterSale.getNum() + orderItem.getReturnGoodsNumber());
//
// //修改子订单订单中的退货数量
// orderItemService.updateById(orderItem);
// }
//
// /**
// * 订单取消
// *
// * @param afterSale
// */
// public void cancel(AfterSale afterSale) {
////
//// Order order = orderService.getBySn(afterSale.getOrderSn());
//// RefundLog refundLog = RefundLog.builder()
//// .isRefund(false)
//// .totalAmount(afterSale.getActualRefundPrice())
//// .payPrice(afterSale.getActualRefundPrice())
//// .memberId(afterSale.getMemberId())
//// .paymentName(order.getPaymentMethod())
//// .afterSaleNo(afterSale.getSn())
//// .paymentReceivableNo(order.getReceivableNo())
//// .outOrderNo("AF" + SnowFlake.getIdStr())
//// .orderSn(afterSale.getOrderSn())
//// .refundReason(afterSale.getReason())
//// .build();
//// PaymentMethodEnum paymentMethodEnum = PaymentMethodEnum.paymentNameOf(order.getPaymentMethod());
//// Payment payment = (Payment) SpringContextUtil.getBean(paymentMethodEnum.getPlugin());
//// payment.refund(refundLog);
// }
//
//
// /**
// * 退款通知
// *
// * @param paymentMethodEnum 支付渠道
// */
// public void notify(PaymentMethodEnum paymentMethodEnum,
// HttpServletRequest request) {
//
// //获取支付插件
// Payment payment = (Payment) SpringContextUtil.getBean(paymentMethodEnum.getPlugin());
// payment.refundNotify(request);
// }
}

View File

@ -0,0 +1,70 @@
package com.wzj.soopin.transaction.kit.core;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.CaseInsensitiveMap;
import cn.hutool.core.util.StrUtil;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* 支付接口响应
*
* @author Chopper
* @since 2020/12/18 15:13
*/
public class PaymentHttpResponse implements Serializable {
private static final long serialVersionUID = 6089103955998013402L;
private String body;
private int status;
private Map<String, List<String>> headers;
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public Map<String, List<String>> getHeaders() {
return headers;
}
public void setHeaders(Map<String, List<String>> headers) {
this.headers = headers;
}
public String getHeader(String name) {
List<String> values = this.headerList(name);
return CollectionUtil.isEmpty(values) ? null : values.get(0);
}
private List<String> headerList(String name) {
if (StrUtil.isBlank(name)) {
return null;
} else {
CaseInsensitiveMap<String, List<String>> headersIgnoreCase = new CaseInsensitiveMap<>(getHeaders());
return headersIgnoreCase.get(name.trim());
}
}
@Override
public String toString() {
return "IJPayHttpResponse{" +
"body='" + body + '\'' +
", status=" + status +
", headers=" + headers +
'}';
}
}

View File

@ -0,0 +1,249 @@
package com.wzj.soopin.transaction.kit.core;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
/**
* xpath解析xml
*
* <pre>
* 文档地址
* http://www.w3school.com.cn/xpath/index.asp
* </pre>
* @author L.cm
*/
public class XmlHelper {
private final XPath path;
private final Document doc;
private XmlHelper(InputSource inputSource) throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory dbf = getDocumentBuilderFactory();
//This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all
//XML entity attacks are prevented
//Xerces 2 only -
//http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
//If you can't completely disable DTDs, then at least do the following:
//Xerces 1 -
//http://xerces.apache.org/xerces-j/features.html#external-general-entities
//Xerces 2 -
//http://xerces.apache.org/xerces2-j/features.html#external-general-entities
//JDK7+ - http://xml.org/sax/features/external-general-entities
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
//Xerces 1 -
//http://xerces.apache.org/xerces-j/features.html#external-parameter-entities
//Xerces 2 -
//http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities
//JDK7+ - http://xml.org/sax/features/external-parameter-entities
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
//Disable external DTDs as well
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
//and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and
//Entity Attacks"
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
DocumentBuilder db = dbf.newDocumentBuilder();
doc = db.parse(inputSource);
path = getXpathFactory().newXPath();
}
private static XmlHelper create(InputSource inputSource) {
try {
return new XmlHelper(inputSource);
} catch (ParserConfigurationException | SAXException | IOException e) {
throw new RuntimeException(e);
}
}
public static XmlHelper of(InputStream is) {
InputSource inputSource = new InputSource(is);
return create(inputSource);
}
public static XmlHelper of(File file) {
InputSource inputSource = new InputSource(file.toURI().toASCIIString());
return create(inputSource);
}
public static XmlHelper of(String xmlStr) {
StringReader sr = new StringReader(xmlStr.trim());
InputSource inputSource = new InputSource(sr);
XmlHelper xmlHelper = create(inputSource);
sr.close();
return xmlHelper;
}
private Object evalXpath(String expression, Object item, QName returnType) {
item = null == item ? doc : item;
try {
return path.evaluate(expression, item, returnType);
} catch (XPathExpressionException e) {
throw new RuntimeException(e);
}
}
/**
* 获取String
*
* @param expression 路径
* @return String
*/
public String getString(String expression) {
return (String) evalXpath(expression, null, XPathConstants.STRING);
}
/**
* 获取Boolean
*
* @param expression 路径
* @return String
*/
public Boolean getBoolean(String expression) {
return (Boolean) evalXpath(expression, null, XPathConstants.BOOLEAN);
}
/**
* 获取Number
*
* @param expression 路径
* @return {Number}
*/
public Number getNumber(String expression) {
return (Number) evalXpath(expression, null, XPathConstants.NUMBER);
}
/**
* 获取某个节点
*
* @param expression 路径
* @return {Node}
*/
public Node getNode(String expression) {
return (Node) evalXpath(expression, null, XPathConstants.NODE);
}
/**
* 获取子节点
*
* @param expression 路径
* @return NodeList
*/
public NodeList getNodeList(String expression) {
return (NodeList) evalXpath(expression, null, XPathConstants.NODESET);
}
/**
* 获取String
*
* @param node 节点
* @param expression 相对于node的路径
* @return String
*/
public String getString(Object node, String expression) {
return (String) evalXpath(expression, node, XPathConstants.STRING);
}
/**
* 获取
*
* @param node 节点
* @param expression 相对于node的路径
* @return String
*/
public Boolean getBoolean(Object node, String expression) {
return (Boolean) evalXpath(expression, node, XPathConstants.BOOLEAN);
}
/**
* 获取
*
* @param node 节点
* @param expression 相对于node的路径
* @return {Number}
*/
public Number getNumber(Object node, String expression) {
return (Number) evalXpath(expression, node, XPathConstants.NUMBER);
}
/**
* 获取某个节点
*
* @param node 节点
* @param expression 路径
* @return {Node}
*/
public Node getNode(Object node, String expression) {
return (Node) evalXpath(expression, node, XPathConstants.NODE);
}
/**
* 获取子节点
*
* @param node 节点
* @param expression 相对于node的路径
* @return NodeList
*/
public NodeList getNodeList(Object node, String expression) {
return (NodeList) evalXpath(expression, node, XPathConstants.NODESET);
}
/**
* 针对没有嵌套节点的简单处理
*
* @return map集合
*/
public Map<String, String> toMap() {
Element root = doc.getDocumentElement();
//将节点封装成map形式
NodeList list = root.getChildNodes();
Map<String, String> params = new HashMap<String, String>(list.getLength());
for (int i = 0; i < list.getLength(); i++) {
Node node = list.item(i);
params.put(node.getNodeName(), node.getTextContent());
}
//含有空白符会生成一个#text参数
params.remove("#text");
return params;
}
private static DocumentBuilderFactory getDocumentBuilderFactory() {
return XmlHelperHolder.documentBuilderFactory;
}
private static XPathFactory getXpathFactory() {
return XmlHelperHolder.xPathFactory;
}
/**
* 内部类单例
*/
private static class XmlHelperHolder {
private static DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
private static XPathFactory xPathFactory = XPathFactory.newInstance();
}
}

View File

@ -0,0 +1,57 @@
package com.wzj.soopin.transaction.kit.core.enums;
/**
* HTTP 请求的方法
*
* @author Chopper
* @since 2021-01-25 15:10
*/
public enum RequestMethodEnums {
/**
* 上传实质是 post 请求
*/
UPLOAD("POST"),
/**
* post 请求
*/
POST("POST"),
/**
* get 请求
*/
GET("GET"),
/**
* put 请求
*/
PUT("PUT"),
/**
* delete 请求
*/
DELETE("DELETE"),
/**
* options 请求
*/
OPTIONS("OPTIONS"),
/**
* head 请求
*/
HEAD("HEAD"),
/**
* trace 请求
*/
TRACE("TRACE"),
/**
* connect 请求
*/
CONNECT("CONNECT");
private final String method;
RequestMethodEnums(String method) {
this.method = method;
}
@Override
public String toString() {
return this.method;
}
}

View File

@ -0,0 +1,37 @@
package com.wzj.soopin.transaction.kit.core.enums;
/**
* 签名方式
*
* @author Chopper
* @since 2021-01-25 15:10
*/
public enum SignType {
/**
* HMAC-SHA256 加密
*/
HMACSHA256("HMAC-SHA256"),
/**
* MD5 加密
*/
MD5("MD5"),
/**
* RSA
*/
RSA("RSA");
SignType(String type) {
this.type = type;
}
private final String type;
public String getType() {
return type;
}
@Override
public String toString() {
return type;
}
}

View File

@ -0,0 +1,498 @@
package com.wzj.soopin.transaction.kit.core.http;
import cn.hutool.core.io.FileUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.ssl.SSLSocketFactoryBuilder;
import com.wzj.soopin.transaction.kit.core.PaymentHttpResponse;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Map;
/**
* Http 代理类
*
* @author Chopper
* @since 2021-01-25 15:10
*/
public abstract class AbstractHttpDelegate {
/**
* get 请求
*
* @param url 请求url
* @return {@link String} 请求返回的结果
*/
public String get(String url) {
return HttpUtil.get(url);
}
/**
* get 请求
*
* @param url 请求url
* @param paramMap 请求参数
* @return {@link String} 请求返回的结果
*/
public String get(String url, Map<String, Object> paramMap) {
return HttpUtil.get(url, paramMap);
}
/**
* get 请求
*
* @param url 请求url
* @param paramMap 请求参数
* @param headers 请求头
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public PaymentHttpResponse get(String url, Map<String, Object> paramMap, Map<String, String> headers) {
PaymentHttpResponse response = new PaymentHttpResponse();
HttpResponse httpResponse = getToResponse(url, paramMap, headers);
response.setBody(httpResponse.body());
response.setStatus(httpResponse.getStatus());
response.setHeaders(httpResponse.headers());
return response;
}
/**
* post 请求
*
* @param url 请求url
* @param data 请求参数
* @return {@link String} 请求返回的结果
*/
public String post(String url, String data) {
return HttpUtil.post(url, data);
}
/**
* post 请求
*
* @param url 请求url
* @param paramMap 请求参数
* @return {@link String} 请求返回的结果
*/
public String post(String url, Map<String, Object> paramMap) {
return HttpUtil.post(url, paramMap);
}
/**
* post 请求
*
* @param url 请求url
* @param paramMap 请求参数
* @param headers 请求头
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public PaymentHttpResponse post(String url, Map<String, Object> paramMap, Map<String, String> headers) {
PaymentHttpResponse response = new PaymentHttpResponse();
HttpResponse httpResponse = postToResponse(url, headers, paramMap);
response.setBody(httpResponse.body());
response.setStatus(httpResponse.getStatus());
response.setHeaders(httpResponse.headers());
return response;
}
/**
* post 请求
*
* @param url 请求url
* @param data 请求参数
* @param headers 请求头
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public PaymentHttpResponse post(String url, String data, Map<String, String> headers) {
PaymentHttpResponse response = new PaymentHttpResponse();
HttpResponse httpResponse = postToResponse(url, headers, data);
response.setBody(httpResponse.body());
response.setStatus(httpResponse.getStatus());
response.setHeaders(httpResponse.headers());
return response;
}
/**
* patch 请求
*
* @param url 请求url
* @param paramMap 请求参数
* @param headers 请求头
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public PaymentHttpResponse patch(String url, Map<String, Object> paramMap, Map<String, String> headers) {
PaymentHttpResponse response = new PaymentHttpResponse();
HttpResponse httpResponse = patchToResponse(url, headers, paramMap);
response.setBody(httpResponse.body());
response.setStatus(httpResponse.getStatus());
response.setHeaders(httpResponse.headers());
return response;
}
/**
* patch 请求
*
* @param url 请求url
* @param data 请求参数
* @param headers 请求头
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public PaymentHttpResponse patch(String url, String data, Map<String, String> headers) {
PaymentHttpResponse response = new PaymentHttpResponse();
HttpResponse httpResponse = patchToResponse(url, headers, data);
response.setBody(httpResponse.body());
response.setStatus(httpResponse.getStatus());
response.setHeaders(httpResponse.headers());
return response;
}
/**
* delete 请求
*
* @param url 请求url
* @param paramMap 请求参数
* @param headers 请求头
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public PaymentHttpResponse delete(String url, Map<String, Object> paramMap, Map<String, String> headers) {
PaymentHttpResponse response = new PaymentHttpResponse();
HttpResponse httpResponse = deleteToResponse(url, headers, paramMap);
response.setBody(httpResponse.body());
response.setStatus(httpResponse.getStatus());
response.setHeaders(httpResponse.headers());
return response;
}
/**
* delete 请求
*
* @param url 请求url
* @param data 请求参数
* @param headers 请求头
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public PaymentHttpResponse delete(String url, String data, Map<String, String> headers) {
PaymentHttpResponse response = new PaymentHttpResponse();
HttpResponse httpResponse = deleteToResponse(url, headers, data);
response.setBody(httpResponse.body());
response.setStatus(httpResponse.getStatus());
response.setHeaders(httpResponse.headers());
return response;
}
/**
* put 请求
*
* @param url 请求url
* @param paramMap 请求参数
* @param headers 请求头
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public PaymentHttpResponse put(String url, Map<String, Object> paramMap, Map<String, String> headers) {
PaymentHttpResponse response = new PaymentHttpResponse();
HttpResponse httpResponse = putToResponse(url, headers, paramMap);
response.setBody(httpResponse.body());
response.setStatus(httpResponse.getStatus());
response.setHeaders(httpResponse.headers());
return response;
}
/**
* put 请求
*
* @param url 请求url
* @param data 请求参数
* @param headers 请求头
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public PaymentHttpResponse put(String url, String data, Map<String, String> headers) {
PaymentHttpResponse response = new PaymentHttpResponse();
HttpResponse httpResponse = putToResponse(url, headers, data);
response.setBody(httpResponse.body());
response.setStatus(httpResponse.getStatus());
response.setHeaders(httpResponse.headers());
return response;
}
/**
* 上传文件
*
* @param url 请求url
* @param data 请求参数
* @param certPath 证书路径
* @param certPass 证书密码
* @param filePath 上传文件路径
* @param protocol 协议
* @return {@link String} 请求返回的结果
*/
public String upload(String url, String data, String certPath, String certPass, String filePath, String protocol) {
try {
File file = FileUtil.newFile(filePath);
return HttpRequest.post(url)
.setSSLSocketFactory(SSLSocketFactoryBuilder
.create()
.setProtocol(protocol)
.setKeyManagers(getKeyManager(certPass, certPath, null))
.setSecureRandom(new SecureRandom())
.build()
)
.header("Content-Type", "multipart/form-data;boundary=\"boundary\"")
.form("file", file)
.form("meta", data)
.execute()
.body();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 上传文件
*
* @param url 请求url
* @param data 请求参数
* @param certPath 证书路径
* @param certPass 证书密码
* @param filePath 上传文件路径
* @return {@link String} 请求返回的结果
*/
public String upload(String url, String data, String certPath, String certPass, String filePath) {
return upload(url, data, certPath, certPass, filePath, SSLSocketFactoryBuilder.TLSv1);
}
/**
* post 请求
*
* @param url 请求url
* @param data 请求参数
* @param certPath 证书路径
* @param certPass 证书密码
* @param protocol 协议
* @return {@link String} 请求返回的结果
*/
public String post(String url, String data, String certPath, String certPass, String protocol) {
try {
return HttpRequest.post(url)
.setSSLSocketFactory(SSLSocketFactoryBuilder
.create()
.setProtocol(protocol)
.setKeyManagers(getKeyManager(certPass, certPath, null))
.setSecureRandom(new SecureRandom())
.build()
)
.body(data)
.execute()
.body();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* post 请求
*
* @param url 请求url
* @param data 请求参数
* @param certPath 证书路径
* @param certPass 证书密码
* @return {@link String} 请求返回的结果
*/
public String post(String url, String data, String certPath, String certPass) {
return post(url, data, certPath, certPass, SSLSocketFactoryBuilder.TLSv1);
}
/**
* post 请求
*
* @param url 请求url
* @param data 请求参数
* @param certFile 证书文件输入流
* @param certPass 证书密码
* @param protocol 协议
* @return {@link String} 请求返回的结果
*/
public String post(String url, String data, InputStream certFile, String certPass, String protocol) {
try {
return HttpRequest.post(url)
.setSSLSocketFactory(SSLSocketFactoryBuilder
.create()
.setProtocol(protocol)
.setKeyManagers(getKeyManager(certPass, null, certFile))
.setSecureRandom(new SecureRandom())
.build()
)
.body(data)
.execute()
.body();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* post 请求
*
* @param url 请求url
* @param data 请求参数
* @param certFile 证书文件输入流
* @param certPass 证书密码
* @return {@link String} 请求返回的结果
*/
public String post(String url, String data, InputStream certFile, String certPass) {
return post(url, data, certFile, certPass, SSLSocketFactoryBuilder.TLSv1);
}
/**
* get 请求
*
* @param url 请求url
* @param paramMap 请求参数
* @param headers 请求头
* @return {@link HttpResponse} 请求返回的结果
*/
private HttpResponse getToResponse(String url, Map<String, Object> paramMap, Map<String, String> headers) {
return HttpRequest.get(url)
.addHeaders(headers)
.form(paramMap)
.execute();
}
/**
* post 请求
*
* @param url 请求url
* @param headers 请求头
* @param data 请求参数
* @return {@link HttpResponse} 请求返回的结果
*/
private HttpResponse postToResponse(String url, Map<String, String> headers, String data) {
return HttpRequest.post(url)
.addHeaders(headers)
.body(data)
.execute();
}
/**
* post 请求
*
* @param url 请求url
* @param headers 请求头
* @param paramMap 请求参数
* @return {@link HttpResponse} 请求返回的结果
*/
private HttpResponse postToResponse(String url, Map<String, String> headers, Map<String, Object> paramMap) {
return HttpRequest.post(url)
.addHeaders(headers)
.form(paramMap)
.execute();
}
/**
* patch 请求
*
* @param url 请求url
* @param headers 请求头
* @param paramMap 请求参数
* @return {@link HttpResponse} 请求返回的结果
*/
private HttpResponse patchToResponse(String url, Map<String, String> headers, Map<String, Object> paramMap) {
return HttpRequest.patch(url)
.addHeaders(headers)
.form(paramMap)
.execute();
}
/**
* patch 请求
*
* @param url 请求url
* @param headers 请求头
* @param data 请求参数
* @return {@link HttpResponse} 请求返回的结果
*/
private HttpResponse patchToResponse(String url, Map<String, String> headers, String data) {
return HttpRequest.patch(url)
.addHeaders(headers)
.body(data)
.execute();
}
/**
* delete 请求
*
* @param url 请求url
* @param headers 请求头
* @param data 请求参数
* @return {@link HttpResponse} 请求返回的结果
*/
private HttpResponse deleteToResponse(String url, Map<String, String> headers, String data) {
return HttpRequest.delete(url)
.addHeaders(headers)
.body(data)
.execute();
}
/**
* delete 请求
*
* @param url 请求url
* @param headers 请求头
* @param paramMap 请求参数
* @return {@link HttpResponse} 请求返回的结果
*/
private HttpResponse deleteToResponse(String url, Map<String, String> headers, Map<String, Object> paramMap) {
return HttpRequest.delete(url)
.addHeaders(headers)
.form(paramMap)
.execute();
}
/**
* put 请求
*
* @param url 请求url
* @param headers 请求头
* @param data 请求参数
* @return {@link HttpResponse} 请求返回的结果
*/
private HttpResponse putToResponse(String url, Map<String, String> headers, String data) {
return HttpRequest.put(url)
.addHeaders(headers)
.body(data)
.execute();
}
/**
* put 请求
*
* @param url 请求url
* @param headers 请求头
* @param paramMap 请求参数
* @return {@link HttpResponse} 请求返回的结果
*/
private HttpResponse putToResponse(String url, Map<String, String> headers, Map<String, Object> paramMap) {
return HttpRequest.put(url)
.addHeaders(headers)
.form(paramMap)
.execute();
}
private KeyManager[] getKeyManager(String certPass, String certPath, InputStream certFile) throws Exception {
KeyStore clientStore = KeyStore.getInstance("PKCS12");
if (certFile != null) {
clientStore.load(certFile, certPass.toCharArray());
} else {
clientStore.load(new FileInputStream(certPath), certPass.toCharArray());
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientStore, certPass.toCharArray());
return kmf.getKeyManagers();
}
}

View File

@ -0,0 +1,62 @@
package com.wzj.soopin.transaction.kit.core.kit;
import cn.hutool.core.codec.Base64;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/**
* <p>工具类 AesUtil</p>
*
* @author 微信
*/
public class AesUtil {
static final int KEY_LENGTH_BYTE = 32;
static final int TAG_LENGTH_BIT = 128;
private final byte[] aesKey;
/**
* @param key APIv3 密钥
*/
public AesUtil(byte[] key) {
if (key.length != KEY_LENGTH_BYTE) {
throw new IllegalArgumentException("无效的ApiV3Key长度必须为32个字节");
}
this.aesKey = key;
}
/**
* 证书和回调报文解密
*
* @param associatedData associated_data
* @param nonce nonce
* @param cipherText ciphertext
* @return {String} 平台证书明文
* @throws GeneralSecurityException 异常
*/
public String decryptToString(byte[] associatedData, byte[] nonce, String cipherText) throws GeneralSecurityException {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData);
return new String(cipher.doFinal(Base64.decode(cipherText)), StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
}

View File

@ -0,0 +1,82 @@
package com.wzj.soopin.transaction.kit.core.kit;
import com.wzj.soopin.transaction.kit.core.http.AbstractHttpDelegate;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* <p>Http 工具类</p>
*
* @author
*/
@Slf4j
public class HttpKit {
private static AbstractHttpDelegate delegate = new DefaultHttpKit();
public static AbstractHttpDelegate getDelegate() {
return delegate;
}
public static void setDelegate(AbstractHttpDelegate delegate) {
HttpKit.delegate = delegate;
}
public static String readData(HttpServletRequest request) {
BufferedReader br = null;
try {
StringBuilder result = new StringBuilder();
br = request.getReader();
for (String line; (line = br.readLine()) != null; ) {
if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
log.error("readData错误",e);
}
}
}
}
/**
* 将同步通知的参数转化为Map
*
* @param request {@link HttpServletRequest}
* @return 转化后的 Map
*/
public static Map<String, String> toMap(HttpServletRequest request) {
Map<String, String> params = new HashMap<>(16);
Map<String, String[]> requestParams = request.getParameterMap();
for (String name : requestParams.keySet()) {
String[] values = requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
params.put(name, valueStr);
}
return params;
}
}
/**
* 使用 huTool 实现的 Http 工具类
*
* @author
*/
class DefaultHttpKit extends AbstractHttpDelegate {
}

View File

@ -0,0 +1,38 @@
package com.wzj.soopin.transaction.kit.core.kit;
import jakarta.servlet.http.HttpServletRequest;
/**
* @author
*/
public class IpKit {
public IpKit() {
}
public static String getRealIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
//对于通过多个代理的情况第一个 IP 为客户端真实 IP多个IP按照','分割
if (ip != null && ip.length() > 15) {
if (ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
}
// return ip;
return "27.189.225.9";
}
}

View File

@ -0,0 +1,481 @@
package com.wzj.soopin.transaction.kit.core.kit;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.HmacAlgorithm;
import com.wzj.soopin.transaction.kit.core.XmlHelper;
import com.wzj.soopin.transaction.kit.core.enums.RequestMethodEnums;
import lombok.extern.slf4j.Slf4j;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.*;
import java.util.*;
/**
* 支付工具
*
* @author Chopper
* @version v4.0
* @since 2020/12/18 15:24
*/
@Slf4j
public class PayKit {
/**
* 生成16进制的 sha256 字符串
*
* @param data 数据
* @param key 密钥
* @return sha256 字符串
*/
public static String hmacSha256(String data, String key) {
return SecureUtil.hmac(HmacAlgorithm.HmacSHA256, key).digestHex(data);
}
/**
* SHA1加密文件生成16进制SHA1字符串<br>
*
* @param dataFile 被加密文件
* @return SHA1 字符串
*/
public static String sha1(File dataFile) {
return SecureUtil.sha1(dataFile);
}
/**
* SHA1加密生成16进制SHA1字符串<br>
*
* @param data 数据
* @return SHA1 字符串
*/
public static String sha1(InputStream data) {
return SecureUtil.sha1(data);
}
/**
* SHA1加密生成16进制SHA1字符串<br>
*
* @param data 数据
* @return SHA1 字符串
*/
public static String sha1(String data) {
return SecureUtil.sha1(data);
}
/**
* 生成16进制 MD5 字符串
*
* @param data 数据
* @return MD5 字符串
*/
public static String md5(String data) {
return SecureUtil.md5(data);
}
/**
* AES 解密
*
* @param base64Data 需要解密的数据
* @param key 密钥
* @return 解密后的数据
*/
public static String decryptData(String base64Data, String key) {
return SecureUtil.aes(md5(key).toLowerCase().getBytes()).decryptStr(base64Data);
}
/**
* AES 加密
*
* @param data 需要加密的数据
* @param key 密钥
* @return 加密后的数据
*/
public static String encryptData(String data, String key) {
return SecureUtil.aes(md5(key).toLowerCase().getBytes()).encryptBase64(data.getBytes());
}
/**
* 把所有元素排序
*
* @param params 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
*/
public static String createLinkString(Map<String, String> params) {
return createLinkString(params, false);
}
/**
* @param params 需要排序并参与字符拼接的参数组
* @param encode 是否进行URLEncoder
* @return 拼接后字符串
*/
public static String createLinkString(Map<String, String> params, boolean encode) {
return createLinkString(params, "&", encode);
}
/**
* @param params 需要排序并参与字符拼接的参数组
* @param connStr 连接符号
* @param encode 是否进行URLEncoder
* @return 拼接后字符串
*/
public static String createLinkString(Map<String, String> params, String connStr, boolean encode) {
return createLinkString(params, connStr, encode, false);
}
public static String createLinkString(Map<String, String> params, String connStr, boolean encode, boolean quotes) {
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
StringBuilder content = new StringBuilder();
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
//拼接时不包括最后一个&字符
if (i == keys.size() - 1) {
if (quotes) {
content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"');
} else {
content.append(key).append("=").append(encode ? urlEncode(value) : value);
}
} else {
if (quotes) {
content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"').append(connStr);
} else {
content.append(key).append("=").append(encode ? urlEncode(value) : value).append(connStr);
}
}
}
return content.toString();
}
/**
* URL 编码
*
* @param src 需要编码的字符串
* @return 编码后的字符串
*/
public static String urlEncode(String src) {
try {
return URLEncoder.encode(src, CharsetUtil.UTF_8).replace("+", "%20");
} catch (UnsupportedEncodingException e) {
log.error("URL 编码错误",e);
return null;
}
}
/**
* 遍历 Map 并构建 xml 数据
*
* @param params 需要遍历的 Map
* @param prefix xml 前缀
* @param suffix xml 后缀
* @return xml 字符串
*/
public static StringBuffer forEachMap(Map<String, String> params, String prefix, String suffix) {
StringBuffer xml = new StringBuffer();
if (StrUtil.isNotEmpty(prefix)) {
xml.append(prefix);
}
for (Map.Entry<String, String> entry : params.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
//略过空值
if (StrUtil.isEmpty(value)) {
continue;
}
xml.append("<").append(key).append(">");
xml.append(entry.getValue());
xml.append("</").append(key).append(">");
}
if (StrUtil.isNotEmpty(suffix)) {
xml.append(suffix);
}
return xml;
}
/**
* 微信下单 map to xml
*
* @param params Map 参数
* @return xml 字符串
*/
public static String toXml(Map<String, String> params) {
StringBuffer xml = forEachMap(params, "<xml>", "</xml>");
return xml.toString();
}
/**
* 针对支付的 xml没有嵌套节点的简单处理
*
* @param xmlStr xml 字符串
* @return 转化后的 Map
*/
public static Map<String, String> xmlToMap(String xmlStr) {
XmlHelper xmlHelper = XmlHelper.of(xmlStr);
return xmlHelper.toMap();
}
/**
* 构造签名串
*
* @param method {@link RequestMethodEnums} GET,POST,PUT等
* @param url 请求接口 /v3/certificates
* @param timestamp 获取发起请求时的系统当前时间戳
* @param nonceStr 随机字符串
* @param body 请求报文主体
* @return 待签名字符串
*/
public static String buildSignMessage(RequestMethodEnums method, String url, long timestamp, String nonceStr, String body) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add(method.toString());
arrayList.add(url);
arrayList.add(String.valueOf(timestamp));
arrayList.add(nonceStr);
arrayList.add(body);
return buildSignMessage(arrayList);
}
/**
* 构造签名串
*
* @param timestamp 应答时间戳
* @param nonceStr 应答随机串
* @param body 应答报文主体
* @return 应答待签名字符串
*/
public static String buildSignMessage(String timestamp, String nonceStr, String body) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add(timestamp);
arrayList.add(nonceStr);
arrayList.add(body);
return buildSignMessage(arrayList);
}
/**
* 构造签名串
*
* @param signMessage 待签名的参数
* @return 构造后带待签名串
*/
public static String buildSignMessage(ArrayList<String> signMessage) {
if (signMessage == null || signMessage.size() <= 0) {
return null;
}
StringBuilder sbf = new StringBuilder();
for (String str : signMessage) {
sbf.append(str).append("\n");
}
return sbf.toString();
}
/**
* v3 接口创建签名
*
* @param signMessage 待签名的参数
* @param keyPath key.pem 证书路径
* @return 生成 v3 签名
* @throws Exception 异常信息
*/
public static String createSign(ArrayList<String> signMessage, String keyPath) throws Exception {
return createSign(buildSignMessage(signMessage), keyPath);
}
/**
* v3 接口创建签名
*
* @param signMessage 待签名的参数
* @param privateKey 商户私钥
* @return 生成 v3 签名
* @throws Exception 异常信息
*/
public static String createSign(ArrayList<String> signMessage, PrivateKey privateKey) throws Exception {
return createSign(buildSignMessage(signMessage), privateKey);
}
/**
* v3 接口创建签名
*
* @param signMessage 待签名的参数
* @param keyPath key.pem 证书路径
* @return 生成 v3 签名
* @throws Exception 异常信息
*/
public static String createSign(String signMessage, String keyPath) throws Exception {
if (StrUtil.isEmpty(signMessage)) {
return null;
}
//获取商户私钥
PrivateKey privateKey = PayKit.getPrivateKey(keyPath);
//生成签名
return RsaKit.encryptByPrivateKey(signMessage, privateKey);
}
/**
* v3 接口创建签名
*
* @param signMessage 待签名的参数
* @param privateKey 商户私钥
* @return 生成 v3 签名
* @throws Exception 异常信息
*/
public static String createSign(String signMessage, PrivateKey privateKey) throws Exception {
if (StrUtil.isEmpty(signMessage)) {
return null;
}
//生成签名
return RsaKit.encryptByPrivateKey(signMessage, privateKey);
}
/**
* 获取授权认证信息
*
* @param mchId 商户号
* @param serialNo 商户API证书序列号
* @param nonceStr 请求随机串
* @param timestamp 时间戳
* @param signature 签名值
* @param authType 认证类型目前为WECHATPAY2-SHA256-RSA2048
* @return 请求头 Authorization
*/
public static String getAuthorization(String mchId, String serialNo, String nonceStr, String timestamp, String signature, String authType) {
Map<String, String> params = new HashMap<>(5);
params.put("mchid", mchId);
params.put("serial_no", serialNo);
params.put("nonce_str", nonceStr);
params.put("timestamp", timestamp);
params.put("signature", signature);
return authType.concat(" ").concat(createLinkString(params, ",", false, true));
}
/**
* 获取商户私钥
*
* @param keyPath 商户私钥证书路径
* @return {@link String} 商户私钥
* @throws Exception 异常信息
*/
public static String getPrivateKeyStr(String keyPath) throws Exception {
return RsaKit.getPrivateKeyStr(getPrivateKey(keyPath));
}
/**
* 获取商户私钥
*
* @param keyPath 商户私钥证书路径
* @return {@link PrivateKey} 商户私钥
* @throws Exception 异常信息
*/
public static PrivateKey getPrivateKey(String keyPath) throws Exception {
String originalKey = FileUtil.readUtf8String(keyPath);
String privateKey = originalKey
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
return RsaKit.loadPrivateKey(privateKey);
}
/**
* 获取证书
*
* @param inputStream 证书文件
* @return {@link X509Certificate} 获取证书
*/
public static X509Certificate getCertificate(InputStream inputStream) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
cert.checkValidity();
return cert;
} catch (CertificateExpiredException e) {
throw new RuntimeException("证书已过期", e);
} catch (CertificateNotYetValidException e) {
throw new RuntimeException("证书尚未生效", e);
} catch (CertificateException e) {
throw new RuntimeException("无效的证书", e);
}
}
/**
* 获取证书
*
* @param publicKey 证书
* @return {@link X509Certificate} 获取证书
*/
public static X509Certificate getCertificate(String publicKey) {
try {
X509Certificate cert = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
cert.checkValidity();
return cert;
} catch (CertificateExpiredException e) {
throw new RuntimeException("证书已过期", e);
} catch (CertificateNotYetValidException e) {
throw new RuntimeException("证书尚未生效", e);
}
}
/**
* 公钥加密
*
* @param data 待加密数据
* @param certificate 平台公钥证书
* @return 加密后的数据
* @throws Exception 异常信息
*/
public static String rsaEncryptOAEP(String data, X509Certificate certificate) throws Exception {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());
byte[] dataByte = data.getBytes(StandardCharsets.UTF_8);
byte[] cipherData = cipher.doFinal(dataByte);
return Base64.encode(cipherData);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
} catch (InvalidKeyException e) {
throw new IllegalArgumentException("无效的证书", e);
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
}
}
/**
* 私钥解密
*
* @param cipherText 加密字符
* @param privateKey 私钥
* @return 解密后的数据
* @throws Exception 异常信息
*/
public static String rsaDecryptOAEP(String cipherText, PrivateKey privateKey) throws Exception {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] data = Base64.decode(cipherText);
return new String(cipher.doFinal(data), StandardCharsets.UTF_8);
} catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
} catch (InvalidKeyException e) {
throw new IllegalArgumentException("无效的私钥", e);
} catch (BadPaddingException | IllegalBlockSizeException e) {
throw new BadPaddingException("解密失败");
}
}
}

View File

@ -0,0 +1,101 @@
package com.wzj.soopin.transaction.kit.core.kit;
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.client.j2se.MatrixToImageConfig;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import lombok.extern.slf4j.Slf4j;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
/**
* <p> google 开源图形码工具类</p>
*
* @author
*/
@Slf4j
public class QrCodeKit {
/**
* 图形码生成工具
*
* @param contents 内容
* @param barcodeFormat BarcodeFormat对象
* @param format 图片格式可选[png,jpg,bmp]
* @param width
* @param height
* @param margin 边框间距px
* @param saveImgFilePath 存储图片的完整位置包含文件名
* @return {boolean}
*/
public static boolean encode(String contents, BarcodeFormat barcodeFormat, Integer margin,
ErrorCorrectionLevel errorLevel, String format, int width, int height, String saveImgFilePath) {
boolean bool = false;
BufferedImage bufImg;
Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>(3);
//指定纠错等级
hints.put(EncodeHintType.ERROR_CORRECTION, errorLevel);
hints.put(EncodeHintType.MARGIN, margin);
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
try {
BitMatrix bitMatrix = new MultiFormatWriter().encode(contents, barcodeFormat, width, height, hints);
MatrixToImageConfig config = new MatrixToImageConfig(0xFF000001, 0xFFFFFFFF);
bufImg = MatrixToImageWriter.toBufferedImage(bitMatrix, config);
bool = writeToFile(bufImg, format, saveImgFilePath);
} catch (Exception e) {
log.error("图形码生成工具生成错误",e);
}
return bool;
}
/**
* @param srcImgFilePath 要解码的图片地址
* @return {Result}
*/
public static Result decode(String srcImgFilePath) {
Result result = null;
BufferedImage image;
try {
File srcFile = new File(srcImgFilePath);
image = ImageIO.read(srcFile);
if (null != image) {
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
Hashtable<DecodeHintType, String> hints = new Hashtable<DecodeHintType, String>();
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
result = new MultiFormatReader().decode(bitmap, hints);
} else {
throw new IllegalArgumentException("Could not decode image.");
}
} catch (Exception e) {
log.error("图片解码错误",e);
}
return result;
}
/**
* 将BufferedImage对象写入文件
*
* @param bufImg BufferedImage对象
* @param format 图片格式可选[png,jpg,bmp]
* @param saveImgFilePath 存储图片的完整位置包含文件名
* @return {boolean}
*/
public static boolean writeToFile(BufferedImage bufImg, String format, String saveImgFilePath) {
boolean bool = false;
try {
bool = ImageIO.write(bufImg, format, new File(saveImgFilePath));
} catch (Exception e) {
log.error("将BufferedImage对象写入文件错误",e);
}
return bool;
}
}

View File

@ -0,0 +1,362 @@
package com.wzj.soopin.transaction.kit.core.kit;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
/**
* <p>RSA 非对称加密工具类</p>
*
* @author
*/
@Slf4j
public class RsaKit {
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
/**
* 加密算法RSA
*/
private static final String KEY_ALGORITHM = "RSA";
/**
* 生成公钥和私钥
*
* @throws Exception 异常信息
*/
public static Map<String, String> getKeys() throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
keyPairGen.initialize(1024);
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
String publicKeyStr = getPublicKeyStr(publicKey);
String privateKeyStr = getPrivateKeyStr(privateKey);
Map<String, String> map = new HashMap<String, String>(2);
map.put("publicKey", publicKeyStr);
map.put("privateKey", privateKeyStr);
System.out.println("公钥\r\n" + publicKeyStr);
System.out.println("私钥\r\n" + privateKeyStr);
return map;
}
/**
* 使用模和指数生成RSA公钥
* 注意此代码用了默认补位方式为RSA/None/PKCS1Padding不同JDK默认的补位方式可能不同如Android默认是RSA
* /None/NoPadding
*
* @param modulus
* @param exponent 公钥指数
* @return {@link RSAPublicKey}
*/
public static RSAPublicKey getPublicKey(String modulus, String exponent) {
try {
BigInteger b1 = new BigInteger(modulus);
BigInteger b2 = new BigInteger(exponent);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(b1, b2);
return (RSAPublicKey) keyFactory.generatePublic(keySpec);
} catch (Exception e) {
log.error("使用模和指数生成RSA公钥错误",e);
return null;
}
}
/**
* 公钥加密
*
* @param data 需要加密的数据
* @param publicKey 公钥
* @return 加密后的数据
* @throws Exception 异常信息
*/
public static String encryptByPublicKey(String data, String publicKey) throws Exception {
return encryptByPublicKey(data, publicKey, "RSA/ECB/PKCS1Padding");
}
/**
* 公钥加密
*
* @param data 需要加密的数据
* @param publicKey 公钥
* @return 加密后的数据
* @throws Exception 异常信息
*/
public static String encryptByPublicKeyByWx(String data, String publicKey) throws Exception {
return encryptByPublicKey(data, publicKey, "RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING");
}
/**
* 公钥加密
*
* @param data 需要加密的数据
* @param publicKey 公钥
* @param fillMode 填充模式
* @return 加密后的数据
* @throws Exception 异常信息
*/
public static String encryptByPublicKey(String data, String publicKey, String fillMode) throws Exception {
byte[] dataByte = data.getBytes(StandardCharsets.UTF_8);
byte[] keyBytes = Base64.decode(publicKey);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key key = keyFactory.generatePublic(x509KeySpec);
//对数据加密
Cipher cipher = Cipher.getInstance(fillMode);
cipher.init(Cipher.ENCRYPT_MODE, key);
int inputLen = dataByte.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
//对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(dataByte, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(dataByte, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
return StrUtil.str(Base64.encode(encryptedData));
}
/**
* 私钥签名
*
* @param data 需要加密的数据
* @param privateKey 私钥
* @return 加密后的数据
* @throws Exception 异常信息
*/
public static String encryptByPrivateKey(String data, String privateKey) throws Exception {
PKCS8EncodedKeySpec priPkcs8 = new PKCS8EncodedKeySpec(Base64.decode(privateKey));
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PrivateKey priKey = keyFactory.generatePrivate(priPkcs8);
Signature signature = Signature.getInstance("SHA256WithRSA");
signature.initSign(priKey);
signature.update(data.getBytes(StandardCharsets.UTF_8));
byte[] signed = signature.sign();
return StrUtil.str(Base64.encode(signed));
}
/**
* 私钥签名
*
* @param data 需要加密的数据
* @param privateKey 私钥
* @return 加密后的数据
* @throws Exception 异常信息
*/
public static String encryptByPrivateKey(String data, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("SHA256WithRSA");
signature.initSign(privateKey);
signature.update(data.getBytes(StandardCharsets.UTF_8));
byte[] signed = signature.sign();
return StrUtil.str(Base64.encode(signed));
}
/**
* 公钥验证签名
*
* @param data 需要加密的数据
* @param sign 签名
* @param publicKey 公钥
* @return 验证结果
* @throws Exception 异常信息
*/
public static boolean checkByPublicKey(String data, String sign, String publicKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
byte[] encodedKey = Base64.decode(publicKey);
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
Signature signature = Signature.getInstance("SHA256WithRSA");
signature.initVerify(pubKey);
signature.update(data.getBytes(StandardCharsets.UTF_8));
return signature.verify(Base64.decode(sign.getBytes(StandardCharsets.UTF_8)));
}
/**
* 公钥验证签名
*
* @param data 需要加密的数据
* @param sign 签名
* @param publicKey 公钥
* @return 验证结果
* @throws Exception 异常信息
*/
public static boolean checkByPublicKey(String data, String sign, PublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance("SHA256WithRSA");
signature.initVerify(publicKey);
signature.update(data.getBytes(StandardCharsets.UTF_8));
return signature.verify(Base64.decode(sign.getBytes(StandardCharsets.UTF_8)));
}
/**
* 私钥解密
*
* @param data 需要解密的数据
* @param privateKey 私钥
* @return 解密后的数据
* @throws Exception 异常信息
*/
public static String decryptByPrivateKey(String data, String privateKey) throws Exception {
return decryptByPrivateKey(data, privateKey, "RSA/ECB/PKCS1Padding");
}
/**
* 私钥解密
*
* @param data 需要解密的数据
* @param privateKey 私钥
* @return 解密后的数据
* @throws Exception 异常信息
*/
public static String decryptByPrivateKeyByWx(String data, String privateKey) throws Exception {
return decryptByPrivateKey(data, privateKey, "RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING");
}
/**
* 私钥解密
*
* @param data 需要解密的数据
* @param privateKey 私钥
* @param fillMode 填充模式
* @return 解密后的数据
* @throws Exception 异常信息
*/
public static String decryptByPrivateKey(String data, String privateKey, String fillMode) throws Exception {
byte[] encryptedData = Base64.decode(data);
byte[] keyBytes = Base64.decode(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key key = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(fillMode);
cipher.init(Cipher.DECRYPT_MODE, key);
int inputLen = encryptedData.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
//对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
return new String(decryptedData);
}
/**
* 从字符串中加载公钥
*
* @param publicKeyStr 公钥数据字符串
* @throws Exception 异常信息
*/
public static PublicKey loadPublicKey(String publicKeyStr) throws Exception {
try {
byte[] buffer = Base64.decode(publicKeyStr);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
return keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此算法");
} catch (InvalidKeySpecException e) {
throw new Exception("公钥非法");
} catch (NullPointerException e) {
throw new Exception("公钥数据为空");
}
}
/**
* 从字符串中加载私钥<br>
* 加载时使用的是PKCS8EncodedKeySpecPKCS#8编码的Key指令
*
* @param privateKeyStr 私钥
* @return {@link PrivateKey}
* @throws Exception 异常信息
*/
public static PrivateKey loadPrivateKey(String privateKeyStr) throws Exception {
try {
byte[] buffer = Base64.decode(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
return keyFactory.generatePrivate(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此算法");
} catch (InvalidKeySpecException e) {
throw new Exception("私钥非法");
} catch (NullPointerException e) {
throw new Exception("私钥数据为空");
}
}
public static String getPrivateKeyStr(PrivateKey privateKey) {
return Base64.encode(privateKey.getEncoded());
}
public static String getPublicKeyStr(PublicKey publicKey) {
return Base64.encode(publicKey.getEncoded());
}
public static void main(String[] args) throws Exception {
Map<String, String> keys = getKeys();
String publicKey = keys.get("publicKey");
String privateKey = keys.get("privateKey");
String content = "我是,I am ";
String encrypt = encryptByPublicKey(content, publicKey);
String decrypt = decryptByPrivateKey(encrypt, privateKey);
System.out.println("加密之后:" + encrypt);
System.out.println("解密之后:" + decrypt);
System.out.println("======华丽的分割线=========");
content = "我是,I am ";
encrypt = encryptByPublicKeyByWx(content, publicKey);
decrypt = decryptByPrivateKeyByWx(encrypt, privateKey);
System.out.println("加密之后:" + encrypt);
System.out.println("解密之后:" + decrypt);
//OPPO
String sign = encryptByPrivateKey(content, privateKey);
System.out.println("加密之后:" + sign);
System.out.println(checkByPublicKey(content, sign, publicKey));
}
}

View File

@ -0,0 +1,718 @@
package com.wzj.soopin.transaction.kit.core.kit;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.wzj.soopin.transaction.kit.core.PaymentHttpResponse;
import com.wzj.soopin.transaction.kit.core.enums.RequestMethodEnums;
import com.wzj.soopin.transaction.kit.core.enums.SignType;
import org.dromara.common.core.constant.ResultCode;
import org.dromara.common.core.exception.ServiceException;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* <p>微信支付工具类</p>
*
* @author
*/
public class WxPayKit {
private static final String FIELD_SIGN = "sign";
private static final String FIELD_SIGN_TYPE = "sign_type";
public static String hmacSha256(String data, String key) {
return PayKit.hmacSha256(data, key);
}
public static String md5(String data) {
return PayKit.md5(data);
}
/**
* AES 解密
*
* @param base64Data 需要解密的数据
* @param key 密钥
* @return 解密后的数据
*/
public static String decryptData(String base64Data, String key) {
return PayKit.decryptData(base64Data, key);
}
/**
* AES 加密
*
* @param data 需要加密的数据
* @param key 密钥
* @return 加密后的数据
*/
public static String encryptData(String data, String key) {
return PayKit.encryptData(data, key);
}
public static String generateStr() {
return IdUtil.fastSimpleUUID();
}
/**
* 支付异步通知时校验 sign
*
* @param params 参数
* @param partnerKey 支付密钥
* @return {boolean}
*/
public static boolean verifyNotify(Map<String, String> params, String partnerKey) {
String sign = params.get("sign");
String localSign = createSign(params, partnerKey, SignType.MD5);
return sign.equals(localSign);
}
/**
* 支付异步通知时校验 sign
*
* @param params 参数
* @param partnerKey 支付密钥
* @param signType {@link SignType}
* @return {@link Boolean} 验证签名结果
*/
public static boolean verifyNotify(Map<String, String> params, String partnerKey, SignType signType) {
String sign = params.get("sign");
String localSign = createSign(params, partnerKey, signType);
return sign.equals(localSign);
}
/**
* 生成签名
*
* @param params 需要签名的参数
* @param partnerKey 密钥
* @param signType 签名类型
* @return 签名后的数据
*/
public static String createSign(Map<String, String> params, String partnerKey, SignType signType) {
if (signType == null) {
signType = SignType.MD5;
}
//生成签名前先去除sign
params.remove(FIELD_SIGN);
String tempStr = PayKit.createLinkString(params);
String stringSignTemp = tempStr + "&key=" + partnerKey;
if (signType == SignType.MD5) {
return md5(stringSignTemp).toUpperCase();
} else {
return hmacSha256(stringSignTemp, partnerKey).toUpperCase();
}
}
/**
* APP 单独生成签名
* app 支付环境中如果遇到签名错误百思不得其解则可以使用这个方法调用签名尝试解决
*
* @param params 需要签名的参数
* @return 签名后的数据
*/
public static String createAppSign(Map<String, String> params, String privateKey) {
String appid = params.get("appid");
String timestamp = params.get("timestamp");
String noncestr = params.get("noncestr");
String prepayid = params.get("prepayid");
String encrypt = appid + "\n" + timestamp + "\n" + noncestr + "\n" + prepayid + "\n";
try {
return PayKit.createSign(encrypt, privateKey);
} catch (Exception e) {
throw new ServiceException(ResultCode.ERROR);
}
}
/**
* 生成签名
*
* @param params 需要签名的参数
* @param secret 企业微信支付应用secret
* @return 签名后的数据
*/
public static String createSign(Map<String, String> params, String secret) {
//生成签名前先去除sign
params.remove(FIELD_SIGN);
String tempStr = PayKit.createLinkString(params);
String stringSignTemp = tempStr + "&secret=" + secret;
return md5(stringSignTemp).toUpperCase();
}
/**
* 构建签名
*
* @param params 需要签名的参数
* @param partnerKey 密钥
* @param signType 签名类型
* @return 签名后的 Map
*/
public static Map<String, String> buildSign(Map<String, String> params, String partnerKey, SignType signType) {
return buildSign(params, partnerKey, signType, true);
}
/**
* 构建签名
*
* @param params 需要签名的参数
* @param partnerKey 密钥
* @param signType 签名类型
* @param haveSignType 签名是否包含 sign_type 字段
* @return 签名后的 Map
*/
public static Map<String, String> buildSign(Map<String, String> params, String partnerKey, SignType signType, boolean haveSignType) {
if (haveSignType) {
params.put(FIELD_SIGN_TYPE, signType.getType());
}
String sign = createSign(params, partnerKey, signType);
params.put(FIELD_SIGN, sign);
return params;
}
public static StringBuffer forEachMap(Map<String, String> params, String prefix, String suffix) {
return PayKit.forEachMap(params, prefix, suffix);
}
/**
* 微信下单 map to xml
*
* @param params Map 参数
* @return xml 字符串
*/
public static String toXml(Map<String, String> params) {
return PayKit.toXml(params);
}
/**
* 针对支付的 xml没有嵌套节点的简单处理
*
* @param xmlStr xml 字符串
* @return 转化后的 Map
*/
public static Map<String, String> xmlToMap(String xmlStr) {
return PayKit.xmlToMap(xmlStr);
}
/**
* <p>生成二维码链接</p>
* <p>原生支付接口模式一(扫码模式一)</p>
*
* @param sign 签名
* @param appId 公众账号ID
* @param mchId 商户号
* @param productId 商品ID
* @param timeStamp 时间戳
* @param nonceStr 随机字符串
* @return {String}
*/
public static String bizPayUrl(String sign, String appId, String mchId, String productId, String timeStamp, String nonceStr) {
String rules = "weixin://wxpay/bizpayurl?sign=Temp&appid=Temp&mch_id=Temp&product_id=Temp&time_stamp=Temp&nonce_str=Temp";
return replace(rules, "Temp", sign, appId, mchId, productId, timeStamp, nonceStr);
}
/**
* <p>生成二维码链接</p>
* <p>原生支付接口模式一(扫码模式一)</p>
*
* @param partnerKey 密钥
* @param appId 公众账号ID
* @param mchId 商户号
* @param productId 商品ID
* @param timeStamp 时间戳
* @param nonceStr 随机字符串
* @param signType 签名类型
* @return {String}
*/
public static String bizPayUrl(String partnerKey, String appId, String mchId, String productId, String timeStamp, String nonceStr, SignType signType) {
HashMap<String, String> map = new HashMap<>(5);
map.put("appid", appId);
map.put("mch_id", mchId);
map.put("time_stamp", StrUtil.isEmpty(timeStamp) ? Long.toString(System.currentTimeMillis() / 1000) : timeStamp);
map.put("nonce_str", StrUtil.isEmpty(nonceStr) ? IdUtil.fastSimpleUUID() : nonceStr);
map.put("product_id", productId);
return bizPayUrl(createSign(map, partnerKey, signType), appId, mchId, productId, timeStamp, nonceStr);
}
/**
* <p>生成二维码链接</p>
* <p>原生支付接口模式一(扫码模式一)</p>
*
* @param partnerKey 密钥
* @param appId 公众账号ID
* @param mchId 商户号
* @param productId 商品ID
* @return {String}
*/
public static String bizPayUrl(String partnerKey, String appId, String mchId, String productId) {
String timeStamp = Long.toString(System.currentTimeMillis() / 1000);
String nonceStr = IdUtil.fastSimpleUUID();
HashMap<String, String> map = new HashMap<>(5);
map.put("appid", appId);
map.put("mch_id", mchId);
map.put("time_stamp", timeStamp);
map.put("nonce_str", nonceStr);
map.put("product_id", productId);
return bizPayUrl(createSign(map, partnerKey, null), appId, mchId, productId, timeStamp, nonceStr);
}
/**
* 替换url中的参数
*
* @param str 原始字符串
* @param regex 表达式
* @param args 替换字符串
* @return {String}
*/
public static String replace(String str, String regex, String... args) {
for (String arg : args) {
str = str.replaceFirst(regex, arg);
}
return str;
}
/**
* 判断接口返回的 code
*
* @param codeValue code
* @return 是否是 SUCCESS
*/
public static boolean codeIsOk(String codeValue) {
return StrUtil.isNotEmpty(codeValue) && "SUCCESS".equals(codeValue);
}
/**
* <p>公众号支付-预付订单再次签名</p>
* <p>注意此处签名方式需与统一下单的签名类型一致</p>
*
* @param prepayId 预付订单号
* @param appId 应用编号
* @param partnerKey API Key
* @param signType 签名方式
* @return 再次签名后的 Map
*/
public static Map<String, String> prepayIdCreateSign(String prepayId, String appId, String partnerKey, SignType signType) {
Map<String, String> packageParams = new HashMap<>(6);
packageParams.put("appId", appId);
packageParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
packageParams.put("nonceStr", String.valueOf(System.currentTimeMillis()));
packageParams.put("package", "prepay_id=" + prepayId);
if (signType == null) {
signType = SignType.MD5;
}
packageParams.put("signType", signType.getType());
String packageSign = WxPayKit.createSign(packageParams, partnerKey, signType);
packageParams.put("paySign", packageSign);
return packageParams;
}
/**
* JS 调起支付签名
*
* @param appId 应用编号
* @param prepayId 预付订单号
* @param keyPath key.pem 证书路径
* @return 唤起支付需要的参数
* @throws Exception 错误信息
*/
public static Map<String, String> jsApiCreateSign(String appId, String prepayId, String keyPath) throws Exception {
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = String.valueOf(System.currentTimeMillis());
String packageStr = "prepay_id=" + prepayId;
Map<String, String> packageParams = new HashMap<>(6);
packageParams.put("appId", appId);
packageParams.put("timeStamp", timeStamp);
packageParams.put("nonceStr", nonceStr);
packageParams.put("package", packageStr);
packageParams.put("signType", SignType.RSA.toString());
ArrayList<String> list = new ArrayList<>();
list.add(appId);
list.add(timeStamp);
list.add(nonceStr);
list.add(packageStr);
String packageSign = PayKit.createSign(
PayKit.buildSignMessage(list),
keyPath
);
packageParams.put("paySign", packageSign);
return packageParams;
}
/**
* <p>APP 支付-预付订单再次签名</p>
* <p>注意此处签名方式需与统一下单的签名类型一致</p>
*
* @param appId 应用编号
* @param partnerId 商户号
* @param prepayId 预付订单号
* @param partnerKey API Key
* @param signType 签名方式
* @return 再次签名后的 Map
*/
public static Map<String, String> appPrepayIdCreateSign(String appId, String partnerId, String prepayId, String partnerKey, SignType signType) {
Map<String, String> packageParams = new HashMap<>(8);
packageParams.put("appid", appId);
packageParams.put("partnerid", partnerId);
packageParams.put("prepayid", prepayId);
packageParams.put("package", "Sign=WXPay");
packageParams.put("noncestr", String.valueOf(System.currentTimeMillis()));
packageParams.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
if (signType == null) {
signType = SignType.MD5;
}
// String packageSign = createSign(packageParams, partnerKey, signType);
// 部分微信APP支付 提示签名错误 解开下方注释 替换上边的代码就好
String packageSign = createAppSign(packageParams, partnerKey);
packageParams.put("sign", packageSign);
return packageParams;
}
/**
* App 调起支付签名
*
* @param appId 应用编号
* @param partnerId 商户编号
* @param prepayId 预付订单号
* @param keyPath key.pem 证书路径
* @return 唤起支付需要的参数
* @throws Exception 错误信息
*/
public static Map<String, String> appCreateSign(String appId, String partnerId, String prepayId, String keyPath) throws Exception {
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = String.valueOf(System.currentTimeMillis());
Map<String, String> packageParams = new HashMap<>(8);
packageParams.put("appId", appId);
packageParams.put("partnerid", partnerId);
packageParams.put("prepayid", prepayId);
packageParams.put("package", "Sign=WXPay");
packageParams.put("timeStamp", timeStamp);
packageParams.put("nonceStr", nonceStr);
packageParams.put("signType", SignType.RSA.toString());
ArrayList<String> list = new ArrayList<>();
list.add(appId);
list.add(timeStamp);
list.add(nonceStr);
list.add(prepayId);
String packageSign = PayKit.createSign(
PayKit.buildSignMessage(list),
keyPath
);
packageParams.put("sign", packageSign);
return packageParams;
}
/**
* <p>小程序-预付订单再次签名</p>
* <p>注意此处签名方式需与统一下单的签名类型一致</p>
*
* @param appId 应用编号
* @param prepayId 预付订单号
* @param partnerKey API Key
* @param signType 签名方式
* @return 再次签名后的 Map
*/
public static Map<String, String> miniAppPrepayIdCreateSign(String appId, String prepayId, String partnerKey, SignType signType) {
Map<String, String> packageParams = new HashMap<>(6);
packageParams.put("appId", appId);
packageParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
packageParams.put("nonceStr", String.valueOf(System.currentTimeMillis()));
packageParams.put("package", "prepay_id=" + prepayId);
if (signType == null) {
signType = SignType.MD5;
}
packageParams.put("signType", signType.getType());
String packageSign = createSign(packageParams, partnerKey, signType);
packageParams.put("paySign", packageSign);
return packageParams;
}
/**
* 构建 v3 接口所需的 Authorization
*
* @param method {@link RequestMethodEnums} 请求方法
* @param urlSuffix 可通过 WxApiType 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param keyPath key.pem 证书路径
* @param body 接口请求参数
* @param nonceStr 随机字符库
* @param timestamp 时间戳
* @param authType 认证类型
* @return {@link String} 返回 v3 所需的 Authorization
* @throws Exception 异常信息
*/
public static String buildAuthorization(RequestMethodEnums method, String urlSuffix, String mchId,
String serialNo, String keyPath, String body, String nonceStr,
long timestamp, String authType) throws Exception {
//构建签名参数
String buildSignMessage = PayKit.buildSignMessage(method, urlSuffix, timestamp, nonceStr, body);
String signature = PayKit.createSign(buildSignMessage, keyPath);
//根据平台规则生成请求头 authorization
return PayKit.getAuthorization(mchId, serialNo, nonceStr, String.valueOf(timestamp), signature, authType);
}
/**
* 构建 v3 接口所需的 Authorization
*
* @param method {@link RequestMethodEnums} 请求方法
* @param urlSuffix 可通过 WxApiType 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param privateKey 商户私钥
* @param body 接口请求参数
* @param nonceStr 随机字符库
* @param timestamp 时间戳
* @param authType 认证类型
* @return {@link String} 返回 v3 所需的 Authorization
* @throws Exception 异常信息
*/
public static String buildAuthorization(RequestMethodEnums method, String urlSuffix, String mchId,
String serialNo, PrivateKey privateKey, String body, String nonceStr,
long timestamp, String authType) throws Exception {
//构建签名参数
String buildSignMessage = PayKit.buildSignMessage(method, urlSuffix, timestamp, nonceStr, body);
String signature = PayKit.createSign(buildSignMessage, privateKey);
//根据平台规则生成请求头 authorization
return PayKit.getAuthorization(mchId, serialNo, nonceStr, String.valueOf(timestamp), signature, authType);
}
/**
* 构建 v3 接口所需的 Authorization
*
* @param method {@link RequestMethodEnums} 请求方法
* @param urlSuffix 可通过 WxApiType 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param keyPath key.pem 证书路径
* @param body 接口请求参数
* @return {@link String} 返回 v3 所需的 Authorization
* @throws Exception 异常信息
*/
public static String buildAuthorization(RequestMethodEnums method, String urlSuffix, String mchId,
String serialNo, String keyPath, String body) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = "WECHATPAY2-SHA256-RSA2048";
String nonceStr = IdUtil.fastSimpleUUID();
return buildAuthorization(method, urlSuffix, mchId, serialNo, keyPath, body, nonceStr, timestamp, authType);
}
/**
* 构建 v3 接口所需的 Authorization
*
* @param method {@link RequestMethodEnums} 请求方法
* @param urlSuffix 可通过 WxApiType 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param privateKey key.pem 证书路径
* @param body 接口请求参数
* @return {@link String} 返回 v3 所需的 Authorization
* @throws Exception 异常信息
*/
public static String buildAuthorization(RequestMethodEnums method, String urlSuffix, String mchId,
String serialNo, PrivateKey privateKey, String body) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = "WECHATPAY2-SHA256-RSA2048";
String nonceStr = IdUtil.fastSimpleUUID();
return buildAuthorization(method, urlSuffix, mchId, serialNo, privateKey, body, nonceStr, timestamp, authType);
}
/**
* 验证签名
*
* @param response 接口请求返回的 {@link PaymentHttpResponse}
* @param certPath 平台证书路径
* @return 签名结果
* @throws Exception 异常信息
*/
public static boolean verifySignature(PaymentHttpResponse response, String certPath) throws Exception {
String timestamp = response.getHeader("Wechatpay-Timestamp");
String nonceStr = response.getHeader("Wechatpay-Nonce");
String signature = response.getHeader("Wechatpay-Signature");
String body = response.getBody();
return verifySignature(signature, body, nonceStr, timestamp, FileUtil.getInputStream(certPath));
}
/**
* 验证签名
*
* @param response 接口请求返回的 {@link PaymentHttpResponse}
* @param cert 平台证书
* @return 签名结果
* @throws Exception 异常信息
*/
public static boolean verifySignature(PaymentHttpResponse response, X509Certificate cert) throws Exception {
String timestamp = response.getHeader("Wechatpay-Timestamp");
String nonceStr = response.getHeader("Wechatpay-Nonce");
String signature = response.getHeader("Wechatpay-Signature");
String body = response.getBody();
return verifySignature(signature, body, nonceStr, timestamp, cert);
}
/**
* 验证签名
*
* @param signature 待验证的签名
* @param body 应答主体
* @param nonce 随机串
* @param timestamp 时间戳
* @param publicKey 微信支付平台公钥
* @return 签名结果
* @throws Exception 异常信息
*/
public static boolean verifySignature(String signature, String body, String nonce, String timestamp, String publicKey) throws Exception {
String buildSignMessage = PayKit.buildSignMessage(timestamp, nonce, body);
return RsaKit.checkByPublicKey(buildSignMessage, signature, publicKey);
}
/**
* 验证签名
*
* @param signature 待验证的签名
* @param body 应答主体
* @param nonce 随机串
* @param timestamp 时间戳
* @param publicKey {@link PublicKey} 微信支付平台公钥
* @return 签名结果
* @throws Exception 异常信息
*/
public static boolean verifySignature(String signature, String body, String nonce, String timestamp, PublicKey publicKey) throws Exception {
String buildSignMessage = PayKit.buildSignMessage(timestamp, nonce, body);
return RsaKit.checkByPublicKey(buildSignMessage, signature, publicKey);
}
/**
* 验证签名
*
* @param signature 待验证的签名
* @param body 应答主体
* @param nonce 随机串
* @param timestamp 时间戳
* @param certInputStream 微信支付平台证书输入流
* @return 签名结果
* @throws Exception 异常信息
*/
public static boolean verifySignature(String signature, String body, String nonce, String timestamp, InputStream certInputStream) throws Exception {
String buildSignMessage = PayKit.buildSignMessage(timestamp, nonce, body);
//获取证书
X509Certificate certificate = PayKit.getCertificate(certInputStream);
PublicKey publicKey = certificate.getPublicKey();
return RsaKit.checkByPublicKey(buildSignMessage, signature, publicKey);
}
/**
* 验证签名
*
* @param signature 待验证的签名
* @param body 应答主体
* @param nonce 随机串
* @param timestamp 时间戳
* @param certificate 微信支付平台证书
* @return 签名结果
* @throws Exception 异常信息
*/
public static boolean verifySignature(String signature, String body, String nonce, String timestamp, X509Certificate certificate) throws Exception {
String buildSignMessage = PayKit.buildSignMessage(timestamp, nonce, body);
PublicKey publicKey = certificate.getPublicKey();
return RsaKit.checkByPublicKey(buildSignMessage, signature, publicKey);
}
/**
* v3 支付异步通知验证签名
*
* @param serialNo 证书序列号
* @param body 异步通知密文
* @param signature 签名
* @param nonce 随机字符串
* @param timestamp 时间戳
* @param key api 密钥
* @param certPath 平台证书路径
* @return 异步通知明文
* @throws Exception 异常信息
*/
public static String verifyNotify(String serialNo, String body, String signature, String nonce,
String timestamp, String key, String certPath) throws Exception {
BufferedInputStream inputStream = FileUtil.getInputStream(certPath);
//获取平台证书序列号
X509Certificate certificate = PayKit.getCertificate(inputStream);
String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase();
System.out.println(serialNumber);
//验证证书序列号
if (serialNumber.equals(serialNo)) {
boolean verifySignature = WxPayKit.verifySignature(signature, body, nonce, timestamp, certificate.getPublicKey());
if (verifySignature) {
JSONObject resultObject = JSONUtil.parseObj(body);
JSONObject resource = resultObject.getJSONObject("resource");
String cipherText = resource.getStr("ciphertext");
String nonceStr = resource.getStr("nonce");
String associatedData = resource.getStr("associated_data");
AesUtil aesUtil = new AesUtil(key.getBytes(StandardCharsets.UTF_8));
//密文解密
return aesUtil.decryptToString(
associatedData.getBytes(StandardCharsets.UTF_8),
nonceStr.getBytes(StandardCharsets.UTF_8),
cipherText
);
}
}
return null;
}
/**
* v3 支付异步通知验证签名
*
* @param serialNo 证书序列号
* @param body 异步通知密文
* @param signature 签名
* @param nonce 随机字符串
* @param timestamp 时间戳
* @param key api 密钥
* @return 异步通知明文
* @throws Exception 异常信息
*/
public static String verifyNotify(String serialNo, String body, String signature, String nonce,
String timestamp, String key, X509Certificate certificate) throws Exception {
String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase();
//验证证书序列号
if (serialNumber.equals(serialNo)) {
boolean verifySignature = WxPayKit.verifySignature(signature, body, nonce, timestamp, certificate.getPublicKey());
if (verifySignature) {
JSONObject resultObject = JSONUtil.parseObj(body);
JSONObject resource = resultObject.getJSONObject("resource");
String cipherText = resource.getStr("ciphertext");
String nonceStr = resource.getStr("nonce");
String associatedData = resource.getStr("associated_data");
AesUtil aesUtil = new AesUtil(key.getBytes(StandardCharsets.UTF_8));
//密文解密
return aesUtil.decryptToString(
associatedData.getBytes(StandardCharsets.UTF_8),
nonceStr.getBytes(StandardCharsets.UTF_8),
cipherText
);
}
}
return null;
}
}

View File

@ -0,0 +1,82 @@
package com.wzj.soopin.transaction.kit.core.utils;
import cn.hutool.core.util.StrUtil;
import java.io.Serializable;
import java.time.ZonedDateTime;
import java.util.Date;
/**
* 时间工具类
* 依赖 xk-time
*
* @author YunGouOS
*/
public class DateTimeZoneUtil implements Serializable {
// private static final long serialVersionUID = -1331008203306650395L;
/**
* 时间转 TimeZone
* <p>
* 2020-08-17T16:46:37+08:00
*
* @param time 时间戳
* @return {@link String} TimeZone 格式时间字符串
* @throws Exception 异常信息
*/
public static String dateToTimeZone(long time) throws Exception {
return dateToTimeZone(new Date(time));
}
/**
* 时间转 TimeZone
* <p>
* 2020-08-17T16:46:37+08:00
*
* @param date {@link Date}
* @return {@link String} TimeZone 格式时间字符串
* @throws Exception 异常信息
*/
public static String dateToTimeZone(Date date) throws Exception {
String time = "";
if (date == null) {
throw new Exception("date is not null");
}
// ZonedDateTime zonedDateTime = DateTimeConverterUtil.toZonedDateTime(date);
// time = DateTimeFormatterUtil.format(zonedDateTime, DateTimeFormatterUtil.YYYY_MM_DD_T_HH_MM_SS_XXX_FMT);
return time;
}
/**
* TimeZone 时间转标准时间
* <p>
* 2020-08-17T16:46:37+08:00 to 2020-08-17 16:46:37
*
* @param str TimeZone格式时间字符串
* @return {@link String} 标准时间字符串
* @throws Exception 异常信息
*/
public static String timeZoneDateToStr(String str) throws Exception {
String time=null;
if (StrUtil.isBlank(str)) {
throw new Exception("str is not null");
}
// ZonedDateTime zonedDateTime = DateTimeFormatterUtil.parseToZonedDateTime(str, DateTimeFormatterUtil.YYYY_MM_DD_T_HH_MM_SS_XXX_FMT);
// if (zonedDateTime == null) {
// throw new Exception("str to zonedDateTime fail");
// }
// time = zonedDateTime.format(DateTimeFormatterUtil.YYYY_MM_DD_HH_MM_SS_FMT);
return time;
}
//
// public static void main(String[] args) throws Exception {
// String timeZone = dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
// String timeZone2 = dateToTimeZone(new Date());
// System.out.println(timeZone + " " + timeZone2);
// String date = timeZoneDateToStr(timeZone);
// System.out.println(date);
// }
}

View File

@ -0,0 +1,38 @@
package com.wzj.soopin.transaction.kit.dto;
import io.swagger.annotations.ApiModelProperty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.ToString;
import org.springframework.web.bind.annotation.PathVariable;
/**
* 支付参数
*
* @author Chopper
* @since 2020/12/19 11:46
*/
@Data
@ToString
public class PayParam {
@NotNull
@ApiModelProperty(value = "交易类型", allowableValues = "TRADE,ORDER,RECHARGE")
private String orderType;
@NotNull
@ApiModelProperty(value = "订单号")
private String sn;
@NotNull
@ApiModelProperty(value = "客户端类型")
private String clientType;
private String paymentMethod;
private String paymentClient;
}

View File

@ -0,0 +1,37 @@
package com.wzj.soopin.transaction.kit.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* PaymentSuccessParams
*
* @author Chopper
* @version v1.0
* 2021-04-27 16:24
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PaymentSuccessParams {
/**
* 支付方式
*/
private String paymentMethod;
/**
* 第三方流水
*/
private String receivableNo;
/**
* 支付金额
*/
private Double price;
/**
* 支付参数
*/
private PayParam payParam;
}

View File

@ -0,0 +1,46 @@
package com.wzj.soopin.transaction.kit.params;
import com.wzj.soopin.transaction.enums.CashierEnum;
import com.wzj.soopin.transaction.kit.dto.PayParam;
import com.wzj.soopin.transaction.kit.dto.PaymentSuccessParams;
import com.wzj.soopin.transaction.kit.params.dto.CashierParam;
/**
* 收银台接口
*
* @author Chopper
* @since 2021-01-25 19:08
*/
public interface CashierExecute {
/**
* 获取支付参数
*
* @param payParam 收银台支付参数
* @return 收银台所需支付参数
*/
CashierParam getPaymentParams(PayParam payParam);
/**
* 支付成功
*
* @param paymentSuccessParams 支付回调
*/
void paymentSuccess(PaymentSuccessParams paymentSuccessParams);
/**
* 支付结果查询
*
* @param payParam
* @return
*/
Boolean paymentResult(PayParam payParam);
/**
* 服务的枚举类型
*
* @return
*/
CashierEnum cashierEnum();
}

View File

@ -0,0 +1,54 @@
package com.wzj.soopin.transaction.kit.params.dto;
import com.wzj.soopin.order.utils.StringUtils;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.ToString;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
/**
* 支付参数
*
* @author Chopper
* @since 2021-01-25 19:09
*/
@Data
@ToString
public class CashierParam {
@ApiModelProperty(value = "价格")
private BigDecimal price;
@ApiModelProperty(value = "支付title")
private String title;
@ApiModelProperty(value = "支付详细描述")
private String detail;
@ApiModelProperty(value = "订单sn集合")
private String orderSns;
@ApiModelProperty(value = "支持支付方式")
private List<String> support;
@ApiModelProperty(value = "订单创建时间")
private LocalDateTime createTime;
@ApiModelProperty(value = "支付自动结束时间")
private Long autoCancel;
@ApiModelProperty(value = "剩余余额")
private Double walletValue;
public String getDetail() {
if (StringUtils.isEmpty(detail)) {
return "清单详细";
}
return StringUtils.filterSpecialChart(detail);
}
}

View File

@ -0,0 +1,118 @@
package com.wzj.soopin.transaction.kit.params.impl;
import com.wzj.soopin.order.domain.entity.Order;
import com.wzj.soopin.order.domain.entity.OrderItem;
import com.wzj.soopin.order.emum.OrderStatusEnum;
import com.wzj.soopin.order.mapper.OrderMapper;
import com.wzj.soopin.order.service.OrderItemService;
import com.wzj.soopin.order.service.OrderService;
import com.wzj.soopin.transaction.enums.CashierEnum;
import com.wzj.soopin.transaction.kit.dto.PayParam;
import com.wzj.soopin.transaction.kit.dto.PaymentSuccessParams;
import com.wzj.soopin.transaction.kit.params.CashierExecute;
import com.wzj.soopin.transaction.kit.params.dto.CashierParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.ResultCode;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.system.service.ISysConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 订单支付信息获取
*
* @author Chopper
* @since 2021-01-25 20:00
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class OrderCashier implements CashierExecute {
/**
* 订单
*/
private final OrderService orderService;
private final OrderItemService orderItemService;
/**
* 设置
*/
private final ISysConfigService settingService;
@Override
public CashierEnum cashierEnum() {
return CashierEnum.ORDER;
}
@Override
public CashierParam getPaymentParams(PayParam payParam) {
if (payParam.getOrderType().equals(CashierEnum.ORDER.name())) {
//准备返回的数据
CashierParam cashierParam = new CashierParam();
//订单信息获取
Order order = orderService.getByNo(payParam.getSn());
// //如果订单已支付则不能发器支付
if (order.getStatus().equals(OrderStatusEnum.WAITING_FOR_DELIVERY.name())) {
throw new ServiceException(ResultCode.PAY_DOUBLE_ERROR);
}
//如果订单状态不是待付款则抛出异常
if (!order.getStatus().equals(OrderStatusEnum.UNPAID.getValue())) {
throw new ServiceException(ResultCode.PAY_BAN);
}
cashierParam.setPrice(order.getPayAmount());
//
// try {
// BaseSetting baseSetting = JSONUtil.toBean(settingService.get(SettingEnum.BASE_SETTING.name()).getSettingValue(), BaseSetting.class);
// cashierParam.setTitle(baseSetting.getSiteName());
// } catch (Exception e) {
// cashierParam.setTitle("多用户商城,在线支付");
// }
//
//
List<OrderItem> orderItemList = orderItemService.findByOrderId(order.getId());
StringBuilder subject = new StringBuilder();
for (OrderItem orderItem : orderItemList) {
subject.append(orderItem.getProductName()).append(";");
}
cashierParam.setDetail(subject.toString());
cashierParam.setOrderSns(payParam.getSn());
cashierParam.setCreateTime(order.getCreateTime());
return cashierParam;
}
return null;
}
@Override
public void paymentSuccess(PaymentSuccessParams paymentSuccessParams) {
PayParam payParam = paymentSuccessParams.getPayParam();
if (payParam.getOrderType().equals(CashierEnum.ORDER.name())) {
// orderService.payOrder(payParam.getSn(),
// paymentSuccessParams.getPaymentMethod(),
// paymentSuccessParams.getReceivableNo());
log.info("订单{}支付成功,金额{},方式{}", payParam.getSn(),
paymentSuccessParams.getPaymentMethod(),
paymentSuccessParams.getReceivableNo());
}
}
@Override
public Boolean paymentResult(PayParam payParam) {
if (payParam.getOrderType().equals(CashierEnum.ORDER.name())) {
// Order order = orderService.getBySn(payParam.getSn());
// if (order != null) {
// return PayStatusEnum.PAID.name().equals(order.getPayStatus());
// } else {
// throw new ServiceException(ResultCode.PAY_NOT_EXIST_ORDER);
// }
}
return false;
}
}

View File

@ -0,0 +1,94 @@
package com.wzj.soopin.transaction.kit.params.impl;
import cn.hutool.json.JSONUtil;
import com.wzj.soopin.transaction.enums.CashierEnum;
import com.wzj.soopin.transaction.kit.dto.PayParam;
import com.wzj.soopin.transaction.kit.dto.PaymentSuccessParams;
import com.wzj.soopin.transaction.kit.params.CashierExecute;
import com.wzj.soopin.transaction.kit.params.dto.CashierParam;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.ResultCode;
import org.dromara.common.core.exception.ServiceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 充值信息获取
*
* @author Chopper
* @since 2021-01-25 20:00
*/
@Slf4j
@Component
public class RechargeCashier implements CashierExecute {
/**
* 余额
*/
// @Autowired
// private RechargeService rechargeService;
// /**
// * 设置
// */
// @Autowired
// private SettingService settingService;
@Override
public CashierEnum cashierEnum() {
return CashierEnum.RECHARGE;
}
@Override
public void paymentSuccess(PaymentSuccessParams paymentSuccessParams) {
PayParam payParam = paymentSuccessParams.getPayParam();
if (payParam.getOrderType().equals(CashierEnum.RECHARGE.name())) {
// rechargeService.paySuccess(payParam.getSn(), paymentSuccessParams.getReceivableNo(),paymentSuccessParams.getPaymentMethod());
log.info("会员充值-订单号{},第三方流水:{}", payParam.getSn(), paymentSuccessParams.getReceivableNo());
}
}
@Override
public CashierParam getPaymentParams(PayParam payParam) {
if (payParam.getOrderType().equals(CashierEnum.RECHARGE.name())) {
//准备返回的数据
CashierParam cashierParam = new CashierParam();
//订单信息获取
// Recharge recharge = rechargeService.getRecharge(payParam.getSn());
//
// //如果订单已支付则不能发器支付
// if (recharge.getPayStatus().equals(PayStatusEnum.PAID.name())) {
// throw new ServiceException(ResultCode.PAY_DOUBLE_ERROR);
// }
//
//
// cashierParam.setPrice(recharge.getRechargeMoney());
//
// try {
// BaseSetting baseSetting = JSONUtil.toBean(settingService.get(SettingEnum.BASE_SETTING.name()).getSettingValue(), BaseSetting.class);
// cashierParam.setTitle(baseSetting.getSiteName());
// } catch (Exception e) {
// cashierParam.setTitle("多用户商城,在线充值");
// }
// cashierParam.setDetail("余额充值");
// cashierParam.setCreateTime(recharge.getCreateTime());
// return cashierParam;
}
return null;
}
@Override
public Boolean paymentResult(PayParam payParam) {
if (payParam.getOrderType().equals(CashierEnum.RECHARGE.name())) {
// Recharge recharge = rechargeService.getRecharge(payParam.getSn());
// if (recharge != null) {
// return recharge.getPayStatus().equals(PayStatusEnum.PAID.name());
// } else {
// throw new ServiceException(ResultCode.PAY_NOT_EXIST_ORDER);
// }
}
return false;
}
}

View File

@ -0,0 +1,116 @@
package com.wzj.soopin.transaction.kit.params.impl;
import com.wzj.soopin.order.service.OrderService;
import com.wzj.soopin.transaction.enums.CashierEnum;
import com.wzj.soopin.transaction.kit.dto.PayParam;
import com.wzj.soopin.transaction.kit.dto.PaymentSuccessParams;
import com.wzj.soopin.transaction.kit.params.CashierExecute;
import com.wzj.soopin.transaction.kit.params.dto.CashierParam;
import lombok.extern.slf4j.Slf4j;
import org.dromara.system.service.ISysConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 整笔交易信息获取
*
* @author Chopper
* @since 2021-01-25 20:00
*/
@Slf4j
@Component
public class TradeCashier implements CashierExecute {
/**
* 交易
*/
// @Autowired
// private TradeService tradeService;
/**
* 订单
*/
@Autowired
private OrderService orderService;
/**
* 设置
*/
@Autowired
private ISysConfigService sysConfigService;
@Override
public CashierEnum cashierEnum() {
return CashierEnum.TRADE;
}
@Override
public CashierParam getPaymentParams(PayParam payParam) {
if (payParam.getOrderType().equals(CashierEnum.TRADE.name())) {
//准备返回的数据
// CashierParam cashierParam = new CashierParam();
// //订单信息获取
// Trade trade = tradeService.getBySn(payParam.getSn());
//
// List<Order> orders = orderService.getByTradeSn(payParam.getSn());
//
//
// String orderSns = orders.stream().map(Order::getSn).collect(Collectors.joining(", "));
// cashierParam.setOrderSns(orderSns);
//
// for (Order order : orders) {
// //如果订单已支付则不能发器支付
// if (order.getPayStatus().equals(PayStatusEnum.PAID.name())) {
// throw new ServiceException(ResultCode.PAY_PARTIAL_ERROR);
// }
// //如果订单状态不是待付款则抛出异常
// if (!order.getOrderStatus().equals(OrderStatusEnum.UNPAID.name())) {
// throw new ServiceException(ResultCode.PAY_BAN);
// }
// }
//
//
// cashierParam.setPrice(trade.getFlowPrice());
//
// try {
//// BaseSetting baseSetting = JSONUtil.toBean(settingService.get(SettingEnum.BASE_SETTING.name()).getSettingValue(), BaseSetting.class);
//// cashierParam.setTitle(baseSetting.getSiteName());
// } catch (Exception e) {
// cashierParam.setTitle("多用户商城,在线支付");
// }
// String subject = "在线支付";
// cashierParam.setDetail(subject);
//
// cashierParam.setCreateTime(trade.getCreateTime());
// return cashierParam;
}
return null;
}
@Override
public void paymentSuccess(PaymentSuccessParams paymentSuccessParams) {
if (paymentSuccessParams.getPayParam().getOrderType().equals(CashierEnum.TRADE.name())) {
// tradeService.payTrade(paymentSuccessParams.getPayParam().getSn(),
// paymentSuccessParams.getPaymentMethod(),
// paymentSuccessParams.getReceivableNo());
// log.info("交易{}支付成功,方式{},流水号{},", paymentSuccessParams.getPayParam().getSn(),
// paymentSuccessParams.getPaymentMethod(),
// paymentSuccessParams.getReceivableNo());
}
}
@Override
public Boolean paymentResult(PayParam payParam) {
// if (payParam.getOrderType().equals(CashierEnum.TRADE.name())) {
// Trade trade = tradeService.getBySn(payParam.getSn());
// if (trade != null) {
// return PayStatusEnum.PAID.name().equals(trade.getPayStatus());
// } else {
// throw new ServiceException(ResultCode.PAY_NOT_EXIST_ORDER);
// }
// }
return false;
}
}

View File

@ -0,0 +1,681 @@
package com.wzj.soopin.transaction.kit.plugin.easypay;
import cn.hutool.core.net.URLEncoder;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.wzj.soopin.transaction.domain.po.RefundLog;
import com.wzj.soopin.transaction.enums.PaymentMethodEnum;
import com.wzj.soopin.transaction.kit.CashierSupport;
import com.wzj.soopin.transaction.kit.Payment;
import com.wzj.soopin.transaction.kit.core.PaymentHttpResponse;
import com.wzj.soopin.transaction.kit.core.kit.HttpKit;
import com.wzj.soopin.transaction.kit.core.kit.WxPayKit;
import com.wzj.soopin.transaction.kit.dto.PayParam;
import com.wzj.soopin.transaction.kit.params.dto.CashierParam;
import com.wzj.soopin.transaction.kit.plugin.wechat.model.Amount;
import com.wzj.soopin.transaction.kit.plugin.wechat.model.UnifiedOrderModel;
import com.wzj.soopin.transaction.util.SnowFlake;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.ResultCode;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.redis.redis.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
/**
* 微信支付
*
* @author Chopper
* @since 2020/12/21 17:44
*/
@Slf4j
@Component
public class EasyPayPlugin implements Payment {
/**
* 收银台
*/
@Autowired
private CashierSupport cashierSupport;
// /**
// * 支付日志
// */
// @Autowired
// private PaymentService paymentService;
/**
* 缓存
*/
@Autowired
private RedisCache cache;
// /**
// * 退款日志
// */
// @Autowired
// te RefundLogService refundLogService;
/**
* API域名
*/
// @Autowired
// private ApiProperties apiProperties;
// /**
// * 配置
// */
// @Autowired
// private SettingService settingService;
// /**
// * 联合登陆
// */
// @Autowired
// private ConnectService connectService;
// /**
// * 联合登陆
// */
// @Autowired
// private OrderService orderService;
@Override
public R<Object> h5pay(HttpServletRequest request, HttpServletResponse response1, PayParam payParam) {
try {
// CashierParam cashierParam = cashierSupport.cashierParam(payParam);
//
// //支付参数准备
// SceneInfo sceneInfo = new SceneInfo();
// sceneInfo.setPayer_client_ip(IpKit.getRealIp(request));
// H5Info h5Info = new H5Info();
// h5Info.setType("WAP");
// sceneInfo.setH5_info(h5Info);
//
// //支付金额
// Integer fen = CurrencyUtil.fen(cashierParam.getPrice());
// //第三方付款订单
// String outOrderNo = SnowFlake.getIdStr();
// //过期时间
// String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
//
// //回传数据
// String attach = URLEncoder.createDefault().encode(JSONUtil.toJsonStr(payParam), StandardCharsets.UTF_8);
//
//
// WechatPaymentSetting setting = wechatPaymentSetting();
// String appid = setting.getServiceAppId();
// if (appid == null) {
// throw new ServiceException(ResultCode.WECHAT_PAYMENT_NOT_SETTING);
// }
// UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
// .setAppid(appid)
// .setMchid(setting.getMchId())
// .setDescription(cashierParam.getDetail())
// .setOut_trade_no(outOrderNo)
// .setTime_expire(timeExpire)
// .setAttach(attach)
// .setNotify_url(notifyUrl(apiProperties.getBuyer(), PaymentMethodEnum.WECHAT))
// .setAmount(new Amount().setTotal(fen)).setScene_info(sceneInfo);
//
// log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
// PaymentHttpResponse response = WechatApi.v3(
// RequestMethodEnums.POST,
// WechatDomain.CHINA.toString(),
// WechatApiEnum.H5_PAY.toString(),
// setting.getMchId(),
// setting.getSerialNumber(),
// null,
// setting.getApiclient_key(),
// JSONUtil.toJsonStr(unifiedOrderModel)
// );
return null;
// return R.ok(JSONUtil.toJsonStr(response.getBody()));
} catch (Exception e) {
log.error("微信H5支付错误", e);
throw new ServiceException(ResultCode.PAY_ERROR);
}
}
@Override
public R<Object> jsApiPay(HttpServletRequest request, PayParam payParam) {
try {
// Connect connect = connectService.queryConnect(
// ConnectQueryDTO.builder().userId(UserContext.getCurrentUser().getId()).unionType(ConnectEnum.WECHAT.name()).build()
// );
// if (connect == null) {
// return null;
// }
//
// Payer payer = new Payer();
// payer.setOpenid(connect.getUnionId());
//
// CashierParam cashierParam = cashierSupport.cashierParam(payParam);
//
// //支付金额
// Integer fen = CurrencyUtil.fen(cashierParam.getPrice());
// //第三方付款订单
// String outOrderNo = SnowFlake.getIdStr();
// //过期时间
// String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
//
// String attach = URLEncoder.createDefault().encode(JSONUtil.toJsonStr(payParam), StandardCharsets.UTF_8);
//
// WechatPaymentSetting setting = wechatPaymentSetting();
// String appid = setting.getServiceAppId();
// if (appid == null) {
// throw new ServiceException(ResultCode.WECHAT_PAYMENT_NOT_SETTING);
// }
// UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
// .setAppid(appid)
// .setMchid(setting.getMchId())
// .setDescription(cashierParam.getDetail())
// .setOut_trade_no(outOrderNo)
// .setTime_expire(timeExpire)
// .setAttach(attach)
// .setNotify_url(notifyUrl(apiProperties.getBuyer(), PaymentMethodEnum.WECHAT))
// .setAmount(new Amount().setTotal(fen))
// .setPayer(payer);
//
// log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
// PaymentHttpResponse response = WechatApi.v3(
// RequestMethodEnums.POST,
// WechatDomain.CHINA.toString(),
// WechatApiEnum.JS_API_PAY.toString(),
// setting.getMchId(),
// setting.getSerialNumber(),
// null,
// setting.getApiclient_key(),
// JSONUtil.toJsonStr(unifiedOrderModel)
// );
// //根据证书序列号查询对应的证书来验证签名结果
// boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCert());
// log.info("verifySignature: {}", verifySignature);
// log.info("统一下单响应 {}", response);
//
// if (verifySignature) {
// String body = response.getBody();
// JSONObject jsonObject = JSONUtil.parseObj(body);
// String prepayId = jsonObject.getStr("prepay_id");
// Map<String, String> map = WxPayKit.jsApiCreateSign(appid, prepayId, setting.getApiclient_key());
// log.info("唤起支付参数:{}", map);
//
// return ResultUtil.data(map);
// }
log.error("微信支付参数验证错误,请及时处理");
throw new ServiceException(ResultCode.PAY_ERROR);
} catch (Exception e) {
log.error("支付异常", e);
throw new ServiceException(ResultCode.PAY_ERROR);
}
}
@Override
public R<Object> appPay(HttpServletRequest request, PayParam payParam) {
try {
CashierParam cashierParam = cashierSupport.cashierParam(payParam);
//支付金额
// Integer fen = CurrencyUtil.fen(cashierParam.getPrice());
// //第三方付款订单
// String outOrderNo = SnowFlake.getIdStr();
// //过期时间
// String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
//
// String attach = URLEncoder.createDefault().encode(JSONUtil.toJsonStr(payParam), StandardCharsets.UTF_8);
//
// WechatPaymentSetting setting = wechatPaymentSetting();
// String appid = setting.getAppId();
// if (appid == null) {
// throw new ServiceException(ResultCode.WECHAT_PAYMENT_NOT_SETTING);
// }
// UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
// .setAppid(appid)
// .setMchid(setting.getMchId())
// .setDescription(cashierParam.getDetail())
// .setOut_trade_no(outOrderNo)
// .setTime_expire(timeExpire)
// .setAttach(attach)
// .setNotify_url(notifyUrl(apiProperties.getBuyer(), PaymentMethodEnum.WECHAT))
// .setAmount(new Amount().setTotal(fen));
//
//
// log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
// PaymentHttpResponse response = WechatApi.v3(
// RequestMethodEnums.POST,
// WechatDomain.CHINA.toString(),
// WechatApiEnum.APP_PAY.toString(),
// setting.getMchId(),
// setting.getSerialNumber(),
// null,
// setting.getApiclient_key(),
// JSONUtil.toJsonStr(unifiedOrderModel)
// );
// //根据证书序列号查询对应的证书来验证签名结果
// boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCert());
// log.info("verifySignature: {}", verifySignature);
// log.info("统一下单响应 {}", response);
//
// if (verifySignature) {
// JSONObject jsonObject = JSONUtil.parseObj(response.getBody());
// String prepayId = jsonObject.getStr("prepay_id");
// Map<String, String> map = WxPayKit.appPrepayIdCreateSign(appid,
// setting.getMchId(),
// prepayId,
// setting.getApiclient_key(), SignType.HMACSHA256);
// log.info("唤起支付参数:{}", map);
//
// return R.ok(map);
// }
log.error("微信支付参数验证错误,请及时处理");
throw new ServiceException(ResultCode.PAY_ERROR);
} catch (Exception e) {
log.error("支付异常", e);
throw new ServiceException(ResultCode.PAY_ERROR);
}
}
@Override
public R<Object> nativePay(HttpServletRequest request, PayParam payParam) {
try {
CashierParam cashierParam = cashierSupport.cashierParam(payParam);
//支付金额
BigDecimal fen =cashierParam.getPrice();
//第三方付款订单
String outOrderNo = SnowFlake.getIdStr();
//过期时间
// String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
String attach = URLEncoder.createDefault().encode(JSONUtil.toJsonStr(payParam), StandardCharsets.UTF_8);
String appid = "setting.getServiceAppId()";
if (appid == null) {
throw new ServiceException(ResultCode.WECHAT_PAYMENT_NOT_SETTING);
}
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
.setAppid(appid)
.setMchid("setting.getMchId()")
.setDescription(cashierParam.getDetail())
.setOut_trade_no(outOrderNo)
// .setTime_expire(timeExpire)
//回传参数
.setAttach(attach)
.setNotify_url(notifyUrl("apiProperties.getBuyer()", PaymentMethodEnum.WECHAT))
.setAmount(new Amount().setTotal(fen));
log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
// PaymentHttpResponse response = WechatApi.v3(
// RequestMethodEnums.POST,
// WechatDomain.CHINA.toString(),
// WechatApiEnum.NATIVE_PAY.toString(),
//// setting.getMchId(),
//// setting.getSerialNumber(),
// null,
//// setting.getApiclient_key(),
// JSONUtil.toJsonStr(unifiedOrderModel)
// );
PaymentHttpResponse response=null;
log.info("统一下单响应 {}", response);
//根据证书序列号查询对应的证书来验证签名结果
// boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCert());
// log.info("verifySignature: {}", verifySignature);
//
// if (verifySignature) {
// return R.ok(new JSONObject(response.getBody()).getStr("code_url"));
// } else {
// log.error("微信支付参数验证错误,请及时处理");
// throw new ServiceException(ResultCode.PAY_ERROR);
// }
return R.ok("统一下单成功",outOrderNo);
} catch (ServiceException e) {
log.error("支付异常", e);
throw new ServiceException(ResultCode.PAY_ERROR);
} catch (Exception e) {
log.error("支付异常", e);
throw new ServiceException(ResultCode.PAY_ERROR);
}
}
@Override
public R<Object> mpPay(HttpServletRequest request, PayParam payParam) {
try {
// Connect connect = connectService.queryConnect(
// ConnectQueryDTO.builder().userId(UserContext.getCurrentUser().getId()).unionType(ConnectEnum.WECHAT_MP_OPEN_ID.name()).build()
// );
// if (connect == null) {
// return null;
// }
//
// Payer payer = new Payer();
// payer.setOpenid(connect.getUnionId());
//
// CashierParam cashierParam = cashierSupport.cashierParam(payParam);
//
// //支付金额
// Integer fen = CurrencyUtil.fen(cashierParam.getPrice());
// //第三方付款订单
// String outOrderNo = SnowFlake.getIdStr();
// //过期时间
// String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
//
// //微信小程序appid 需要单独获取这里读取了联合登陆配置的appid 实际场景小程序自动登录所以这个appid是最为保险的做法
// //如果有2开需求这里需要调整修改这个appid的获取途径即可
// String appid = wechatPaymentSetting().getMpAppId();
// if (StringUtils.isEmpty(appid)) {
// throw new ServiceException(ResultCode.WECHAT_PAYMENT_NOT_SETTING);
// }
// String attach = URLEncoder.createDefault().encode(JSONUtil.toJsonStr(payParam), StandardCharsets.UTF_8);
//
// WechatPaymentSetting setting = wechatPaymentSetting();
// UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
// .setAppid(appid)
// .setMchid(setting.getMchId())
// .setDescription(cashierParam.getDetail())
// .setOut_trade_no(outOrderNo)
// .setTime_expire(timeExpire)
// .setAttach(attach)
// .setNotify_url(notifyUrl(apiProperties.getBuyer(), PaymentMethodEnum.WECHAT))
// .setAmount(new Amount().setTotal(fen))
// .setPayer(payer);
//
// log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
// PaymentHttpResponse response = WechatApi.v3(
// RequestMethodEnums.POST,
// WechatDomain.CHINA.toString(),
// WechatApiEnum.JS_API_PAY.toString(),
// setting.getMchId(),
// setting.getSerialNumber(),
// null,
// setting.getApiclient_key(),
// JSONUtil.toJsonStr(unifiedOrderModel)
// );
// //根据证书序列号查询对应的证书来验证签名结果
// boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCert());
// log.info("verifySignature: {}", verifySignature);
// log.info("统一下单响应 {}", response);
//
// if (verifySignature) {
// String body = response.getBody();
// JSONObject jsonObject = JSONUtil.parseObj(body);
// String prepayId = jsonObject.getStr("prepay_id");
// Map<String, String> map = WxPayKit.jsApiCreateSign(appid, prepayId, setting.getApiclient_key());
// log.info("唤起支付参数:{}", map);
//
// return R.ok(map);
// }
log.error("微信支付参数验证错误,请及时处理");
throw new ServiceException(ResultCode.PAY_ERROR);
} catch (Exception e) {
log.error("支付异常", e);
throw new ServiceException(ResultCode.PAY_ERROR);
}
}
@Override
public void callBack(HttpServletRequest request) {
try {
verifyNotify(request);
} catch (Exception e) {
log.error("支付异常", e);
}
}
@Override
public void notify(HttpServletRequest request) {
try {
verifyNotify(request);
} catch (Exception e) {
log.error("支付异常", e);
}
}
/**
* 验证结果执行支付回调
*
* @param request
* @throws Exception
*/
private void verifyNotify(HttpServletRequest request) throws Exception {
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String serialNo = request.getHeader("Wechatpay-Serial");
String signature = request.getHeader("Wechatpay-Signature");
log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
String result = HttpKit.readData(request);
log.info("微信支付通知密文 {}", result);
//校验服务器端响应¬7
// String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
// setting.getApiKey3(), Objects.requireNonNull(getPlatformCert()));
//
// log.info("微信支付通知明文 {}", plainText);
//
// JSONObject jsonObject = JSONUtil.parseObj(plainText);
//
// String payParamStr = jsonObject.getStr("attach");
// String payParamJson = URLDecoder.decode(payParamStr, StandardCharsets.UTF_8);
// PayParam payParam = JSONUtil.toBean(payParamJson, PayParam.class);
// String tradeNo = jsonObject.getStr("transaction_id");
// Double totalAmount = CurrencyUtil.reversalFen(jsonObject.getJSONObject("amount").getDouble("total"));
//
// PaymentSuccessParams paymentSuccessParams = new PaymentSuccessParams(
// PaymentMethodEnum.WECHAT.name(),
// tradeNo,
// totalAmount,
// payParam
// );
//
// paymentService.success(paymentSuccessParams);
// log.info("微信支付回调:支付成功{}", plainText);
}
@Override
public void refund(RefundLog refundLog) {
try {
// Amount amount = new Amount().setRefund(CurrencyUtil.fen(refundLog.getTotalAmount()))
// .setTotal(CurrencyUtil.fen(orderService.getPaymentTotal(refundLog.getOrderSn())));
//
// //退款参数准备
// RefundModel refundModel = new RefundModel()
// .setTransaction_id(refundLog.getPaymentReceivableNo())
// .setOut_refund_no(refundLog.getOutOrderNo())
// .setReason(refundLog.getRefundReason())
// .setAmount(amount)
// .setNotify_url(refundNotifyUrl(apiProperties.getBuyer(), PaymentMethodEnum.WECHAT));
//
// WechatPaymentSetting setting = wechatPaymentSetting();
//
// log.info("微信退款参数 {}", JSONUtil.toJsonStr(refundModel));
// PaymentHttpResponse response = WechatApi.v3(
// RequestMethodEnums.POST,
// WechatDomain.CHINA.toString(),
// WechatApiEnum.DOMESTIC_REFUNDS.toString(),
// setting.getMchId(),
// setting.getSerialNumber(),
// null,
// setting.getApiclient_key(),
// JSONUtil.toJsonStr(refundModel)
// );
// log.info("微信退款响应 {}", response);
// //退款申请成功
// if (response.getStatus() == 200) {
// refundLogService.save(refundLog);
// } else {
// //退款申请失败
// refundLog.setErrorMessage(response.getBody());
// refundLogService.save(refundLog);
// }
} catch (Exception e) {
log.error("微信退款申请失败", e);
}
}
@Override
public void cancel(RefundLog refundLog) {
this.refund(refundLog);
}
@Override
public void refundNotify(HttpServletRequest request) {
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String serialNo = request.getHeader("Wechatpay-Serial");
String signature = request.getHeader("Wechatpay-Signature");
log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
String result = HttpKit.readData(request);
log.info("微信退款通知密文 {}", result);
JSONObject ciphertext = JSONUtil.parseObj(result);
try { //校验服务器端响应¬
// String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
// wechatPaymentSetting().getApiKey3(), Objects.requireNonNull(getPlatformCert()));
// log.info("微信退款通知明文 {}", plainText);
// if (("REFUND.SUCCESS").equals(ciphertext.getStr("event_type"))) {
// log.info("退款成功 {}", plainText);
// //校验服务器端响应
// JSONObject jsonObject = JSONUtil.parseObj(plainText);
// String transactionId = jsonObject.getStr("transaction_id");
// String refundId = jsonObject.getStr("refund_id");
//
// RefundLog refundLog = refundLogService.getOne(new LambdaQueryWrapper<RefundLog>().eq(RefundLog::getPaymentReceivableNo, transactionId));
// if (refundLog != null) {
// refundLog.setIsRefund(true);
// refundLog.setReceivableNo(refundId);
// refundLogService.saveOrUpdate(refundLog);
// }
//
// } else {
// log.info("退款失败 {}", plainText);
// JSONObject jsonObject = JSONUtil.parseObj(plainText);
// String transactionId = jsonObject.getStr("transaction_id");
// String refundId = jsonObject.getStr("refund_id");
//
// RefundLog refundLog = refundLogService.getOne(new LambdaQueryWrapper<RefundLog>().eq(RefundLog::getPaymentReceivableNo, transactionId));
// if (refundLog != null) {
// refundLog.setReceivableNo(refundId);
// refundLog.setErrorMessage(ciphertext.getStr("summary"));
// refundLogService.saveOrUpdate(refundLog);
// }
// }
} catch (Exception e) {
log.error("微信退款失败", e);
}
}
// /**
// * 获取微信支付配置
// *
// * @return
// */
// private WechatPaymentSetting wechatPaymentSetting() {
// try {
// Setting systemSetting = settingService.get(SettingEnum.WECHAT_PAYMENT.name());
// WechatPaymentSetting wechatPaymentSetting = JSONUtil.toBean(systemSetting.getSettingValue(), WechatPaymentSetting.class);
// return wechatPaymentSetting;
// } catch (Exception e) {
// log.error("微信支付暂不支持", e);
// throw new ServiceException(ResultCode.PAY_NOT_SUPPORT);
// }
// }
/**
* 获取平台公钥
*
* @return 平台公钥
*/
private X509Certificate getPlatformCert() {
//获取缓存中的平台公钥如果有则直接返回否则去微信请求
// String publicCert = cache.get(CachePrefix.WECHAT_PLAT_FORM_CERT.getPrefix());
// if (!StringUtils.isEmpty(publicCert)) {
// return PayKit.getCertificate(publicCert);
// }
//获取平台证书列表
try {
// WechatPaymentSetting setting = wechatPaymentSetting();
// PaymentHttpResponse response = WechatApi.v3(
// RequestMethodEnums.GET,
// WechatDomain.CHINA.toString(),
// WechatApiEnum.GET_CERTIFICATES.toString(),
// setting.getMchId(),
// setting.getSerialNumber(),
// null,
// setting.getApiclient_key(),
// ""
// );
// String body = response.getBody();
// log.info("获取微信平台证书body: {}", body);
//
// if (response.getStatus() == 200) {
// JSONObject jsonObject = JSONUtil.parseObj(body);
// JSONArray dataArray = jsonObject.getJSONArray("data");
// log.info("证书信息: {}", dataArray);
//
// //默认认为只有一个平台证书
// JSONObject encryptObject = dataArray.getJSONObject(0);
// JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
// String associatedData = encryptCertificate.getStr("associated_data");
// String cipherText = encryptCertificate.getStr("ciphertext");
// String nonce = encryptCertificate.getStr("nonce");
// publicCert = getPlatformCertStr(associatedData, nonce, cipherText);
// long second = (PayKit.getCertificate(publicCert).getNotAfter().getTime() - System.currentTimeMillis()) / 1000;
// cache.put(CachePrefix.WECHAT_PLAT_FORM_CERT.getPrefix(), publicCert, second);
// } else {
// log.error("证书获取失败:{}" + body);
// throw new ServiceException(ResultCode.WECHAT_CERT_ERROR);
// }
// return PayKit.getCertificate(publicCert);
} catch (Exception e) {
log.error("证书获取失败", e);
}
return null;
}
//
// /**
// * 获取平台证书缓存的字符串
// * 下列各个密钥参数
// *
// * @param associatedData 密钥参数
// * @param nonce 密钥参数
// * @param cipherText 密钥参数
// * @return platform key
// * @throws GeneralSecurityException 密钥获取异常
// */
// private String getPlatformCertStr(String associatedData, String nonce, String cipherText) throws GeneralSecurityException {
//
//
// AesUtil aesUtil = new AesUtil(wechatPaymentSetting().getApiKey3().getBytes(StandardCharsets.UTF_8));
// //平台证书密文解密
// //encrypt_certificate 中的 associated_data nonce ciphertext
// return aesUtil.decryptToString(
// associatedData.getBytes(StandardCharsets.UTF_8),
// nonce.getBytes(StandardCharsets.UTF_8),
// cipherText
// );
// }
}

View File

@ -0,0 +1,205 @@
package com.wzj.soopin.transaction.kit.plugin.wallet;
import com.wzj.soopin.member.enums.AccountBillChangeTypeEnum;
import com.wzj.soopin.member.enums.AccountBillSourceEnum;
import com.wzj.soopin.member.enums.AccountTypeEnum;
import com.wzj.soopin.member.service.IMemberAccountService;
import com.wzj.soopin.transaction.domain.po.RefundLog;
import com.wzj.soopin.transaction.enums.CashierEnum;
import com.wzj.soopin.transaction.enums.PaymentMethodEnum;
import com.wzj.soopin.transaction.kit.CashierSupport;
import com.wzj.soopin.transaction.kit.Payment;
import com.wzj.soopin.transaction.kit.dto.PayParam;
import com.wzj.soopin.transaction.kit.dto.PaymentSuccessParams;
import com.wzj.soopin.transaction.kit.params.dto.CashierParam;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.ResultCode;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.event.Constants;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.satoken.utils.LoginHelper;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
/**
* WalletPlugin
*
* @author Chopper
* @version v1.0
* 2021-02-20 10:14
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class WalletPlugin implements Payment {
/**
* 支付日志
*/
// private final paymentService;
/**
* 退款日志
*/
// private final RefundLogService refundLogService;
/**
* 会员余额
*/
private final IMemberAccountService memberAccountService;
/**
* 收银台
*/
private final CashierSupport cashierSupport;
private final RedissonClient redisson;
@Override
public R<Object> h5pay(HttpServletRequest request, HttpServletResponse response, PayParam payParam) {
savePaymentLog(payParam);
return R.ok(ResultCode.PAY_SUCCESS);
}
@Override
public R<Object> jsApiPay(HttpServletRequest request, PayParam payParam) {
savePaymentLog(payParam);
return R.ok(ResultCode.PAY_SUCCESS);
}
@Override
public R<Object> appPay(HttpServletRequest request, PayParam payParam) {
savePaymentLog(payParam);
return R.ok(ResultCode.PAY_SUCCESS);
}
@Override
public R<Object> nativePay(HttpServletRequest request, PayParam payParam) {
if (payParam.getOrderType().equals(CashierEnum.RECHARGE.name())) {
throw new ServiceException(ResultCode.CAN_NOT_RECHARGE_WALLET);
}
savePaymentLog(payParam);
return R.ok(ResultCode.PAY_SUCCESS);
}
@Override
public R<Object> mpPay(HttpServletRequest request, PayParam payParam) {
savePaymentLog(payParam);
return R.ok(ResultCode.PAY_SUCCESS);
}
@Override
public void cancel(RefundLog refundLog) {
try {
// memberWalletService.increase(new MemberWalletUpdateDTO(refundLog.getTotalAmount(),
// refundLog.getMemberId(),
// "取消[" + refundLog.getOrderSn() + "]订单,退还金额[" + refundLog.getTotalAmount() + "]",
// DepositServiceTypeEnum.WALLET_REFUND.name()));
// refundLog.setIsRefund(true);
// refundLogService.save(refundLog);
} catch (Exception e) {
log.error("订单取消错误", e);
}
}
/**
* 保存支付日志
*
* @param payParam 支付参数
*/
private void savePaymentLog(PayParam payParam) {
//同一个会员如果在不同的客户端使用预存款支付会存在同时支付无法保证预存款的正确性所以对会员加锁
LoginUser loginUser = LoginHelper.getLoginUser();
if (loginUser == null) {
throw new ServiceException("用户未登录");
}
RLock lock = redisson.getLock(loginUser + "");
lock.lock();
try {
//获取支付收银参数
CashierParam cashierParam = cashierSupport.cashierParam(payParam);
this.callBack(payParam, cashierParam);
} catch (Exception e) {
throw e;
} finally {
lock.unlock();
}
}
@Override
public void refund(RefundLog refundLog) {
try {
// memberWalletService.increase(new MemberWalletUpdateDTO(refundLog.getTotalAmount(),
// refundLog.getMemberId(),
// "售后[" + refundLog.getAfterSaleNo() + "]审批,退还金额[" + refundLog.getTotalAmount() + "]",
// DepositServiceTypeEnum.WALLET_REFUND.name()));
// refundLog.setIsRefund(true);
// refundLogService.save(refundLog);
} catch (Exception e) {
log.error("退款失败", e);
}
}
/**
* 支付订单
*
* @param payParam 支付参数
* @param cashierParam 收银台参数
*/
public void callBack(PayParam payParam, CashierParam cashierParam) {
LoginUser loginUser = LoginHelper.getLoginUser();
//支付信息
try {
if (loginUser.getUserId() == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
//个人账户扣减
boolean result = memberAccountService.reduceMoney(
cashierParam.getPrice(),
loginUser.getUserId(),
AccountBillSourceEnum.PAYMENT,
"订单[" + cashierParam.getOrderSns() + "]支付金额[" + cashierParam.getPrice() + "]"
);
//更新支付日志
if (result) {
try {
// PaymentSuccessParams paymentSuccessParams = new PaymentSuccessParams(
// PaymentMethodEnum.WALLET.name(),
// "",
// cashierParam.getPrice(),
// payParam
// );
//
// paymentService.success(paymentSuccessParams);
log.info("支付回调通知:余额支付:{}", payParam);
} catch (ServiceException e) {
//业务异常则支付手动回滚
// memberWalletService.increase(new MemberWalletUpdateDTO(
// cashierParam.getPrice(),
// UserContext.getCurrentUser().getId(),
// "订单[" + cashierParam.getOrderSns() + "]支付异常,余额返还[" + cashierParam.getPrice() + "]",
// DepositServiceTypeEnum.WALLET_REFUND.name())
// );
throw e;
}
} else {
throw new ServiceException(ResultCode.WALLET_INSUFFICIENT);
}
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
log.info("余额支付异常", e);
throw new ServiceException(ResultCode.WALLET_INSUFFICIENT);
}
}
}

View File

@ -0,0 +1,859 @@
package com.wzj.soopin.transaction.kit.plugin.wechat;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import com.wzj.soopin.transaction.kit.core.PaymentHttpResponse;
import com.wzj.soopin.transaction.kit.core.enums.RequestMethodEnums;
import com.wzj.soopin.transaction.kit.core.kit.HttpKit;
import com.wzj.soopin.transaction.kit.core.kit.PayKit;
import com.wzj.soopin.transaction.kit.core.kit.WxPayKit;
import com.wzj.soopin.transaction.kit.plugin.wechat.enums.WechatApiEnum;
import com.wzj.soopin.transaction.kit.plugin.wechat.enums.WechatDomain;
import java.io.File;
import java.io.InputStream;
import java.security.PrivateKey;
import java.util.HashMap;
import java.util.Map;
/**
* 微信支付相关接口
*
* @author Chopper
* @since 2021/1/26 15:25
*/
public class WechatApi {
private WechatApi() {
}
/**
* 获取接口请求的 URL
*
* @param wechatApiEnum {@link WechatApiEnum} 支付 API 接口枚举
* @return {@link String} 返回完整的接口请求URL
*/
public static String getReqUrl(WechatApiEnum wechatApiEnum) {
return getReqUrl(wechatApiEnum, null, false);
}
/**
* 获取接口请求的 URL
*
* @param wechatApiEnum {@link WechatApiEnum} 支付 API 接口枚举
* @param isSandBox 是否是沙箱环境
* @return {@link String} 返回完整的接口请求URL
*/
public static String getReqUrl(WechatApiEnum wechatApiEnum, boolean isSandBox) {
return getReqUrl(wechatApiEnum, null, isSandBox);
}
/**
* 获取接口请求的 URL
*
* @param wechatApiEnum {@link WechatApiEnum} 支付 API 接口枚举
* @param wechatDomain {@link WechatDomain} 支付 API 接口域名枚举
* @param isSandBox 是否是沙箱环境
* @return {@link String} 返回完整的接口请求URL
*/
public static String getReqUrl(WechatApiEnum wechatApiEnum, WechatDomain wechatDomain, boolean isSandBox) {
if (wechatDomain == null) {
wechatDomain = WechatDomain.CHINA;
}
return wechatDomain.getType()
.concat(isSandBox ? WechatApiEnum.SAND_BOX_NEW.getUrl() : "")
.concat(wechatApiEnum.getUrl());
}
/**
* 发起请求
*
* @param apiUrl 接口 URL
* 通过 {@link WechatApi#getReqUrl(WechatApiEnum)}
* 或者 {@link WechatApi#getReqUrl(WechatApiEnum, WechatDomain, boolean)} 来获取
* @param params 接口请求参数
* @return {@link String} 请求返回的结果
*/
public static String execution(String apiUrl, Map<String, String> params) {
return doPost(apiUrl, params);
}
/**
* 发起请求
*
* @param apiUrl 接口 URL
* 通过 {@link WechatApi#getReqUrl(WechatApiEnum)}
* 或者 {@link WechatApi#getReqUrl(WechatApiEnum, WechatDomain, boolean)} 来获取
* @param params 接口请求参数
* @return {@link String} 请求返回的结果
*/
public static String executionByGet(String apiUrl, Map<String, Object> params) {
return doGet(apiUrl, params);
}
/**
* 发起请求
*
* @param apiUrl 接口 URL
* 通过 {@link WechatApi#getReqUrl(WechatApiEnum)}
* 或者 {@link WechatApi#getReqUrl(WechatApiEnum, WechatDomain, boolean)} 来获取
* @param params 接口请求参数
* @param certPath 证书文件路径
* @param certPass 证书密码
* @return {@link String} 请求返回的结果
*/
public static String execution(String apiUrl, Map<String, String> params, String certPath, String certPass) {
return doPostSsl(apiUrl, params, certPath, certPass);
}
/**
* 发起请求
*
* @param apiUrl 接口 URL
* 通过 {@link WechatApi#getReqUrl(WechatApiEnum)}
* 或者 {@link WechatApi#getReqUrl(WechatApiEnum, WechatDomain, boolean)} 来获取
* @param params 接口请求参数
* @param certPath 证书文件路径
* @return {@link String} 请求返回的结果
*/
public static String execution(String apiUrl, Map<String, String> params, String certPath) {
return doPostSsl(apiUrl, params, certPath);
}
/**
* 发起请求
*
* @param apiUrl 接口 URL
* 通过 {@link WechatApi#getReqUrl(WechatApiEnum)}
* 或者 {@link WechatApi#getReqUrl(WechatApiEnum, WechatDomain, boolean)} 来获取
* @param params 接口请求参数
* @param certFile 证书文件输入流
* @param certPass 证书密码
* @return {@link String} 请求返回的结果
*/
public static String execution(String apiUrl, Map<String, String> params, InputStream certFile, String certPass) {
return doPostSsl(apiUrl, params, certFile, certPass);
}
/**
* 发起请求
*
* @param apiUrl 接口 URL
* 通过 {@link WechatApi#getReqUrl(WechatApiEnum)}
* 或者 {@link WechatApi#getReqUrl(WechatApiEnum, WechatDomain, boolean)} 来获取
* @param params 接口请求参数
* @param certFile 证书文件输入流
* @return {@link String} 请求返回的结果
*/
public static String execution(String apiUrl, Map<String, String> params, InputStream certFile) {
return doPostSsl(apiUrl, params, certFile);
}
public static String execution(String apiUrl, Map<String, String> params,
String certPath, String certPass, String filePath) {
return doUploadSsl(apiUrl, params, certPath, certPass, filePath);
}
/**
* V3 接口统一执行入口
*
* @param method {@link RequestMethodEnums} 请求方法
* @param urlPrefix 可通过 {@link WechatDomain}来获取
* @param urlSuffix 可通过 {@link WechatApiEnum} 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param platSerialNo 平台序列号接口中包含敏感信息时必传
* @param keyPath apiclient_key.pem 证书路径
* @param body 接口请求参数
* @param nonceStr 随机字符库
* @param timestamp 时间戳
* @param authType 认证类型
* @param file 文件
* @return {@link PaymentHttpResponse} 请求返回的结果
* @throws Exception 接口执行异常
*/
public static PaymentHttpResponse v3(RequestMethodEnums method, String urlPrefix, String urlSuffix,
String mchId, String serialNo, String platSerialNo, String keyPath,
String body, String nonceStr, long timestamp, String authType,
File file) throws Exception {
//构建 Authorization
String authorization = WxPayKit.buildAuthorization(method, urlSuffix, mchId, serialNo,
keyPath, body, nonceStr, timestamp, authType);
if (StrUtil.isEmpty(platSerialNo)) {
platSerialNo = serialNo;
}
if (method == RequestMethodEnums.GET) {
return get(urlPrefix.concat(urlSuffix), authorization, platSerialNo, null);
} else if (method == RequestMethodEnums.POST) {
return post(urlPrefix.concat(urlSuffix), authorization, platSerialNo, body);
} else if (method == RequestMethodEnums.DELETE) {
return delete(urlPrefix.concat(urlSuffix), authorization, platSerialNo, body);
} else if (method == RequestMethodEnums.UPLOAD) {
return upload(urlPrefix.concat(urlSuffix), authorization, platSerialNo, body, file);
} else if (method == RequestMethodEnums.PUT) {
return put(urlPrefix.concat(urlSuffix), authorization, platSerialNo, body);
}
return null;
}
/**
* V3 接口统一执行入口
*
* @param method {@link RequestMethodEnums} 请求方法
* @param urlPrefix 可通过 {@link WechatDomain}来获取
* @param urlSuffix 可通过 {@link WechatApiEnum} 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param platSerialNo 平台序列号接口中包含敏感信息时必传
* @param privateKey 商户私钥
* @param body 接口请求参数
* @param nonceStr 随机字符库
* @param timestamp 时间戳
* @param authType 认证类型
* @param file 文件
* @return {@link PaymentHttpResponse} 请求返回的结果
* @throws Exception 接口执行异常
*/
public static PaymentHttpResponse v3(RequestMethodEnums method, String urlPrefix, String urlSuffix,
String mchId, String serialNo, String platSerialNo, PrivateKey privateKey,
String body, String nonceStr, long timestamp, String authType,
File file) throws Exception {
//构建 Authorization
String authorization = WxPayKit.buildAuthorization(method, urlSuffix, mchId, serialNo,
privateKey, body, nonceStr, timestamp, authType);
if (StrUtil.isEmpty(platSerialNo)) {
platSerialNo = serialNo;
}
if (method == RequestMethodEnums.GET) {
return get(urlPrefix.concat(urlSuffix), authorization, platSerialNo, null);
} else if (method == RequestMethodEnums.POST) {
return post(urlPrefix.concat(urlSuffix), authorization, platSerialNo, body);
} else if (method == RequestMethodEnums.DELETE) {
return delete(urlPrefix.concat(urlSuffix), authorization, platSerialNo, body);
} else if (method == RequestMethodEnums.UPLOAD) {
return upload(urlPrefix.concat(urlSuffix), authorization, platSerialNo, body, file);
} else if (method == RequestMethodEnums.PUT) {
return put(urlPrefix.concat(urlSuffix), authorization, platSerialNo, body);
}
return null;
}
/**
* V3 接口统一执行入口
*
* @param method {@link RequestMethodEnums} 请求方法
* @param urlPrefix 可通过 {@link WechatDomain}来获取
* @param urlSuffix 可通过 {@link WechatApiEnum} 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param platSerialNo 平台序列号
* @param keyPath apiclient_key.pem 证书路径
* @param body 接口请求参数
* @return {@link PaymentHttpResponse} 请求返回的结果
* @throws Exception 接口执行异常
*/
public static PaymentHttpResponse v3(RequestMethodEnums method, String urlPrefix, String urlSuffix, String mchId,
String serialNo, String platSerialNo, String keyPath, String body) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = "WECHATPAY2-SHA256-RSA2048";
String nonceStr = WxPayKit.generateStr();
return v3(method, urlPrefix, urlSuffix, mchId, serialNo, platSerialNo, keyPath, body, nonceStr, timestamp, authType, null);
}
/**
* V3 接口统一执行入口
*
* @param method {@link RequestMethodEnums} 请求方法
* @param urlPrefix 可通过 {@link WechatDomain}来获取
* @param urlSuffix 可通过 {@link WechatApiEnum} 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param platSerialNo 平台序列号
* @param privateKey 商户私钥
* @param body 接口请求参数
* @return {@link PaymentHttpResponse} 请求返回的结果
* @throws Exception 接口执行异常
*/
public static PaymentHttpResponse v3(RequestMethodEnums method, String urlPrefix, String urlSuffix, String mchId,
String serialNo, String platSerialNo, PrivateKey privateKey, String body) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = "WECHATPAY2-SHA256-RSA2048";
String nonceStr = WxPayKit.generateStr();
return v3(method, urlPrefix, urlSuffix, mchId, serialNo, platSerialNo, privateKey, body, nonceStr, timestamp, authType, null);
}
/**
* V3 接口统一执行入口
*
* @param method {@link RequestMethodEnums} 请求方法
* @param urlPrefix 可通过 {@link WechatDomain}来获取
* @param urlSuffix 可通过 {@link WechatApiEnum} 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param platSerialNo 平台序列号
* @param keyPath apiclient_key.pem 证书路径
* @param params Get 接口请求参数
* @return {@link PaymentHttpResponse} 请求返回的结果
* @throws Exception 接口执行异常
*/
public static PaymentHttpResponse v3(RequestMethodEnums method, String urlPrefix, String urlSuffix,
String mchId, String serialNo, String platSerialNo, String keyPath,
Map<String, String> params) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = "WECHATPAY2-SHA256-RSA2048";
String nonceStr = WxPayKit.generateStr();
if (null != params && !params.keySet().isEmpty()) {
urlSuffix = urlSuffix.concat("?").concat(PayKit.createLinkString(params, true));
}
return v3(method, urlPrefix, urlSuffix, mchId, serialNo, platSerialNo, keyPath, "", nonceStr, timestamp, authType, null);
}
/**
* V3 接口统一执行入口
*
* @param method {@link RequestMethodEnums} 请求方法
* @param urlPrefix 可通过 {@link WechatDomain}来获取
* @param urlSuffix 可通过 {@link WechatApiEnum} 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param platSerialNo 平台序列号
* @param privateKey 商户私钥
* @param params Get 接口请求参数
* @return {@link PaymentHttpResponse} 请求返回的结果
* @throws Exception 接口执行异常
*/
public static PaymentHttpResponse v3(RequestMethodEnums method, String urlPrefix, String urlSuffix,
String mchId, String serialNo, String platSerialNo, PrivateKey privateKey,
Map<String, String> params) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = "WECHATPAY2-SHA256-RSA2048";
String nonceStr = WxPayKit.generateStr();
if (null != params && !params.keySet().isEmpty()) {
urlSuffix = urlSuffix.concat("?").concat(PayKit.createLinkString(params, true));
}
return v3(method, urlPrefix, urlSuffix, mchId, serialNo, platSerialNo, privateKey, "", nonceStr, timestamp, authType, null);
}
/**
* V3 接口统一执行入口
*
* @param urlPrefix 可通过 {@link WechatDomain}来获取
* @param urlSuffix 可通过 {@link WechatApiEnum} 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param platSerialNo 平台序列号
* @param keyPath apiclient_key.pem 证书路径
* @param body 接口请求参数
* @param file 文件
* @return {@link PaymentHttpResponse} 请求返回的结果
* @throws Exception 接口执行异常
*/
public static PaymentHttpResponse v3(String urlPrefix, String urlSuffix, String mchId, String serialNo, String platSerialNo, String keyPath, String body, File file) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = "WECHATPAY2-SHA256-RSA2048";
String nonceStr = WxPayKit.generateStr();
return v3(RequestMethodEnums.UPLOAD, urlPrefix, urlSuffix, mchId, serialNo, platSerialNo, keyPath, body, nonceStr, timestamp, authType, file);
}
/**
* V3 接口统一执行入口
*
* @param urlPrefix 可通过 {@link WechatDomain}来获取
* @param urlSuffix 可通过 {@link WechatApiEnum} 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param platSerialNo 平台序列号
* @param privateKey 商户私钥
* @param body 接口请求参数
* @param file 文件
* @return {@link PaymentHttpResponse} 请求返回的结果
* @throws Exception 接口执行异常
*/
public static PaymentHttpResponse v3(String urlPrefix, String urlSuffix, String mchId, String serialNo,
String platSerialNo, PrivateKey privateKey, String body, File file) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = "WECHATPAY2-SHA256-RSA2048";
String nonceStr = WxPayKit.generateStr();
return v3(RequestMethodEnums.UPLOAD, urlPrefix, urlSuffix, mchId, serialNo, platSerialNo, privateKey, body, nonceStr, timestamp, authType, file);
}
/**
* V3 接口统一执行入口
*
* @param method {@link RequestMethodEnums} 请求方法
* @param urlPrefix 可通过 {@link WechatDomain}来获取
* @param urlSuffix 可通过 {@link WechatApiEnum} 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param platSerialNo 平台序列号接口中包含敏感信息时必传
* @param keyPath apiclient_key.pem 证书路径
* @param body 接口请求参数
* @param nonceStr 随机字符库
* @param timestamp 时间戳
* @param authType 认证类型
* @param file 文件
* @return {@link Map} 请求返回的结果
* @throws Exception 接口执行异常
*/
@Deprecated
public static Map<String, Object> v3Execution(RequestMethodEnums method, String urlPrefix, String urlSuffix,
String mchId, String serialNo, String platSerialNo, String keyPath,
String body, String nonceStr, long timestamp, String authType,
File file) throws Exception {
PaymentHttpResponse response = v3(method, urlPrefix, urlSuffix, mchId, serialNo, platSerialNo, keyPath, body, nonceStr, timestamp, authType, file);
return buildResMap(response);
}
/**
* V3 接口统一执行入口
*
* @param method {@link RequestMethodEnums} 请求方法
* @param urlPrefix 可通过 {@link WechatDomain}来获取
* @param urlSuffix 可通过 {@link WechatApiEnum} 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param keyPath apiclient_key.pem 证书路径
* @param body 接口请求参数
* @return {@link Map} 请求返回的结果
*/
@Deprecated
public static Map<String, Object> v3Execution(RequestMethodEnums method, String urlPrefix, String urlSuffix, String mchId,
String serialNo, String keyPath, String body) throws Exception {
PaymentHttpResponse response = v3(method, urlPrefix, urlSuffix, mchId, serialNo, null, keyPath, body);
return buildResMap(response);
}
/**
* V3 接口统一执行入口
*
* @param method {@link RequestMethodEnums} 请求方法
* @param urlPrefix 可通过 {@link WechatDomain}来获取
* @param urlSuffix 可通过 {@link WechatApiEnum} 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param platSerialNo 平台序列号
* @param keyPath apiclient_key.pem 证书路径
* @param body 接口请求参数
* @return {@link Map} 请求返回的结果
* @throws Exception 接口执行异常
*/
@Deprecated
public static Map<String, Object> v3Execution(RequestMethodEnums method, String urlPrefix, String urlSuffix, String mchId,
String serialNo, String platSerialNo, String keyPath, String body) throws Exception {
PaymentHttpResponse response = v3(method, urlPrefix, urlSuffix, mchId, serialNo, platSerialNo, keyPath, body);
return buildResMap(response);
}
/**
* V3 接口统一执行入口
*
* @param method {@link RequestMethodEnums} 请求方法
* @param urlPrefix 可通过 {@link WechatDomain}来获取
* @param urlSuffix 可通过 {@link WechatApiEnum} 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param platSerialNo 平台序列号
* @param keyPath apiclient_key.pem 证书路径
* @param params Get 接口请求参数
* @return {@link Map} 请求返回的结果
* @throws Exception 接口执行异常
*/
@Deprecated
public static Map<String, Object> v3Execution(RequestMethodEnums method, String urlPrefix, String urlSuffix,
String mchId, String serialNo, String platSerialNo, String keyPath,
Map<String, String> params) throws Exception {
PaymentHttpResponse response = v3(method, urlPrefix, urlSuffix, mchId, serialNo, platSerialNo, keyPath, params);
return buildResMap(response);
}
/**
* V3 接口统一执行入口
*
* @param method {@link RequestMethodEnums} 请求方法
* @param urlPrefix 可通过 {@link WechatDomain}来获取
* @param urlSuffix 可通过 {@link WechatApiEnum} 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param keyPath apiclient_key.pem 证书路径
* @param params Get 接口请求参数
* @return {@link Map} 请求返回的结果
* @throws Exception 接口执行异常
*/
@Deprecated
public static Map<String, Object> v3Execution(RequestMethodEnums method, String urlPrefix, String urlSuffix,
String mchId, String serialNo, String keyPath,
Map<String, String> params) throws Exception {
PaymentHttpResponse response = v3(method, urlPrefix, urlSuffix, mchId, serialNo, null, keyPath, params);
return buildResMap(response);
}
/**
* V3 接口统一执行入口
*
* @param urlPrefix 可通过 {@link WechatDomain}来获取
* @param urlSuffix 可通过 {@link WechatApiEnum} 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param platSerialNo 平台序列号
* @param keyPath apiclient_key.pem 证书路径
* @param body 接口请求参数
* @param file 文件
* @return {@link Map} 请求返回的结果
* @throws Exception 接口执行异常
*/
@Deprecated
public static Map<String, Object> v3Upload(String urlPrefix, String urlSuffix, String mchId, String serialNo, String platSerialNo, String keyPath, String body, File file) throws Exception {
PaymentHttpResponse response = v3(urlPrefix, urlSuffix, mchId, serialNo, platSerialNo, keyPath, body, file);
return buildResMap(response);
}
/**
* V3 接口统一执行入口
*
* @param urlPrefix 可通过 {@link WechatDomain}来获取
* @param urlSuffix 可通过 {@link WechatApiEnum} 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param keyPath apiclient_key.pem 证书路径
* @param body 接口请求参数
* @param file 文件
* @return {@link Map} 请求返回的结果
* @throws Exception 接口执行异常
*/
@Deprecated
public static Map<String, Object> v3Upload(String urlPrefix, String urlSuffix, String mchId, String serialNo, String keyPath, String body, File file) throws Exception {
return v3Upload(urlPrefix, urlSuffix, mchId, serialNo, null, keyPath, body, file);
}
/**
* 发放企业红包
*
* @param params 请求参数
* @param certPath 证书文件路径
* @param certPass 证书密码
* @return {@link String} 请求返回的结果
*/
public static String sendWorkWxRedPack(Map<String, String> params, String certPath, String certPass) {
return execution(getReqUrl(WechatApiEnum.SEND_WORK_WX_RED_PACK), params, certPath, certPass);
}
/**
* 发放企业红包
*
* @param params 请求参数
* @param certFile 证书文件的 InputStream
* @param certPass 证书密码
* @return {@link String} 请求返回的结果
*/
public static String sendWorkWxRedPack(Map<String, String> params, InputStream certFile, String certPass) {
return execution(getReqUrl(WechatApiEnum.SEND_WORK_WX_RED_PACK), params, certFile, certPass);
}
/**
* 查询向员工付款记录
*
* @param params 请求参数
* @param certPath 证书文件路径
* @param certPass 证书密码
* @return {@link String} 请求返回的结果
*/
public static String queryWorkWxRedPack(Map<String, String> params, String certPath, String certPass) {
return execution(getReqUrl(WechatApiEnum.QUERY_WORK_WX_RED_PACK), params, certPath, certPass);
}
/**
* 查询向员工付款记录
*
* @param params 请求参数
* @param certFile 证书文件的 InputStream
* @param certPass 证书密码
* @return {@link String} 请求返回的结果
*/
public static String queryWorkWxRedPack(Map<String, String> params, InputStream certFile, String certPass) {
return execution(getReqUrl(WechatApiEnum.QUERY_WORK_WX_RED_PACK), params, certFile, certPass);
}
/**
* 向员工付款
*
* @param params 请求参数
* @param certPath 证书文件路径
* @param certPass 证书密码
* @return {@link String} 请求返回的结果
*/
public static String trans2pocket(Map<String, String> params, String certPath, String certPass) {
return execution(getReqUrl(WechatApiEnum.PAY_WWS_TRANS_2_POCKET), params, certPath, certPass);
}
/**
* 向员工付款
*
* @param params 请求参数
* @param certFile 证书文件的 InputStream
* @param certPass 证书密码
* @return {@link String} 请求返回的结果
*/
public static String trans2pocket(Map<String, String> params, InputStream certFile, String certPass) {
return execution(getReqUrl(WechatApiEnum.PAY_WWS_TRANS_2_POCKET), params, certFile, certPass);
}
/**
* 查询向员工付款记录
*
* @param params 请求参数
* @param certPath 证书文件路径
* @param certPass 证书密码
* @return {@link String} 请求返回的结果
*/
public static String queryTrans2pocket(Map<String, String> params, String certPath, String certPass) {
return execution(getReqUrl(WechatApiEnum.QUERY_WWS_TRANS_2_POCKET), params, certPath, certPass);
}
/**
* 查询向员工付款记录
*
* @param params 请求参数
* @param certFile 证书文件的 InputStream
* @param certPass 证书密码
* @return {@link String} 请求返回的结果
*/
public static String queryTrans2pocket(Map<String, String> params, InputStream certFile, String certPass) {
return execution(getReqUrl(WechatApiEnum.QUERY_WWS_TRANS_2_POCKET), params, certFile, certPass);
}
/**
* @param url 请求url
* @param params 请求参数
* @return {@link String} 请求返回的结果
*/
public static String doGet(String url, Map<String, Object> params) {
return HttpKit.getDelegate().get(url, params);
}
/**
* get 请求
*
* @param url 请求url
* @param params 请求参数
* @param headers 请求头
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public static PaymentHttpResponse get(String url, Map<String, Object> params, Map<String, String> headers) {
return HttpKit.getDelegate().get(url, params, headers);
}
/**
* get 请求
*
* @param url 请求url
* @param authorization 授权信息
* @param serialNumber 公钥证书序列号
* @param params 请求参数
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public static PaymentHttpResponse get(String url, String authorization, String serialNumber, Map<String, Object> params) {
return get(url, params, getHeaders(authorization, serialNumber));
}
/**
* post 请求
*
* @param url 请求url
* @param data 请求参数
* @param headers 请求头
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public static PaymentHttpResponse post(String url, String data, Map<String, String> headers) {
return HttpKit.getDelegate().post(url, data, headers);
}
/**
* post 请求
*
* @param url 请求url
* @param authorization 授权信息
* @param serialNumber 公钥证书序列号
* @param data 请求参数
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public static PaymentHttpResponse post(String url, String authorization, String serialNumber, String data) {
return post(url, data, getHeaders(authorization, serialNumber));
}
/**
* delete 请求
*
* @param url 请求url
* @param data 请求参数
* @param headers 请求头
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public static PaymentHttpResponse delete(String url, String data, Map<String, String> headers) {
return HttpKit.getDelegate().delete(url, data, headers);
}
/**
* delete 请求
*
* @param url 请求url
* @param authorization 授权信息
* @param serialNumber 公钥证书序列号
* @param data 请求参数
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public static PaymentHttpResponse delete(String url, String authorization, String serialNumber, String data) {
return delete(url, data, getHeaders(authorization, serialNumber));
}
/**
* upload 请求
*
* @param url 请求url
* @param params 请求参数
* @param headers 请求头
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public static PaymentHttpResponse upload(String url, Map<String, Object> params, Map<String, String> headers) {
return HttpKit.getDelegate().post(url, params, headers);
}
/**
* upload 请求
*
* @param url 请求url
* @param authorization 授权信息
* @param serialNumber 公钥证书序列号
* @param data 请求参数
* @param file 上传文件
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public static PaymentHttpResponse upload(String url, String authorization, String serialNumber, String data, File file) {
Map<String, Object> paramMap = new HashMap<>(2);
paramMap.put("file", file);
paramMap.put("meta", data);
return upload(url, paramMap, getUploadHeaders(authorization, serialNumber));
}
/**
* put 请求
*
* @param url 请求url
* @param data 请求参数
* @param headers 请求头
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public static PaymentHttpResponse put(String url, String data, Map<String, String> headers) {
return HttpKit.getDelegate().put(url, data, headers);
}
/**
* put 请求
*
* @param url 请求url
* @param authorization 授权信息
* @param serialNumber 公钥证书序列号
* @param data 请求参数
* @return {@link PaymentHttpResponse} 请求返回的结果
*/
public static PaymentHttpResponse put(String url, String authorization, String serialNumber, String data) {
return put(url, data, getHeaders(authorization, serialNumber));
}
public static String doPost(String url, Map<String, String> params) {
return HttpKit.getDelegate().post(url, WxPayKit.toXml(params));
}
public static String doPostSsl(String url, Map<String, String> params, String certPath, String certPass) {
return HttpKit.getDelegate().post(url, WxPayKit.toXml(params), certPath, certPass);
}
public static String doPostSsl(String url, Map<String, String> params, InputStream certFile, String certPass) {
return HttpKit.getDelegate().post(url, WxPayKit.toXml(params), certFile, certPass);
}
public static String doPostSsl(String url, Map<String, String> params, String certPath) {
if (params.isEmpty() || !params.containsKey("mch_id")) {
throw new RuntimeException("请求参数中必须包含 mch_id如接口参考中不包 mch_id 请使用其他同名构造方法。");
}
String certPass = params.get("mch_id");
return doPostSsl(url, params, certPath, certPass);
}
public static String doPostSsl(String url, Map<String, String> params, InputStream certFile) {
if (params.isEmpty() || !params.containsKey("mch_id")) {
throw new RuntimeException("请求参数中必须包含 mch_id如接口参考中不包 mch_id 请使用其他同名构造方法。");
}
String certPass = params.get("mch_id");
return doPostSsl(url, params, certFile, certPass);
}
public static String doUploadSsl(String url, Map<String, String> params, String certPath, String certPass, String filePath) {
return HttpKit.getDelegate().upload(url, WxPayKit.toXml(params), certPath, certPass, filePath);
}
public static String doUploadSsl(String url, Map<String, String> params, String certPath, String filePath) {
if (params.isEmpty() || !params.containsKey("mch_id")) {
throw new RuntimeException("请求参数中必须包含 mch_id如接口参考中不包 mch_id 请使用其他同名构造方法。");
}
String certPass = params.get("mch_id");
return doUploadSsl(url, params, certPath, certPass, filePath);
}
public static Map<String, String> getBaseHeaders(String authorization) {
Map<String, String> headers = new HashMap<>(5);
headers.put("Accept", ContentType.JSON.toString());
headers.put("Authorization", authorization);
return headers;
}
public static Map<String, String> getHeaders(String authorization, String serialNumber) {
Map<String, String> headers = getBaseHeaders(authorization);
headers.put("Content-Type", ContentType.JSON.toString());
if (StrUtil.isNotEmpty(serialNumber)) {
headers.put("Wechatpay-Serial", serialNumber);
}
return headers;
}
public static Map<String, String> getUploadHeaders(String authorization, String serialNumber) {
Map<String, String> headers = getBaseHeaders(authorization);
headers.put("Content-Type", "multipart/form-data;boundary=\"boundary\"");
if (StrUtil.isNotEmpty(serialNumber)) {
headers.put("Wechatpay-Serial", serialNumber);
}
return headers;
}
/**
* 构建返回参数
*
* @param response {@link PaymentHttpResponse}
* @return {@link Map}
*/
public static Map<String, Object> buildResMap(PaymentHttpResponse response) {
if (response == null) {
return null;
}
Map<String, Object> map = new HashMap<>(6);
String timestamp = response.getHeader("Wechatpay-Timestamp");
String nonceStr = response.getHeader("Wechatpay-Nonce");
String serialNo = response.getHeader("Wechatpay-Serial");
String signature = response.getHeader("Wechatpay-Signature");
String body = response.getBody();
int status = response.getStatus();
map.put("timestamp", timestamp);
map.put("nonceStr", nonceStr);
map.put("serialNumber", serialNo);
map.put("signature", signature);
map.put("body", body);
map.put("status", status);
return map;
}
}

View File

@ -0,0 +1,694 @@
package com.wzj.soopin.transaction.kit.plugin.wechat;
import cn.hutool.cache.Cache;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.net.URLEncoder;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.wzj.soopin.transaction.domain.po.RefundLog;
import com.wzj.soopin.transaction.enums.PaymentMethodEnum;
import com.wzj.soopin.transaction.kit.CashierSupport;
import com.wzj.soopin.transaction.kit.Payment;
import com.wzj.soopin.transaction.kit.core.PaymentHttpResponse;
import com.wzj.soopin.transaction.kit.core.enums.RequestMethodEnums;
import com.wzj.soopin.transaction.kit.core.enums.SignType;
import com.wzj.soopin.transaction.kit.core.kit.HttpKit;
import com.wzj.soopin.transaction.kit.core.kit.IpKit;
import com.wzj.soopin.transaction.kit.core.kit.WxPayKit;
import com.wzj.soopin.transaction.kit.core.utils.DateTimeZoneUtil;
import com.wzj.soopin.transaction.kit.dto.PayParam;
import com.wzj.soopin.transaction.kit.dto.PaymentSuccessParams;
import com.wzj.soopin.transaction.kit.params.dto.CashierParam;
import com.wzj.soopin.transaction.kit.plugin.wechat.enums.WechatApiEnum;
import com.wzj.soopin.transaction.kit.plugin.wechat.enums.WechatDomain;
import com.wzj.soopin.transaction.kit.plugin.wechat.model.*;
import com.wzj.soopin.transaction.service.PaymentService;
import com.wzj.soopin.transaction.service.RefundLogService;
import com.wzj.soopin.transaction.util.CurrencyUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.ResultCode;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.Objects;
/**
* 微信支付
*
* @author Chopper
* @since 2020/12/21 17:44
*/
@Slf4j
@Component
public class WechatPlugin implements Payment {
/**
* 收银台
*/
@Autowired
private CashierSupport cashierSupport;
// /**
// * 支付日志
// */
// @Autowired
// private PaymentService paymentService;
// /**
// * 缓存
// */
// @Autowired
// private Cache<String> cache;
// /**
// * 退款日志
// */
// @Autowired
// private RefundLogService refundLogService;
/**
* API域名
*/
// @Autowired
// private ApiProperties apiProperties;
// /**
// * 配置
// */
// @Autowired
// private SettingService settingService;
// /**
// * 联合登陆
// */
// @Autowired
// private ConnectService connectService;
// /**
// * 联合登陆
// */
// @Autowired
// private OrderService orderService;
@Override
public R<Object> h5pay(HttpServletRequest request, HttpServletResponse response1, PayParam payParam) {
try {
// CashierParam cashierParam = cashierSupport.cashierParam(payParam);
//
// //支付参数准备
// SceneInfo sceneInfo = new SceneInfo();
// sceneInfo.setPayer_client_ip(IpKit.getRealIp(request));
// H5Info h5Info = new H5Info();
// h5Info.setType("WAP");
// sceneInfo.setH5_info(h5Info);
//
// //支付金额
// Integer fen = CurrencyUtil.fen(cashierParam.getPrice());
// //第三方付款订单
// String outOrderNo = SnowFlake.getIdStr();
// //过期时间
// String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
//
// //回传数据
// String attach = URLEncoder.createDefault().encode(JSONUtil.toJsonStr(payParam), StandardCharsets.UTF_8);
//
//
// WechatPaymentSetting setting = wechatPaymentSetting();
// String appid = setting.getServiceAppId();
// if (appid == null) {
// throw new ServiceException(ResultCode.WECHAT_PAYMENT_NOT_SETTING);
// }
// UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
// .setAppid(appid)
// .setMchid(setting.getMchId())
// .setDescription(cashierParam.getDetail())
// .setOut_trade_no(outOrderNo)
// .setTime_expire(timeExpire)
// .setAttach(attach)
// .setNotify_url(notifyUrl(apiProperties.getBuyer(), PaymentMethodEnum.WECHAT))
// .setAmount(new Amount().setTotal(fen)).setScene_info(sceneInfo);
//
// log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
// PaymentHttpResponse response = WechatApi.v3(
// RequestMethodEnums.POST,
// WechatDomain.CHINA.toString(),
// WechatApiEnum.H5_PAY.toString(),
// setting.getMchId(),
// setting.getSerialNumber(),
// null,
// setting.getApiclient_key(),
// JSONUtil.toJsonStr(unifiedOrderModel)
// );
return null;
// return R.ok(JSONUtil.toJsonStr(response.getBody()));
} catch (Exception e) {
log.error("微信H5支付错误", e);
throw new ServiceException(ResultCode.PAY_ERROR);
}
}
@Override
public R<Object> jsApiPay(HttpServletRequest request, PayParam payParam) {
try {
// Connect connect = connectService.queryConnect(
// ConnectQueryDTO.builder().userId(UserContext.getCurrentUser().getId()).unionType(ConnectEnum.WECHAT.name()).build()
// );
// if (connect == null) {
// return null;
// }
//
// Payer payer = new Payer();
// payer.setOpenid(connect.getUnionId());
//
// CashierParam cashierParam = cashierSupport.cashierParam(payParam);
//
// //支付金额
// Integer fen = CurrencyUtil.fen(cashierParam.getPrice());
// //第三方付款订单
// String outOrderNo = SnowFlake.getIdStr();
// //过期时间
// String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
//
// String attach = URLEncoder.createDefault().encode(JSONUtil.toJsonStr(payParam), StandardCharsets.UTF_8);
//
// WechatPaymentSetting setting = wechatPaymentSetting();
// String appid = setting.getServiceAppId();
// if (appid == null) {
// throw new ServiceException(ResultCode.WECHAT_PAYMENT_NOT_SETTING);
// }
// UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
// .setAppid(appid)
// .setMchid(setting.getMchId())
// .setDescription(cashierParam.getDetail())
// .setOut_trade_no(outOrderNo)
// .setTime_expire(timeExpire)
// .setAttach(attach)
// .setNotify_url(notifyUrl(apiProperties.getBuyer(), PaymentMethodEnum.WECHAT))
// .setAmount(new Amount().setTotal(fen))
// .setPayer(payer);
//
// log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
// PaymentHttpResponse response = WechatApi.v3(
// RequestMethodEnums.POST,
// WechatDomain.CHINA.toString(),
// WechatApiEnum.JS_API_PAY.toString(),
// setting.getMchId(),
// setting.getSerialNumber(),
// null,
// setting.getApiclient_key(),
// JSONUtil.toJsonStr(unifiedOrderModel)
// );
// //根据证书序列号查询对应的证书来验证签名结果
// boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCert());
// log.info("verifySignature: {}", verifySignature);
// log.info("统一下单响应 {}", response);
//
// if (verifySignature) {
// String body = response.getBody();
// JSONObject jsonObject = JSONUtil.parseObj(body);
// String prepayId = jsonObject.getStr("prepay_id");
// Map<String, String> map = WxPayKit.jsApiCreateSign(appid, prepayId, setting.getApiclient_key());
// log.info("唤起支付参数:{}", map);
//
// return ResultUtil.data(map);
// }
log.error("微信支付参数验证错误,请及时处理");
throw new ServiceException(ResultCode.PAY_ERROR);
} catch (Exception e) {
log.error("支付异常", e);
throw new ServiceException(ResultCode.PAY_ERROR);
}
}
@Override
public R<Object> appPay(HttpServletRequest request, PayParam payParam) {
try {
CashierParam cashierParam = cashierSupport.cashierParam(payParam);
//支付金额
// Integer fen = CurrencyUtil.fen(cashierParam.getPrice());
// //第三方付款订单
// String outOrderNo = SnowFlake.getIdStr();
// //过期时间
// String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
//
// String attach = URLEncoder.createDefault().encode(JSONUtil.toJsonStr(payParam), StandardCharsets.UTF_8);
//
// WechatPaymentSetting setting = wechatPaymentSetting();
// String appid = setting.getAppId();
// if (appid == null) {
// throw new ServiceException(ResultCode.WECHAT_PAYMENT_NOT_SETTING);
// }
// UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
// .setAppid(appid)
// .setMchid(setting.getMchId())
// .setDescription(cashierParam.getDetail())
// .setOut_trade_no(outOrderNo)
// .setTime_expire(timeExpire)
// .setAttach(attach)
// .setNotify_url(notifyUrl(apiProperties.getBuyer(), PaymentMethodEnum.WECHAT))
// .setAmount(new Amount().setTotal(fen));
//
//
// log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
// PaymentHttpResponse response = WechatApi.v3(
// RequestMethodEnums.POST,
// WechatDomain.CHINA.toString(),
// WechatApiEnum.APP_PAY.toString(),
// setting.getMchId(),
// setting.getSerialNumber(),
// null,
// setting.getApiclient_key(),
// JSONUtil.toJsonStr(unifiedOrderModel)
// );
// //根据证书序列号查询对应的证书来验证签名结果
// boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCert());
// log.info("verifySignature: {}", verifySignature);
// log.info("统一下单响应 {}", response);
//
// if (verifySignature) {
// JSONObject jsonObject = JSONUtil.parseObj(response.getBody());
// String prepayId = jsonObject.getStr("prepay_id");
// Map<String, String> map = WxPayKit.appPrepayIdCreateSign(appid,
// setting.getMchId(),
// prepayId,
// setting.getApiclient_key(), SignType.HMACSHA256);
// log.info("唤起支付参数:{}", map);
//
// return R.ok(map);
// }
log.error("微信支付参数验证错误,请及时处理");
throw new ServiceException(ResultCode.PAY_ERROR);
} catch (Exception e) {
log.error("支付异常", e);
throw new ServiceException(ResultCode.PAY_ERROR);
}
}
@Override
public R<Object> nativePay(HttpServletRequest request, PayParam payParam) {
try {
// CashierParam cashierParam = cashierSupport.cashierParam(payParam);
//
// //支付金额
// Integer fen = CurrencyUtil.fen(cashierParam.getPrice());
// //第三方付款订单
// String outOrderNo = SnowFlake.getIdStr();
// //过期时间
// String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
//
// String attach = URLEncoder.createDefault().encode(JSONUtil.toJsonStr(payParam), StandardCharsets.UTF_8);
//
// WechatPaymentSetting setting = wechatPaymentSetting();
//
// String appid = setting.getServiceAppId();
// if (appid == null) {
// throw new ServiceException(ResultCode.WECHAT_PAYMENT_NOT_SETTING);
// }
// UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
// .setAppid(appid)
// .setMchid(setting.getMchId())
// .setDescription(cashierParam.getDetail())
// .setOut_trade_no(outOrderNo)
// .setTime_expire(timeExpire)
// //回传参数
// .setAttach(attach)
// .setNotify_url(notifyUrl(apiProperties.getBuyer(), PaymentMethodEnum.WECHAT))
// .setAmount(new Amount().setTotal(fen));
//
// log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
// PaymentHttpResponse response = WechatApi.v3(
// RequestMethodEnums.POST,
// WechatDomain.CHINA.toString(),
// WechatApiEnum.NATIVE_PAY.toString(),
// setting.getMchId(),
// setting.getSerialNumber(),
// null,
// setting.getApiclient_key(),
// JSONUtil.toJsonStr(unifiedOrderModel)
// );
// log.info("统一下单响应 {}", response);
// //根据证书序列号查询对应的证书来验证签名结果
// boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCert());
// log.info("verifySignature: {}", verifySignature);
//
// if (verifySignature) {
// return R.ok(new JSONObject(response.getBody()).getStr("code_url"));
// } else {
// log.error("微信支付参数验证错误,请及时处理");
// throw new ServiceException(ResultCode.PAY_ERROR);
// }
return null;
} catch (ServiceException e) {
log.error("支付异常", e);
throw new ServiceException(ResultCode.PAY_ERROR);
} catch (Exception e) {
log.error("支付异常", e);
throw new ServiceException(ResultCode.PAY_ERROR);
}
}
@Override
public R<Object> mpPay(HttpServletRequest request, PayParam payParam) {
try {
// Connect connect = connectService.queryConnect(
// ConnectQueryDTO.builder().userId(UserContext.getCurrentUser().getId()).unionType(ConnectEnum.WECHAT_MP_OPEN_ID.name()).build()
// );
// if (connect == null) {
// return null;
// }
//
// Payer payer = new Payer();
// payer.setOpenid(connect.getUnionId());
//
// CashierParam cashierParam = cashierSupport.cashierParam(payParam);
//
// //支付金额
// Integer fen = CurrencyUtil.fen(cashierParam.getPrice());
// //第三方付款订单
// String outOrderNo = SnowFlake.getIdStr();
// //过期时间
// String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
//
// //微信小程序appid 需要单独获取这里读取了联合登陆配置的appid 实际场景小程序自动登录所以这个appid是最为保险的做法
// //如果有2开需求这里需要调整修改这个appid的获取途径即可
// String appid = wechatPaymentSetting().getMpAppId();
// if (StringUtils.isEmpty(appid)) {
// throw new ServiceException(ResultCode.WECHAT_PAYMENT_NOT_SETTING);
// }
// String attach = URLEncoder.createDefault().encode(JSONUtil.toJsonStr(payParam), StandardCharsets.UTF_8);
//
// WechatPaymentSetting setting = wechatPaymentSetting();
// UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
// .setAppid(appid)
// .setMchid(setting.getMchId())
// .setDescription(cashierParam.getDetail())
// .setOut_trade_no(outOrderNo)
// .setTime_expire(timeExpire)
// .setAttach(attach)
// .setNotify_url(notifyUrl(apiProperties.getBuyer(), PaymentMethodEnum.WECHAT))
// .setAmount(new Amount().setTotal(fen))
// .setPayer(payer);
//
// log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
// PaymentHttpResponse response = WechatApi.v3(
// RequestMethodEnums.POST,
// WechatDomain.CHINA.toString(),
// WechatApiEnum.JS_API_PAY.toString(),
// setting.getMchId(),
// setting.getSerialNumber(),
// null,
// setting.getApiclient_key(),
// JSONUtil.toJsonStr(unifiedOrderModel)
// );
// //根据证书序列号查询对应的证书来验证签名结果
// boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCert());
// log.info("verifySignature: {}", verifySignature);
// log.info("统一下单响应 {}", response);
//
// if (verifySignature) {
// String body = response.getBody();
// JSONObject jsonObject = JSONUtil.parseObj(body);
// String prepayId = jsonObject.getStr("prepay_id");
// Map<String, String> map = WxPayKit.jsApiCreateSign(appid, prepayId, setting.getApiclient_key());
// log.info("唤起支付参数:{}", map);
//
// return R.ok(map);
// }
log.error("微信支付参数验证错误,请及时处理");
throw new ServiceException(ResultCode.PAY_ERROR);
} catch (Exception e) {
log.error("支付异常", e);
throw new ServiceException(ResultCode.PAY_ERROR);
}
}
@Override
public void callBack(HttpServletRequest request) {
try {
verifyNotify(request);
} catch (Exception e) {
log.error("支付异常", e);
}
}
@Override
public void notify(HttpServletRequest request) {
try {
verifyNotify(request);
} catch (Exception e) {
log.error("支付异常", e);
}
}
/**
* 验证结果执行支付回调
*
* @param request
* @throws Exception
*/
private void verifyNotify(HttpServletRequest request) throws Exception {
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String serialNo = request.getHeader("Wechatpay-Serial");
String signature = request.getHeader("Wechatpay-Signature");
log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
String result = HttpKit.readData(request);
log.info("微信支付通知密文 {}", result);
//校验服务器端响应¬7
// String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
// setting.getApiKey3(), Objects.requireNonNull(getPlatformCert()));
//
// log.info("微信支付通知明文 {}", plainText);
//
// JSONObject jsonObject = JSONUtil.parseObj(plainText);
//
// String payParamStr = jsonObject.getStr("attach");
// String payParamJson = URLDecoder.decode(payParamStr, StandardCharsets.UTF_8);
// PayParam payParam = JSONUtil.toBean(payParamJson, PayParam.class);
// String tradeNo = jsonObject.getStr("transaction_id");
// Double totalAmount = CurrencyUtil.reversalFen(jsonObject.getJSONObject("amount").getDouble("total"));
//
// PaymentSuccessParams paymentSuccessParams = new PaymentSuccessParams(
// PaymentMethodEnum.WECHAT.name(),
// tradeNo,
// totalAmount,
// payParam
// );
//
// paymentService.success(paymentSuccessParams);
// log.info("微信支付回调:支付成功{}", plainText);
}
@Override
public void refund(RefundLog refundLog) {
try {
// Amount amount = new Amount().setRefund(CurrencyUtil.fen(refundLog.getTotalAmount()))
// .setTotal(CurrencyUtil.fen(orderService.getPaymentTotal(refundLog.getOrderSn())));
//
// //退款参数准备
// RefundModel refundModel = new RefundModel()
// .setTransaction_id(refundLog.getPaymentReceivableNo())
// .setOut_refund_no(refundLog.getOutOrderNo())
// .setReason(refundLog.getRefundReason())
// .setAmount(amount)
// .setNotify_url(refundNotifyUrl(apiProperties.getBuyer(), PaymentMethodEnum.WECHAT));
//
// WechatPaymentSetting setting = wechatPaymentSetting();
//
// log.info("微信退款参数 {}", JSONUtil.toJsonStr(refundModel));
// PaymentHttpResponse response = WechatApi.v3(
// RequestMethodEnums.POST,
// WechatDomain.CHINA.toString(),
// WechatApiEnum.DOMESTIC_REFUNDS.toString(),
// setting.getMchId(),
// setting.getSerialNumber(),
// null,
// setting.getApiclient_key(),
// JSONUtil.toJsonStr(refundModel)
// );
// log.info("微信退款响应 {}", response);
// //退款申请成功
// if (response.getStatus() == 200) {
// refundLogService.save(refundLog);
// } else {
// //退款申请失败
// refundLog.setErrorMessage(response.getBody());
// refundLogService.save(refundLog);
// }
} catch (Exception e) {
log.error("微信退款申请失败", e);
}
}
@Override
public void cancel(RefundLog refundLog) {
this.refund(refundLog);
}
@Override
public void refundNotify(HttpServletRequest request) {
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String serialNo = request.getHeader("Wechatpay-Serial");
String signature = request.getHeader("Wechatpay-Signature");
log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
String result = HttpKit.readData(request);
log.info("微信退款通知密文 {}", result);
JSONObject ciphertext = JSONUtil.parseObj(result);
try { //校验服务器端响应¬
// String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
// wechatPaymentSetting().getApiKey3(), Objects.requireNonNull(getPlatformCert()));
// log.info("微信退款通知明文 {}", plainText);
// if (("REFUND.SUCCESS").equals(ciphertext.getStr("event_type"))) {
// log.info("退款成功 {}", plainText);
// //校验服务器端响应
// JSONObject jsonObject = JSONUtil.parseObj(plainText);
// String transactionId = jsonObject.getStr("transaction_id");
// String refundId = jsonObject.getStr("refund_id");
//
// RefundLog refundLog = refundLogService.getOne(new LambdaQueryWrapper<RefundLog>().eq(RefundLog::getPaymentReceivableNo, transactionId));
// if (refundLog != null) {
// refundLog.setIsRefund(true);
// refundLog.setReceivableNo(refundId);
// refundLogService.saveOrUpdate(refundLog);
// }
//
// } else {
// log.info("退款失败 {}", plainText);
// JSONObject jsonObject = JSONUtil.parseObj(plainText);
// String transactionId = jsonObject.getStr("transaction_id");
// String refundId = jsonObject.getStr("refund_id");
//
// RefundLog refundLog = refundLogService.getOne(new LambdaQueryWrapper<RefundLog>().eq(RefundLog::getPaymentReceivableNo, transactionId));
// if (refundLog != null) {
// refundLog.setReceivableNo(refundId);
// refundLog.setErrorMessage(ciphertext.getStr("summary"));
// refundLogService.saveOrUpdate(refundLog);
// }
// }
} catch (Exception e) {
log.error("微信退款失败", e);
}
}
// /**
// * 获取微信支付配置
// *
// * @return
// */
// private WechatPaymentSetting wechatPaymentSetting() {
// try {
// Setting systemSetting = settingService.get(SettingEnum.WECHAT_PAYMENT.name());
// WechatPaymentSetting wechatPaymentSetting = JSONUtil.toBean(systemSetting.getSettingValue(), WechatPaymentSetting.class);
// return wechatPaymentSetting;
// } catch (Exception e) {
// log.error("微信支付暂不支持", e);
// throw new ServiceException(ResultCode.PAY_NOT_SUPPORT);
// }
// }
// /**
// * 获取平台公钥
// *
// * @return 平台公钥
// */
// private X509Certificate getPlatformCert() {
// //获取缓存中的平台公钥如果有则直接返回否则去微信请求
// String publicCert = cache.getString(CachePrefix.WECHAT_PLAT_FORM_CERT.getPrefix());
// if (!StringUtils.isEmpty(publicCert)) {
// return PayKit.getCertificate(publicCert);
// }
// //获取平台证书列表
// try {
//
// WechatPaymentSetting setting = wechatPaymentSetting();
//
// PaymentHttpResponse response = WechatApi.v3(
// RequestMethodEnums.GET,
// WechatDomain.CHINA.toString(),
// WechatApiEnum.GET_CERTIFICATES.toString(),
// setting.getMchId(),
// setting.getSerialNumber(),
// null,
// setting.getApiclient_key(),
// ""
// );
// String body = response.getBody();
// log.info("获取微信平台证书body: {}", body);
//
// if (response.getStatus() == 200) {
// JSONObject jsonObject = JSONUtil.parseObj(body);
// JSONArray dataArray = jsonObject.getJSONArray("data");
// log.info("证书信息: {}", dataArray);
//
// //默认认为只有一个平台证书
// JSONObject encryptObject = dataArray.getJSONObject(0);
// JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
// String associatedData = encryptCertificate.getStr("associated_data");
// String cipherText = encryptCertificate.getStr("ciphertext");
// String nonce = encryptCertificate.getStr("nonce");
// publicCert = getPlatformCertStr(associatedData, nonce, cipherText);
// long second = (PayKit.getCertificate(publicCert).getNotAfter().getTime() - System.currentTimeMillis()) / 1000;
// cache.put(CachePrefix.WECHAT_PLAT_FORM_CERT.getPrefix(), publicCert, second);
// } else {
// log.error("证书获取失败:{}" + body);
// throw new ServiceException(ResultCode.WECHAT_CERT_ERROR);
// }
// return PayKit.getCertificate(publicCert);
// } catch (Exception e) {
// log.error("证书获取失败", e);
// }
// return null;
// }
//
// /**
// * 获取平台证书缓存的字符串
// * 下列各个密钥参数
// *
// * @param associatedData 密钥参数
// * @param nonce 密钥参数
// * @param cipherText 密钥参数
// * @return platform key
// * @throws GeneralSecurityException 密钥获取异常
// */
// private String getPlatformCertStr(String associatedData, String nonce, String cipherText) throws GeneralSecurityException {
//
//
// AesUtil aesUtil = new AesUtil(wechatPaymentSetting().getApiKey3().getBytes(StandardCharsets.UTF_8));
// //平台证书密文解密
// //encrypt_certificate 中的 associated_data nonce ciphertext
// return aesUtil.decryptToString(
// associatedData.getBytes(StandardCharsets.UTF_8),
// nonce.getBytes(StandardCharsets.UTF_8),
// cipherText
// );
// }
}

View File

@ -0,0 +1,577 @@
package com.wzj.soopin.transaction.kit.plugin.wechat.enums;
/**
* 微信api列表
*
* @author Chopper
* @since 2020/12/17 17:43
*/
public enum WechatApiEnum {
/**
* 沙箱环境
*/
SAND_BOX_NEW("/sandboxnew"),
/**
* 获取沙箱环境验签秘钥
*/
GET_SIGN_KEY("/sandboxnew/pay/getsignkey"),
/**
* 获取平台证书列表
*/
GET_CERTIFICATES("/v3/certificates"),
/**
* 营销专用-图片上传
*/
MARKETING_UPLOAD_MEDIA("/v3/marketing/favor/media/image-upload"),
/**
* 通用接口-图片上传
*/
MERCHANT_UPLOAD_MEDIA("/v3/merchant/media/upload"),
/**
* 通用接口-视频上传
*/
MERCHANT_VIDEO_UPLOAD("/v3/merchant/media/video_upload"),
/**
* 创建/查询支付分订单
*/
PAY_SCORE_SERVICE_ORDER("/v3/payscore/serviceorder"),
/**
* 取消支付分订单
*/
PAY_SCORE_SERVICE_ORDER_CANCEL("/v3/payscore/serviceorder/%s/cancel"),
/**
* 修改支付分订单金额
*/
PAY_SCORE_SERVICE_ORDER_MODIFY("/v3/payscore/serviceorder/%s/modify"),
/**
* 完结支付分订单
*/
PAY_SCORE_SERVICE_ORDER_COMPLETE("/v3/payscore/serviceorder/%s/complete"),
/**
* 支付分订单收款
*/
PAY_SCORE_SERVICE_ORDER_PAY("/v3/payscore/serviceorder/%s/pay"),
/**
* 同步服务订单信息
*/
PAY_SCORE_SERVICE_ORDER_SYNC("/v3/payscore/serviceorder/%s/sync"),
/**
* 查询用户支付分开启状态
*/
PAY_SCORE_USER_SERVICE_STATE("/v3/payscore/user-service-state"),
/**
* 商户解除用户授权关系
*/
PAY_SCORE_PERMISSIONS_TERMINATE("/payscore/users/%s/permissions/%s/terminate"),
/**
* 特约商户进件-提交申请单
*/
APPLY_4_SUB("/v3/applyment4sub/applyment/"),
/**
* 特约商户进件-通过业务申请编号查询申请状态
*/
GET_APPLY_STATE("/v3/applyment4sub/applyment/business_code/%s"),
/**
* 特约商户进件-通过申请单号查询申请状态
*/
GET_APPLY_STATE_BY_ID("/v3/applyment4sub/applyment/applyment_id/%s"),
/**
* 特约商户进件-修改结算帐号
*/
MODIFY_SETTLEMENT("/v3/apply4sub/sub_merchants/%s/modify-settlement"),
/**
* 特约商户进件-查询结算账户
*/
GET_SETTLEMENT("/v3/apply4sub/sub_merchants/%s/settlement"),
/**
* 商户开户意愿确认-提交申请单 OR 查询申请单审核结果
*/
MER_OPEN_APPLY_SUBMIT_OR_RESULT("/v3/apply4subject/applyment"),
/**
* 商户开户意愿确认-撤销申请单
*/
MER_OPEN_APPLY_CANCEL("/v3/apply4subject/applyment/%s/cancel"),
/**
* 商户开户意愿确认-获取商户开户意愿确认状态
*/
GET_MER_OPEN_APPLY_STATE("/v3/apply4subject/applyment/merchants/%s/state"),
/**
* 商业支付投诉-查询投诉信息
*/
MERCHANT_SERVICE_COMPLAINTS("/v3/merchant-service/complaints"),
/**
* 商业支付投诉-创建/查询/更新/删除投诉通知回调
*/
MERCHANT_SERVICE_COMPLAINTS_NOTIFICATIONS("/v3/merchant-service/complaint-notifications"),
/**
* 代金券-创建代金券批次
*/
CREATE_COUPON_STOCKS("/v3/marketing/favor/coupon-stocks"),
/**
* 代金券-激活代金券批次
*/
START_COUPON_STOCKS("/v3/marketing/favor/stocks/%s/start"),
/**
* 代金券-发放代金券
*/
COUPON_SEND("/v3/marketing/favor/users/%s/coupons"),
/**
* 代金券-暂停代金券批次
*/
PAUSE_COUPON_STOCKS("/v3/marketing/favor/stocks/%s/pause"),
/**
* 代金券-重启代金券批次
*/
RESTART_COUPON_STOCKS("/v3/marketing/favor/stocks/%s/restart"),
/**
* 代金券-条件查询批次列表
*/
QUERY_COUPON_STOCKS("/v3/marketing/favor/stocks"),
/**
* 代金券-查询批次详情
*/
QUERY_COUPON_STOCKS_INFO("/v3/marketing/favor/stocks/%s"),
/**
* 代金券-查询代金券详情
*/
QUERY_COUPON_INFO("/v3/marketing/favor/users/%s/coupons/%s"),
/**
* 代金券-查询代金券可用商户
*/
QUERY_COUPON_MERCHANTS("/v3/marketing/favor/stocks/%s/merchants"),
/**
* 代金券-查询代金券可用单品
*/
QUERY_COUPON_ITEMS("/v3/marketing/favor/stocks/%s/items"),
/**
* 代金券-根据商户号查用户的券
*/
QUERY_USER_COUPON("/v3/marketing/favor/users/%s/coupons"),
/**
* 代金券-下载批次核销明细
*/
COUPON_STOCKS_USER_FLOW_DOWNLOAD("/v3/marketing/favor/stocks/%s/use-flow"),
/**
* 代金券-下载批次退款明细
*/
COUPON_STOCKS_REFUND_FLOW_DOWNLOAD("/v3/marketing/favor/stocks/%s/refund-flow"),
/**
* 代金券-设置消息通知地址
*/
SETTING_COUPON_CALLBACKS("/v3/marketing/favor/callbacks"),
/**
* 商家券-创建商家券
*/
CREATE_BUSINESS_COUPON("/v3/marketing/busifavor/stocks"),
/**
* 发放消费卡
*/
SEND_BUSINESS_COUPON("/v3/marketing/busifavor/coupons/%s/send"),
/**
* H5 发券
*/
H5_SEND_COUPON("/busifavor/getcouponinfo"),
/**
* 商家券-查询商家券批次详情
*/
QUERY_BUSINESS_COUPON_STOCKS_INFO("/v3/marketing/busifavor/stocks/%s"),
/**
* 商家券-查询商家券批次详情
*/
USE_BUSINESS_COUPON("/v3/marketing/busifavor/coupons/use"),
/**
* 商家券-根据过滤条件查询用户券
*/
QUERY_BUSINESS_USER_COUPON("/v3/marketing/busifavor/users/%s/coupons"),
/**
* 商家券-查询用户单张券详情
*/
QUERY_BUSINESS_USER_COUPON_INFO("/v3/marketing/busifavor/users/%s/coupons/%s/appids/%s"),
/**
* 商家券-上传预存code
*/
BUSINESS_COUPON_UPLOAD_CODE("/v3/marketing/busifavor/stocks/%/couponcodes"),
/**
* 商家券-设置/查询商家券事件通知地址
*/
BUSINESS_COUPON_CALLBACKS("/v3/marketing/busifavor/callbacks"),
/**
* 关联订单信息
*/
BUSINESS_COUPON_ASSOCIATE("/v3/marketing/busifavor/coupons/associate"),
/**
* 取消关联订单信息
*/
BUSINESS_COUPON_DISASSOCIATE("/v3/marketing/busifavor/coupons/disassociate"),
/**
* 支付有礼-创建全场满额送活动
*/
PAY_GIFT_ACTIVITY("/v3/marketing/paygiftactivity/unique-threshold-activity"),
/**
* 支付有礼-获取支付有礼活动列表
*/
PAY_GIFT_ACTIVITY_GET("/v3/marketing/paygiftactivity/activities"),
/**
* 支付有礼-查询活动详情接口
*/
PAY_GIFT_ACTIVITY_INFO("/v3/marketing/paygiftactivity/activities/%s"),
/**
* 支付有礼-查询活动发券商户号
*/
PAY_GIFT_ACTIVITY_QUERY_MER("/v3/marketing/paygiftactivity/activities/%s/merchants"),
/**
* 支付有礼-查询活动指定商品列表
*/
PAY_GIFT_ACTIVITY_QUERY_GOODS("/v3/marketing/paygiftactivity/activities/%s/goods"),
/**
* 支付有礼-终止活动
*/
PAY_GIFT_ACTIVITY_TERMINATE("/v3/marketing/paygiftactivity/activities/%s/terminate"),
/**
* 支付有礼-新增活动发券商户号
*/
PAY_GIFT_ACTIVITY_ADD_MERCHANTS("/v3/marketing/paygiftactivity/activities/%s/merchants/add"),
/**
* 支付有礼-删除活动发券商户号
*/
PAY_GIFT_ACTIVITY_DELETE_MERCHANTS("/v3/marketing/paygiftactivity/activities/%s/merchants/delete"),
/**
* 点金计划-点金计划管理
*/
CHANGE_GOLD_PLAN_STATUS("/v3/goldplan/merchants/changegoldplanstatus"),
/**
* 点金计划-商家小票管理
*/
CHANGE_CUSTOM_PAGE_STATUS("/v3/goldplan/merchants/changecustompagestatus"),
/**
* 点金计划-同业过滤标签管理
*/
SET_ADVERTISING_INDUSTRY_FILTER("/v3/goldplan/merchants/set-advertising-industry-filter"),
/**
* 电商收付通-二级商户进件
*/
E_COMMERCE_APPLY("/v3/ecommerce/applyments/"),
/**
* 电商收付通-查询进件申请状态
*/
E_COMMERCE_APPLY_STATE("/v3/ecommerce/applyments/%s"),
/**
* 电商收付通-通过业务申请编号查询申请状态
*/
E_COMMERCE_APPLY_STATE_BY_NO("/v3/ecommerce/applyments/out-request-no/%s"),
/**
* 合单下单-APP支付
*/
COMBINE_TRANSACTIONS_APP("/v3/combine-transactions/app"),
/**
* 合单下单-JS支付
*/
COMBINE_TRANSACTIONS_JS("/v3/combine-transactions/jsapi"),
/**
* 合单下单-H5支付
*/
COMBINE_TRANSACTIONS_H5("/v3/combine-transactions/h5"),
/**
* 合单下单-Native支付
*/
COMBINE_TRANSACTIONS_NATIVE("/v3/combine-transactions/native"),
/**
* 合单下单-合单查询订单
*/
COMBINE_TRANSACTIONS_QUERY("/v3/combine-transactions/out-trade-no/%s"),
/**
* 合单下单-合单关闭订单
*/
COMBINE_TRANSACTIONS_CLOSE("/v3/combine-transactions/out-trade-no/%s/close"),
/**
* 电商收付通-补差接口-请求补差
*/
CREATE_SUBSIDIES("v3/ecommerce/subsidies/create"),
/**
* 电商收付通-补差接口-请求补差回退
*/
RETURN_SUBSIDIES("/v3/ecommerce/subsidies/return"),
/**
* 电商收付通-补差接口-取消补差
*/
CANCEL_SUBSIDIES("/v3/ecommerce/subsidies/cancel"),
/**
* 电商收付通-分账接口-请求分账/查询分账结果
*/
PROFIT_SHARING_ORDERS("/v3/ecommerce/profitsharing/orders"),
/**
* 电商收付通-分账接口-查询分账回退结果
*/
PROFIT_SHARING_RETURN_ORDERS("/v3/ecommerce/profitsharing/returnorders"),
/**
* 电商收付通-分账接口-完结分账
*/
PROFIT_SHARING_FINISH_ORDER("/v3/ecommerce/profitsharing/finish-order"),
/**
* 添加分账接收方
*/
PROFIT_SHARING_RECEIVERS_ADD("/v3/ecommerce/profitsharing/receivers/add"),
/**
* 删除分账接收方
*/
PROFIT_SHARING_RECEIVERS_DELETE("/v3/ecommerce/profitsharing/receivers/delete"),
/**
* 退款接口-退款申请
*/
DOMESTIC_REFUNDS("/v3/refund/domestic/refunds"),
/**
* 电商收付通-退款接口-退款申请
*/
E_COMMERCE_REFUNDS("/v3/ecommerce/refunds/apply"),
/**
* 电商收付通-退款接口-通过微信支付退款单号查询退款
*/
QUERY_REFUND("/v3/ecommerce/refunds/id/%s"),
/**
* 电商收付通-退款接口-通过商户退款单号查询退款
*/
QUERY_REFUNDS_BY_REFUND_NO("/v3/ecommerce/refunds/out-refund-no/%s"),
/**
* 电商收付通-余额查询接口
*/
QUERY_BALANCE("/v3/ecommerce/fund/balance/%s"),
/**
* 查询二级商户账户日终余额
*/
QUERY_END_DAY_BALANCE("/v3/ecommerce/fund/enddaybalance/%s"),
/**
* 查询电商平台账户实时余额
*/
QUERY_MERCHANT_BALANCE("/v3/merchant/fund/balance/%s"),
/**
* 查询电商平台账户日终余额
*/
QUERY_MERCHANT_END_DAY_BALANCE("/v3/merchant/fund/dayendbalance/%s"),
/**
* 电商收付通-提现接口-账户余额提现
*/
WITHDRAW("/v3/ecommerce/fund/withdraw"),
/**
* 电商收付通-提现接口-提现状态查询
*/
QUERY_WITHDRAW("/v3/ecommerce/fund/withdraw/%s"),
/**
* 电商收付通-提现接口-商户提现单号查询
*/
QUERY_WITHDRAW_BY_OUT_REQUEST_NO("/v3/ecommerce/fund/withdraw/out-request-no/%s"),
/**
* 电商收付通-提现接口-电商平台提现
*/
MERCHANT_WITHDRAW("/v3/merchant/fund/withdraw"),
/**
* 电商收付通-提现接口-微信支付提现单号查询
*/
QUERY_MERCHANT_WITHDRAW("/v3/ecommerce/fund/withdraw/%s"),
/**`
* 电商收付通-提现接口-商户提现单号查询
*/
QUERY_MERCHANT_WITHDRAW_BY_OUT_REQUEST_NO("/v3/merchant/fund/withdraw/out-request-no/%s"),
/**
* 电商收付通-提现接口-按日下载提现异常文件
*/
WITHDRAW_BILL("/v3/merchant/fund/withdraw/bill-type/%s"),
/**
* 申请交易账单
*/
TRADE_BILL("/v3/bill/tradebill"),
/**
* 申请资金账单
*/
FUND_FLOW_BILL("/v3/bill/fundflowbill"),
/**
* 下载账单
*/
BILL_DOWNLOAD("/v3/billdownload/file"),
/**
* 银行特约商户违规信息查询
*/
GET_VIOLATION("/risk/getviolation"),
/**
* 事前-风险商户核查接口
*/
QUERY_MCH_RISK("/mchrisk/querymchrisk"),
/**
* 事后-风险商户处理结果同步接口
*/
SYNC_MCH_RISK_RESULT("/mchrisk/syncmchriskresult"),
/**
* 间联模式查询商户审核状态开放接口
*/
BANK_QUERY_MCH_AUDIT_INFO("/mchrisk/bankquerymchauditinfo"),
/**
* 渠道商查询商户审核信息
*/
CHANNEL_QUERY_MCH_AUDIT_INFO("/mchrisk/channelquerymchauditinfo"),
/**
* 设置风险通知回调链接
*/
SET_MCH_RISK_CALLBACK("/mchrisk/setmchriskcallback"),
/**
* 向员工付款
*/
PAY_WWS_TRANS_2_POCKET("/mmpaymkttransfers/promotion/paywwsptrans2pocket"),
/**
* 查询向员工付款记录
*/
QUERY_WWS_TRANS_2_POCKET("/mmpaymkttransfers/promotion/querywwsptrans2pocket"),
/**
* 发放企业红包
*/
SEND_WORK_WX_RED_PACK("/mmpaymkttransfers/sendworkwxredpack"),
/**
* 查询企业红包记录
*/
QUERY_WORK_WX_RED_PACK("/mmpaymkttransfers/queryworkwxredpack"),
/**
* 查询/更新先享卡订单
*/
DISCOUNT_CARD_ORDER("/v3/discount-card/orders/%s"),
/**
* 查询先享卡订单
*/
DISCOUNT_CARD_ORDER_TRADE_NO("/v3/discount-card/orders/out-trade-no/%s"),
/**
* 服务人员注册
*/
SMART_GUIDE_GUIDES("/v3/smartguide/guides"),
/**
* 服务人员分配
*/
SMART_GUIDE_GUIDES_ASSIGN("/v3/smartguide/guides/%s/assign"),
/**
* 服务人员信息更新
*/
SMART_GUIDE_GUIDES_UPDATE("/v3/smartguide/guides/%s"),
/**
* 报关接口-订单附加信息提交接口
*/
CUSTOM_DECLARE_ORDER("/cgi-bin/mch/customs/customdeclareorder"),
/**
* 报关接口-订单附加信息查询接口
*/
CUSTOM_DECLARE_QUERY("/cgi-bin/mch/customs/customdeclarequery"),
/**
* 报关接口-订单附加信息重推接口
*/
CUSTOM_DECLARE_RE_DECLARE("/cgi-bin/mch/newcustoms/customdeclareredeclare"),
/**
* APP 下单 API
*/
APP_PAY("/v3/pay/transactions/app"),
PARTNER_APP_PAY("/v3/pay/partner/transactions/app"),
/**
* JS API 下单 API
*/
JS_API_PAY("/v3/pay/transactions/jsapi"),
PARTNER_JS_API_PAY("/v3/pay/partner/transactions/jsapi"),
/**
* Native 下单 API
*/
NATIVE_PAY("/v3/pay/transactions/native"),
PARTNER_NATIVE_PAY("/v3/pay/partner/transactions/native"),
/**
* H5 下单 API
*/
H5_PAY("/v3/pay/transactions/h5"),
PARTNER_H5_PAY("/v3/pay/partner/transactions/h5"),
/**
* 微信支付订单号查询
*/
ORDER_QUERY_BY_ID("/v3/pay/transactions/id/%s"),
PARTNER_ORDER_QUERY_BY_ID("/v3/pay/partner/transactions/id/%s"),
/**
* 商户订单号查询
*/
ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s"),
PARTNER_ORDER_QUERY_BY_NO("/v3/pay/partner/transactions/out-trade-no/%s"),
/**
* 关闭订单
*/
CLOSE_ORDER_BY_NO("/v3/pay/transactions/out-trade-no/%s/close"),
PARTNER_CLOSE_ORDER_BY_NO("/v3/pay/partner/transactions/out-trade-no/%s/close"),
/**
* 委托营销-建立合作关系
*/
PARTNERSHIPS_BUILD("/v3/marketing/partnerships/build"),
/**
* 委托营销-终止合作关系
*/
PARTNERSHIPS_TERMINATE("/v3/marketing/partnerships/terminate"),
/**
* 智慧商圈-商圈积分同步
*/
BUSINESS_CIRCLE_POINTS_NOTIFY("/v3/businesscircle/points/notify"),
/**
* 连锁品牌-分账-查询分账
*/
BRAND_PROFIT_SHARING_ORDERS("/v3/brand/profitsharing/orders"),
/**
* 连锁品牌-分账回退-查询分账回退
*/
BRAND_PROFIT_SHARING_RETURN_ORDERS("/v3/brand/profitsharing/returnorders"),
/**
* 连锁品牌-完结分账
*/
BRAND_PROFIT_SHARING_FINISH_ORDER("/v3/brand/profitsharing/finish-order"),
/**
* 连锁品牌-添加分账接收方
*/
BRAND_PROFIT_SHARING_RECEIVERS_ADD("/v3/brand/profitsharing/receivers/add"),
/**
* 连锁品牌-删除分账接收方
*/
BRAND_PROFIT_SHARING_RECEIVERS_delete("/v3/brand/profitsharing/receivers/delete"),
;
/**
* 类型
*/
private final String url;
WechatApiEnum(String url) {
this.url = url;
}
public String getUrl() {
return url;
}
@Override
public String toString() {
return url;
}
}

View File

@ -0,0 +1,58 @@
package com.wzj.soopin.transaction.kit.plugin.wechat.enums;
/**
* 微信支付域名
*
* @author Chopper
* @since 2020/12/17 17:44
*/
public enum WechatDomain {
/**
* 中国国内
*/
CHINA("https://api.mch.weixin.qq.com"),
/**
* 中国国内(备用域名)
*/
CHINA2("https://api2.mch.weixin.qq.com"),
/**
* 东南亚
*/
HK("https://apihk.mch.weixin.qq.com"),
/**
* 其它
*/
US("https://apius.mch.weixin.qq.com"),
/**
* 获取公钥
*/
FRAUD("https://fraud.mch.weixin.qq.com"),
/**
* 活动
*/
ACTION("https://action.weixin.qq.com"),
/**
* 刷脸支付
* PAY_APP
*/
PAY_APP("https://payapp.weixin.qq.com");
/**
* 域名
*/
private final String domain;
WechatDomain(String domain) {
this.domain = domain;
}
public String getType() {
return domain;
}
@Override
public String toString() {
return domain;
}
}

View File

@ -0,0 +1,32 @@
package com.wzj.soopin.transaction.kit.plugin.wechat.model;
import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
/**
* 统一下单-订单金额
*
* @author Chopper
* @since 2020/12/17 17:44
*/
@Data
@Accessors(chain = true)
public class Amount {
/**
* 总金额
*/
private BigDecimal total;
/**
* 货币类型
*/
private String currency = "CNY";
/**
* 退款金额
*/
private BigDecimal refund;
}

View File

@ -0,0 +1,30 @@
package com.wzj.soopin.transaction.kit.plugin.wechat.model;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 统一下单-优惠功能
*
* @author Chopper
* @since 2020/12/17 17:46
*/
@Data
@Accessors(chain = true)
public class Detail {
/**
* 订单原价
*/
private int cost_price;
/**
* 商品小票ID
*/
private String invoice_id;
/**
* 单品列表
*/
private List<GoodsDetail> goods_detail;
}

View File

@ -0,0 +1,36 @@
package com.wzj.soopin.transaction.kit.plugin.wechat.model;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 统一下单-单品列表
*
* @author Chopper
* @since 2020/12/17 17:47
*/
@Data
@Accessors(chain = true)
public class GoodsDetail {
/**
* 商户侧商品编码
*/
private String merchant_goods_id;
/**
* 微信侧商品编码
*/
private String wechatpay_goods_id;
/**
* 商品名称
*/
private String goods_name;
/**
* 商品数量
*/
private int quantity;
/**
* 商品单价
*/
private int unit_price;
}

View File

@ -0,0 +1,36 @@
package com.wzj.soopin.transaction.kit.plugin.wechat.model;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 统一下单-H5 场景信息
*
* @author Chopper
* @since 2020/12/17 17:56
*/
@Data
@Accessors(chain = true)
public class H5Info {
/**
* 场景类型
*/
private String type;
/**
* 应用名称
*/
private String app_name;
/**
* 网站URL
*/
private String app_url;
/**
* iOS 平台 BundleID
*/
private String bundle_id;
/**
* Android 平台 PackageName
*/
private String package_name;
}

View File

@ -0,0 +1,28 @@
package com.wzj.soopin.transaction.kit.plugin.wechat.model;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 统一下单-支付者
*
* @author Chopper
* @since 2020/12/17 17:56
*/
@Data
@Accessors(chain = true)
public class Payer {
/**
* 用户标识
*/
private String openid;
/**
* 用户服务标识
*/
private String sp_openid;
/**
* 用户子标识
*/
private String sub_openid;
}

View File

@ -0,0 +1,50 @@
package com.wzj.soopin.transaction.kit.plugin.wechat.model;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 国内退款-退款申请
*
* @author Chopper
* @since 2020/12/17 17:58
*/
@Data
@Accessors(chain = true)
public class RefundModel {
/**
* 原支付交易对应的微信订单号
*/
private String transaction_id;
/**
* 商户订单号
*/
private String out_trade_no;
/**
* 商户退款单号
*/
private String out_refund_no;
/**
* 退款理由
*/
private String reason;
/**
* 退款金额
*/
private Amount amount;
/**
* 通知地址
*/
private String notify_url;
}

View File

@ -0,0 +1,32 @@
package com.wzj.soopin.transaction.kit.plugin.wechat.model;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 统一下单-场景信息
*
* @author Chopper
* @since 2020/12/17 17:57
*/
@Data
@Accessors(chain = true)
public class SceneInfo {
/**
* 用户终端IP
*/
private String payer_client_ip;
/**
* 商户端设备号
*/
private String device_id;
/**
* 商户门店信息
*/
private StoreInfo store_info;
/**
* H5 场景信息
*/
private H5Info h5_info;
}

View File

@ -0,0 +1,23 @@
package com.wzj.soopin.transaction.kit.plugin.wechat.model;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 统一下单-结算信息
*
* @author Chopper
* @since 2020/12/17 17:58
*/
@Data
@Accessors(chain = true)
public class SettleInfo {
/**
* 是否指定分账
*/
private boolean profit_sharing;
/**
* 补差金额
*/
private int subsidy_amount;
}

View File

@ -0,0 +1,32 @@
package com.wzj.soopin.transaction.kit.plugin.wechat.model;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 统一下单-商户门店信息
*
* @author Chopper
* @since 2020/12/17 17:58
*/
@Data
@Accessors(chain = true)
public class StoreInfo {
/**
* 门店编号
*/
private String id;
/**
* 门店名称
*/
private String name;
/**
* 地区编码
*/
private String area_code;
/**
* 详细地址
*/
private String address;
}

View File

@ -0,0 +1,87 @@
package com.wzj.soopin.transaction.kit.plugin.wechat.model;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 统一下单-商户门店信息
*
* @author Chopper
* @since 2020/12/17 17:58
*/
@Data
@Accessors(chain = true)
public class UnifiedOrderModel {
/**
* 公众号ID
*/
private String appid;
/**
* 服务商公众号ID
*/
private String sp_appid;
/**
* 直连商户号
*/
private String mchid;
/**
* 服务商户号
*/
private String sp_mchid;
/**
* 子商户公众号ID
*/
private String sub_appid;
/**
* 子商户号
*/
private String sub_mchid;
/**
* 商品描述
*/
private String description;
/**
* 商户订单号
*/
private String out_trade_no;
/**
* 交易结束时间
*/
private String time_expire;
/**
* 附加数据
*/
private String attach;
/**
* 通知地址
*/
private String notify_url;
/**
* 订单优惠标记
*/
private String goods_tag;
/**
* 结算信息
*/
private SettleInfo settle_info;
/**
* 订单金额
*/
private Amount amount;
/**
* 支付者
*/
private Payer payer;
/**
* 优惠功能
*/
private Detail detail;
/**
* 场景信息
*/
private SceneInfo scene_info;
}

View File

@ -0,0 +1,14 @@
package com.wzj.soopin.transaction.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wzj.soopin.transaction.domain.po.RefundLog;
/**
* 退款日志数据层
* @author Chopper
* @since 2020-12-19 09:25
*/
public interface RefundLogMapper extends BaseMapper<RefundLog> {
}

View File

@ -7,4 +7,6 @@ public interface IChargeService extends IService<Charge> {
boolean audit(Long id, String status); boolean audit(Long id, String status);
boolean charge(Charge charge);
} }

View File

@ -7,5 +7,9 @@ import com.wzj.soopin.transaction.domain.po.Withdraw;
public interface IWithdrawService extends IService<Withdraw> { public interface IWithdrawService extends IService<Withdraw> {
boolean audit(WithdrawBO bo); boolean audit(WithdrawBO bo);
boolean withdraw(Long id); boolean withdrawCallback(WithdrawBO withdraw);
boolean withdrawWallet(Withdraw withdraw);
boolean withdrawRevenue (Withdraw withdraw);
} }

View File

@ -0,0 +1,29 @@
package com.wzj.soopin.transaction.service;
import com.wzj.soopin.transaction.kit.dto.PaymentSuccessParams;
/**
* 支付日志 业务层
*
* @author Chopper
* @since 2020-12-19 09:25
*/
public interface PaymentService {
/**
* 支付成功通知
*
* @param paymentSuccessParams 支付成功回调参数
*/
void success(PaymentSuccessParams paymentSuccessParams);
/**
* 平台支付成功
*
* @param paymentSuccessParams 支付成功回调参数
*/
void adminPaySuccess(PaymentSuccessParams paymentSuccessParams);
}

View File

@ -0,0 +1,19 @@
package com.wzj.soopin.transaction.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.wzj.soopin.transaction.domain.po.RefundLog;
/**
* 退款日志 业务层
*
* @author Chopper
* @since 2020-12-19 09:25
*/
public interface RefundLogService extends IService<RefundLog> {
/**
* 根据售后sn查询退款日志
* @param sn
* @return
*/
RefundLog queryByAfterSaleSn(String sn);
}

View File

@ -1,13 +1,23 @@
package com.wzj.soopin.transaction.service.impl; package com.wzj.soopin.transaction.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wzj.soopin.member.domain.po.AccountBill;
import com.wzj.soopin.member.domain.po.MemberAccount;
import com.wzj.soopin.member.enums.AccountBillChangeTypeEnum;
import com.wzj.soopin.member.enums.AccountBillSourceEnum;
import com.wzj.soopin.member.service.IAccountBillService;
import com.wzj.soopin.member.service.IMemberAccountService;
import com.wzj.soopin.transaction.domain.po.Charge; import com.wzj.soopin.transaction.domain.po.Charge;
import com.wzj.soopin.transaction.enums.ChargeStatus;
import com.wzj.soopin.transaction.mapper.ChargeMapper; import com.wzj.soopin.transaction.mapper.ChargeMapper;
import com.wzj.soopin.transaction.service.IChargeService; import com.wzj.soopin.transaction.service.IChargeService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.math.BigDecimal;
/** /**
* 会员封禁 * 会员封禁
* *
@ -18,13 +28,52 @@ import org.springframework.stereotype.Service;
@Slf4j @Slf4j
public class ChargeServiceImpl extends ServiceImpl<ChargeMapper, Charge> implements IChargeService { public class ChargeServiceImpl extends ServiceImpl<ChargeMapper, Charge> implements IChargeService {
private final IMemberAccountService memberAccountService;
private final IAccountBillService accountBillService;
@Override @Override
public boolean audit(Long id, String status) { public boolean audit(Long id, String status) {
Charge charge = getById(id); Charge charge = getById(id);
//调用三方充值接口 //调用三方充值接口
boolean chargeSuccess = true; boolean chargeSuccess = true;
//充值成功后更新会员账户余额 //充值成功后更新会员账户余额
if (chargeSuccess) {
//更新会员账户余额
MemberAccount memberAccount = memberAccountService.getById(charge.getMemberId());
memberAccount.setWallet(memberAccount.getWallet().add(charge.getMoney()));
memberAccountService.updateById(memberAccount);
//生成充值记录
AccountBill accountBill=AccountBill.builder()
.accountId(charge.getMemberId())
.changeAmount(charge.getMoney())
.changeType(AccountBillChangeTypeEnum.IN.getCode())
.changeDesc("充值")
.source(AccountBillSourceEnum.RECHARGE.getCode())
.build();
accountBillService.save(accountBill);
return true;
}
//生成充值记录 //生成充值记录
return false; return false;
} }
@Override
public boolean charge(Charge charge) {
//判断充值金额不能为零
if (charge.getMoney().compareTo(BigDecimal.ZERO) == 0) {
throw new ServiceException("充值金额不能为零");
}
//状态为待审核
charge.setStatus(ChargeStatus.WAITING.getCode());
charge.setType(1);
//手续费
charge.setFee(new BigDecimal("0.01").multiply(charge.getMoney()));
//实际金额
charge.setActualMoney(charge.getMoney().subtract(charge.getFee()));
//保存充值记录
save(charge);
return false;
}
} }

View File

@ -0,0 +1,54 @@
package com.wzj.soopin.transaction.service.impl;
import com.wzj.soopin.transaction.kit.CashierSupport;
import com.wzj.soopin.transaction.kit.dto.PaymentSuccessParams;
import com.wzj.soopin.transaction.kit.params.CashierExecute;
import com.wzj.soopin.transaction.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 支付日志 业务实现
*
* @author Chopper
* @since 2020-12-19 09:25
*/
@Slf4j
@Service
public class PaymentServiceImpl implements PaymentService {
@Autowired
private List<CashierExecute> cashierExecutes;
@Autowired
private CashierSupport cashierSupport;
@Override
public void success(PaymentSuccessParams paymentSuccessParams) {
boolean paymentResult = cashierSupport.paymentResult(paymentSuccessParams.getPayParam());
if (paymentResult) {
log.warn("订单支付状态后,调用支付成功接口,流水号:{}", paymentSuccessParams.getReceivableNo());
return;
}
log.debug("支付成功,第三方流水:{}", paymentSuccessParams.getReceivableNo());
//支付结果处理
for (CashierExecute cashierExecute : cashierExecutes) {
cashierExecute.paymentSuccess(paymentSuccessParams);
}
}
@Override
public void adminPaySuccess(PaymentSuccessParams paymentSuccessParams) {
log.debug("支付状态修改成功->银行转账");
//支付结果处理
for (CashierExecute cashierExecute : cashierExecutes) {
cashierExecute.paymentSuccess(paymentSuccessParams);
}
}
}

View File

@ -0,0 +1,23 @@
package com.wzj.soopin.transaction.service.impl;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wzj.soopin.transaction.domain.po.RefundLog;
import com.wzj.soopin.transaction.mapper.RefundLogMapper;
import com.wzj.soopin.transaction.service.RefundLogService;
import org.springframework.stereotype.Service;
/**
* 退款日志 业务实现
*
* @author Chopper
* @since 2020-12-19 09:25
*/
@Service
public class RefundLogServiceImpl extends ServiceImpl<RefundLogMapper, RefundLog> implements RefundLogService {
@Override
public RefundLog queryByAfterSaleSn(String sn) {
return this.getOne(new LambdaUpdateWrapper<RefundLog>().eq(RefundLog::getAfterSaleNo, sn));
}
}

View File

@ -9,6 +9,7 @@ import com.wzj.soopin.member.enums.AccountBillChangeTypeEnum;
import com.wzj.soopin.member.enums.AccountBillSourceEnum; import com.wzj.soopin.member.enums.AccountBillSourceEnum;
import com.wzj.soopin.transaction.enums.WithdrawAuditStatus; import com.wzj.soopin.transaction.enums.WithdrawAuditStatus;
import com.wzj.soopin.transaction.enums.WithdrawStatus; import com.wzj.soopin.transaction.enums.WithdrawStatus;
import com.wzj.soopin.transaction.enums.WithdrawType;
import com.wzj.soopin.transaction.mapper.WithdrawMapper; import com.wzj.soopin.transaction.mapper.WithdrawMapper;
import com.wzj.soopin.member.service.*; import com.wzj.soopin.member.service.*;
import com.wzj.soopin.transaction.domain.vo.YishengAccountVO; import com.wzj.soopin.transaction.domain.vo.YishengAccountVO;
@ -17,6 +18,8 @@ import com.wzj.soopin.transaction.service.IYishengService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.SysTenantAccount;
import org.dromara.system.service.ISysTenantAccountService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.math.BigDecimal; import java.math.BigDecimal;
@ -35,12 +38,15 @@ public class WithdrawServiceImpl extends ServiceImpl<WithdrawMapper, Withdraw> i
private final IMemberAccountService memberAccountService; private final IMemberAccountService memberAccountService;
private final ISysTenantAccountService sysTenantAccountService;
/** /**
* 易生账户充值服务 * 易生账户充值服务
*/ */
private final IYishengService yishengService; private final IYishengService yishengService;
private final IAccountBillService memberAccountChangeRecordService; private final IAccountBillService accountBillService;
@Override @Override
public boolean audit(WithdrawBO bo) { public boolean audit(WithdrawBO bo) {
@ -51,13 +57,43 @@ public class WithdrawServiceImpl extends ServiceImpl<WithdrawMapper, Withdraw> i
if (!Objects.equals(WithdrawAuditStatus.PENDING.getCode(), withdraw.getAuditStatus())) { if (!Objects.equals(WithdrawAuditStatus.PENDING.getCode(), withdraw.getAuditStatus())) {
throw new RuntimeException("提现申请已处理"); throw new RuntimeException("提现申请已处理");
} }
//发起提现
boolean chargeSuccess = yishengService.withdraw(withdraw.getMemberId(), withdraw.getMoney());
if (!chargeSuccess) {
throw new RuntimeException("提现失败");
}
withdraw = Withdraw.builder().id(bo.getId()) withdraw = Withdraw.builder().id(bo.getId())
.auditReason(bo.getAuditReason()) .auditReason(bo.getAuditReason())
.auditTime(LocalDateTime.now()) .auditTime(LocalDateTime.now())
.auditStatus(bo.getAuditStatus()) .auditStatus(bo.getAuditStatus())
.auditBy(LoginHelper.getUserId()) .auditBy(LoginHelper.getUserId())
.build(); .build();
return this.updateById(withdraw); this.updateById(withdraw);
return true;
}
@Override
public boolean withdrawCallback(WithdrawBO withdraw) {
MemberAccount memberAccount = memberAccountService.getMemberAccount(withdraw.getMemberId());
BigDecimal balance = memberAccount.getWallet();
////提现成功后更新会员账户余额
//从易生取别用自己计算的
//// TODO: 2025/6/21 测试的时候用计算的 测试完用易生的
BigDecimal finalBalance = balance.subtract(withdraw.getMoney());
YishengAccountVO yishengAccountVO = yishengService.getYishengAccount(withdraw.getMemberId());
memberAccountService.updateById(memberAccount.toBuilder().wallet(finalBalance).build());
//生成账户变动记录bh
AccountBill memberAccountChangeRecord = AccountBill.builder()
.accountId(withdraw.getMemberId())
.moneyBalance(finalBalance)
.beforeBalance(balance)
.afterBalance(yishengAccountVO.getBalance())
.changeType(AccountBillChangeTypeEnum.OUT.getCode())
.changeDesc("提现")
.source(AccountBillSourceEnum.WITHDRAW.getCode()).build();
accountBillService.save(memberAccountChangeRecord);
return true;
} }
@Override @Override
@ -66,59 +102,77 @@ public class WithdrawServiceImpl extends ServiceImpl<WithdrawMapper, Withdraw> i
return super.save(entity); return super.save(entity);
} }
public boolean withdraw(Long id) {
Withdraw withdraw = getById(id); public boolean withdrawRevenue(Withdraw withdraw) {
//获取用户余额信息 //调用三方支付平台获取用户余额
MemberAccount memberAccount = memberAccountService.getById(withdraw.getMemberId()); SysTenantAccount tenantAccount = sysTenantAccountService.getByTenantId(withdraw.getMemberId());
if (tenantAccount == null) {
throw new RuntimeException("用户不存在");
}
BigDecimal balance = tenantAccount.getRevenue();
YishengAccountVO yishengAccountVO = yishengService.getYishengAccount(withdraw.getMemberId());
if (yishengAccountVO == null) {
throw new RuntimeException("账户余额获取失败");
}
//生成费用
BigDecimal fee = withdraw.getMoney().multiply(new BigDecimal("0.01"));
withdraw.setFee(fee);
withdraw.setActualMoney(withdraw.getMoney().subtract(fee));
save(withdraw);
BigDecimal newBalance = balance.subtract(withdraw.getMoney());
tenantAccount.setRevenue(newBalance);
//锁定用户余额
sysTenantAccountService.updateById(tenantAccount);
//生成账单
AccountBill memberAccountChangeRecord = AccountBill.builder()
.accountId(withdraw.getMemberId())
.moneyBalance(balance.subtract(withdraw.getMoney()))
.beforeBalance(balance)
.afterBalance(newBalance)
.changeType(AccountBillChangeTypeEnum.OUT.getCode())
.changeDesc("提现锁定")
.source(AccountBillSourceEnum.WITHDRAW.getCode())
.build();
accountBillService.save(memberAccountChangeRecord);
return true;
}
public boolean withdrawWallet(Withdraw withdraw) {
MemberAccount memberAccount = memberAccountService.getMemberAccount(withdraw.getMemberId());
if (memberAccount == null) { if (memberAccount == null) {
throw new RuntimeException("用户不存在"); throw new RuntimeException("用户不存在");
} }
//检查当前用于的账户余额是否充足 //检查当前用于的账户余额是否充足
BigDecimal balance = memberAccount.getWallet(); BigDecimal balance = memberAccount.getWallet();
if (balance.compareTo(withdraw.getMoney()) < 0) { if (balance.compareTo(withdraw.getMoney()) < 0) {
throw new RuntimeException("用户余额不足"); throw new RuntimeException("用户余额不足");
} }
//调用三方支付平台获取用户余额 //生成费用
YishengAccountVO yishengAccountVO = yishengService.getYishengAccount(withdraw.getMemberId()); BigDecimal fee = withdraw.getMoney().multiply(new BigDecimal("0.01"));
withdraw.setFee(fee);
withdraw.setActualMoney(withdraw.getMoney().subtract(fee));
save(withdraw);
if (yishengAccountVO == null) { BigDecimal newBalance = balance.subtract(withdraw.getMoney());
throw new RuntimeException("用户余额获取失败"); //锁定用户余额
} memberAccountService.updateById(memberAccount.toBuilder().wallet(newBalance).build());
BigDecimal yishengBalance = yishengAccountVO.getBalance(); //生成账单
if (yishengBalance.compareTo(withdraw.getMoney()) < 0) { AccountBill memberAccountChangeRecord = AccountBill.builder()
throw new RuntimeException("用户余额不足"); .accountId(withdraw.getMemberId())
} .moneyBalance(newBalance)
.beforeBalance(balance)
if (!yishengBalance.equals(balance)) { .afterBalance(newBalance)
throw new RuntimeException("用户余额不一致"); .changeType(AccountBillChangeTypeEnum.OUT.getCode())
} .changeDesc("提现锁定")
.source(AccountBillSourceEnum.WITHDRAW.getCode())
//发起提现 .build();
boolean chargeSuccess = yishengService.withdraw(withdraw.getMemberId(), withdraw.getMoney()); accountBillService.save(memberAccountChangeRecord);
if (chargeSuccess) {
//提现成功后更新会员账户余额
//从易生取别用自己计算的
//// TODO: 2025/6/21 测试的时候用计算的 测试完用易生的
BigDecimal finalBalance = balance.subtract(withdraw.getMoney());
yishengAccountVO = yishengService.getYishengAccount(withdraw.getMemberId());
memberAccountService.updateById(memberAccount.toBuilder().wallet(balance.subtract(finalBalance)).build());
//生成账户变动记录bh
AccountBill memberAccountChangeRecord = AccountBill.builder()
.accountId(withdraw.getMemberId())
.moneyBalance(finalBalance)
.beforeBalance(balance)
.afterBalance(yishengAccountVO.getBalance())
.changeType(AccountBillChangeTypeEnum.OUT.getCode())
.changeDesc("提现")
.source(AccountBillSourceEnum.WITHDRAW.getCode()).build();
memberAccountChangeRecordService.save(memberAccountChangeRecord);
} else {
return false;
}
return true; return true;
} }
} }

View File

@ -0,0 +1,137 @@
package com.wzj.soopin.transaction.util;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
/**
* 金额计算工具
*
* @author Bulbasaur
* @since 2021/7/9 1:40 上午
*/
public final class CurrencyUtil {
/**
* 默认除法运算精度
*/
private static final int DEF_DIV_SCALE = 2;
/**
* 这个类不能实例化
*/
private CurrencyUtil() {
}
/**
* 提供精确的加法运算
*
* @return 累加之和
*/
public static Double add(double... params) {
BigDecimal result = new BigDecimal("0");
for (double param : params) {
BigDecimal bigParam = BigDecimal.valueOf(param);
result = result.add(bigParam).setScale(2, RoundingMode.HALF_UP);
}
return result.doubleValue();
}
/**
* 提供精确的减法运算
*
* @return 第一个参数为被减数其余数字为减数
*/
public static Double sub(double... params) {
BigDecimal result = BigDecimal.valueOf(params[0]);
params = Arrays.stream(params).skip(1).toArray();
for (double param : params) {
BigDecimal bigParam = BigDecimal.valueOf(param);
result = result.subtract(bigParam).setScale(2, RoundingMode.HALF_UP);
}
return result.doubleValue();
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static Double mul(double v1, double v2) {
BigDecimal b1 = BigDecimal.valueOf(v1);
BigDecimal b2 = BigDecimal.valueOf(v2);
return b1.multiply(b2).setScale(2, RoundingMode.HALF_UP).doubleValue();
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @param scale 表示表示需要精确到小数点以后几位
* @return 两个参数的积
*/
public static Double mul(double v1, double v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = BigDecimal.valueOf(v1);
BigDecimal b2 = BigDecimal.valueOf(v2);
return b1.multiply(b2).setScale(scale, RoundingMode.HALF_UP).doubleValue();
}
/**
* 提供相对精确的除法运算当发生除不尽的情况时 精确到小数点以后10位以后的数字四舍五入
*
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1, double v2) {
return div(v1, v2, DEF_DIV_SCALE);
}
/**
* 提供相对精确的除法运算 当发生除不尽的情况时由scale参数指定精度以后的数字四舍五入
*
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位
* @return 两个参数的商
*/
public static double div(double v1, double v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
//如果被除数等于0则返回0
if (v2 == 0) {
return 0;
}
BigDecimal b1 = BigDecimal.valueOf(v1);
BigDecimal b2 = BigDecimal.valueOf(v2);
return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
}
/**
* 金额转分
*
* @param money 金额
* @return 转换单位为分
*/
public static Integer fen(Double money) {
double price = mul(money, 100);
return (int) price;
}
/**
* 金额转分
*
* @param money 金额
* @return double类型分
*/
public static double reversalFen(Double money) {
return div(money, 100);
}
}

View File

@ -0,0 +1,59 @@
package com.wzj.soopin.transaction.util;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import com.wzj.soopin.order.utils.DateUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.Date;
/**
* 雪花分布式id获取
*
* @author Chopper
*/
@Slf4j
public class SnowFlake {
/**
* 机器id
*/
private static long workerId = 0L;
/**
* 机房id
*/
public static long datacenterId = 0L;
private static Snowflake snowflake;
/**
* 初始化配置
*
* @param workerId
* @param datacenterId
*/
public static void initialize(long workerId, long datacenterId) {
snowflake = IdUtil.getSnowflake(workerId, datacenterId);
}
public static long getId() {
return snowflake.nextId();
}
/**
* 生成字符带有前缀
*
* @param prefix
* @return
*/
public static String createStr(String prefix) {
return prefix + DateUtil.toString(new Date(), "yyyyMMdd") + SnowFlake.getId();
}
public static String getIdStr() {
if (snowflake == null) {
initialize(workerId, datacenterId);
}
return snowflake.nextId() + "";
}
}