|
|
@ -6,14 +6,15 @@ import cn.hutool.core.util.IdUtil; |
|
|
|
import cn.hutool.core.util.NumberUtil; |
|
|
|
import cn.hutool.core.util.ObjectUtil; |
|
|
|
import cn.hutool.core.util.StrUtil; |
|
|
|
import com.alibaba.fastjson.JSONObject; |
|
|
|
import com.gxwebsoft.common.core.config.ConfigProperties; |
|
|
|
import com.gxwebsoft.common.core.config.CertificateProperties; |
|
|
|
import com.gxwebsoft.common.core.utils.RedisUtil; |
|
|
|
import com.gxwebsoft.common.core.utils.CertificateLoader; |
|
|
|
import com.gxwebsoft.common.core.utils.WechatCertAutoConfig; |
|
|
|
import com.gxwebsoft.common.core.utils.WechatPayConfigValidator; |
|
|
|
import com.gxwebsoft.common.core.utils.*; |
|
|
|
import com.gxwebsoft.common.core.web.BaseController; |
|
|
|
import com.gxwebsoft.common.system.entity.DictData; |
|
|
|
import com.gxwebsoft.common.system.entity.Payment; |
|
|
|
import com.gxwebsoft.shop.entity.ShopGoods; |
|
|
|
import com.gxwebsoft.shop.service.ShopGoodsService; |
|
|
|
import com.gxwebsoft.shop.service.ShopOrderGoodsService; |
|
|
|
import com.gxwebsoft.shop.service.ShopOrderService; |
|
|
|
import com.gxwebsoft.shop.service.OrderBusinessService; |
|
|
@ -39,7 +40,9 @@ import org.springframework.security.access.prepost.PreAuthorize; |
|
|
|
import org.springframework.web.bind.annotation.*; |
|
|
|
|
|
|
|
import javax.annotation.Resource; |
|
|
|
import javax.validation.Valid; |
|
|
|
import java.math.BigDecimal; |
|
|
|
import java.util.LinkedHashMap; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Map; |
|
|
|
|
|
|
@ -53,329 +56,354 @@ import java.util.Map; |
|
|
|
@RestController |
|
|
|
@RequestMapping("/api/shop/shop-order") |
|
|
|
public class ShopOrderController extends BaseController { |
|
|
|
private static final Logger logger = LoggerFactory.getLogger(ShopOrderController.class); |
|
|
|
@Resource |
|
|
|
private ShopOrderService shopOrderService; |
|
|
|
@Resource |
|
|
|
private ShopOrderGoodsService shopOrderGoodsService; |
|
|
|
@Resource |
|
|
|
private OrderBusinessService orderBusinessService; |
|
|
|
@Resource |
|
|
|
private RedisUtil redisUtil; |
|
|
|
@Resource |
|
|
|
private ConfigProperties conf; |
|
|
|
@Resource |
|
|
|
private CertificateProperties certConfig; |
|
|
|
@Resource |
|
|
|
private CertificateLoader certificateLoader; |
|
|
|
@Resource |
|
|
|
private WechatCertAutoConfig wechatCertAutoConfig; |
|
|
|
@Resource |
|
|
|
private WechatPayConfigValidator wechatPayConfigValidator; |
|
|
|
@Value("${spring.profiles.active}") |
|
|
|
String active; |
|
|
|
|
|
|
|
@Operation(summary = "分页查询订单") |
|
|
|
@GetMapping("/page") |
|
|
|
public ApiResult<PageResult<ShopOrder>> page(ShopOrderParam param) { |
|
|
|
// 使用关联查询
|
|
|
|
return success(shopOrderService.pageRel(param)); |
|
|
|
} |
|
|
|
|
|
|
|
@Operation(summary = "查询全部订单") |
|
|
|
@GetMapping() |
|
|
|
public ApiResult<List<ShopOrder>> list(ShopOrderParam param) { |
|
|
|
// 使用关联查询
|
|
|
|
return success(shopOrderService.listRel(param)); |
|
|
|
} |
|
|
|
|
|
|
|
@PreAuthorize("hasAuthority('shop:shopOrder:list')") |
|
|
|
@Operation(summary = "根据id查询订单") |
|
|
|
@GetMapping("/{id}") |
|
|
|
public ApiResult<ShopOrder> get(@PathVariable("id") Integer id) { |
|
|
|
// 使用关联查询
|
|
|
|
return success(shopOrderService.getByIdRel(id)); |
|
|
|
} |
|
|
|
|
|
|
|
@Operation(summary = "添加订单") |
|
|
|
@PostMapping() |
|
|
|
public ApiResult<?> save(@RequestBody OrderCreateRequest request) { |
|
|
|
User loginUser = getLoginUser(); |
|
|
|
if (loginUser == null) { |
|
|
|
return fail("用户未登录"); |
|
|
|
private static final Logger logger = LoggerFactory.getLogger(ShopOrderController.class); |
|
|
|
@Resource |
|
|
|
private ShopOrderService shopOrderService; |
|
|
|
@Resource |
|
|
|
private ShopOrderGoodsService shopOrderGoodsService; |
|
|
|
@Resource |
|
|
|
private OrderBusinessService orderBusinessService; |
|
|
|
@Resource |
|
|
|
private RedisUtil redisUtil; |
|
|
|
@Resource |
|
|
|
private ConfigProperties conf; |
|
|
|
@Resource |
|
|
|
private CertificateProperties certConfig; |
|
|
|
@Resource |
|
|
|
private CertificateLoader certificateLoader; |
|
|
|
@Resource |
|
|
|
private WechatCertAutoConfig wechatCertAutoConfig; |
|
|
|
@Resource |
|
|
|
private WechatPayConfigValidator wechatPayConfigValidator; |
|
|
|
@Resource |
|
|
|
private ShopGoodsService shopGoodsService; |
|
|
|
@Value("${spring.profiles.active}") |
|
|
|
String active; |
|
|
|
@Resource |
|
|
|
private RequestUtil requestUtil; |
|
|
|
|
|
|
|
@Operation(summary = "分页查询订单") |
|
|
|
@GetMapping("/test") |
|
|
|
public ApiResult<?> test() { |
|
|
|
requestUtil.setTenantId("10550"); |
|
|
|
User user = requestUtil.getByUserIdWithoutLogin(33035); |
|
|
|
return success(user); |
|
|
|
} |
|
|
|
|
|
|
|
try { |
|
|
|
Map<String, String> wxOrderInfo = orderBusinessService.createOrder(request, loginUser); |
|
|
|
return success("下单成功", wxOrderInfo); |
|
|
|
} catch (Exception e) { |
|
|
|
logger.error("创建订单失败 - 用户ID:{},请求:{}", loginUser.getUserId(), request, e); |
|
|
|
return fail(e.getMessage()); |
|
|
|
@Operation(summary = "分页查询订单") |
|
|
|
@GetMapping("/page") |
|
|
|
public ApiResult<PageResult<ShopOrder>> page(ShopOrderParam param) { |
|
|
|
// 使用关联查询
|
|
|
|
return success(shopOrderService.pageRel(param)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Operation(summary = "添加订单(兼容旧版本)") |
|
|
|
@PostMapping("/legacy") |
|
|
|
public ApiResult<?> saveLegacy(@RequestBody ShopOrder shopOrder) { |
|
|
|
// 记录当前登录用户id
|
|
|
|
User loginUser = getLoginUser(); |
|
|
|
if (loginUser != null) { |
|
|
|
shopOrder.setUserId(loginUser.getUserId()); |
|
|
|
shopOrder.setOpenid(loginUser.getOpenid()); |
|
|
|
shopOrder.setPayUserId(loginUser.getUserId()); |
|
|
|
if (shopOrder.getOrderNo() == null) { |
|
|
|
shopOrder.setOrderNo(Long.toString(IdUtil.getSnowflakeNextId())); |
|
|
|
} |
|
|
|
if (shopOrder.getComments() == null) { |
|
|
|
shopOrder.setComments("暂无"); |
|
|
|
} |
|
|
|
// 微信支付(商品金额不能为0)
|
|
|
|
if (shopOrder.getTotalPrice().compareTo(BigDecimal.ZERO) == 0) { |
|
|
|
return fail("商品金额不能为0"); |
|
|
|
} |
|
|
|
// 百色中学项目捐赠金额不能低于20元
|
|
|
|
if (shopOrder.getTenantId().equals(10324) && shopOrder.getTotalPrice().compareTo(new BigDecimal("10")) < 0) { |
|
|
|
return fail("捐款金额最低不能少于10元,感谢您的爱心捐赠^_^"); |
|
|
|
} |
|
|
|
// 测试支付
|
|
|
|
if (loginUser.getPhone().equals("13737128880")) { |
|
|
|
shopOrder.setPrice(new BigDecimal("0.01")); |
|
|
|
shopOrder.setTotalPrice(new BigDecimal("0.01")); |
|
|
|
} |
|
|
|
if (shopOrderService.save(shopOrder)) { |
|
|
|
return success("下单成功", shopOrderService.createWxOrder(shopOrder)); |
|
|
|
} |
|
|
|
@Operation(summary = "查询全部订单") |
|
|
|
@GetMapping() |
|
|
|
public ApiResult<List<ShopOrder>> list(ShopOrderParam param) { |
|
|
|
// 使用关联查询
|
|
|
|
return success(shopOrderService.listRel(param)); |
|
|
|
} |
|
|
|
return fail("添加失败"); |
|
|
|
} |
|
|
|
|
|
|
|
@PreAuthorize("hasAuthority('shop:shopOrder:update')") |
|
|
|
@Operation(summary = "修改订单") |
|
|
|
@PutMapping() |
|
|
|
public ApiResult<?> update(@RequestBody ShopOrder shopOrder) { |
|
|
|
if (shopOrderService.updateById(shopOrder)) { |
|
|
|
return success("修改成功"); |
|
|
|
@PreAuthorize("hasAuthority('shop:shopOrder:list')") |
|
|
|
@Operation(summary = "根据id查询订单") |
|
|
|
@GetMapping("/{id}") |
|
|
|
public ApiResult<ShopOrder> get(@PathVariable("id") Integer id) { |
|
|
|
// 使用关联查询
|
|
|
|
return success(shopOrderService.getByIdRel(id)); |
|
|
|
} |
|
|
|
return fail("修改失败"); |
|
|
|
} |
|
|
|
|
|
|
|
@Operation(summary = "删除订单") |
|
|
|
@DeleteMapping("/{id}") |
|
|
|
public ApiResult<?> remove(@PathVariable("id") Integer id) { |
|
|
|
if (shopOrderService.removeById(id)) { |
|
|
|
return success("删除成功"); |
|
|
|
@Operation(summary = "添加订单") |
|
|
|
@PostMapping() |
|
|
|
public ApiResult<?> save(@Valid @RequestBody OrderCreateRequest request) { |
|
|
|
User loginUser = getLoginUser(); |
|
|
|
if (loginUser == null) { |
|
|
|
return fail("用户未登录"); |
|
|
|
} |
|
|
|
if (request.getTotalPrice() == null || request.getTotalPrice().compareTo(BigDecimal.ZERO) <= 0) { |
|
|
|
if (request.getGoodsItems() != null && !request.getGoodsItems().isEmpty()) { |
|
|
|
BigDecimal totalPrice = BigDecimal.ZERO; |
|
|
|
int totalNum = 0; |
|
|
|
for (OrderCreateRequest.OrderGoodsItem item : request.getGoodsItems()) { |
|
|
|
ShopGoods goods = shopGoodsService.getById(item.getGoodsId().toString()); |
|
|
|
if (goods != null) { |
|
|
|
BigDecimal price = goods.getPrice().multiply(new BigDecimal(item.getQuantity().toString())); |
|
|
|
totalPrice = totalPrice.add(price); |
|
|
|
totalNum += Integer.parseInt(item.getQuantity().toString()); |
|
|
|
} |
|
|
|
} |
|
|
|
request.setTotalPrice(totalPrice); |
|
|
|
request.setPayPrice(totalPrice); |
|
|
|
request.setTotalNum(totalNum); |
|
|
|
request.setFormId(Integer.parseInt(request.getGoodsItems().get(0).getQuantity().toString())); |
|
|
|
} |
|
|
|
} |
|
|
|
if (request.getTenantId() == null) { |
|
|
|
request.setTenantId(getTenantId()); |
|
|
|
} |
|
|
|
try { |
|
|
|
Map<String, String> wxOrderInfo = orderBusinessService.createOrder(request, loginUser); |
|
|
|
return success("下单成功", wxOrderInfo); |
|
|
|
} catch (Exception e) { |
|
|
|
logger.error("创建订单失败", e); |
|
|
|
return fail(e.getMessage()); |
|
|
|
} |
|
|
|
} |
|
|
|
return fail("删除失败"); |
|
|
|
} |
|
|
|
|
|
|
|
@Operation(summary = "批量添加订单") |
|
|
|
@PostMapping("/batch") |
|
|
|
public ApiResult<?> saveBatch(@RequestBody List<ShopOrder> list) { |
|
|
|
if (shopOrderService.saveBatch(list)) { |
|
|
|
return success("添加成功"); |
|
|
|
@Operation(summary = "添加订单(兼容旧版本)") |
|
|
|
@PostMapping("/legacy") |
|
|
|
public ApiResult<?> saveLegacy(@RequestBody ShopOrder shopOrder) { |
|
|
|
// 记录当前登录用户id
|
|
|
|
User loginUser = getLoginUser(); |
|
|
|
if (loginUser != null) { |
|
|
|
shopOrder.setUserId(loginUser.getUserId()); |
|
|
|
shopOrder.setOpenid(loginUser.getOpenid()); |
|
|
|
shopOrder.setPayUserId(loginUser.getUserId()); |
|
|
|
if (shopOrder.getOrderNo() == null) { |
|
|
|
shopOrder.setOrderNo(Long.toString(IdUtil.getSnowflakeNextId())); |
|
|
|
} |
|
|
|
if (shopOrder.getComments() == null) { |
|
|
|
shopOrder.setComments("暂无"); |
|
|
|
} |
|
|
|
// 微信支付(商品金额不能为0)
|
|
|
|
if (shopOrder.getTotalPrice().compareTo(BigDecimal.ZERO) == 0) { |
|
|
|
return fail("商品金额不能为0"); |
|
|
|
} |
|
|
|
// 百色中学项目捐赠金额不能低于20元
|
|
|
|
if (shopOrder.getTenantId().equals(10324) && shopOrder.getTotalPrice().compareTo(new BigDecimal("10")) < 0) { |
|
|
|
return fail("捐款金额最低不能少于10元,感谢您的爱心捐赠^_^"); |
|
|
|
} |
|
|
|
// 测试支付
|
|
|
|
if (loginUser.getPhone().equals("13737128880")) { |
|
|
|
shopOrder.setPrice(new BigDecimal("0.01")); |
|
|
|
shopOrder.setTotalPrice(new BigDecimal("0.01")); |
|
|
|
} |
|
|
|
if (shopOrderService.save(shopOrder)) { |
|
|
|
return success("下单成功", shopOrderService.createWxOrder(shopOrder)); |
|
|
|
} |
|
|
|
} |
|
|
|
return fail("添加失败"); |
|
|
|
} |
|
|
|
return fail("添加失败"); |
|
|
|
} |
|
|
|
|
|
|
|
@Operation(summary = "批量修改订单") |
|
|
|
@PutMapping("/batch") |
|
|
|
public ApiResult<?> removeBatch(@RequestBody BatchParam<ShopOrder> batchParam) { |
|
|
|
if (batchParam.update(shopOrderService, "order_id")) { |
|
|
|
return success("修改成功"); |
|
|
|
@PreAuthorize("hasAuthority('shop:shopOrder:update')") |
|
|
|
@Operation(summary = "修改订单") |
|
|
|
@PutMapping() |
|
|
|
public ApiResult<?> update(@RequestBody ShopOrder shopOrder) { |
|
|
|
if (shopOrderService.updateById(shopOrder)) { |
|
|
|
return success("修改成功"); |
|
|
|
} |
|
|
|
return fail("修改失败"); |
|
|
|
} |
|
|
|
return fail("修改失败"); |
|
|
|
} |
|
|
|
|
|
|
|
@Operation(summary = "批量删除订单") |
|
|
|
@DeleteMapping("/batch") |
|
|
|
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) { |
|
|
|
if (shopOrderService.removeByIds(ids)) { |
|
|
|
return success("删除成功"); |
|
|
|
@Operation(summary = "删除订单") |
|
|
|
@DeleteMapping("/{id}") |
|
|
|
public ApiResult<?> remove(@PathVariable("id") Integer id) { |
|
|
|
if (shopOrderService.removeById(id)) { |
|
|
|
return success("删除成功"); |
|
|
|
} |
|
|
|
return fail("删除失败"); |
|
|
|
} |
|
|
|
return fail("删除失败"); |
|
|
|
} |
|
|
|
|
|
|
|
@Operation(summary = "修复订单") |
|
|
|
@PutMapping("/repair") |
|
|
|
public ApiResult<?> repair(@RequestBody ShopOrder shopOrder) { |
|
|
|
final ShopOrder order = shopOrderService.getByOutTradeNo(shopOrder.getOrderNo()); |
|
|
|
if(order != null){ |
|
|
|
shopOrderService.queryOrderByOutTradeNo(order); |
|
|
|
return success("修复成功"); |
|
|
|
@Operation(summary = "批量添加订单") |
|
|
|
@PostMapping("/batch") |
|
|
|
public ApiResult<?> saveBatch(@RequestBody List<ShopOrder> list) { |
|
|
|
if (shopOrderService.saveBatch(list)) { |
|
|
|
return success("添加成功"); |
|
|
|
} |
|
|
|
return fail("添加失败"); |
|
|
|
} |
|
|
|
return fail("修复失败"); |
|
|
|
} |
|
|
|
|
|
|
|
@Operation(summary = "统计订单总金额") |
|
|
|
@GetMapping("/total") |
|
|
|
public ApiResult<BigDecimal> total() { |
|
|
|
return success(shopOrderService.total()); |
|
|
|
} |
|
|
|
|
|
|
|
@Schema(description = "异步通知") |
|
|
|
@PostMapping("/notify/{tenantId}") |
|
|
|
public String wxNotify(@RequestHeader Map<String, String> header, @RequestBody String body, @PathVariable("tenantId") Integer tenantId) { |
|
|
|
logger.info("异步通知*************** = " + tenantId); |
|
|
|
|
|
|
|
// 获取支付配置信息用于解密
|
|
|
|
String key = "Payment:1:".concat(tenantId.toString()); |
|
|
|
Payment payment = redisUtil.get(key, Payment.class); |
|
|
|
|
|
|
|
// 检查支付配置
|
|
|
|
if (ObjectUtil.isEmpty(payment)) { |
|
|
|
throw new RuntimeException("未找到租户支付配置信息,租户ID: " + tenantId); |
|
|
|
@Operation(summary = "批量修改订单") |
|
|
|
@PutMapping("/batch") |
|
|
|
public ApiResult<?> removeBatch(@RequestBody BatchParam<ShopOrder> batchParam) { |
|
|
|
if (batchParam.update(shopOrderService, "order_id")) { |
|
|
|
return success("修改成功"); |
|
|
|
} |
|
|
|
return fail("修改失败"); |
|
|
|
} |
|
|
|
|
|
|
|
logger.info("开始处理微信支付异步通知 - 租户ID: {}", tenantId); |
|
|
|
logger.info("支付配置信息 - 商户号: {}, 应用ID: {}", payment.getMchId(), payment.getAppId()); |
|
|
|
|
|
|
|
// 验证微信支付配置
|
|
|
|
WechatPayConfigValidator.ValidationResult validation = wechatPayConfigValidator.validateWechatPayConfig(payment, tenantId); |
|
|
|
if (!validation.isValid()) { |
|
|
|
logger.error("❌ 微信支付配置验证失败: {}", validation.getErrors()); |
|
|
|
logger.info("📋 配置诊断报告:\n{}", wechatPayConfigValidator.generateDiagnosticReport(payment, tenantId)); |
|
|
|
throw new RuntimeException("微信支付配置验证失败: " + validation.getErrors()); |
|
|
|
@Operation(summary = "批量删除订单") |
|
|
|
@DeleteMapping("/batch") |
|
|
|
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) { |
|
|
|
if (shopOrderService.removeByIds(ids)) { |
|
|
|
return success("删除成功"); |
|
|
|
} |
|
|
|
return fail("删除失败"); |
|
|
|
} |
|
|
|
logger.info("✅ 微信支付配置验证通过"); |
|
|
|
|
|
|
|
RequestParam requestParam = new RequestParam.Builder() |
|
|
|
.serialNumber(header.get("wechatpay-serial")) |
|
|
|
.nonce(header.get("wechatpay-nonce")) |
|
|
|
.signature(header.get("wechatpay-signature")) |
|
|
|
.timestamp(header.get("wechatpay-timestamp")) |
|
|
|
.body(body) |
|
|
|
.build(); |
|
|
|
|
|
|
|
// 创建通知配置 - 使用与下单方法相同的证书配置逻辑
|
|
|
|
NotificationConfig config; |
|
|
|
try { |
|
|
|
if (active.equals("dev")) { |
|
|
|
// 开发环境 - 使用配置文件的upload-path构建证书路径
|
|
|
|
String uploadPath = conf.getUploadPath(); |
|
|
|
String tenantCertPath = uploadPath + "dev/wechat/" + tenantId; |
|
|
|
String privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile(); |
|
|
|
|
|
|
|
logger.info("开发环境异步通知证书路径: {}", privateKeyPath); |
|
|
|
logger.info("租户ID: {}, 证书目录: {}", tenantId, tenantCertPath); |
|
|
|
|
|
|
|
// 检查证书文件是否存在
|
|
|
|
if (!certificateLoader.certificateExists(privateKeyPath)) { |
|
|
|
logger.error("证书文件不存在: {}", privateKeyPath); |
|
|
|
throw new RuntimeException("证书文件不存在: " + privateKeyPath); |
|
|
|
@Operation(summary = "修复订单") |
|
|
|
@PutMapping("/repair") |
|
|
|
public ApiResult<?> repair(@RequestBody ShopOrder shopOrder) { |
|
|
|
final ShopOrder order = shopOrderService.getByOutTradeNo(shopOrder.getOrderNo()); |
|
|
|
if (order != null) { |
|
|
|
shopOrderService.queryOrderByOutTradeNo(order); |
|
|
|
return success("修复成功"); |
|
|
|
} |
|
|
|
return fail("修复失败"); |
|
|
|
} |
|
|
|
|
|
|
|
String privateKey = certificateLoader.loadCertificatePath(privateKeyPath); |
|
|
|
|
|
|
|
// 使用验证器获取有效的 APIv3 密钥
|
|
|
|
String apiV3Key = wechatPayConfigValidator.getValidApiV3Key(payment); |
|
|
|
|
|
|
|
logger.info("私钥文件加载成功: {}", privateKey); |
|
|
|
logger.info("使用APIv3密钥来源: {}", payment.getApiKey() != null && !payment.getApiKey().trim().isEmpty() ? "数据库配置" : "配置文件默认"); |
|
|
|
logger.info("APIv3密钥长度: {}", apiV3Key != null ? apiV3Key.length() : 0); |
|
|
|
logger.info("商户证书序列号: {}", payment.getMerchantSerialNumber()); |
|
|
|
|
|
|
|
// 使用自动证书配置
|
|
|
|
config = new RSAAutoCertificateConfig.Builder() |
|
|
|
.merchantId(payment.getMchId()) |
|
|
|
.privateKeyFromPath(privateKey) |
|
|
|
.merchantSerialNumber(payment.getMerchantSerialNumber()) |
|
|
|
.apiV3Key(apiV3Key) |
|
|
|
.build(); |
|
|
|
|
|
|
|
logger.info("✅ 开发环境使用自动证书配置创建通知解析器成功"); |
|
|
|
} else { |
|
|
|
// 生产环境 - 使用自动证书配置
|
|
|
|
final String certRootPath = certConfig.getCertRootPath(); |
|
|
|
final String certBasePath = certRootPath + "/file"; |
|
|
|
|
|
|
|
String privateKeyRelativePath = payment.getApiclientKey(); |
|
|
|
String privateKeyFullPath = privateKeyRelativePath.startsWith("/") |
|
|
|
? certBasePath + privateKeyRelativePath |
|
|
|
: certBasePath + "/" + privateKeyRelativePath; |
|
|
|
String privateKey = certificateLoader.loadCertificatePath(privateKeyFullPath); |
|
|
|
String apiV3Key = payment.getApiKey(); |
|
|
|
|
|
|
|
// 使用自动证书配置
|
|
|
|
config = new RSAAutoCertificateConfig.Builder() |
|
|
|
.merchantId(payment.getMchId()) |
|
|
|
.privateKeyFromPath(privateKey) |
|
|
|
.merchantSerialNumber(payment.getMerchantSerialNumber()) |
|
|
|
.apiV3Key(apiV3Key) |
|
|
|
.build(); |
|
|
|
@Schema(description = "异步通知") |
|
|
|
@PostMapping("/notify/{tenantId}") |
|
|
|
public String wxNotify(@RequestHeader Map<String, String> header, @RequestBody String body, @PathVariable("tenantId") Integer tenantId) { |
|
|
|
logger.info("异步通知*************** = " + tenantId); |
|
|
|
|
|
|
|
logger.info("✅ 生产环境使用自动证书配置创建通知解析器成功"); |
|
|
|
} |
|
|
|
} catch (Exception e) { |
|
|
|
logger.error("❌ 创建通知配置失败 - 租户ID: {}, 商户号: {}", tenantId, payment.getMchId(), e); |
|
|
|
logger.error("🔍 错误详情: {}", e.getMessage()); |
|
|
|
logger.error("💡 请检查:"); |
|
|
|
logger.error("1. 证书文件是否存在且路径正确"); |
|
|
|
logger.error("2. APIv3密钥是否配置正确"); |
|
|
|
logger.error("3. 商户证书序列号是否正确"); |
|
|
|
logger.error("4. 网络连接是否正常"); |
|
|
|
throw new RuntimeException("微信支付通知配置失败: " + e.getMessage(), e); |
|
|
|
} |
|
|
|
// 获取支付配置信息用于解密
|
|
|
|
String key = "Payment:1:".concat(tenantId.toString()); |
|
|
|
Payment payment = redisUtil.get(key, Payment.class); |
|
|
|
|
|
|
|
// 初始化 NotificationParser
|
|
|
|
NotificationParser parser = new NotificationParser(config); |
|
|
|
logger.info("✅ 通知解析器创建成功,准备解析异步通知"); |
|
|
|
// 检查支付配置
|
|
|
|
if (ObjectUtil.isEmpty(payment)) { |
|
|
|
throw new RuntimeException("未找到租户支付配置信息,租户ID: " + tenantId); |
|
|
|
} |
|
|
|
|
|
|
|
// 以支付通知回调为例,验签、解密并转换成 Transaction
|
|
|
|
try { |
|
|
|
logger.info("开始解析微信支付异步通知..."); |
|
|
|
Transaction transaction = parser.parse(requestParam, Transaction.class); |
|
|
|
logger.info("✅ 异步通知解析成功 - 交易状态: {}, 商户订单号: {}", |
|
|
|
transaction.getTradeStateDesc(), transaction.getOutTradeNo()); |
|
|
|
logger.info("开始处理微信支付异步通知 - 租户ID: {}", tenantId); |
|
|
|
logger.info("支付配置信息 - 商户号: {}, 应用ID: {}", payment.getMchId(), payment.getAppId()); |
|
|
|
|
|
|
|
if (StrUtil.equals("支付成功", transaction.getTradeStateDesc())) { |
|
|
|
final String outTradeNo = transaction.getOutTradeNo(); |
|
|
|
final String transactionId = transaction.getTransactionId(); |
|
|
|
final Integer total = transaction.getAmount().getTotal(); |
|
|
|
final String tradeStateDesc = transaction.getTradeStateDesc(); |
|
|
|
final Transaction.TradeStateEnum tradeState = transaction.getTradeState(); |
|
|
|
final Transaction.TradeTypeEnum tradeType = transaction.getTradeType(); |
|
|
|
System.out.println("transaction = " + transaction); |
|
|
|
System.out.println("tradeStateDesc = " + tradeStateDesc); |
|
|
|
System.out.println("tradeType = " + tradeType); |
|
|
|
System.out.println("tradeState = " + tradeState); |
|
|
|
System.out.println("outTradeNo = " + outTradeNo); |
|
|
|
System.out.println("amount = " + total); |
|
|
|
// 1. 查询要处理的订单
|
|
|
|
ShopOrder order = shopOrderService.getByOutTradeNo(outTradeNo); |
|
|
|
logger.info("order = " + order); |
|
|
|
// 2. 已支付则跳过
|
|
|
|
if (order.getPayStatus().equals(true)) { |
|
|
|
return "SUCCESS"; |
|
|
|
// 验证微信支付配置
|
|
|
|
WechatPayConfigValidator.ValidationResult validation = wechatPayConfigValidator.validateWechatPayConfig(payment, tenantId); |
|
|
|
if (!validation.isValid()) { |
|
|
|
logger.error("❌ 微信支付配置验证失败: {}", validation.getErrors()); |
|
|
|
logger.info("📋 配置诊断报告:\n{}", wechatPayConfigValidator.generateDiagnosticReport(payment, tenantId)); |
|
|
|
throw new RuntimeException("微信支付配置验证失败: " + validation.getErrors()); |
|
|
|
} |
|
|
|
// 2. 未支付则处理更新订单状态
|
|
|
|
if (order.getPayStatus().equals(false)) { |
|
|
|
// 5. TODO 处理订单状态
|
|
|
|
order.setPayTime(DateUtil.date()); |
|
|
|
order.setExpirationTime(order.getCreateTime()); |
|
|
|
order.setPayStatus(true); |
|
|
|
order.setTransactionId(transactionId); |
|
|
|
order.setPayPrice(new BigDecimal(NumberUtil.decimalFormat("0.00", total * 0.01))); |
|
|
|
order.setExpirationTime(DateUtil.offset(DateUtil.date(), DateField.YEAR, 10)); |
|
|
|
System.out.println("实际付款金额 = " + order.getPayPrice()); |
|
|
|
shopOrderService.updateByOutTradeNo(order); |
|
|
|
return "SUCCESS"; |
|
|
|
logger.info("✅ 微信支付配置验证通过"); |
|
|
|
|
|
|
|
RequestParam requestParam = new RequestParam.Builder() |
|
|
|
.serialNumber(header.get("wechatpay-serial")) |
|
|
|
.nonce(header.get("wechatpay-nonce")) |
|
|
|
.signature(header.get("wechatpay-signature")) |
|
|
|
.timestamp(header.get("wechatpay-timestamp")) |
|
|
|
.body(body) |
|
|
|
.build(); |
|
|
|
|
|
|
|
// 创建通知配置 - 使用与下单方法相同的证书配置逻辑
|
|
|
|
NotificationConfig config; |
|
|
|
try { |
|
|
|
// if (active.equals("dev")) {
|
|
|
|
// // 开发环境 - 构建包含租户号的私钥路径
|
|
|
|
// String tenantCertPath = "dev/wechat/" + tenantId;
|
|
|
|
// String privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile();
|
|
|
|
//
|
|
|
|
// logger.info("开发环境异步通知证书路径: {}", privateKeyPath);
|
|
|
|
// logger.info("租户ID: {}, 证书目录: {}", tenantId, tenantCertPath);
|
|
|
|
//
|
|
|
|
// // 检查证书文件是否存在
|
|
|
|
// if (!certificateLoader.certificateExists(privateKeyPath)) {
|
|
|
|
// logger.error("证书文件不存在: {}", privateKeyPath);
|
|
|
|
// throw new RuntimeException("证书文件不存在: " + privateKeyPath);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// String privateKey = certificateLoader.loadCertificatePath(privateKeyPath);
|
|
|
|
//
|
|
|
|
// // 使用验证器获取有效的 APIv3 密钥
|
|
|
|
// String apiV3Key = wechatPayConfigValidator.getValidApiV3Key(payment);
|
|
|
|
//
|
|
|
|
// logger.info("私钥文件加载成功: {}", privateKey);
|
|
|
|
// logger.info("使用APIv3密钥来源: {}", payment.getApiKey() != null && !payment.getApiKey().trim().isEmpty() ? "数据库配置" : "配置文件默认");
|
|
|
|
// logger.info("APIv3密钥长度: {}", apiV3Key != null ? apiV3Key.length() : 0);
|
|
|
|
// logger.info("商户证书序列号: {}", payment.getMerchantSerialNumber());
|
|
|
|
//
|
|
|
|
// // 使用自动证书配置
|
|
|
|
// config = new RSAAutoCertificateConfig.Builder()
|
|
|
|
// .merchantId(payment.getMchId())
|
|
|
|
// .privateKeyFromPath(privateKey)
|
|
|
|
// .merchantSerialNumber(payment.getMerchantSerialNumber())
|
|
|
|
// .apiV3Key(apiV3Key)
|
|
|
|
// .build();
|
|
|
|
//
|
|
|
|
// logger.info("✅ 开发环境使用自动证书配置创建通知解析器成功");
|
|
|
|
// } else {
|
|
|
|
// 生产环境 - 使用自动证书配置
|
|
|
|
final String certRootPath = certConfig.getCertRootPath(); |
|
|
|
final String certBasePath = certRootPath + "/file"; |
|
|
|
|
|
|
|
String privateKeyRelativePath = payment.getApiclientKey(); |
|
|
|
String privateKeyFullPath = privateKeyRelativePath.startsWith("/") |
|
|
|
? certBasePath + privateKeyRelativePath |
|
|
|
: certBasePath + "/" + privateKeyRelativePath; |
|
|
|
String privateKey = certificateLoader.loadCertificatePath(privateKeyFullPath); |
|
|
|
String apiV3Key = payment.getApiKey(); |
|
|
|
|
|
|
|
// 使用自动证书配置
|
|
|
|
config = new RSAAutoCertificateConfig.Builder() |
|
|
|
.merchantId(payment.getMchId()) |
|
|
|
.privateKeyFromPath(privateKey) |
|
|
|
.merchantSerialNumber(payment.getMerchantSerialNumber()) |
|
|
|
.apiV3Key(apiV3Key) |
|
|
|
.build(); |
|
|
|
|
|
|
|
logger.info("✅ 生产环境使用自动证书配置创建通知解析器成功"); |
|
|
|
// }
|
|
|
|
} catch (Exception e) { |
|
|
|
logger.error("❌ 创建通知配置失败 - 租户ID: {}, 商户号: {}", tenantId, payment.getMchId(), e); |
|
|
|
logger.error("🔍 错误详情: {}", e.getMessage()); |
|
|
|
logger.error("💡 请检查:"); |
|
|
|
logger.error("1. 证书文件是否存在且路径正确"); |
|
|
|
logger.error("2. APIv3密钥是否配置正确"); |
|
|
|
logger.error("3. 商户证书序列号是否正确"); |
|
|
|
logger.error("4. 网络连接是否正常"); |
|
|
|
throw new RuntimeException("微信支付通知配置失败: " + e.getMessage(), e); |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (Exception e) { |
|
|
|
logger.error("❌ 处理微信支付异步通知失败 - 租户ID: {}, 商户号: {}", tenantId, payment.getMchId(), e); |
|
|
|
logger.error("🔍 异常详情: {}", e.getMessage()); |
|
|
|
logger.error("💡 可能的原因:"); |
|
|
|
logger.error("1. 证书配置错误或证书文件损坏"); |
|
|
|
logger.error("2. 微信支付平台证书已过期"); |
|
|
|
logger.error("3. 签名验证失败"); |
|
|
|
logger.error("4. 请求参数格式错误"); |
|
|
|
|
|
|
|
// 返回失败,微信会重试
|
|
|
|
return "fail"; |
|
|
|
} |
|
|
|
// 初始化 NotificationParser
|
|
|
|
NotificationParser parser = new NotificationParser(config); |
|
|
|
logger.info("✅ 通知解析器创建成功,准备解析异步通知"); |
|
|
|
|
|
|
|
// 以支付通知回调为例,验签、解密并转换成 Transaction
|
|
|
|
try { |
|
|
|
logger.info("开始解析微信支付异步通知..."); |
|
|
|
Transaction transaction = parser.parse(requestParam, Transaction.class); |
|
|
|
logger.info("✅ 异步通知解析成功 - 交易状态: {}, 商户订单号: {}", |
|
|
|
transaction.getTradeStateDesc(), transaction.getOutTradeNo()); |
|
|
|
|
|
|
|
if (StrUtil.equals("支付成功", transaction.getTradeStateDesc())) { |
|
|
|
final String outTradeNo = transaction.getOutTradeNo(); |
|
|
|
final String transactionId = transaction.getTransactionId(); |
|
|
|
final Integer total = transaction.getAmount().getTotal(); |
|
|
|
final String tradeStateDesc = transaction.getTradeStateDesc(); |
|
|
|
final Transaction.TradeStateEnum tradeState = transaction.getTradeState(); |
|
|
|
final Transaction.TradeTypeEnum tradeType = transaction.getTradeType(); |
|
|
|
System.out.println("transaction = " + transaction); |
|
|
|
System.out.println("tradeStateDesc = " + tradeStateDesc); |
|
|
|
System.out.println("tradeType = " + tradeType); |
|
|
|
System.out.println("tradeState = " + tradeState); |
|
|
|
System.out.println("outTradeNo = " + outTradeNo); |
|
|
|
System.out.println("amount = " + total); |
|
|
|
// 1. 查询要处理的订单
|
|
|
|
ShopOrder order = shopOrderService.getByOutTradeNo(outTradeNo); |
|
|
|
logger.info("order = " + order); |
|
|
|
// 2. 已支付则跳过
|
|
|
|
if (order.getPayStatus().equals(true)) { |
|
|
|
return "SUCCESS"; |
|
|
|
} |
|
|
|
// 2. 未支付则处理更新订单状态
|
|
|
|
if (order.getPayStatus().equals(false)) { |
|
|
|
// 5. TODO 处理订单状态
|
|
|
|
order.setPayTime(DateUtil.date()); |
|
|
|
order.setExpirationTime(order.getCreateTime()); |
|
|
|
order.setPayStatus(true); |
|
|
|
order.setTransactionId(transactionId); |
|
|
|
order.setPayPrice(new BigDecimal(NumberUtil.decimalFormat("0.00", total * 0.01))); |
|
|
|
order.setExpirationTime(DateUtil.offset(DateUtil.date(), DateField.YEAR, 10)); |
|
|
|
System.out.println("实际付款金额 = " + order.getPayPrice()); |
|
|
|
shopOrderService.updateByOutTradeNo(order); |
|
|
|
return "SUCCESS"; |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (Exception e) { |
|
|
|
logger.error("❌ 处理微信支付异步通知失败 - 租户ID: {}, 商户号: {}", tenantId, payment.getMchId(), e); |
|
|
|
logger.error("🔍 异常详情: {}", e.getMessage()); |
|
|
|
logger.error("💡 可能的原因:"); |
|
|
|
logger.error("1. 证书配置错误或证书文件损坏"); |
|
|
|
logger.error("2. 微信支付平台证书已过期"); |
|
|
|
logger.error("3. 签名验证失败"); |
|
|
|
logger.error("4. 请求参数格式错误"); |
|
|
|
|
|
|
|
// 返回失败,微信会重试
|
|
|
|
return "fail"; |
|
|
|
} |
|
|
|
|
|
|
|
logger.warn("⚠️ 异步通知处理完成但未找到匹配的支付成功状态"); |
|
|
|
return "fail"; |
|
|
|
} |
|
|
|
logger.warn("⚠️ 异步通知处理完成但未找到匹配的支付成功状态"); |
|
|
|
return "fail"; |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|