diff --git a/app/Amqp/Consumer/RefundOrderConsumer.php b/app/Amqp/Consumer/RefundOrderConsumer.php index 311a5b6..cb2660a 100644 --- a/app/Amqp/Consumer/RefundOrderConsumer.php +++ b/app/Amqp/Consumer/RefundOrderConsumer.php @@ -9,6 +9,7 @@ use App\Lib\Log; use App\Model\Order; use App\Service\Amqp\Refund\GoodOrderAllRefundService; use App\Service\Amqp\Refund\RefundFactory; +use App\Service\Amqp\Refund\RefundService; use App\Service\ServiceTrait\Api\OrderTrait; use Exception; use Hyperf\Amqp\Message\Type; @@ -41,11 +42,6 @@ class RefundOrderConsumer extends ConsumerMessage #[Inject] protected Order $orderModel; - /** - * @var RefundFactory - */ - #[Inject] - protected RefundFactory $refundFactory; public function consumeMessage($data, AMQPMessage $message): Result { @@ -58,45 +54,10 @@ class RefundOrderConsumer extends ConsumerMessage $orderType = (int)$data['type']; $amount = $data['amount']; - $orderInfo = match ($orderType) { - OrderCode::ORDER_TYPE_GOOD => $this->orderModel->getInfoById($orderId), - default => null, - }; - - if (empty($orderInfo)) { - $this->log->debug('RefundOrderConsumer:error:NoOrderData:'.json_encode($data)); - return Result::ACK; - } - - if (!in_array($orderInfo->status,OrderCode::CAN_REFUND_STATUS)) { - $this->log->debug('CancelOrderConsumer:error:orderStatusError:'.json_encode($orderInfo->toArray())); - return Result::ACK; - } - - //余额订单必须退款全部金额 - if ($orderType == OrderCode::ORDER_TYPE_BALANCE && $amount != 0) { - $this->log->debug('CancelOrderConsumer:error:orderStatusError:'.json_encode($orderInfo->toArray())); - return Result::ACK; - } - try { - $service = null; + $service = new RefundService(); - switch ($orderType) { - case OrderCode::ORDER_TYPE_GOOD: - if ($amount == 0) { - $service = $this->refundFactory->newGoodOrderRefundAll(); - } else { - $service = $this->refundFactory->newGoodOrderRefundPart(); - } - break; - case OrderCode::ORDER_TYPE_BALANCE: - $service = $this->refundFactory->newBalanceOrderRefund(); - break; - default: - throw new Exception('service not found or orderType error'); - } } catch (Exception $e) { diff --git a/app/Constants/Common/RefundCode.php b/app/Constants/Common/RefundCode.php new file mode 100644 index 0000000..2aa5b3a --- /dev/null +++ b/app/Constants/Common/RefundCode.php @@ -0,0 +1,27 @@ +process(); } + + /** + * 微信js通道支付回调 + * @return ResponseInterface + * @throws ContainerExceptionInterface + * @throws InvalidParamsException + * @throws NotFoundExceptionInterface + */ + #[RequestMapping(path: "/common/wxPay/order/js/callBack", methods: "post")] + public function wxJsPayCallBack(): ResponseInterface + { + return (new WxJsRechargeOrderService)->callBackHandle(); + } + + /** + * 微信js通道退款回调 + * @return ResponseInterface + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws InvalidParamsException + */ + #[RequestMapping(path: "/common/wxPay/order/js/refund/callBack", methods: "post")] + public function wxJsRefundCallBack(): ResponseInterface + { + return (new WxJsRechargeOrderService)->refundCallBackHandle(); + } } diff --git a/app/Model/RefundOrder.php b/app/Model/RefundOrder.php new file mode 100644 index 0000000..7ceb29f --- /dev/null +++ b/app/Model/RefundOrder.php @@ -0,0 +1,98 @@ + 'integer', 'user_id' => 'integer', 'order_type' => 'integer', 'order_id' => 'integer', 'pay_id' => 'integer', 'refund_status' => 'integer', 'refund_type' => 'integer']; + + const string CREATED_AT = 'create_time'; + const string UPDATED_AT = 'update_time'; + + /** + * @param int $orderId + * @param int $type + * @return int|mixed|string + */ + public function getAllMoneyByOrderId(int $orderId, int $type): mixed + { + return $this + ->where('order_id', $orderId) + ->where('order_type', $type) + ->whereIn('refund_status',[ + RefundCode::WAIT_REFUND, + RefundCode::REFUND_SUCCESS + ])->sum('refund_amount') ?? 0; + } + + public function getSuccessMoneyByOrderId(int $orderId, int $type): mixed + { + return $this + ->where('order_id', $orderId) + ->where('order_type', $type) + ->whereIn('refund_status',[ + RefundCode::WAIT_REFUND, + RefundCode::REFUND_SUCCESS + ])->sum('refund_amount') ?? 0; + } + + /** + * @param int $id + * @param int $type + * @return \Hyperf\Database\Model\Model|null + */ + public function getInfoByOrderIdAndTypeWaitRefund(int $id,int $type): \Hyperf\Database\Model\Model|null + { + return $this->where('order_id',$id)->where('order_type',$type)->where('refund_status',RefundCode::WAIT_REFUND)->first(); + } + + /** + * @param string $orderSno + * @param int $type + * @return \Hyperf\Database\Model\Model|null + */ + public function getInfoByOrderSnoAndType(string $orderSno, int $type): \Hyperf\Database\Model\Model|null + { + return $this->where('order_sno',$orderSno)->where('order_type',$type)->first(); + } +} diff --git a/app/Service/Amqp/Refund/BalanceOrderRefundService.php b/app/Service/Amqp/Refund/BalanceOrderRefundService.php deleted file mode 100644 index ff00b52..0000000 --- a/app/Service/Amqp/Refund/BalanceOrderRefundService.php +++ /dev/null @@ -1,24 +0,0 @@ -type = OrderCode::ORDER_TYPE_GOOD; - } - - - public function handle() - { - $this->getOrderInfo(); - - $this->getPayOrder(); - - $this->getRefundAmount(); - - if ($this->refundAmount <= 0) throw new Exception('退款金额不能小于等于0'); - } -} \ No newline at end of file diff --git a/app/Service/Amqp/Refund/GoodOrderPartRefundService.php b/app/Service/Amqp/Refund/GoodOrderPartRefundService.php deleted file mode 100644 index 3ecfc1c..0000000 --- a/app/Service/Amqp/Refund/GoodOrderPartRefundService.php +++ /dev/null @@ -1,22 +0,0 @@ -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 退款 参考调起支付 - } -} \ No newline at end of file diff --git a/app/Service/Amqp/Refund/RefundFactory.php b/app/Service/Amqp/Refund/RefundFactory.php deleted file mode 100644 index 498cb73..0000000 --- a/app/Service/Amqp/Refund/RefundFactory.php +++ /dev/null @@ -1,30 +0,0 @@ -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(); + } +} \ No newline at end of file diff --git a/app/Service/Common/Pay/Wx/WxJsRechargeBaseService.php b/app/Service/Common/Pay/Wx/WxJsRechargeBaseService.php index d08fee8..a49ea6d 100644 --- a/app/Service/Common/Pay/Wx/WxJsRechargeBaseService.php +++ b/app/Service/Common/Pay/Wx/WxJsRechargeBaseService.php @@ -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()); + } + } } \ No newline at end of file diff --git a/app/Service/Common/Pay/Wx/WxJsRechargeOrderService.php b/app/Service/Common/Pay/Wx/WxJsRechargeOrderService.php index 0f108e7..0b9996d 100644 --- a/app/Service/Common/Pay/Wx/WxJsRechargeOrderService.php +++ b/app/Service/Common/Pay/Wx/WxJsRechargeOrderService.php @@ -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'; + } } \ No newline at end of file diff --git a/app/Service/ServiceTrait/Api/CheckOrderCallBackTrait.php b/app/Service/ServiceTrait/Api/CheckOrderCallBackTrait.php index cb41bc2..ed5aba4 100644 --- a/app/Service/ServiceTrait/Api/CheckOrderCallBackTrait.php +++ b/app/Service/ServiceTrait/Api/CheckOrderCallBackTrait.php @@ -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']; + } } \ No newline at end of file diff --git a/app/Service/ServiceTrait/Api/GoodOrderTrait.php b/app/Service/ServiceTrait/Api/GoodOrderTrait.php index 44779c5..961cb29 100644 --- a/app/Service/ServiceTrait/Api/GoodOrderTrait.php +++ b/app/Service/ServiceTrait/Api/GoodOrderTrait.php @@ -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 diff --git a/app/Service/ServiceTrait/Api/RefundOrderTrait.php b/app/Service/ServiceTrait/Api/RefundOrderTrait.php new file mode 100644 index 0000000..d80a796 --- /dev/null +++ b/app/Service/ServiceTrait/Api/RefundOrderTrait.php @@ -0,0 +1,89 @@ +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()); + } + } +} \ No newline at end of file