feat : refund

This commit is contained in:
2025-02-24 15:06:26 +08:00
parent 6754d2969e
commit 27a6ad3e2e
15 changed files with 591 additions and 281 deletions

View File

@@ -1,24 +0,0 @@
<?php
/**
* This service file is part of item.
*
* @author ctexthuang
* @contact ctexthuang@qq.com
*/
declare(strict_types=1);
namespace App\Service\Amqp\Refund;
class BalanceOrderRefundService
{
/**
* @var int
*/
public int $orderId;
public function handle()
{
return true;
}
}

View File

@@ -1,41 +0,0 @@
<?php
/**
* This service file is part of item.
*
* @author ctexthuang
* @contact ctexthuang@qq.com
*/
declare(strict_types=1);
namespace App\Service\Amqp\Refund;
use App\Constants\Common\OrderCode;
use App\Model\Order;
use App\Model\PayOrder;
use Exception;
use Hyperf\Di\Annotation\Inject;
class GoodOrderAllRefundService extends RefundBaseService
{
/**
* @return void
*/
public function __construct()
{
$this->type = OrderCode::ORDER_TYPE_GOOD;
}
public function handle()
{
$this->getOrderInfo();
$this->getPayOrder();
$this->getRefundAmount();
if ($this->refundAmount <= 0) throw new Exception('退款金额不能小于等于0');
}
}

View File

@@ -1,22 +0,0 @@
<?php
/**
* This service file is part of item.
*
* @author ctexthuang
* @contact ctexthuang@qq.com
*/
declare(strict_types=1);
namespace App\Service\Amqp\Refund;
use App\Model\Order;
use Hyperf\Di\Annotation\Inject;
class GoodOrderPartRefundService
{
public function handle()
{
//todo Write logic
}
}

View File

@@ -1,114 +0,0 @@
<?php
namespace App\Service\Amqp\Refund;
use App\Constants\Common\OrderCode;
use App\Model\Order;
use App\Model\PayOrder;
use Exception;
use Hyperf\Di\Annotation\Inject;
class RefundBaseService
{
/**
* @var int
*/
public int $orderId;
/**
* @var int
*/
protected int $type;
/**
* @var PayOrder
*/
#[Inject]
protected PayOrder $payOrderModel;
/**
* @var Order
*/
#[Inject]
protected Order $orderModel;
/**
* @var mixed
*/
protected mixed $payInfo;
/**
* @var mixed
*/
protected mixed $orderInfo;
/**
* @var float
*/
protected float $refundAmount;
/**
* @return void
*/
protected function getPayOrder()
{
$this->payInfo = $this->payOrderModel->getInfoByOrderIdAndType($this->orderId, $this->type);
}
/**
* @return void
*/
protected function getOrderInfo()
{
$this->orderInfo = match ($this->type) {
OrderCode::ORDER_TYPE_GOOD => $this->orderModel->getInfoById($this->orderId),
default => null,
};
}
protected function getAllRefundByOrderId()
{
}
/**
* @return void
* @throws Exception
*/
protected function checkInfo()
{
if (!$this->payInfo || !$this->orderInfo) {
throw new Exception('订单信息不存在');
}
}
protected function getUser()
{
//todo 获取用户信息
}
/**
* @return void
*/
protected function getRefundAmount(): void
{
//todo 等于支付金额 减去 已退款金额
$this->refundAmount = 0;
}
protected function insertRefund()
{
//todo 写入退款记录
}
protected function updateOrderInfo()
{
//todo 更新订单信息
}
protected function refund()
{
//todo 退款 参考调起支付
}
}

View File

@@ -1,30 +0,0 @@
<?php
namespace App\Service\Amqp\Refund;
class RefundFactory
{
/**
* @return GoodOrderAllRefundService
*/
public function newGoodOrderRefundAll(): GoodOrderAllRefundService
{
return new GoodOrderAllRefundService();
}
/**
* @return GoodOrderPartRefundService
*/
public function newGoodOrderRefundPart(): GoodOrderPartRefundService
{
return new GoodOrderPartRefundService();
}
/**
* @return BalanceOrderRefundService
*/
public function newBalanceOrderRefund(): BalanceOrderRefundService
{
return new BalanceOrderRefundService();
}
}

View File

@@ -0,0 +1,218 @@
<?php
namespace App\Service\Amqp\Refund;
use App\Constants\Common\OrderCode;
use App\Constants\Common\PayCode;
use App\Constants\Common\RefundCode;
use App\Lib\Log;
use App\Model\Order;
use App\Model\PayOrder;
use App\Model\RefundOrder;
use App\Service\Common\Pay\Wx\WxJsRechargeOrderService;
use Exception;
use Hyperf\Di\Annotation\Inject;
class RefundService
{
/**
* @var int
*/
public int $orderId;
/**
* @var int
*/
protected int $type;
/**
* @var PayOrder
*/
#[Inject]
protected PayOrder $payOrderModel;
/**
* @var Order
*/
#[Inject]
protected Order $orderModel;
/**
* @var RefundOrder
*/
#[Inject]
protected RefundOrder $refundOrderModel;
/**
* @var Log
*/
#[Inject]
protected Log $log;
/**
* @var mixed
*/
protected mixed $payInfo;
/**
* @var mixed
*/
protected mixed $orderInfo;
/**
* @var mixed
*/
protected mixed $refundInfo;
/**
* @var float
*/
public float $refundAmount;
/**
* @var float
*/
protected float $isRefundMoney;
/**
*
*/
public function handle(): void
{
$errMsg = null;
try {
$this->getOrderInfo();
$this->checkOrder();
$this->getPayAndRefundOrder();
$this->getAllRefundMoneyByOrderId();
$this->checkRefundAmount();
$this->refund();
} catch (Exception $e) {
$this->log->error('RefundOrderConsumer:RefundService:error:'.$e->getMessage());
$errArr = explode(":", $e->getMessage());
$errMsg = $errArr[0];
}
$this->updateRefund($errMsg);
}
/**
* @return void
* @throws Exception
*/
protected function getPayAndRefundOrder(): void
{
$this->payInfo = $this->payOrderModel->getInfoByOrderIdAndType($this->orderId, $this->type);
$this->refundInfo = $this->refundOrderModel->getInfoByOrderIdAndTypeWaitRefund($this->orderId,$this->type);
if (!$this->payInfo || !$this->refundInfo) throw new Exception('订单信息不存在');
}
/**
* @return void
*/
protected function getOrderInfo()
{
$this->orderInfo = match ($this->type) {
OrderCode::ORDER_TYPE_GOOD => $this->orderModel->getInfoById($this->orderId),
default => null,
};
}
/**
* @return void
* @throws Exception
*/
protected function checkOrder()
{
if (empty($orderInfo)) throw new Exception('订单信息不存在:'.json_encode(['order_id' => $this->orderId,'order_type' => $this->type]));
if (!in_array($orderInfo->status,OrderCode::CAN_REFUND_STATUS)) throw new Exception('订单状态不能退款:'.json_encode($this->orderInfo->toArray()));
//余额订单必须退款全部金额
if ($this->type == OrderCode::ORDER_TYPE_BALANCE && $this->refundAmount != $this->orderInfo->actual_price) throw new Exception('余额订单必须退款全部金额:'.json_encode($this->orderInfo->toArray()));
}
/**
* @return void
*/
protected function getAllRefundMoneyByOrderId(): void
{
$this->isRefundMoney = $this->refundOrderModel->getSuccessMoneyByOrderId($this->orderId,$this->type);
}
protected function getUser()
{
//todo 获取用户信息
}
/**
* @return void
* @throws Exception
*/
protected function checkRefundAmount(): void
{
// 等于支付金额 减去 已退款金额 减去 本次退款金额 小于 0 就是错误的 大于等于 0 就是正确的
if ($this->orderInfo->actual_price - $this->isRefundMoney - $this->refundAmount < 0) throw new Exception('退款金额不能大于订单金额');
$this->refundAmount = 0;
}
/**
* @return void
*/
protected function updateRefund($errMsg = null)
{
$status = RefundCode::REFUND_FAIL;
if (empty($errMsg)) {
$status = RefundCode::WAIT_BY_PAY_TOOL;
$errMsg = '等待支付工具退款';
}
$this->refundOrderModel->where('id',$this->refundInfo->id)->update([
'refund_status' => $status,
'remark' => $errMsg
]);
}
protected function refund()
{
$rechargeService = match ($this->refundInfo->refund_type)
{
PayCode::ALIPAY => $this->setAliPayRefund(),
PayCode::WECHAT_PAY => $this->setWechatPayRefund(),
};
$rechargeService->setConfig();
$rechargeService->setNotify();
$payData = $rechargeService->pay(
(float)$this->orderInfo->actual_price,
$this->request->input('body','订单支付'),
$this->orderInfo->order_no,
$this->userId
);
}
protected function setAliPayRefund()
{
return null;
}
/**
* @return WxJsRechargeOrderService
*/
protected function setWechatPayRefund(): WxJsRechargeOrderService
{
return new WxJsRechargeOrderService();
}
}

View File

@@ -66,11 +66,21 @@ abstract class WxJsRechargeBaseService implements ThirdPayInterface
*/
abstract public function callBackHandle();
/**
* 退款回调
*/
abstract public function refundCallbackHandle();
/**
* 设置回调地址
*/
abstract public function setNotify();
/**
* 设置退款回调地址
*/
abstract public function setRefundNotify();
// /**
// * 设置商户号id
// */
@@ -90,16 +100,16 @@ abstract class WxJsRechargeBaseService implements ThirdPayInterface
try {
$this->callbackData = $this->ysdPay->wechat($this->config)->callback($this->request)->toArray()['resource']['ciphertext'];
}catch (Exception $e) {
$this->log->debug(__CLASS__.'微信支付回调解密失败'.json_encode($e->getMessage()));
throw new ErrException('微信支付回调解密失败');
$this->log->debug(__CLASS__.'wxPay回调解密失败'.json_encode($e->getMessage()));
throw new ErrException('wxPay回调解密失败');
}
if (!empty($this->callbackData)) {
$this->log->debug(__CLASS__.'获取回调失败'.json_encode($this->request));
throw new ErrException('获取回调失败');
$this->log->debug(__CLASS__.'wxPay获取回调失败'.json_encode($this->request));
throw new ErrException('wxPay获取回调失败');
}
$this->log->info(__CLASS__.'微信支付完成回调'.json_encode($this->callbackData));
$this->log->info(__CLASS__.'wxPay完成回调'.json_encode($this->callbackData));
}
@@ -156,4 +166,39 @@ abstract class WxJsRechargeBaseService implements ThirdPayInterface
throw new ErrException($e->getMessage());
}
}
/**
* @param float $refundAmount
* @param float $totalAmount
* @param string $outTradeNo
* @param string $outRefundNo
* @return Collection|Rocket
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function refund(float $refundAmount,float $totalAmount, string $outTradeNo, string $outRefundNo): Collection|Rocket
{
if (empty($this->config)) throw new ErrException('调起支付失败-微信支付配置项不存在');
try {
$order = [
'out_trade_no' => $outTradeNo,
'out_refund_no' => $outRefundNo,
'amount' => [
'refund' => (int)bcmul((string)$refundAmount, "100"),
'total' => (int)bcmul((string)$totalAmount, "100"),
'currency' => 'CNY',
],
'_action' => 'mini', // jsapi 退款,默认
];
$result = $this->ysdPay->wechat($this->config)->refund($order);
$this->log->callbackLog(__CLASS__.'微信退款调起数据|回调地址:'. json_encode($this->config['wechat']['default']).'|回调数据:'.json_encode($result).'|请求数据:'.json_encode($order));
return $result;
} catch (Exception $e) {
$this->log->error(__CLASS__.'微信退款调起失败。reason:'.$e->getMessage());
throw new ErrException($e->getMessage());
}
}
}

View File

@@ -8,6 +8,7 @@ use App\Service\ServiceTrait\Api\CheckOrderCallBackTrait;
use App\Service\ServiceTrait\Api\GoodOrderTrait;
use App\Service\ServiceTrait\Api\OrderTrait;
use App\Service\ServiceTrait\Api\PayFinishTrait;
use App\Service\ServiceTrait\Api\RefundOrderTrait;
use Hyperf\DbConnection\Db;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
@@ -19,7 +20,9 @@ class WxJsRechargeOrderService extends WxJsRechargeBaseService
{
use CheckOrderCallBackTrait,
GoodOrderTrait,
PayFinishTrait;
PayFinishTrait,
RefundOrderTrait,
OrderTrait;
/**
*
@@ -47,15 +50,20 @@ class WxJsRechargeOrderService extends WxJsRechargeBaseService
*/
protected mixed $payInfo;
/**
* @var mixed
*/
protected mixed $refundInfo;
/**
* @return ResponseInterface
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws InvalidParamsException
*/
public function callBackHandle()
public function callBackHandle(): ResponseInterface
{
$this-> getWechatData();
$this->getWechatData();
$this->checkWxCallBackOrder();
@@ -79,6 +87,31 @@ class WxJsRechargeOrderService extends WxJsRechargeBaseService
return $this->returnSuccess();
}
/**
* @return ResponseInterface
* @throws ContainerExceptionInterface
* @throws InvalidParamsException
* @throws NotFoundExceptionInterface
*/
public function refundCallBackHandle(): ResponseInterface
{
$this->getWechatData();
$this->checkWxRefundCallBackOrder();
$this->checkRefundOrder();
Db::transaction(function (){
$this->manageRefundOrder();
$this->manageOrderByRefund();
});
$this->sendStockMq($this->orderInfo->id,$this->orderInfo->status);
return $this->returnSuccess();
}
/**
* @return void
*/
@@ -87,4 +120,13 @@ class WxJsRechargeOrderService extends WxJsRechargeBaseService
//返回的回调地址
$this->config['wechat']['default']['notify_url'] = config('system.api_url').'/common/wxPay/order/js/callBack';
}
/**
* @return void
*/
public function setRefundNotify(): void
{
//返回的退款回调地址
$this->config['wechat']['default']['notify_url'] = config('system.api_url').'/common/wxPay/order/js/refund/callBack';
}
}

View File

@@ -6,6 +6,9 @@ use App\Exception\ErrException;
trait CheckOrderCallBackTrait
{
/**
* @return void
*/
public function checkWxCallBackOrder(): void
{
if (
@@ -24,4 +27,23 @@ trait CheckOrderCallBackTrait
$this->orderNo = $this->callbackData['out_trade_no'];
}
/**
* @return void
*/
public function checkWxRefundCallBackOrder(): void
{
if (
!isset($this->callbackData['return_code']) ||
$this->callbackData['return_code']!= 'SUCCESS' ||
!isset($this->callbackData['out_trade_no']) ||
empty($this->callbackData['out_trade_no']) ||
!isset($this->callbackData['mch_id']) ||
empty($this->callbackData['mch_id'])
) {
throw new ErrException('此订单回调异常');
}
$this->orderNo = $this->callbackData['out_trade_no'];
}
}

View File

@@ -4,9 +4,11 @@ namespace App\Service\ServiceTrait\Api;
use App\Constants\Common\OrderCode;
use App\Constants\Common\PayCode;
use App\Constants\Common\RefundCode;
use App\Exception\ErrException;
use App\Model\Order;
use App\Model\PayOrder;
use App\Model\RefundOrder;
use Exception;
use Hyperf\Di\Annotation\Inject;
use Psr\Container\ContainerExceptionInterface;
@@ -27,6 +29,12 @@ trait GoodOrderTrait
#[Inject]
protected PayOrder $payOrderModel;
/**
* @var RefundOrder
*/
#[Inject]
protected RefundOrder $refundOrderModel;
/**
* @return void
* @throws ContainerExceptionInterface
@@ -53,6 +61,8 @@ trait GoodOrderTrait
}
}
/**
* @return void
* @throws ContainerExceptionInterface

View File

@@ -0,0 +1,89 @@
<?php
namespace App\Service\ServiceTrait\Api;
use App\Constants\Common\OrderCode;
use App\Constants\Common\PayCode;
use App\Constants\Common\RefundCode;
use App\Exception\ErrException;
use Exception;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
trait RefundOrderTrait
{
/**
* @return void
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
protected function checkRefundOrder(): void
{
$this->refundInfo = $this->refundOrderModel->getInfoByOrderSnoAndType($this->orderNo,self::OrderType);
$this->orderInfo = $this->orderModel->getInfoById($this->refundInfo->order_id);
$this->payInfo = $this->payOrderModel->getInfoByOrderIdAndType($this->orderInfo->id,self::OrderType);
if (empty($this->orderInfo) || empty($this->payInfo) || empty($this->refundInfo)) {
$this->log->debug(__CLASS__.':订单不存在,订单号:'.$this->orderNo);
throw new ErrException('订单不存在');
}
if ($this->refundInfo->refund_status != RefundCode::WAIT_BY_PAY_TOOL) {
$this->log->debug(__CLASS__.':订单已退款成功,订单信息:'.json_encode($this->refundInfo->toArray()));
throw new ErrException('订单已退款成功');
}
if ($this->refundInfo->refund_type != PayCode::WECHAT_PAY) {
$this->log->debug(__CLASS__.':订单退款渠道错误,订单信息:'.json_encode($this->refundInfo->toArray()));
throw new ErrException('订单退款渠道错误');
}
}
/**
* @return void
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
protected function manageRefundOrder(): void
{
try {
$this->refundInfo->refund_status = RefundCode::REFUND_SUCCESS;
$this->refundInfo->refund_time = date('Y-m-d H:i:s');
$this->refundInfo->wx_transaction_id = $this->callbackData['transaction_id'];
$this->refundInfo->notify_json = json_encode($this->callbackData);
if (!$this->refundInfo->save()) throw new Exception('更新退款订单失败');
}catch (Exception $e) {
$this->log->error(__CLASS__.':Function:manageGoodOrder:'.$e->getMessage().':orderId:'.$this->orderInfo->id);
throw new ErrException($e->getMessage());
}
}
/**
* @return void
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
protected function manageOrderByRefund(): void
{
try {
$isRefundMoney = $this->refundOrderModel->getSuccessMoneyByOrderId($this->refundInfo->order_id,$this->refundInfo->order_type);
if ($isRefundMoney <= 0) throw new Exception('订单退款金额错误');
if ($isRefundMoney == $this->orderInfo->actual_price) {
$this->orderInfo->status = OrderCode::FINISH_REFUND;
$this->orderInfo->is_refund_all = 1;//todo 感觉可以删除这个值
} else {
$this->orderInfo->status = OrderCode::UNCOMPLETED_REFUND;
}
if (!$this->orderInfo->save()) throw new Exception('更新退款订单失败');
}catch (Exception $e) {
$this->log->error(__CLASS__.':Function:manageGoodOrder:'.$e->getMessage().':orderId:'.$this->orderInfo->id);
throw new ErrException($e->getMessage());
}
}
}