diff --git a/api/config/main.php b/api/config/main.php index dc9d5e6..79b792b 100644 --- a/api/config/main.php +++ b/api/config/main.php @@ -38,6 +38,16 @@ return [ 'prefix' => function ($message) { } ], + [ + 'class' => 'yii\log\FileTarget', + 'levels' => ['error', 'warning'], + 'categories' => ['refund_log'], + 'logFile' => '@app/runtime/logs/refund_log.log', + 'logVars' => [], + 'exportInterval' => 1, + 'prefix' => function ($message) { + } + ], ], ], 'user' => [ @@ -96,6 +106,7 @@ return [ 'extraPatterns' => [ 'GET web' => 'web', 'POST notify' => 'notify', + 'POST refund' => 'refund', 'POST test' => 'test', ] ], diff --git a/api/controllers/WxPaymentController.php b/api/controllers/WxPaymentController.php index adefd3d..543ac7f 100644 --- a/api/controllers/WxPaymentController.php +++ b/api/controllers/WxPaymentController.php @@ -73,4 +73,29 @@ class WxPaymentController extends CommonController $data = Yii::$app->request->getBodyParam('notify');/*int 商品id*/ return $data; } + + /** + * @return bool + * @throws BadRequestHttpException + * @throws \yii\db\Exception + * @throws \yii\web\NotFoundHttpException + * 申请退款 + */ + public function actionApplyRefund() + { + return $this->object->applyRefund(); + } + + /** + * @return mixed + * @throws BadRequestHttpException + * @throws \yii\db\Exception + * @throws \yii\web\NotFoundHttpException + * 退款 + */ + public function actionRefund() + { + return $this->object->refund(); + } + } \ No newline at end of file diff --git a/api/logic/WxPaymentLogic.php b/api/logic/WxPaymentLogic.php index 9988be7..6bd07df 100644 --- a/api/logic/WxPaymentLogic.php +++ b/api/logic/WxPaymentLogic.php @@ -8,7 +8,9 @@ namespace api\logic; +use backend\modules\shop\models\ars\Order; use backend\modules\shop\models\ars\PaymentLog; +use backend\modules\shop\models\ars\RefundLog; use backend\modules\shop\models\ars\WxPayConfig; use Yii; use EasyWeChat\Factory; @@ -17,12 +19,21 @@ use yii\helpers\Json; use yii\httpclient\Client; use yii\web\BadRequestHttpException; use yii\base\BaseObject; +use yii\web\NotFoundHttpException; class WxPaymentLogic extends BaseObject { /*支付类型*/ const PAY_TYPE_WEB = 1; const PAY_TYPE_MINI_PROGRAM = 2; + /*支付状态*/ + const STATUS_PAYMENT_WAITING = 0; + const STATUS_PAYMENT_SUCCESS = 1; + /*退款状态*/ + const STATUS_REFUND_WAIT = 0; //退款待审核 + const STATUS_REFUND_CONFIRM = 1; //退款待确认 + const STATUS_REFUND_SUCCESS = 2; //退款成功 + const STATUS_REFUND_PORTION = 3; //部分退款 /*发起支付方式*/ const TRADE_TYPE_JS_API = 'JSAPI'; @@ -36,10 +47,12 @@ class WxPaymentLogic extends BaseObject public $payType; public $app; public $order; + public $viewAction = 'view'; /** * @param $payType + * @return mixed * @throws BadRequestHttpException * @throws Exception * 微信统一下单 @@ -48,10 +61,9 @@ class WxPaymentLogic extends BaseObject { $this->payType = $payType; $unifyParams = $this->applyPaymentData(); - $this->unify($unifyParams); + return $this->unify($unifyParams); } - /** * @return array * @throws BadRequestHttpException @@ -96,13 +108,13 @@ class WxPaymentLogic extends BaseObject $paymentLog->payment_amount = $paymentAmount; $paymentLog->notify_url = $notifyUrl; $paymentLog->type = $this->payType; - $paymentLog->status = PaymentLog::PAYMENT_STATUS_WAITING; + $paymentLog->status = self::STATUS_PAYMENT_WAITING; if (!$paymentLog->save()) { throw new Exception(Helper::errorMessageStr($paymentLog->errors)); } } - private function getPaymentApp() + private function getApp() { $this->initObject(); $config = [ @@ -113,11 +125,11 @@ class WxPaymentLogic extends BaseObject 'key_path' => $this->keyPath, 'notify_url' => $this->notifyUrl, 'trade_type' => $this->tradeType, - 'sandbox' => true, // 设置为 false 或注释则关闭沙箱模式 +// 'sandbox' => true, // 设置为 false 或注释则关闭沙箱模式 ]; $this->app = Factory::payment($config); // 判断当前是否为沙箱模式: - $this->app->inSandbox(); +// $this->app->inSandbox(); } /** @@ -127,16 +139,16 @@ class WxPaymentLogic extends BaseObject { $path = Yii::getAlias('@backend'); $wxPayConfig = WxPayConfig::find()->one(); -// $wxConfig = WxConfig::find()->one(); -// switch ($this->payType) { -// case self::PAY_TYPE_WEB: -// $this->appId = trim($wxConfig->wx_appId); -// break; -// case self::PAY_TYPE_MINI_PROGRAM: -// $this->appId = trim($wxConfig->mini_program_appId); -// break; -// } - + switch ($this->payType) { + case self::PAY_TYPE_WEB: + // $wxConfig = WxConfig::find()->one(); +// $this->appId = trim($wxConfig->appid); + break; + case self::PAY_TYPE_MINI_PROGRAM: + // $miniProgramConfig = MiniProgramConfig::find()->one(); +// $this->appId = trim($miniProgramConfig->appid); + break; + } $this->mchId = $wxPayConfig->mch_id; $this->certPath = trim($path . $wxPayConfig->cert_path); $this->keyPath = trim($path . $wxPayConfig->key_path); @@ -150,7 +162,7 @@ class WxPaymentLogic extends BaseObject */ private function unify($unifyParams) { - $this->getPaymentApp(); + $this->getApp(); return $this->app->order->unify($unifyParams); } @@ -174,14 +186,17 @@ class WxPaymentLogic extends BaseObject if ($notifyData->result_code != 'SUCCESS' || $notifyData->return_code != 'SUCCESS') { throw new BadRequestHttpException('result_code or return_code is false'); } + $paymentLog = PaymentLog::findOne(['order_id' => $notifyData->out_trade_no]); $this->notifyUrl = Yii::$app->request->hostInfo . $paymentLog->notify_url; $paymentLog->mch_id = $notifyData->mch_id; - $paymentLog->wx_refund_id = $notifyData->transaction_id; //交易号 - $paymentLog->status = PaymentLog::PAYMENT_STATUS_SUCCESS; + $paymentLog->wx_payment_id = $notifyData->transaction_id; //交易号 + $paymentLog->status = self::STATUS_PAYMENT_SUCCESS; + $paymentLog->payment_at = time(); if (!$paymentLog->save()) { throw new Exception(Helper::errorMessageStr($paymentLog->errors)); } + if (!$tra->commit()) { throw new Exception('保存数据失败'); } @@ -195,13 +210,16 @@ class WxPaymentLogic extends BaseObject Yii::info($e->getMessage(), 'notify'); return false; } catch (BadRequestHttpException $e) { + $tra->rollBack(); + $this->forwardNotify($notifyData, false); Yii::info($e->getMessage(), 'notify'); return false; } } /** - * @param $notifyData + * @param $notifyData $tra->rollBack(); + * @param $status * @return bool * @throws \yii\base\InvalidConfigException @@ -230,6 +248,104 @@ class WxPaymentLogic extends BaseObject } } + /** + * @return bool + * @throws BadRequestHttpException + * @throws Exception + * @throws NotFoundHttpException + * 申请退款 + */ + public function applyRefund() + { + $orderId = Yii::$app->request->getBodyParam('order_id'); + $refundId = Yii::$app->request->getBodyParam('wx_refund_id'); + $refundAmount = Yii::$app->request->getBodyParam('refund_amount'); + $refundAccount = Yii::$app->request->getBodyParam('refund_account'); + $reason = Yii::$app->request->getBodyParam('reason'); + if (empty($orderId) || empty($refundId) || empty($refundAmount) || empty($reason)) { + throw new BadRequestHttpException(Helper::REQUEST_BAD_PARAMS); + } + $paymentLog = PaymentLog::findOne(['order_id' => $orderId]); + if (empty($paymentLog)) { + throw new NotFoundHttpException('订单支付信息未找到'); + } + if (RefundLog::findOne(['order_id' => $orderId, 'status' => self::STATUS_REFUND_WAIT])) { + throw new BadRequestHttpException('此订单存在等待审核的退款申请'); + } + + $refundedAmount = RefundLog::find() + ->where(['order_id' => $orderId, 'status' => self::STATUS_PAYMENT_SUCCESS]) + ->sum('refund_amount') ?? 0; + + $refundLog = new RefundLog(); + $refundLog->order_id = $orderId; + $refundLog->wx_refund_id = Helper::timeRandomNum(3, 'P'); + $refundLog->reason = $reason; + $refundLog->order_amount = $paymentLog->payment_amount; + $refundLog->refund_amount = $refundAmount; + $refundLog->refunded_amount = $refundedAmount; + $refundLog->type = $paymentLog->type; + $refundLog->status = self::STATUS_REFUND_WAIT; + $refundLog->refund_account = $refundAccount; + $refundLog->applyed_at = time(); + if (!$refundLog->save()) { + throw new Exception(Helper::errorMessageStr($refundLog->errors)); + } + return true; + } + + /** + * @return mixed + * @throws BadRequestHttpException + * @throws Exception + * @throws NotFoundHttpException + * 退款 + */ + public function refund() + { + $orderId = Yii::$app->request->getbodyParam('order_id'); + if (empty($orderId)) { + throw new BadRequestHttpException(Helper::REQUEST_BAD_PARAMS); + } + $paymentLog = PaymentLog::findOne(['order_id' => $orderId]); + if (empty($paymentLog)) { + throw new NotFoundHttpException('订单支付信息未找到'); + } + $refundLog = RefundLog::findOne(['order_id' => $orderId, 'status' => self::STATUS_REFUND_WAIT]); + if (!$refundLog) { + throw new NotFoundHttpException('订单退款信息未找到'); + } + + /*参数分别为:微信订单号、商户退款单号、订单金额、退款金额、其他参数*/ + $this->getApp(); + $config = ['refund_desc' => '退款']; + $result = $this->app->refund->byTransactionId( + $paymentLog->wx_refund_id, + $refundLog->wx_refund_id, + round($paymentLog->payment_amount * 100), + round($refundLog->refund_amount * 100), + $config + ); + Yii::info($result, 'refund_log'); + + if ($result['return_code'] == 'FAIL' || $result['return_msg'] != 'OK' || $result['result_code'] == 'FAIL') { + throw new BadRequestHttpException($result['return_msg']); + } + if ($result['err_code_des']) { + throw new BadRequestHttpException($result['err_code_des']); + } + $paymentLog->status = $refundLog->refund_amount < $paymentLog->payment_amount ? self::STATUS_REFUND_SUCCESS : self::STATUS_REFUND_PORTION; + if (!$paymentLog->save()) { + throw new Exception(Helper::errorMessageStr($paymentLog->errors)); + } + $refundLog->status = $refundLog->refund_amount < $paymentLog->payment_amount ? self::STATUS_REFUND_SUCCESS : self::STATUS_REFUND_PORTION; + $refundLog->finished_at = time(); + if (!$refundLog->save()) { + throw new Exception(Helper::errorMessageStr($refundLog->errors)); + } + return $result; + } + /** * @param $data diff --git a/backend/modules/shop/migrations/m191217_033724_add_column_payment_at_wx_payment_id_in_payment_log.php b/backend/modules/shop/migrations/m191217_033724_add_column_payment_at_wx_payment_id_in_payment_log.php new file mode 100644 index 0000000..62faaa9 --- /dev/null +++ b/backend/modules/shop/migrations/m191217_033724_add_column_payment_at_wx_payment_id_in_payment_log.php @@ -0,0 +1,29 @@ +addColumn('ats_payment_log', 'wx_payment_id', $this->string(64)->after('order_id')->comment('微信支付交易单号')); + $this->addColumn('ats_payment_log', 'payment_at', $this->integer()->comment('支付时间')); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + $this->dropColumn('ats_payment_log', 'wx_payment_id'); + $this->dropColumn('ats_payment_log', 'payment_at'); + return true; + } + +} diff --git a/backend/modules/shop/migrations/m191217_034337_alter_column_order_id_in_payment_log_refund_log.php b/backend/modules/shop/migrations/m191217_034337_alter_column_order_id_in_payment_log_refund_log.php new file mode 100644 index 0000000..0186b45 --- /dev/null +++ b/backend/modules/shop/migrations/m191217_034337_alter_column_order_id_in_payment_log_refund_log.php @@ -0,0 +1,28 @@ +alterColumn('ats_payment_log', 'order_id', $this->string()->comment('订单号')); + $this->alterColumn('ats_refund_log', 'order_id', $this->string()->comment('订单号')); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + return true; + } + + +} diff --git a/backend/modules/shop/models/ars/PaymentLog.php b/backend/modules/shop/models/ars/PaymentLog.php index 9a716d7..8d1e662 100755 --- a/backend/modules/shop/models/ars/PaymentLog.php +++ b/backend/modules/shop/models/ars/PaymentLog.php @@ -10,6 +10,7 @@ use yii\behaviors\TimestampBehavior; * * @property int $id * @property int $order_id 订单id + * @property string $wx_payment_id 微信退款单号 * @property string $wx_refund_id 微信退款单号 * @property string $mch_id 商户号 * @property int $order_amount 订单金额 @@ -20,12 +21,10 @@ use yii\behaviors\TimestampBehavior; * @property string $refund_account 退款账户 * @property int $updated_at 更新时间 * @property int $created_at 创建时间 + * @property int $payment_at 支付时间 */ class PaymentLog extends \yii\db\ActiveRecord { - const PAYMENT_STATUS_WAITING = 0; - const PAYMENT_STATUS_SUCCESS = 1; - /** * {@inheritdoc} */ @@ -40,9 +39,9 @@ class PaymentLog extends \yii\db\ActiveRecord public function rules() { return [ - [['order_id', 'order_amount', 'payment_amount', 'type', 'status'], 'integer'], - [['wx_refund_id', 'mch_id', 'refund_account'], 'string', 'max' => 64], - [['notify_url'], 'string', 'max' => 255], + [['order_amount', 'payment_amount', 'type', 'status', 'payment_at'], 'integer'], + [['wx_payment_id', 'wx_refund_id', 'mch_id', 'refund_account'], 'string', 'max' => 64], + [['order_id', 'notify_url'], 'string', 'max' => 255], ]; } @@ -54,6 +53,7 @@ class PaymentLog extends \yii\db\ActiveRecord return [ 'id' => 'id', 'order_id' => '订单id', + 'wx_payment_id' => '微信支付单号', 'wx_refund_id' => '微信退款单号', 'mch_id' => '商户号', 'order_amount' => '订单金额',