✨ 添加新特性。支付相关功能

This commit is contained in:
pigxcloud committed 2019-06-20 18:28:09 +08:00
1 parent 6908b89731
commit 2f2013b0df
37 files changed
+1122 -155

No files matched your search

@@ -27,6 +27,7 @@ import com.pig4cloud.pigx.common.data.tenant.PigxTenantHandler;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -62,6 +63,7 @@ public class MybatisPlusConfig {
*/
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "mybatisPlus.tenantEnable", havingValue = "true", matchIfMissing = true)
public PaginationInterceptor paginationInterceptor(PigxTenantHandler tenantHandler) {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
List<ISqlParser> sqlParserList = new ArrayList<>();
@@ -17,7 +17,7 @@ import org.springframework.stereotype.Component;
*/
@Data
@Component
@ConfigurationProperties(prefix = "xsequence.db")
@ConfigurationProperties(prefix = "pigx.xsequence.db")
public class SequenceDbProperties extends BaseSequenceProperties {
/**
* 表名称
@@ -0,0 +1,49 @@
/*
*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*
*/
package com.pig4cloud.pigx.admin.api.feign;
import com.pig4cloud.pigx.admin.api.entity.SysTenant;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.constant.ServiceNameConstants;
import com.pig4cloud.pigx.common.core.util.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import java.util.List;
/**
* @author lengleng
* @date 2019/6/19
* <p>
* 租户接口
*/
@FeignClient(contextId = "remoteTenantService", value = ServiceNameConstants.UMPS_SERVICE)
public interface RemoteTenantService {
/**
* 查询全部有效租户
*
* @param from 内部标志
* @return
*/
@GetMapping("/tenant/list")
R<List<SysTenant>> list(@RequestHeader(SecurityConstants.FROM) String from);
}
@@ -0,0 +1,22 @@
package com.pig4cloud.pigx.common.data.tenant;
import cn.hutool.core.thread.ThreadUtil;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* @author lengleng
* @date 2019-06-18
*/
public class TenantContextHolderTest {
public static void main(String[] args) {
TenantContextHolder.setTenantId(10000);
System.out.println(TenantContextHolder.getTenantId());
ThreadUtil.execAsync(() -> {
System.out.println(TenantContextHolder.getTenantId());
});
}
}
+13
View File
@@ -48,6 +48,11 @@
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-sequence</artifactId>
</dependency>
<!--feign 接口-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-upms-api</artifactId>
</dependency>
<!--swagger-->
<dependency>
<groupId>com.pig4cloud</groupId>
@@ -73,6 +78,10 @@
<artifactId>alipay-sdk-java</artifactId>
<version>3.0.52.ALL</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
</dependency>
<!--web 模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -83,6 +92,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
</dependencies>
<build>
@@ -0,0 +1,25 @@
package com.pig4cloud.pigx.pay.config;
import lombok.Data;
/**
* @author lengleng
* @date 2019-06-19
*/
@Data
public class AliPayConfig {
/**
* 订单过期时间
*/
private String expireTime;
/**
* 前端回调地址
*/
private String returnUrl;
/**
* 服务端回调地址
*/
private String notifyUrl;
}
@@ -0,0 +1,30 @@
package com.pig4cloud.pigx.pay.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
/**
* @author lengleng
* @date 2019-06-18
*/
@Data
@RefreshScope
@Configuration
@ConfigurationProperties(prefix = "pigx.pay")
public class PayCommonProperties {
/**
* 支付宝支付参数
*/
private AliPayConfig aliPayConfig;
/**
* 微信支付参数
*/
private WxPayConfig wxPayConfig;
private String test;
}
@@ -1,16 +1,38 @@
package com.pig4cloud.pigx.pay.config;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.annotation.SqlParser;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.jpay.alipay.AliPayApiConfig;
import com.jpay.alipay.AliPayApiConfigKit;
import com.jpay.weixin.api.WxPayApiConfig;
import com.jpay.weixin.api.WxPayApiConfigKit;
import com.pig4cloud.pigx.admin.api.feign.RemoteTenantService;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
import com.pig4cloud.pigx.pay.entity.PayChannel;
import com.pig4cloud.pigx.pay.service.PayChannelService;
import com.pig4cloud.pigx.pay.utils.PayChannelNameEnum;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author lengleng
* @date 2019-05-31
@@ -21,21 +43,69 @@ import org.springframework.scheduling.annotation.Async;
@Configuration
@AllArgsConstructor
public class PayConfigParmaInitRunner {
public static Map<String, WxMpService> mpServiceMap = new HashMap<>();
private final PayChannelService channelService;
private final RemoteTenantService tenantService;
@Async
@Order
@SqlParser(filter = true)
@EventListener({WebServerInitializedEvent.class})
public void initPayConfig() {
AliPayApiConfig rsa2 = AliPayApiConfig.New()
.setAppId("2016102000727659")
.setPrivateKey("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCkejVpiLJlece9KLb8gG/+WODkpe5SRfr65n3aUAvRPoouDUp/URBAtt24mzHFdGkaU/Il7ZxG7KUjLaFNTfq8t/bHEF7HBt7MZidpEjVaDxeG/hkA+xc+X2ePAJXVhy7v1tH+XRrxuggTj+d4q7vVGD7r1mtgX4d5hK9TTKnyhO6IUcthPOUSn07wx+5wlbv4/51ggj4Ku4AWCGaCWiebDFy4VZ/FmOMP5u3O7TocCGSy5UtaFZaVMM1nH603DG6NJ8fG+zKum9IvrdQMMmBWPT8rpy0U9ztchcd+0gTNrTfE/NOCgl/RF6z7Qic7NiTKjLxkGRq73XPzfJx0hyfpAgMBAAECggEAa2/sndAN/90JjNUgmlVnUnRKCvEceJ9/rw6KXOV2oqrAZg6GgB26iRsqP6EYZMuCsBDvlrjcITQJNq5is/Vg+I8OYr+duVISjN+ZlLexI+/BxYsLWCmr6DE3myCdvwn7rezb5NR6ejWzetvALoG3Qx4AU9sO7rfX7ZevUrE8Pc5pa/rTCHOIec9P7Hfm4xrXd0DPmbAwFvUQ4spcC9xjcBhLJ3tfmkoEU11jC6Y/J7N0/TZZxqNC+HcYftSNEmbO3vxYfz/9mbabx7Uzp8xs4qHPJACmaP5tcWOCf1qJVOwP53Cd3H9J9Be9cAvlxKfT48s1ghoxLn7QB27p0UwntQKBgQDzTpCCFDoUH328B3iGTkwZq24DOUh5ZwhMG6VIFPLHwJYK6HVM473SX0nJWkbiBYFG4WiQWUZnOhz/qjTH1vvE/nzJwQ79zZahren2b8XulXKM6S/mafiBAHKl/FArcr14iAWkh+scZKWFKrNf6Hp8rQ6zu0fWdwzUOlaQFaSnTwKBgQCtDtpIagRPyNj0J39uMRJqsaY0TdJ53TD4bflf5UiFhNFH2Dikgw/uX8Pom2qtIvf3xnO2bzmJW2+OSkF7U0fL+UXkAaBHW+7YVcs8pWARSd5LWBXGWysV2wUALwynZSdwtgpgrzMQnuzklQ3hNR9LBSEGN7yJwkU83ZRWPzbvRwKBgQDU3E8g/oExSbu+3Opc1fNOIeTFfUAitjlUHHulbG5aw+qA8I5vDm/rtOHg/tI0u4w2bs4EO5aUiQsFwesbSsJJvjt+ZyCue0blfDnMGE2aRbVKAlidxOhcNAAZp3ycBm4tHROStjbDSGpm7syvg7xlhyHtrFNVFiJrKf7BX64FkQKBgCA8lAzJMuRp1YAlm2c7XOLjFMLJfFuXCHg+hCWI4Gl+xD1N2b9LarxMuoGp8cUurmJJZWSmc2FS1wT6cBg4+zbTyGEgrGqehW9nC+TQKYUO7Ym7btL0SKJZmiTenszP2vjz8Bryh+CguiAaY+t/qcSfv/cYitZeiec8n1UxkVohAoGAa8YwnzkEGBgz0TpdrGFxVBCqMIriMH4PQs2tWZ1nEBk6A7GrODRcRL0FQnRpHyf3mY5N/iEiNQ5GhQ5p1lifTvolTkVkdHpTGFhVfnYegaEAOznigi7dWJPoYlloN36AROxirGJKRIm+Od2bsybM/dNPJfoTDBDPFWGBH/klEBs=")
.setCharset("UTF-8")
.setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuPkP2VJMR6vWCX8RwSFqNIa3klCdvRFJbuS1PN1anzQeeL9eOwtU7kGdI85yxb0dcdPzOYlG+jf9go8W9hBlgjxSRoXxLx03Yfl7cLmzJO9l9vIM1+HmNF0Ctm+el4Yi9dGs/P6q7lcHPUqs/RXGfeLrg33GMVwJbLmRcDZYeIcqPAA1OVF/4SHYr+f+O7glDOd60z+veOOexyoHmvUzYWlEz5+R4kOCNM/Z0w7KGgEYvHbZopexuTuFgUWy/9tYlNrnX+cZUWXVTskLUgD1UGWM1dS5+qfriqY9MPEwJjetcPJkoCK7A4IReE4q1DffUY9KS50/1ML+7na3R/p/UQIDAQAB")
.setServiceUrl("https://openapi.alipaydev.com/gateway.do")
.setSignType("RSA2")
.build();
AliPayApiConfigKit.putApiConfig(rsa2);
List<PayChannel> channelList = new ArrayList<>();
tenantService.list(SecurityConstants.FROM_IN).getData()
.forEach(tenant -> {
TenantContextHolder.setTenantId(tenant.getId());
List<PayChannel> payChannelList = channelService
.list(Wrappers.<PayChannel>lambdaQuery()
.eq(PayChannel::getState, CommonConstants.STATUS_NORMAL));
channelList.addAll(payChannelList);
});
channelList.forEach(channel -> {
JSONObject params = JSONUtil.parseObj(channel.getParam());
//支付宝支付
if (PayChannelNameEnum.ALIPAY_WAP.getName().equals(channel.getChannelId())) {
AliPayApiConfig aliPayApiConfig = AliPayApiConfig.New()
.setAppId(channel.getAppId())
.setPrivateKey(params.getStr("privateKey"))
.setCharset(CharsetUtil.UTF_8)
.setAlipayPublicKey(params.getStr("publicKey"))
.setServiceUrl(params.getStr("serviceUrl"))
.setSignType("RSA2")
.build();
AliPayApiConfigKit.putApiConfig(aliPayApiConfig);
}
// 微信支付
if (PayChannelNameEnum.WEIXIN_MP.getName().equals(channel.getChannelId())) {
WxPayApiConfig wx = WxPayApiConfig.New()
.setAppId(channel.getAppId())
.setMchId(channel.getChannelMchId())
.setPaternerKey(params.getStr("paternerKey"))
.setPayModel(WxPayApiConfig.PayModel.BUSINESSMODEL);
String subMchId = params.getStr("subMchId");
if (StrUtil.isNotBlank(subMchId)) {
wx.setPayModel(WxPayApiConfig.PayModel.SERVICEMODE);
wx.setSubMchId(subMchId);
}
WxPayApiConfigKit.putApiConfig(wx);
WxMpService wxMpService = new WxMpServiceImpl();
WxMpInMemoryConfigStorage storage = new WxMpInMemoryConfigStorage();
storage.setAppId(channel.getAppId());
storage.setSecret(params.getStr("secret"));
storage.setToken(params.getStr("token"));
wxMpService.setWxMpConfigStorage(storage);
mpServiceMap.put(channel.getAppId(), wxMpService);
}
});
}
}
@@ -0,0 +1,20 @@
package com.pig4cloud.pigx.pay.config;
import lombok.Data;
/**
* @author lengleng
* @date 2019-06-19
*/
@Data
public class WxPayConfig {
/**
* 服务端回调地址
*/
private String notifyUrl;
/**
* 订单过期时间
*/
private String expireTime;
}
@@ -21,6 +21,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.log.annotation.SysLog;
import com.pig4cloud.pigx.pay.config.PayConfigParmaInitRunner;
import com.pig4cloud.pigx.pay.entity.PayChannel;
import com.pig4cloud.pigx.pay.service.PayChannelService;
import io.swagger.annotations.Api;
@@ -40,6 +41,7 @@ import org.springframework.web.bind.annotation.*;
@RequestMapping("/paychannel")
@Api(value = "paychannel", tags = "paychannel管理")
public class PayChannelController {
private final PayConfigParmaInitRunner parmaInitRunner;
private final PayChannelService payChannelService;
/**
@@ -76,7 +78,9 @@ public class PayChannelController {
@PostMapping
@PreAuthorize("@pms.hasPermission('pay_paychannel_add')")
public R save(@RequestBody PayChannel payChannel) {
return R.ok(payChannelService.saveChannel(payChannel));
payChannelService.saveChannel(payChannel);
parmaInitRunner.initPayConfig();
return R.ok();
}
/**
@@ -89,7 +93,9 @@ public class PayChannelController {
@PutMapping
@PreAuthorize("@pms.hasPermission('pay_paychannel_edit')")
public R updateById(@RequestBody PayChannel payChannel) {
return R.ok(payChannelService.updateById(payChannel));
payChannelService.updateById(payChannel);
parmaInitRunner.initPayConfig();
return R.ok();
}
/**
@@ -102,7 +108,9 @@ public class PayChannelController {
@DeleteMapping("/{id}")
@PreAuthorize("@pms.hasPermission('pay_paychannel_del')")
public R removeById(@PathVariable Integer id) {
return R.ok(payChannelService.removeById(id));
payChannelService.removeById(id);
parmaInitRunner.initPayConfig();
return R.ok();
}
}
@@ -17,17 +17,27 @@
package com.pig4cloud.pigx.pay.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jpay.weixin.api.WxPayApiConfigKit;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.log.annotation.SysLog;
import com.pig4cloud.pigx.common.security.annotation.Inner;
import com.pig4cloud.pigx.pay.config.PayConfigParmaInitRunner;
import com.pig4cloud.pigx.pay.entity.PayGoodsOrder;
import com.pig4cloud.pigx.pay.service.PayGoodsOrderService;
import com.pig4cloud.pigx.pay.utils.PayConstants;
import io.swagger.annotations.Api;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
@@ -38,7 +48,7 @@ import javax.servlet.http.HttpServletRequest;
* @author lengleng
* @date 2019-05-28 23:58:27
*/
@RestController
@Controller
@AllArgsConstructor
@RequestMapping("/goods")
@Api(value = "goods", tags = "商品订单管理")
@@ -50,17 +60,112 @@ public class PayGoodsOrderController {
/**
* 商品订单
*
* @param amount 金额
* @param goods 商品
* @param modelAndView
* @return R
*/
@Inner(false)
@GetMapping("/buy")
@SysLog("新增商品订单")
public R save(Long amount, HttpServletRequest request) {
@SysLog("购买商品")
public ModelAndView buy(PayGoodsOrder goods, HttpServletRequest request, ModelAndView modelAndView) {
String ua = request.getHeader(HttpHeaders.USER_AGENT);
if (StrUtil.isBlank(ua)) {
return R.failed("终端不合法");
if (ua.contains(PayConstants.MICRO_MESSENGER)) {
String appId = WxPayApiConfigKit.getWxPayApiConfig().getAppId();
modelAndView.setViewName("redirect:https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appId +
"&redirect_uri=http%3a%2f%2fpigx.huaxiadaowei.com%2fpay%2fgoods%2fwx%3famount%3d" + goods.getAmount() +
"&response_type=code&scope=snsapi_base&state=" + appId);
return modelAndView;
}
return payGoodsOrderService.buy(amount);
modelAndView.setViewName("pay");
modelAndView.addAllObjects(payGoodsOrderService.buy(goods));
return modelAndView;
}
/**
* oauth
*
* @param goods 商品信息
* @param state appid
* @param code 回调code
* @param modelAndView
* @return
* @throws WxErrorException
*/
@Inner(false)
@SneakyThrows
@GetMapping("/wx")
public ModelAndView wx(PayGoodsOrder goods, String state, String code, ModelAndView modelAndView) {
WxMpService wxMpService = PayConfigParmaInitRunner.mpServiceMap.get(state);
WxMpOAuth2AccessToken wxMpOAuth2AccessToken = wxMpService.oauth2getAccessToken(code);
goods.setUserId(wxMpOAuth2AccessToken.getOpenId());
goods.setAmount(goods.getAmount());
modelAndView.setViewName("pay");
modelAndView.addAllObjects(payGoodsOrderService.buy(goods));
return modelAndView;
}
/**
* 分页查询
*
* @param page 分页对象
* @param payGoodsOrder 商品订单表
* @return
*/
@GetMapping("/page")
public R getPayGoodsOrderPage(Page page, PayGoodsOrder payGoodsOrder) {
return R.ok(payGoodsOrderService.page(page, Wrappers.query(payGoodsOrder)));
}
/**
* 通过id查询商品订单表
*
* @param goodsOrderId id
* @return R
*/
@GetMapping("/{goodsOrderId}")
public R getById(@PathVariable("goodsOrderId") Integer goodsOrderId) {
return R.ok(payGoodsOrderService.getById(goodsOrderId));
}
/**
* 新增商品订单表
*
* @param payGoodsOrder 商品订单表
* @return R
*/
@SysLog("新增商品订单表")
@PostMapping
@PreAuthorize("@pms.hasPermission('generator_paygoodsorder_add')")
public R save(@RequestBody PayGoodsOrder payGoodsOrder) {
return R.ok(payGoodsOrderService.save(payGoodsOrder));
}
/**
* 修改商品订单表
*
* @param payGoodsOrder 商品订单表
* @return R
*/
@SysLog("修改商品订单表")
@PutMapping
@PreAuthorize("@pms.hasPermission('generator_paygoodsorder_edit')")
public R updateById(@RequestBody PayGoodsOrder payGoodsOrder) {
return R.ok(payGoodsOrderService.updateById(payGoodsOrder));
}
/**
* 通过id删除商品订单表
*
* @param goodsOrderId id
* @return R
*/
@SysLog("删除商品订单表")
@DeleteMapping("/{goodsOrderId}")
@PreAuthorize("@pms.hasPermission('generator_paygoodsorder_del')")
public R removeById(@PathVariable Integer goodsOrderId) {
return R.ok(payGoodsOrderService.removeById(goodsOrderId));
}
}
@@ -19,17 +19,27 @@ package com.pig4cloud.pigx.pay.controller;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jpay.alipay.AliPayApi;
import com.jpay.ext.kit.HttpKit;
import com.jpay.ext.kit.PaymentKit;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.log.annotation.SysLog;
import com.pig4cloud.pigx.common.security.annotation.Inner;
import com.pig4cloud.pigx.pay.entity.PayNotifyRecord;
import com.pig4cloud.pigx.pay.handler.MessageDuplicateCheckerHandler;
import com.pig4cloud.pigx.pay.service.PayNotifyRecordService;
import org.springframework.security.access.prepost.PreAuthorize;
import com.pig4cloud.pigx.pay.utils.PayConstants;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* 异步通知记录
@@ -37,70 +47,120 @@ import org.springframework.web.bind.annotation.*;
* @author lengleng
* @date 2019-05-28 23:57:23
*/
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/paynotifyrecord" )
@Api(value = "paynotifyrecord", tags = "paynotifyrecord管理")
@RequestMapping("/notify")
@Api(value = "notify", tags = "notify管理")
public class PayNotifyRecordController {
private final MessageDuplicateCheckerHandler duplicateCheckerHandler;
private final PayNotifyRecordService payNotifyRecordService;
private final PayNotifyRecordService payNotifyRecordService;
/**
* 分页查询
* @param page 分页对象
* @param payNotifyRecord 异步通知记录
* @return
*/
@GetMapping("/page" )
public R getPayNotifyRecordPage(Page page, PayNotifyRecord payNotifyRecord) {
return R.ok(payNotifyRecordService.page(page, Wrappers.query(payNotifyRecord)));
}
/**
* 分页查询
*
* @param page 分页对象
* @param payNotifyRecord 异步通知记录
* @return
*/
@GetMapping("/page")
public R getPayNotifyRecordPage(Page page, PayNotifyRecord payNotifyRecord) {
return R.ok(payNotifyRecordService.page(page, Wrappers.query(payNotifyRecord)));
}
/**
* 通过id查询异步通知记录
* @param id id
* @return R
*/
@GetMapping("/{id}" )
public R getById(@PathVariable("id" ) Long id) {
return R.ok(payNotifyRecordService.getById(id));
}
/**
* 通过id查询异步通知记录
*
* @param id id
* @return R
*/
@GetMapping("/{id}")
public R getById(@PathVariable("id") Long id) {
return R.ok(payNotifyRecordService.getById(id));
}
/**
* 新增异步通知记录
* @param payNotifyRecord 异步通知记录
* @return R
*/
@SysLog("新增异步通知记录" )
@PostMapping
@PreAuthorize("@pms.hasPermission('pay_paynotifyrecord_add')" )
public R save(@RequestBody PayNotifyRecord payNotifyRecord) {
return R.ok(payNotifyRecordService.save(payNotifyRecord));
}
/**
* 新增异步通知记录
*
* @param payNotifyRecord 异步通知记录
* @return R
*/
@SysLog("新增异步通知记录")
@PostMapping
@PreAuthorize("@pms.hasPermission('pay_paynotifyrecord_add')")
public R save(@RequestBody PayNotifyRecord payNotifyRecord) {
return R.ok(payNotifyRecordService.save(payNotifyRecord));
}
/**
* 修改异步通知记录
* @param payNotifyRecord 异步通知记录
* @return R
*/
@SysLog("修改异步通知记录" )
@PutMapping
@PreAuthorize("@pms.hasPermission('pay_paynotifyrecord_edit')" )
public R updateById(@RequestBody PayNotifyRecord payNotifyRecord) {
return R.ok(payNotifyRecordService.updateById(payNotifyRecord));
}
/**
* 修改异步通知记录
*
* @param payNotifyRecord 异步通知记录
* @return R
*/
@SysLog("修改异步通知记录")
@PutMapping
@PreAuthorize("@pms.hasPermission('pay_paynotifyrecord_edit')")
public R updateById(@RequestBody PayNotifyRecord payNotifyRecord) {
return R.ok(payNotifyRecordService.updateById(payNotifyRecord));
}
/**
* 通过id删除异步通知记录
* @param id id
* @return R
*/
@SysLog("删除异步通知记录" )
@DeleteMapping("/{id}" )
@PreAuthorize("@pms.hasPermission('pay_paynotifyrecord_del')" )
public R removeById(@PathVariable Long id) {
return R.ok(payNotifyRecordService.removeById(id));
}
/**
* 通过id删除异步通知记录
*
* @param id id
* @return R
*/
@SysLog("删除异步通知记录")
@DeleteMapping("/{id}")
@PreAuthorize("@pms.hasPermission('pay_paynotifyrecord_del')")
public R removeById(@PathVariable Long id) {
return R.ok(payNotifyRecordService.removeById(id));
}
/**
* 渠道异步回调
*
* @param request 渠道请求
* @return
*/
@Inner(false)
@SneakyThrows
@PostMapping("/ali/callbak")
public void aliCallbak(HttpServletRequest request, HttpServletResponse response) {
// 解析回调信息
Map<String, String> params = AliPayApi.toMap(request);
// 判断10秒内是否已经回调处理
if (!duplicateCheckerHandler.isDuplicate(params.get(PayConstants.OUT_TRADE_NO))) {
log.warn("支付宝订单重复回调 {} 不做处理", params);
payNotifyRecordService.saveAliRecord(params, "");
return;
}
String result = payNotifyRecordService.aliCallbak(params);
payNotifyRecordService.saveAliRecord(params, result);
//写出处理结果
response.getWriter().print(result);
}
@Inner(false)
@SneakyThrows
@ResponseBody
@PostMapping("/wx/callbak")
public String wxCallbak(HttpServletRequest request, HttpServletResponse response) {
String xmlMsg = HttpKit.readData(request);
log.info("微信订单回调信息:{}", xmlMsg);
Map<String, String> params = PaymentKit.xmlToMap(xmlMsg);
// 判断10秒内是否已经回调处理
if (!duplicateCheckerHandler.isDuplicate(params.get(PayConstants.OUT_TRADE_NO))) {
log.warn("微信订单重复回调 {} 不做处理", params);
payNotifyRecordService.saveAliRecord(params, "");
return null;
}
String result = payNotifyRecordService.wxCallbak(params);
payNotifyRecordService.saveAliRecord(params, result);
return result;
}
}
@@ -23,11 +23,9 @@ import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.log.annotation.SysLog;
import com.pig4cloud.pigx.pay.entity.PayTradeOrder;
import com.pig4cloud.pigx.pay.service.PayTradeOrderService;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@@ -39,8 +37,8 @@ import org.springframework.web.bind.annotation.*;
*/
@RestController
@AllArgsConstructor
@RequestMapping("/paytradeorder" )
@Api(value = "paytradeorder", tags = "paytradeorder管理")
@RequestMapping("/order" )
@Api(value = "order", tags = "订单")
public class PayTradeOrderController {
private final PayTradeOrderService payTradeOrderService;
@@ -45,6 +45,10 @@ public class PayChannel extends Model<PayChannel> {
*/
@TableId
private Integer id;
/**
* 应用ID
*/
private String appId;
/**
* 渠道ID
*/
@@ -57,10 +61,6 @@ public class PayChannel extends Model<PayChannel> {
* 渠道商户ID
*/
private String channelMchId;
/**
* 内部商户ID
*/
private String mchId;
/**
* 渠道状态
*/
@@ -56,7 +56,7 @@ public class PayGoodsOrder extends Model<PayGoodsOrder> {
/**
* 金额,单位分
*/
private Long amount;
private String amount;
/**
* 用户ID
*/
@@ -66,9 +66,9 @@ public class PayRefundOrder extends Model<PayRefundOrder> {
*/
private String channelId;
/**
* 支付金额,单位分
* 支付金额,单位元
*/
private Long payAmount;
private String payAmount;
/**
* 退款金额,单位分
*/
@@ -51,9 +51,9 @@ public class PayTradeOrder extends Model<PayTradeOrder> {
*/
private String channelId;
/**
* 支付金额,单位分
* 支付金额,单位元
*/
private Long amount;
private String amount;
/**
* 三位货币代码,人民币:cny
*/
@@ -3,7 +3,7 @@ package com.pig4cloud.pigx.pay.handler;
import com.pig4cloud.common.sequence.sequence.Sequence;
import com.pig4cloud.pigx.pay.entity.PayGoodsOrder;
import com.pig4cloud.pigx.pay.entity.PayTradeOrder;
import com.pig4cloud.pigx.pay.service.PayGoodsOrderService;
import com.pig4cloud.pigx.pay.mapper.PayGoodsOrderMapper;
import com.pig4cloud.pigx.pay.utils.OrderStatusEnum;
import org.springframework.beans.factory.annotation.Autowired;
@@ -13,7 +13,7 @@ import org.springframework.beans.factory.annotation.Autowired;
*/
public abstract class AbstractPayOrderHandler implements PayOrderHandler {
@Autowired
private PayGoodsOrderService goodsOrderService;
private PayGoodsOrderMapper goodsOrderMapper;
@Autowired
private Sequence paySequence;
@@ -28,17 +28,20 @@ public abstract class AbstractPayOrderHandler implements PayOrderHandler {
public void createGoodsOrder(PayGoodsOrder goodsOrder) {
goodsOrder.setPayOrderId(paySequence.nextNo());
goodsOrder.setStatus(OrderStatusEnum.INIT.getStatus());
goodsOrderService.save(goodsOrder);
goodsOrderMapper.insert(goodsOrder);
}
/**
* 调用入口
*
* @return
*/
@Override
public void handle(PayGoodsOrder payGoodsOrder) {
public Object handle(PayGoodsOrder payGoodsOrder) {
createGoodsOrder(payGoodsOrder);
PayTradeOrder tradeOrder = createTradeOrder(payGoodsOrder);
pay(payGoodsOrder, tradeOrder);
Object result = pay(payGoodsOrder, tradeOrder);
updateOrder(payGoodsOrder, tradeOrder);
return result;
}
}
@@ -1,15 +1,20 @@
package com.pig4cloud.pigx.pay.handler;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.jpay.alipay.AliPayApi;
import com.jpay.alipay.AliPayApiConfigKit;
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
import com.pig4cloud.pigx.pay.config.PayCommonProperties;
import com.pig4cloud.pigx.pay.entity.PayGoodsOrder;
import com.pig4cloud.pigx.pay.entity.PayTradeOrder;
import com.pig4cloud.pigx.pay.service.PayGoodsOrderService;
import com.pig4cloud.pigx.pay.service.PayTradeOrderService;
import com.pig4cloud.pigx.pay.mapper.PayGoodsOrderMapper;
import com.pig4cloud.pigx.pay.mapper.PayTradeOrderMapper;
import com.pig4cloud.pigx.pay.utils.OrderStatusEnum;
import com.pig4cloud.pigx.pay.utils.PayChannelNameEnum;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -28,8 +33,9 @@ import java.io.IOException;
@Service("ALIPAY_WAP")
@AllArgsConstructor
public class AlipayWapPayOrderHandler extends AbstractPayOrderHandler {
private final PayTradeOrderService tradeOrderService;
private final PayGoodsOrderService goodsOrderService;
private final PayCommonProperties payCommonProperties;
private final PayTradeOrderMapper tradeOrderMapper;
private final PayGoodsOrderMapper goodsOrderMapper;
private final HttpServletRequest request;
private final HttpServletResponse response;
@@ -44,14 +50,14 @@ public class AlipayWapPayOrderHandler extends AbstractPayOrderHandler {
PayTradeOrder tradeOrder = new PayTradeOrder();
tradeOrder.setOrderId(goodsOrder.getPayOrderId());
tradeOrder.setAmount(goodsOrder.getAmount());
tradeOrder.setChannelId("ALIPAY_WAP");
tradeOrder.setChannelMchId("1517584931");
tradeOrder.setChannelId(PayChannelNameEnum.ALIPAY_WAP.getName());
tradeOrder.setChannelMchId(AliPayApiConfigKit.getAliPayApiConfig().getAppId());
tradeOrder.setClientIp(ServletUtil.getClientIP(request));
tradeOrder.setCurrency("cny");
tradeOrder.setExpireTime(30L);
tradeOrder.setExpireTime(Long.parseLong(payCommonProperties.getAliPayConfig().getReturnUrl()));
tradeOrder.setStatus(OrderStatusEnum.INIT.getStatus());
tradeOrder.setBody("测试商品");
tradeOrderService.save(tradeOrder);
tradeOrder.setBody(goodsOrder.getGoodsName());
tradeOrderMapper.insert(tradeOrder);
return tradeOrder;
}
@@ -67,12 +73,15 @@ public class AlipayWapPayOrderHandler extends AbstractPayOrderHandler {
model.setBody(tradeOrder.getBody());
model.setSubject(tradeOrder.getBody());
model.setOutTradeNo(tradeOrder.getOrderId());
model.setTimeoutExpress(tradeOrder.getExpireTime() + "m");
model.setTotalAmount(String.valueOf(tradeOrder.getAmount()));
model.setPassbackParams(String.valueOf(TenantContextHolder.getTenantId()));
model.setTimeoutExpress(payCommonProperties.getAliPayConfig().getExpireTime() + "m");
//分转成元 并且保留两位
model.setTotalAmount(NumberUtil.div(tradeOrder.getAmount(), "100", 2).toString());
model.setProductCode(goodsOrder.getGoodsId());
model.setPassbackParams(URLUtil.encode(String.format("tenant=%s", TenantContextHolder.getTenantId())));
try {
AliPayApi.wapPay(response, model, "http://www.baidu.com", "/alipay/notify_url");
AliPayApi.wapPay(response, model, payCommonProperties.getAliPayConfig().getReturnUrl()
, payCommonProperties.getAliPayConfig().getNotifyUrl());
} catch (AlipayApiException e) {
log.warn("支付宝支付失败 {} {}", e.getErrCode(), e.getErrMsg());
tradeOrder.setErrMsg(e.getErrMsg());
@@ -96,7 +105,7 @@ public class AlipayWapPayOrderHandler extends AbstractPayOrderHandler {
*/
@Override
public void updateOrder(PayGoodsOrder goodsOrder, PayTradeOrder tradeOrder) {
tradeOrderService.updateById(tradeOrder);
goodsOrderService.updateById(goodsOrder);
tradeOrderMapper.updateById(tradeOrder);
goodsOrderMapper.updateById(goodsOrder);
}
}
@@ -0,0 +1,17 @@
package com.pig4cloud.pigx.pay.handler;
/**
* @author lengleng
* @date 2019-06-14
* <p>
* 消息去重
*/
public interface MessageDuplicateCheckerHandler {
/**
* 判断回调消息是否重复.
*
* @param messageId messageId需要根据上面讲的方式构造
* @return 如果是重复消息,返回false,否则返回true
*/
boolean isDuplicate(String messageId);
}
@@ -0,0 +1,31 @@
package com.pig4cloud.pigx.pay.handler;
import lombok.AllArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.time.Duration;
/**
* @author lengleng
* @date 2019-06-14
* <p>
* 消息去重
*/
@Service
@AllArgsConstructor
public class MessageRedisDuplicateCheckerHandler implements MessageDuplicateCheckerHandler {
private final RedisTemplate redisTemplate;
/**
* 判断回调消息是否重复.
*
* @param messageId messageId需要根据上面讲的方式构造
* @return 如果是重复消息,返回false,否则返回true
*/
@Override
public boolean isDuplicate(String messageId) {
return redisTemplate.opsForValue()
.setIfAbsent(messageId, messageId, Duration.ofSeconds(10L));
}
}
@@ -33,7 +33,7 @@ public interface PayOrderHandler {
* @param goodsOrder 商品订单
* @param tradeOrder 交易订单
*/
PayTradeOrder pay(PayGoodsOrder goodsOrder, PayTradeOrder tradeOrder);
Object pay(PayGoodsOrder goodsOrder, PayTradeOrder tradeOrder);
/**
* 更新订单信息
@@ -44,6 +44,7 @@ public interface PayOrderHandler {
* 调用入口
*
* @param goodsOrde 商品订单
* @return
*/
void handle(PayGoodsOrder goodsOrde);
Object handle(PayGoodsOrder goodsOrde);
}
@@ -0,0 +1,98 @@
package com.pig4cloud.pigx.pay.handler;
import cn.hutool.extra.servlet.ServletUtil;
import com.jpay.ext.kit.PaymentKit;
import com.jpay.weixin.api.WxPayApi;
import com.jpay.weixin.api.WxPayApiConfigKit;
import com.pig4cloud.pigx.pay.config.PayCommonProperties;
import com.pig4cloud.pigx.pay.entity.PayGoodsOrder;
import com.pig4cloud.pigx.pay.entity.PayTradeOrder;
import com.pig4cloud.pigx.pay.mapper.PayGoodsOrderMapper;
import com.pig4cloud.pigx.pay.mapper.PayTradeOrderMapper;
import com.pig4cloud.pigx.pay.utils.OrderStatusEnum;
import com.pig4cloud.pigx.pay.utils.PayChannelNameEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* @author lengleng
* @date 2019-05-31
* <p>
* 微信公众号支付
*/
@Slf4j
@Service("WEIXIN_MP")
public class WeChatMpPayOrderHandler extends AbstractPayOrderHandler {
@Autowired
private PayCommonProperties payCommonProperties;
@Autowired
private PayTradeOrderMapper tradeOrderMapper;
@Autowired
private PayGoodsOrderMapper goodsOrderMapper;
@Autowired
private HttpServletRequest request;
/**
* 创建交易订单
*
* @param goodsOrder
* @return
*/
@Override
public PayTradeOrder createTradeOrder(PayGoodsOrder goodsOrder) {
PayTradeOrder tradeOrder = new PayTradeOrder();
tradeOrder.setOrderId(goodsOrder.getPayOrderId());
tradeOrder.setAmount(goodsOrder.getAmount());
tradeOrder.setChannelId(PayChannelNameEnum.WEIXIN_MP.getName());
tradeOrder.setChannelMchId(WxPayApiConfigKit.getWxPayApiConfig().getMchId());
tradeOrder.setClientIp(ServletUtil.getClientIP(request));
tradeOrder.setCurrency("CNY");
tradeOrder.setStatus(OrderStatusEnum.INIT.getStatus());
tradeOrder.setBody(goodsOrder.getGoodsName());
tradeOrderMapper.insert(tradeOrder);
return tradeOrder;
}
/**
* 调起渠道支付
*
* @param goodsOrder 商品订单
* @param tradeOrder 交易订单
*/
@Override
public Object pay(PayGoodsOrder goodsOrder, PayTradeOrder tradeOrder) {
String ip = ServletUtil.getClientIP(request);
Map<String, String> params = WxPayApiConfigKit.getWxPayApiConfig()
.setAttach(goodsOrder.getGoodsId())
.setBody(goodsOrder.getGoodsName())
.setSpbillCreateIp(ip)
.setTotalFee(goodsOrder.getAmount())
.setOpenId(goodsOrder.getUserId())
.setTradeType(WxPayApi.TradeType.JSAPI)
.setNotifyUrl(payCommonProperties.getWxPayConfig().getNotifyUrl())
.setOutTradeNo(tradeOrder.getOrderId())
.build();
String xmlResult = WxPayApi.pushOrder(false, params);
log.info(xmlResult);
Map<String, String> resultMap = PaymentKit.xmlToMap(xmlResult);
String prepayId = resultMap.get("prepay_id");
return PaymentKit.prepayIdCreateSign(prepayId);
}
/**
* 更新订单信息
*
* @param goodsOrder 商品订单
* @param tradeOrder 交易订单
*/
@Override
public void updateOrder(PayGoodsOrder goodsOrder, PayTradeOrder tradeOrder) {
tradeOrderMapper.updateById(tradeOrder);
goodsOrderMapper.updateById(goodsOrder);
}
}
@@ -18,9 +18,10 @@
package com.pig4cloud.pigx.pay.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.pay.entity.PayGoodsOrder;
import java.util.Map;
/**
* 商品
*
@@ -32,8 +33,15 @@ public interface PayGoodsOrderService extends IService<PayGoodsOrder> {
/**
* 购买商品
*
* @param amount
* @param goodsOrder goods
* @return
*/
R buy(Long amount);
Map<String, Object> buy(PayGoodsOrder goodsOrder);
/**
* 更新订单
*
* @param params
*/
void updateOrder(Map<String, String> params);
}
@@ -20,6 +20,8 @@ package com.pig4cloud.pigx.pay.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.pig4cloud.pigx.pay.entity.PayNotifyRecord;
import java.util.Map;
/**
* 异步通知记录
*
@@ -28,4 +30,27 @@ import com.pig4cloud.pigx.pay.entity.PayNotifyRecord;
*/
public interface PayNotifyRecordService extends IService<PayNotifyRecord> {
/**
* 处理支付宝渠道回调
*
* @param params
* @return
*/
String aliCallbak(Map<String, String> params);
/**
* 保存支付宝回调结果
*
* @param params
* @param result
*/
void saveAliRecord(Map<String, String> params, String result);
/**
* 处理微信渠道回调
*
* @param params
* @return
*/
String wxCallbak(Map<String, String> params);
}
@@ -20,6 +20,8 @@ package com.pig4cloud.pigx.pay.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.pig4cloud.pigx.pay.entity.PayTradeOrder;
import java.util.Map;
/**
* 支付
*
@@ -28,4 +30,10 @@ import com.pig4cloud.pigx.pay.entity.PayTradeOrder;
*/
public interface PayTradeOrderService extends IService<PayTradeOrder> {
/**
* 更新支付订单
*
* @param params
*/
void updateOrder(Map<String, String> params);
}
@@ -17,7 +17,6 @@
package com.pig4cloud.pigx.pay.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pig4cloud.common.sequence.sequence.Sequence;
import com.pig4cloud.pigx.pay.entity.PayChannel;
import com.pig4cloud.pigx.pay.mapper.PayChannelMapper;
import com.pig4cloud.pigx.pay.service.PayChannelService;
@@ -33,7 +32,6 @@ import org.springframework.stereotype.Service;
@Service
@AllArgsConstructor
public class PayChannelServiceImpl extends ServiceImpl<PayChannelMapper, PayChannel> implements PayChannelService {
private final Sequence channelSequence;
/**
* 新增支付渠道
@@ -43,7 +41,6 @@ public class PayChannelServiceImpl extends ServiceImpl<PayChannelMapper, PayChan
*/
@Override
public Boolean saveChannel(PayChannel payChannel) {
payChannel.setMchId(channelSequence.nextNo());
return save(payChannel);
}
}
@@ -16,20 +16,26 @@
*/
package com.pig4cloud.pigx.pay.service.impl;
import cn.hutool.core.util.EnumUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pig4cloud.common.sequence.sequence.Sequence;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.pay.entity.PayGoodsOrder;
import com.pig4cloud.pigx.pay.handler.PayOrderHandler;
import com.pig4cloud.pigx.pay.mapper.PayGoodsOrderMapper;
import com.pig4cloud.pigx.pay.service.PayGoodsOrderService;
import com.pig4cloud.pigx.pay.service.PayTradeOrderService;
import com.pig4cloud.pigx.pay.utils.PayChannelNameEnum;
import com.pig4cloud.pigx.pay.utils.PayConstants;
import com.pig4cloud.pigx.pay.utils.TradeStatusEnum;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* 商品
@@ -41,27 +47,49 @@ import javax.servlet.http.HttpServletResponse;
@Service
@AllArgsConstructor
public class PayGoodsOrderServiceImpl extends ServiceImpl<PayGoodsOrderMapper, PayGoodsOrder> implements PayGoodsOrderService {
private final PayTradeOrderService tradeOrderService;
private static final String WX = "MicroMessenger";
private final Map<String, PayOrderHandler> orderHandlerMap;
private final HttpServletRequest request;
private final HttpServletResponse response;
private final Sequence paySequence;
/**
* 购买商品
* <p>
* 1、 新增商品订单
* 2、 根据UA 去下单
* 3、构建支付订单
* 下单购买
*
* @param amount
* @param goodsOrder
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public R buy(Long amount) {
return null;
public Map<String, Object> buy(PayGoodsOrder goodsOrder) {
String ua = request.getHeader(HttpHeaders.USER_AGENT);
Enum channel = PayChannelNameEnum.getChannel(ua);
PayOrderHandler orderHandler = orderHandlerMap.get(channel.name());
goodsOrder.setGoodsName("测试产品");
goodsOrder.setGoodsId("10001");
Object params = orderHandler.handle(goodsOrder);
Map<String, Object> result = new HashMap<>(4);
result.put("channel", channel.name());
result.put("goods", goodsOrder);
result.put("params", params);
return result;
}
/**
* 异步更新商品订单
*
* @param params
*/
@Async
@Override
@Transactional(rollbackFor = Exception.class)
public void updateOrder(Map<String, String> params) {
PayGoodsOrder goodsOrder = new PayGoodsOrder();
Integer tradeStatus = EnumUtil.fromString(TradeStatusEnum.class, params.get("trade_status")).getStatus();
goodsOrder.setStatus(tradeStatus);
this.baseMapper.update(goodsOrder, Wrappers.<PayGoodsOrder>lambdaQuery()
.eq(PayGoodsOrder::getPayOrderId, params.
get(PayConstants.OUT_TRADE_NO)));
}
}
@@ -16,19 +16,126 @@
*/
package com.pig4cloud.pigx.pay.service.impl;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jpay.alipay.AliPayApiConfig;
import com.jpay.alipay.AliPayApiConfigKit;
import com.jpay.ext.kit.PaymentKit;
import com.jpay.weixin.api.WxPayApiConfigKit;
import com.pig4cloud.pigx.pay.entity.PayNotifyRecord;
import com.pig4cloud.pigx.pay.mapper.PayNotifyRecordMapper;
import com.pig4cloud.pigx.pay.service.PayGoodsOrderService;
import com.pig4cloud.pigx.pay.service.PayNotifyRecordService;
import com.pig4cloud.pigx.pay.service.PayTradeOrderService;
import com.pig4cloud.pigx.pay.utils.TradeStatusEnum;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* 异步通知记录
*
* @author lengleng
* @date 2019-05-28 23:57:23
*/
@Slf4j
@Service
@AllArgsConstructor
public class PayNotifyRecordServiceImpl extends ServiceImpl<PayNotifyRecordMapper, PayNotifyRecord> implements PayNotifyRecordService {
private final PayTradeOrderService tradeOrderService;
private final PayGoodsOrderService goodsOrderService;
/**
* 处理微信渠道回调
*
* @param params
* @return
*/
@Override
public String wxCallbak(Map<String, String> params) {
if (!PaymentKit.verifyNotify(params, WxPayApiConfigKit.getWxPayApiConfig().getPaternerKey())) {
log.warn("微信支付回调验签失败", params);
return null;
}
params.put("trade_status", TradeStatusEnum.TRADE_SUCCESS.getDescription());
goodsOrderService.updateOrder(params);
tradeOrderService.updateOrder(params);
Map<String, String> xml = new HashMap<>(4);
xml.put("return_code", "SUCCESS");
xml.put("return_msg", "OK");
return PaymentKit.toXml(xml);
}
/**
* 处理渠道回调
*
* @param params
* @return
*/
@Override
public String aliCallbak(Map<String, String> params) {
String callReq = MapUtil.join(params, StrUtil.DASHED, StrUtil.DASHED);
log.info("支付宝发起回调 报文: {}", callReq);
String appId = params.get("app_id");
if (StrUtil.isBlank(appId)) {
log.warn("支付宝回调报文 appid 为空 {}", callReq);
return "failure";
}
AliPayApiConfig apiConfig = AliPayApiConfigKit.getApiConfig(appId);
if (apiConfig == null) {
log.warn("支付宝回调报文 appid 不合法 {}", callReq);
return "failure";
}
boolean verifyResult;
try {
verifyResult = AlipaySignature.rsaCheckV1(params, apiConfig.getAlipayPublicKey(), CharsetUtil.UTF_8,
"RSA2");
} catch (AlipayApiException e) {
log.warn("验签异常 {} {}", e.getErrCode(), e.getErrMsg());
return "failure";
}
if (!verifyResult) {
log.warn("支付宝回调验签:{}", MapUtil.join(params, StrUtil.DASHED, StrUtil.DASHED));
return "failure";
}
goodsOrderService.updateOrder(params);
tradeOrderService.updateOrder(params);
return "success";
}
/**
* 保存支付宝回调结果
*
* @param params
* @param result
*/
@Async
@Override
public void saveAliRecord(Map<String, String> params, String result) {
PayNotifyRecord record = new PayNotifyRecord();
String notifyId = params.get("notify_id");
record.setNotifyId(notifyId);
String orderNo = params.get("out_trade_no");
record.setOrderNo(orderNo);
record.setRequest(MapUtil.join(params, StrUtil.DASHED, StrUtil.DASHED));
record.setResponse(result);
this.baseMapper.insert(record);
}
}
@@ -20,7 +20,13 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pig4cloud.pigx.pay.entity.PayTradeOrder;
import com.pig4cloud.pigx.pay.mapper.PayTradeOrderMapper;
import com.pig4cloud.pigx.pay.service.PayTradeOrderService;
import com.pig4cloud.pigx.pay.utils.PayConstants;
import com.pig4cloud.pigx.pay.utils.TradeStatusEnum;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Map;
/**
* 支付
@@ -31,4 +37,18 @@ import org.springframework.stereotype.Service;
@Service
public class PayTradeOrderServiceImpl extends ServiceImpl<PayTradeOrderMapper, PayTradeOrder> implements PayTradeOrderService {
/**
* 更新支付订单
*
* @param params
*/
@Async
@Override
@Transactional(rollbackFor = Exception.class)
public void updateOrder(Map<String, String> params) {
PayTradeOrder payTradeOrder = this.baseMapper.selectById(params.get(PayConstants.OUT_TRADE_NO));
payTradeOrder.setStatus(TradeStatusEnum.TRADE_SUCCESS.getStatus());
payTradeOrder.setChannelOrderNo(params.get("trade_no"));
this.baseMapper.updateById(payTradeOrder);
}
}
@@ -9,30 +9,46 @@ import lombok.Getter;
* <p>
* 支付渠道名称
*/
@Getter
@AllArgsConstructor
public enum PayChannelNameEnum {
/**
* 支付宝
* 支付宝wap支付
*/
ALIPAY("alipay", "支付宝"),
ALIPAY_WAP("ALIPAY_WAP", "支付宝手机支付"),
/**
* 微信支付
* 微信H5支付
*/
WXPAY("wxpay", "微信支付"),
WEIXIN_WAP("WEIXIN_WAP", "微信H5支付"),
/**
* 微信服务商支付
* 微信公众号支付
*/
WESUBPAY("wxsubpay", "微信服务商支付");
WEIXIN_MP("WEIXIN_MP", "微信公众号支付");
/**
* 名称
*/
@Getter
private String name;
/**
* 描述
*/
private String description;
/**
* 通过ua 判断所属渠道
*
* @param ua 浏览器类型
* @return
*/
public static Enum getChannel(String ua) {
if (ua.contains(PayConstants.ALIPAY)) {
return PayChannelNameEnum.ALIPAY_WAP;
} else if (ua.contains(PayConstants.MICRO_MESSENGER)) {
return PayChannelNameEnum.WEIXIN_MP;
} else {
return PayChannelNameEnum.WEIXIN_WAP;
}
}
}
@@ -0,0 +1,26 @@
package com.pig4cloud.pigx.pay.utils;
/**
* @author lengleng
* @date 2019-06-14
* <p>
* 支付相关的常量
*/
public interface PayConstants {
/**
* 支付宝商户交易编号
*/
String OUT_TRADE_NO = "out_trade_no";
/**
* 支付宝浏览器标志
*/
String ALIPAY = "Alipay";
/**
* 微信浏览器标志
*/
String MICRO_MESSENGER = "MicroMessenger";
}
@@ -0,0 +1,42 @@
package com.pig4cloud.pigx.pay.utils;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author lengleng
* @date 2019-06-14
*/
@Getter
@AllArgsConstructor
public enum TradeStatusEnum {
/**
* 交易完成
*/
WAIT_BUYER_PAY("TRADE_FINISHED", OrderStatusEnum.INIT.getStatus()),
/**
* TRADE_CLOSED
*/
TRADE_CLOSED("TRADE_CLOSED", OrderStatusEnum.FAIL.getStatus()),
/**
* TRADE_SUCCESS
*/
TRADE_SUCCESS("TRADE_SUCCESS", OrderStatusEnum.SUCCESS.getStatus()),
/**
* 交易关闭
*/
TRADE_FINISHED("TRADE_FINISHED", OrderStatusEnum.COMPLETE.getStatus());
/**
* 描述
*/
private String description;
/**
* 描述
*/
private Integer status;
}
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
小技巧: 在根pom里面设置统一存放路径,统一管理方便维护
<properties>
<log-path>/Users/lengleng</log-path>
</properties>
1. 其他模块加日志输出,直接copy本文件放在resources 目录即可
2. 注意修改 <property name="${log-path}/log.path" value=""/> 的value模块
-->
<configuration debug="false" scan="false">
<property name="log.path" value="logs/${project.artifactId}"/>
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- Console log output -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- Log file debug output -->
<appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
</encoder>
</appender>
<!-- Log file error output -->
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<logger name="org.activiti.engine.impl.db" level="DEBUG">
<appender-ref ref="debug"/>
</logger>
<!--nacos 心跳 INFO 屏蔽-->
<logger name="com.alibaba.nacos" level="OFF">
<appender-ref ref="error"/>
</logger>
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
<root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="debug"/>
</root>
</configuration>
@@ -25,10 +25,10 @@
<resultMap id="payChannelMap" type="com.pig4cloud.pigx.pay.entity.PayChannel">
<id property="id" column="id"/>
<result property="channelId" column="channel_id"/>
<result property="appId" column="app_id"/>
<result property="channelName" column="channel_name"/>
<result property="channelMchId" column="channel_mch_id"/>
<result property="mchId" column="mch_id"/>
<result property="channelId" column="channel_id"/>
<result property="state" column="state"/>
<result property="param" column="param"/>
<result property="remark" column="remark"/>
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<meta charset="UTF-8">
<title>Title</title>
</head>
<script src="//cdn.jsdelivr.net/jquery/1.12.1/jquery.min.js"></script>
<body>
<#if channel == 'WEIXIN_WAP'>
<a href="javascript:void(0)" onclick="pay()">确认购买</a>
</#if>
</body>
</html>
<#if channel == 'WEIXIN_MP'>
<script>
function onBridgeReady() {
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId": "${params.appId}", //公众号名称,由商户传入
"timeStamp": "${params.timeStamp}", //时间戳,自1970年以来的秒数
"nonceStr": "${params.nonceStr}", //随机串
"package": "${params.package}",
"signType": "${params.signType}", //微信签名方式:
"paySign": "${params.paySign}" //微信签名,paySign 采用统一的微信支付 Sign 签名生成方法,注意这里 appId 也要参与签名,appId 与 config 中传入的 appId 一致,即最后参与签名的参数有appId, timeStamp, nonceStr, package, signType。
},
function (res) {
if (res.err_msg == "get_brand_wcpay_request:ok") { // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
alert('支付成功!');
} else {
alert('支付失败:' + res.err_msg);
}
WeixinJSBridge.call('closeWindow');
}
);
}
if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
} else {
onBridgeReady();
}
</script>
</#if>
<#if channel == 'WEIXIN_WAP'>
<script>
function pay() {
window.location.href = "${params}"
}
</script>
</#if>
+6 -5
View File
@@ -35,14 +35,15 @@
<developer>
<name>lengleng</name>
<email>wangiegie@gmail.com</email>
<roles>
<role>leader</role>
</roles>
</developer>
<developer>
<name>lisb</name>
<email>121838632@qq.com</email>
</developer>
</developers>
<properties>
<spring-boot.version>2.1.5.RELEASE</spring-boot.version>
<spring-boot.version>2.1.6.RELEASE</spring-boot.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
<spring-platform.version>Cairo-SR8</spring-platform.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -50,7 +51,7 @@
<maven.compiler.target>1.8</maven.compiler.target>
<spring-boot-admin.version>2.1.5</spring-boot-admin.version>
<easyexcel.version>1.1.2-beta5</easyexcel.version>
<hutool.version>4.5.10</hutool.version>
<hutool.version>4.5.12</hutool.version>
<kaptcha.version>0.0.9</kaptcha.version>
<swagger.fox.version>2.9.2</swagger.fox.version>
<swagger-bootstrap-ui.version>1.9.4</swagger-bootstrap-ui.version>