Browse Source

feat: 退款模块、物流模块

wechat_public_accounts
ww519441258 5 years ago
parent
commit
60998c77b4
  1. 12
      api/config/main.php
  2. 40
      api/controllers/WxPaymentController.php
  3. 246
      api/logic/WxPaymentLogic.php
  4. 10
      backend/config/main.php
  5. 14
      backend/config/params.php
  6. 358
      backend/modules/payment/logic/WxPaymentManager.php
  7. 8
      backend/modules/shop/controllers/DeliveryController.php
  8. 20
      backend/modules/shop/controllers/OrderController.php
  9. 69
      backend/modules/shop/logic/delivery/DeliveryManager.php
  10. 71
      backend/modules/shop/logic/payment/WxPaymentManager.php
  11. 41
      backend/modules/shop/migrations/m191218_122251_add_column_invoice_sn_in_delivery.php
  12. 11
      backend/modules/shop/models/ars/Delivery.php
  13. 2
      backend/modules/shop/models/ars/WxPayConfig.php
  14. 7
      backend/modules/shop/models/searchs/DeliverySearch.php
  15. 16
      backend/modules/shop/models/searchs/OrderSearch.php
  16. 4
      backend/modules/shop/views/delivery/_form.php
  17. 37
      backend/modules/shop/views/delivery/info.php
  18. 78
      backend/modules/shop/views/delivery/logistics.php
  19. 11
      backend/modules/shop/views/delivery/view.php
  20. 11
      backend/modules/shop/views/order/logistics.php
  21. 1
      backend/modules/shop/views/order/refund.php
  22. 5
      common/config/params.php

12
api/config/main.php

@ -38,16 +38,6 @@ return [
'prefix' => function ($message) { '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' => [ 'user' => [
@ -104,9 +94,7 @@ return [
'controller' => 'wx-payment', 'controller' => 'wx-payment',
'pluralize' => false, 'pluralize' => false,
'extraPatterns' => [ 'extraPatterns' => [
'GET web' => 'web',
'POST notify' => 'notify', 'POST notify' => 'notify',
'POST apply-refund' => 'apply-refund',
] ]
], ],
], ],

40
api/controllers/WxPaymentController.php

@ -8,10 +8,6 @@
namespace api\controllers; namespace api\controllers;
use api\logic\WxPaymentLogic;
use yii\filters\auth\HttpBearerAuth;
use yii\helpers\ArrayHelper;
use yii\web\Response; use yii\web\Response;
use Yii; use Yii;
use yii\web\BadRequestHttpException; use yii\web\BadRequestHttpException;
@ -21,16 +17,6 @@ class WxPaymentController extends CommonController
public $modelClass = 'backend\modules\shop\models\ars\Order'; public $modelClass = 'backend\modules\shop\models\ars\Order';
public $className = 'api\logic\WxPaymentLogic'; public $className = 'api\logic\WxPaymentLogic';
public function behaviors()
{
return ArrayHelper::merge(parent::behaviors(), [
'authenticatior' => [
'class' => HttpBearerAuth::className(),
'except' => ['notify'],
]
]);
}
public function actions() public function actions()
{ {
$action = parent::actions(); $action = parent::actions();
@ -38,23 +24,9 @@ class WxPaymentController extends CommonController
unset($action['update']); unset($action['update']);
unset($action['view']); unset($action['view']);
unset($action['delete']); unset($action['delete']);
$action['options'] = [
'class' => 'yii\rest\OptionsAction',
'collectionOptions' => ['PUT', 'GET', 'OPTIONS']
];
return $action; return $action;
} }
public function actionWeb()
{
return $this->object->wxPayment(WxPaymentLogic::PAY_TYPE_WEB);
}
public function actionMiniProgram()
{
return $this->object->wxPayment(WxPaymentLogic::PAY_TYPE_MINI_PROGRAM);
}
/** /**
* @return array|bool * @return array|bool
* @throws BadRequestHttpException * @throws BadRequestHttpException
@ -68,16 +40,4 @@ class WxPaymentController extends CommonController
return $this->object->notify(); return $this->object->notify();
} }
/**
* @return bool
* @throws BadRequestHttpException
* @throws \yii\db\Exception
* @throws \yii\web\NotFoundHttpException
* 申请退款
*/
public function actionApplyRefund()
{
return $this->object->applyRefund();
}
} }

246
api/logic/WxPaymentLogic.php

@ -8,166 +8,16 @@
namespace api\logic; 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;
use yii\db\Exception;
use backend\modules\payment\logic\WxPaymentManager;
use yii\helpers\Json; use yii\helpers\Json;
use yii\httpclient\Client; use yii\httpclient\Client;
use yii\web\BadRequestHttpException; use yii\web\BadRequestHttpException;
use yii\base\BaseObject;
use yii\web\NotFoundHttpException;
use yii\web\ServerErrorHttpException;
use backend\modules\shop\models\ars\PaymentLog;
use yii\db\Exception;
use Yii;
class WxPaymentLogic extends BaseObject
class WxPaymentLogic extends WxPaymentManager
{ {
/*支付类型*/
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';
public $appId;
public $mchId;
public $key;
public $certPath;
public $keyPath;
public $notifyUrl;
public $tradeType;
public $payType;
public $app;
public $order;
public $viewAction = 'view';
/**
* @param $payType
* @return mixed
* @throws BadRequestHttpException
* @throws Exception
* 微信统一下单
*/
public function wxPayment($payType)
{
$this->payType = $payType;
$unifyParams = $this->applyPaymentData();
return $this->unify($unifyParams);
}
/**
* @return array
* @throws BadRequestHttpException
* @throws Exception
* 生成支付参数
*/
private function applyPaymentData()
{
$orderId = Yii::$app->request->getBodyParam('order_id');/*int 商品id*/
$paymentAmount = Yii::$app->request->getBodyParam('payment_amount');/*int 商品id*/
$notifyUrl = Yii::$app->request->getBodyParam('notify_url');/*int 商品id*/
if (empty($orderId) || empty($paymentAmount) || empty($notifyUrl)) {
throw new BadRequestHttpException(Helper::REQUEST_BAD_PARAMS);
}
$this->tradeType = self::TRADE_TYPE_JS_API;
$this->savePaymentLog($orderId, $paymentAmount, $notifyUrl);
$params = [
'body' => '订单支付',
'out_trade_no' => $orderId,
'total_fee' => round($paymentAmount * 100),
'openid' => Yii::$app->user->identity->wx_openid,
];
return $params;
}
/**
* @param string $orderId
* @param float $paymentAmount
* @param string $notifyUrl
* @throws Exception
* 保存支付信息
*/
private function savePaymentLog($orderId, $paymentAmount, $notifyUrl)
{
$paymentLog = PaymentLog::findOne(['order_id' => $this->order->order_sn]);
if (!$paymentLog) {
$paymentLog = new PaymentLog();
}
$paymentLog->order_id = $orderId;
$paymentLog->payment_amount = $paymentAmount;
$paymentLog->notify_url = $notifyUrl;
$paymentLog->type = $this->payType;
$paymentLog->status = self::STATUS_PAYMENT_WAITING;
if (!$paymentLog->save()) {
throw new Exception(Helper::errorMessageStr($paymentLog->errors));
}
}
protected function getApp()
{
$this->initObject();
$config = [
'app_id' => $this->appId,
'mch_id' => $this->mchId,
'key' => $this->key,
'cert_path' => $this->certPath,
'key_path' => $this->keyPath,
'notify_url' => $this->notifyUrl,
'trade_type' => $this->tradeType,
// 'sandbox' => true, // 设置为 false 或注释则关闭沙箱模式
];
$this->app = Factory::payment($config);
// 判断当前是否为沙箱模式:
// $this->app->inSandbox();
}
/**
* @var WxPayConfig $wxPayConfig
*/
private function initObject()
{
$path = Yii::getAlias('@backend');
$wxPayConfig = WxPayConfig::find()->one();
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);
$this->notifyUrl = Yii::$app->request->hostInfo . '/wx-payment/notify';
}
/**
* @param $unifyParams
* @return mixed
* 统一下单
*/
private function unify($unifyParams)
{
$this->getApp();
return $this->app->order->unify($unifyParams);
}
/** /**
* @return array|bool * @return array|bool
* @throws BadRequestHttpException * @throws BadRequestHttpException
@ -219,15 +69,14 @@ class WxPaymentLogic extends BaseObject
} }
/** /**
* @param $notifyData $tra->rollBack();
* @param $notifyData
* @param $status * @param $status
* @return bool * @return bool
* @throws \yii\base\InvalidConfigException * @throws \yii\base\InvalidConfigException
* @throws \yii\httpclient\Exception * @throws \yii\httpclient\Exception
* 转发异步回调信息 * 转发异步回调信息
*/ */
private function forwardNotify($notifyData, $status)
protected function forwardNotify($notifyData, $status)
{ {
$notify = [ $notify = [
'notify' => [ 'notify' => [
@ -249,85 +98,4 @@ 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;
}
/**
* @param $data
* @return bool
* 支付成功回调验证签名和支付金额
*/
public function checkSign($data)
{
$this->initObject();
$notifySign = $data['sign'];
unset($data['sign']);
$sign = $this->_sign($data);
if ($notifySign == $sign) {
return true;
} else {
return false;
}
}
/**
* @param $arr
* @return string
* 微信签名方法
*/
private function _sign($arr)
{
$arr = array_filter($arr);
ksort($arr);
$arr['key'] = $this->key;
$queryString = http_build_query($arr);
$queryString = urldecode($queryString);
return strtoupper(md5($queryString));
}
} }

10
backend/config/main.php

@ -53,6 +53,16 @@ return [
'prefix' => function ($message) { '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) {
}
],
], ],
], ],
'errorHandler' => [ 'errorHandler' => [

14
backend/config/params.php

@ -1,4 +1,18 @@
<?php <?php
return [ return [
'adminEmail' => 'admin@example.com', 'adminEmail' => 'admin@example.com',
'logistics' => [
'SELF' => '商家配送',
'shunfeng' => '顺丰速运',
'suteng' => '速腾快递',
'shentong' => '申通快递',
'zhongtong' => '中通快递',
'yuantong' => '圆通快递',
'huitong' => '百世快递(原汇通)',
'yunda' => '韵达快递',
'yousu' => 'UC优速快递',
'gnxb' => '邮政小包',
'youzhengguonei' => '邮政包裹/平邮/挂号信',
'jingdong' => '京东快递',
],
]; ];

358
backend/modules/payment/logic/WxPaymentManager.php

@ -0,0 +1,358 @@
<?php
namespace backend\modules\payment\logic;
use api\logic\Helper;
use backend\modules\shop\models\ars\PaymentLog;
use backend\modules\shop\models\ars\RefundLog;
use backend\modules\shop\models\ars\WxPayConfig;
use EasyWeChat\Factory;
use yii\db\Exception;
use yii\helpers\Json;
use yii\web\BadRequestHttpException;
use yii\web\NotFoundHttpException;
use Yii;
class WxPaymentManager
{
/*支付类型*/
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';
public $appId;
public $mchId;
public $key;
public $certPath;
public $keyPath;
public $notifyUrl;
public $tradeType;
public $payType;
public $app;
public $order;
/**
* @param $orderId
* @param $orderAmount
* @param $paymentAmount
* @param $notifyUrl
* @param $payType
* @return mixed
* @throws BadRequestHttpException
* @throws Exception
* 微信统一下单
*/
public function wxPayment($orderId, $orderAmount, $paymentAmount, $notifyUrl, $payType)
{
if (empty($orderId) || empty($orderAmount) ||empty($paymentAmount) || empty($notifyUrl) || empty($payType)) {
throw new BadRequestHttpException(Helper::REQUEST_BAD_PARAMS);
}
$this->tradeType = self::TRADE_TYPE_JS_API;
$this->payType = $payType;
$this->notifyUrl = $notifyUrl;
$unifyParams = $this->applyPaymentData($orderId, $orderAmount, $paymentAmount, $notifyUrl);
$result = $this->unify($unifyParams);
if ($result['return_code'] == 'FAIL') {
throw new BadRequestHttpException($result['return_msg']);
}
if ($result['return_code'] == 'SUCCESS' && $result['return_msg'] == 'OK' && $result['result_code'] == 'FAIL') {
throw new BadRequestHttpException($result['err_code_des']);
}
return Json::decode($this->getBridgeConfig($result['prepay_id']), true);
}
/**
* @param $prepayId
* @return array|string
*/
private function getBridgeConfig($prepayId)
{
$this->initObject();
return $this->app->jssdk->bridgeConfig($prepayId);
}
/**
* @param string $orderId
* @param int $orderAmount
* @param int $paymentAmount
* @param string $notifyUrl
* @return array
* @throws BadRequestHttpException
* @throws Exception
* 生成支付参数
*/
private function applyPaymentData($orderId, $orderAmount, $paymentAmount, $notifyUrl)
{
$this->savePaymentLog($orderId, $orderAmount, $paymentAmount, $notifyUrl);
$params = [
'body' => '订单支付',
'out_trade_no' => $orderId,
'total_fee' => $paymentAmount,
'openid' => Yii::$app->user->identity->wx_openid,
];
return $params;
}
/**
* @param string $orderId
* @param int $orderAmount
* @param int $paymentAmount
* @param string $notifyUrl
* @throws BadRequestHttpException
* @throws Exception
* 保存支付信息
*/
private function savePaymentLog($orderId, $orderAmount, $paymentAmount, $notifyUrl)
{
$paymentLog = PaymentLog::findOne(['order_id' => $orderId]);
if (!$paymentLog) {
$paymentLog = new PaymentLog();
} elseif (!empty($paymentLog->wx_payment_id)) {
throw new BadRequestHttpException('此订单号也有支付信息,不能重复');
}
$paymentLog->order_id = $orderId;
$paymentLog->order_amount = $orderAmount;
$paymentLog->payment_amount = $paymentAmount;
$paymentLog->notify_url = $this->notifyUrl;
$paymentLog->type = $this->payType;
$paymentLog->status = self::STATUS_PAYMENT_WAITING;
if (!$paymentLog->save()) {
throw new Exception(Helper::errorMessageStr($paymentLog->errors));
}
}
protected function getApp()
{
$this->initObject();
$config = [
'app_id' => $this->appId,
'mch_id' => $this->mchId,
'key' => $this->key,
'cert_path' => $this->certPath,
'key_path' => $this->keyPath,
'notify_url' => $this->notifyUrl,
'trade_type' => $this->tradeType,
'sandbox' => true, // 设置为 false 或注释则关闭沙箱模式
];
$this->app = Factory::payment($config);
//判断当前是否为沙箱模式:
$this->app->inSandbox();
}
/**
* @var WxPayConfig $wxPayConfig
*/
private function initObject()
{
$path = Yii::getAlias('@backend');
$wxPayConfig = WxPayConfig::find()->one();
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 = trim($wxPayConfig->mch_id);
$this->key = trim($wxPayConfig->key);
$this->certPath = trim($path . $wxPayConfig->cert_path);
$this->keyPath = trim($path . $wxPayConfig->key_path);
$this->notifyUrl = Yii::$app->request->hostInfo . '/wx-payment/notify';
$this->mchId = '1395812402';
$this->key = '51CF5EE3B2E35B9843E17E6099325A65';
}
/**
* @param $unifyParams
* @return mixed
* 统一下单
*/
private function unify($unifyParams)
{
$unifyParams['trade_type'] = $this->tradeType;
$this->getApp();
return $this->app->order->unify($unifyParams);
}
/**
* @param $data
* @return bool
* 支付成功回调验证签名和支付金额
*/
public function checkSign($data)
{
$this->initObject();
$notifySign = $data['sign'];
unset($data['sign']);
$sign = $this->_sign($data);
if ($notifySign == $sign) {
return true;
} else {
return false;
}
}
/**
* @param $arr
* @return string
* 微信签名方法
*/
private function _sign($arr)
{
$arr = array_filter($arr);
ksort($arr);
$arr['key'] = $this->key;
$queryString = http_build_query($arr);
$queryString = urldecode($queryString);
return strtoupper(md5($queryString));
}
/*************************************** Refund ************************************************/
/**
* @param string $orderId
* @param int $refundAmount
* @param string $refundAccount
* @param int $reason
* @return RefundLog
* @throws BadRequestHttpException
* @throws Exception
* @throws NotFoundHttpException
* 申请退款
*/
public function applyRefund($orderId, $refundAmount, $refundAccount, $reason)
{
if (empty($orderId) || empty($refundAmount) || empty($refundAccount) || 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, 'R');
$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 $refundLog;
}
/**
* @param string $orderId
* @param int $refundAmount
* @param int $operatorId
* @return array|mixed
* @throws BadRequestHttpException
* @throws NotFoundHttpException
*/
public function refund($orderId, $refundAmount, $operatorId)
{
$paymentLog = PaymentLog::findOne(['order_id' => $orderId]);
if (empty($paymentLog)) {
throw new NotFoundHttpException('订单支付信息未找到');
}
$refundLog = RefundLog::findOne(['order_id' => $orderId, 'status' => self::STATUS_REFUND_WAIT]);
if (empty($refundLog)) {
throw new NotFoundHttpException('订单退款信息未找到');
}
$this->executeRefund($paymentLog, $refundLog, $refundAmount);
return $this->updateRefundInfo($paymentLog, $refundLog, $operatorId);
}
/**
* @param PaymentLog $paymentLog
* @param RefundLog $refundLog
* @param $refundAmount
* @throws BadRequestHttpException
*/
private function executeRefund($paymentLog, $refundLog, $refundAmount)
{
/*参数分别为:微信订单号、商户退款单号、订单金额、退款金额、其他参数*/
$this->getApp();
$config = [
'refund_desc' => '退款'
];
$result = $this->app->refund->byTransactionId(
$paymentLog->wx_payment_id,
$refundLog->wx_refund_id,
$paymentLog->payment_amount,
$refundAmount,
$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']);
}
}
/**
* @param PaymentLog $paymentLog
* @param RefundLog $refundLog
* @param int $operatorId
* @return array
*/
private function updateRefundInfo($paymentLog, $refundLog, $operatorId)
{
$tra = Yii::$app->db->beginTransaction();
try {
$paymentLog->wx_refund_id = $refundLog->wx_refund_id;
$paymentLog->refund_account = $refundLog->refund_account;
$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->operator_id = $operatorId;
$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));
}
$tra->commit();
return ['status' => true];
} catch (Exception $e) {
$tra->rollBack();
return ['status' => false, 'info' => $e->getMessage()];
}
}
}

8
backend/modules/shop/controllers/DeliveryController.php

@ -2,6 +2,7 @@
namespace backend\modules\shop\controllers; namespace backend\modules\shop\controllers;
use backend\modules\shop\logic\delivery\DeliveryManager;
use Yii; use Yii;
use backend\modules\shop\models\ars\Delivery; use backend\modules\shop\models\ars\Delivery;
use backend\modules\shop\models\searchs\DeliverySearch; use backend\modules\shop\models\searchs\DeliverySearch;
@ -53,8 +54,12 @@ class DeliveryController extends Controller
*/ */
public function actionView($id) public function actionView($id)
{ {
$model = $this->findModel($id);
$logistics = DeliveryManager::queryLogistics($model->order_id);
return $this->render('view', [ return $this->render('view', [
'model' => $this->findModel($id),
'model' => $model,
'logistics' => $logistics,
]); ]);
} }
@ -146,4 +151,5 @@ class DeliveryController extends Controller
'columns' => $searchModel->columns() 'columns' => $searchModel->columns()
]); ]);
} }
} }

20
backend/modules/shop/controllers/OrderController.php

@ -102,11 +102,11 @@ class OrderController extends Controller
} }
/** /**
* Deletes an existing Order model.
* If deletion is successful, the browser will be redirected to the 'index' page.
* @param integer $id
* @return mixed
* @throws NotFoundHttpException if the model cannot be found
* @param $id
* @return \yii\web\Response
* @throws NotFoundHttpException
* @throws \Throwable
* @throws \yii\db\StaleObjectException
*/ */
public function actionDelete($id) public function actionDelete($id)
{ {
@ -190,15 +190,15 @@ class OrderController extends Controller
public function actionRefund($id) public function actionRefund($id)
{ {
$model = RefundLog::findOne([ $model = RefundLog::findOne([
'order_id' => $id,
'order_id' => Order::findOne($id)->order_sn,
'status' => WxPaymentLogic::STATUS_REFUND_WAIT 'status' => WxPaymentLogic::STATUS_REFUND_WAIT
]); ]);
if (Yii::$app->request->post('RefundLog')) {
if (Yii::$app->request->post()) {
$wxPayment = Yii::createObject([ $wxPayment = Yii::createObject([
'class' => 'backend\modules\shop\logic\payment\WxPaymentManager'
'class' => 'backend\modules\payment\logic\WxPaymentManager'
]); ]);
$res = $wxPayment->refund($model, Yii::$app->user->id);
$res = $wxPayment->refund($model->order_id, $model->refund_amount, Yii::$app->user->id);
if ($res['status']) { if ($res['status']) {
return $this->redirect(['index']); return $this->redirect(['index']);
} else { } else {
@ -207,7 +207,7 @@ class OrderController extends Controller
} }
return $this->render('refund', [ return $this->render('refund', [
'model' => $model
'model' => $model,
]); ]);
} }

69
backend/modules/shop/logic/delivery/DeliveryManager.php

@ -10,9 +10,13 @@ use backend\modules\shop\models\ars\OrderGoods;
use Yii; use Yii;
use yii\db\Exception; use yii\db\Exception;
use yii\helpers\ArrayHelper; use yii\helpers\ArrayHelper;
use yii\helpers\Json;
use yii\web\NotFoundHttpException;
class DeliveryManager class DeliveryManager
{ {
const LOGISTICS_URL = 'http://route.showapi.com/64-19';
/** /**
* @param Order $order * @param Order $order
* @param Delivery $delivery * @param Delivery $delivery
@ -40,7 +44,7 @@ class DeliveryManager
throw new Exception('order shipping_status update false'); throw new Exception('order shipping_status update false');
} }
if ($orderStatus == Order::STATUS_SHIPMENT_PORTION || Delivery::findOne(['!=', 'id', $delivery->id])) {
if ($orderStatus == Order::STATUS_SHIPMENT_PORTION || Delivery::find()->where(['!=', 'id', $delivery->id])->andWhere(['order_id' => $delivery->order_id])->one()) {
$delivery->type = Delivery::TYPE_SHIPMENT_PORTION; $delivery->type = Delivery::TYPE_SHIPMENT_PORTION;
} else { } else {
$delivery->type = Delivery::TYPE_SHIPMENT_ALL; $delivery->type = Delivery::TYPE_SHIPMENT_ALL;
@ -155,9 +159,70 @@ class DeliveryManager
$data['unShipped'][] = $orderGoods; $data['unShipped'][] = $orderGoods;
} }
} }
return $data; return $data;
} }
/**
* @param $orderId
* @return array|mixed
* @throws NotFoundHttpException
* 查询物流接口
*/
public static function queryLogistics($orderId)
{
$delivery = Delivery::findOne(['order_id' => $orderId]);
if (!$delivery) {
throw new NotFoundHttpException('发货信息未找到');
}
$cache = Yii::$app->cache;
if ($cache->exists($delivery->shipping_id)) {
return $cache->get($delivery->shipping_id);
}
$logisticsParams = self::createLogisticsParam($delivery); //创建参数(包括签名的处理)
$url = self::LOGISTICS_URL . '?' . $logisticsParams;
$result = Json::decode(file_get_contents($url), false);
if (isset($result->showapi_res_body)) {
$cache->set($delivery->shipping_id, $result->showapi_res_body, 7200); //设置缓存2个小时
return $result->showapi_res_body;
} else {
return [];
}
}
/**
* @param Delivery $delivery
* @return string
* 查询物流 创建参数(包括签名的处理)
*/
private static function createLogisticsParam($delivery)
{
$params = [
'showapi_appid' => Yii::$app->params['logistics_config']['logistics_appid'],
'com' => $delivery->shipping_name , //物流单位
'nu' => $delivery->invoice_sn, //运单号
//添加其他参数
];
$paraStr = "";
$signStr = "";
ksort($params);
foreach ($params as $key => $val) {
if ($key != '' && $val != '') {
$signStr .= $key.$val;
$paraStr .= $key . '=' . urlencode($val) . '&';
}
}
$signStr .= Yii::$app->params['logistics_config']['logistics_secret'];
//排好序的参数加上secret,进行md5,将md5后的值作为参数,便于服务器的效验
$sign = strtolower(md5($signStr));
$paraStr .= 'showapi_sign=' . $sign;
return $paraStr;
}
} }

71
backend/modules/shop/logic/payment/WxPaymentManager.php

@ -1,71 +0,0 @@
<?php
namespace backend\modules\shop\logic\payment;
use api\logic\Helper;
use api\logic\WxPaymentLogic;
use backend\modules\shop\models\ars\PaymentLog;
use backend\modules\shop\models\ars\RefundLog;
use yii\db\Exception;
use yii\web\BadRequestHttpException;
use yii\web\NotFoundHttpException;
use Yii;
class WxPaymentManager extends WxPaymentLogic
{
/**
* @param RefundLog $refundLog
* @param int $operatorId
* @return array|mixed
* @throws BadRequestHttpException
* @throws NotFoundHttpException
*/
public function refund($refundLog, $operatorId)
{
$paymentLog = PaymentLog::findOne(['order_id' => $refundLog->order_id]);
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']);
}
$tra = Yii::$app->db->beginTransaction();
try {
$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->operator_id = $operatorId;
$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));
}
$tra->commit();
return ['status' => 'true'];
} catch (Exception $e) {
$tra->rollBack();
return ['status' => false, 'info' => $e->getMessage()];
}
}
}

41
backend/modules/shop/migrations/m191218_122251_add_column_invoice_sn_in_delivery.php

@ -0,0 +1,41 @@
<?php
use yii\db\Migration;
/**
* Class m191218_122251_add_column_invoice_sn_in_delivery
*/
class m191218_122251_add_column_invoice_sn_in_delivery extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$this->addColumn('ats_delivery', 'invoice_sn', $this->string()->after('shipping_id')->comment('运单号'));
}
/**
* {@inheritdoc}
*/
public function safeDown()
{
$this->dropColumn('ats_delivery', 'invoice_sn');
return true;
}
/*
// Use up()/down() to run migration code without a transaction.
public function up()
{
}
public function down()
{
echo "m191218_122251_add_column_invoice_sn_in_delivery cannot be reverted.\n";
return false;
}
*/
}

11
backend/modules/shop/models/ars/Delivery.php

@ -12,6 +12,7 @@ use yii\behaviors\TimestampBehavior;
* @property int $order_id 订单id * @property int $order_id 订单id
* @property string $shipping_name 货流名称 * @property string $shipping_name 货流名称
* @property string $shipping_id 运货单位 * @property string $shipping_id 运货单位
* @property string $invoice_sn 运单号
* @property int $type 类型 * @property int $type 类型
* @property string $goods 商品 * @property string $goods 商品
* @property int $status 状态 * @property int $status 状态
@ -45,6 +46,7 @@ class Delivery extends \yii\db\ActiveRecord
[['goods', 'decription'], 'string'], [['goods', 'decription'], 'string'],
[['shipping_name'], 'string', 'max' => 50], [['shipping_name'], 'string', 'max' => 50],
[['shipping_id'], 'string', 'max' => 10], [['shipping_id'], 'string', 'max' => 10],
[['invoice_sn'], 'string', 'max' => 255],
]; ];
} }
@ -58,6 +60,7 @@ class Delivery extends \yii\db\ActiveRecord
'order_id' => '订单id', 'order_id' => '订单id',
'shipping_name' => '货流名称', 'shipping_name' => '货流名称',
'shipping_id' => '运货单位', 'shipping_id' => '运货单位',
'invoice_sn' => '运单号',
'type' => '类型', 'type' => '类型',
'goods' => '商品', 'goods' => '商品',
'status' => '状态', 'status' => '状态',
@ -112,4 +115,12 @@ class Delivery extends \yii\db\ActiveRecord
return array_key_exists($column, $dropDownList) ? $dropDownList[$column] : false; return array_key_exists($column, $dropDownList) ? $dropDownList[$column] : false;
} }
public function beforeSave($insert)
{
if ($insert) {
$this->shipping_name = Yii::$app->params['logistics'][$this->shipping_id];
}
return parent::beforeSave($insert); // TODO: Change the autogenerated stub
}
} }

2
backend/modules/shop/models/ars/WxPayConfig.php

@ -13,7 +13,7 @@ use yii\redis\ActiveRecord;
* @property int $id * @property int $id
* @property int $user_id 用户id * @property int $user_id 用户id
* @property int $mch_id 商户号 * @property int $mch_id 商户号
* @property string $key
* @property string $key 支付秘钥
* @property string $cert_path cert文件路径 * @property string $cert_path cert文件路径
* @property string $key_path key文件路径 * @property string $key_path key文件路径
*/ */

7
backend/modules/shop/models/searchs/DeliverySearch.php

@ -65,7 +65,7 @@ class DeliverySearch extends Delivery
'width' => '10%', 'width' => '10%',
], ],
[ [
'attribute' => 'shipping_id',
'attribute' => 'invoice_sn',
'width' => '10%', 'width' => '10%',
], ],
[ [
@ -99,11 +99,6 @@ class DeliverySearch extends Delivery
'icon' => 'list', 'icon' => 'list',
'title' => '详情', 'title' => '详情',
], ],
[
'name' => 'update',
'icon' => 'pencil',
'title' => '修改'
],
], ],
], ],

16
backend/modules/shop/models/searchs/OrderSearch.php

@ -135,6 +135,11 @@ class OrderSearch extends Order
'icon' => 'list', 'icon' => 'list',
'title' => '详情', 'title' => '详情',
], ],
[
'name' => 'update',
'icon' => 'pencil',
'title' => '修改'
],
[ [
'name' => 'delivery', 'name' => 'delivery',
'icon' => 'box', 'icon' => 'box',
@ -163,9 +168,9 @@ class OrderSearch extends Order
] ]
], ],
[ [
'name' => 'delivery',
'icon' => 'box',
'title' => '发货',
'name' => 'refund',
'icon' => 'dollar',
'title' => '退款',
'hide' => [ 'hide' => [
'attributes' => [ 'attributes' => [
'status', 'status',
@ -188,11 +193,6 @@ class OrderSearch extends Order
'rule' => 'or' 'rule' => 'or'
] ]
], ],
[
'name' => 'update',
'icon' => 'pencil',
'title' => '修改'
]
], ],
], ],
]; ];

4
backend/modules/shop/views/delivery/_form.php

@ -12,9 +12,9 @@ use yii\bootstrap4\ActiveForm;
<?php $form = ActiveForm::begin(); ?> <?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'shipping_name')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'shipping_id')->dropDownList(Yii::$app->params['logistics']) ?>
<?= $form->field($model, 'shipping_id')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'invoice_sn')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'status')->textInput() ?> <?= $form->field($model, 'status')->textInput() ?>

37
backend/modules/shop/views/delivery/info.php

@ -0,0 +1,37 @@
<?php
use yii\widgets\DetailView;
use backend\modules\shop\models\ars\Delivery;
?>
<?=
DetailView::widget([
'model' => $model,
'attributes' => [
'id',
'order_id',
'shipping_name',
'shipping_id',
[
'attribute' => 'type',
'value' => function ($model) {
return Delivery::dropDown('type', $model->type);
}
],
'status',
'decription:ntext',
[
'attribute' => 'created_at',
'value' => function ($model) {
return date('Y-m-d H:i:s', $model->created_at);
}
],
[
'attribute' => 'updated_at',
'value' => function ($model) {
return date('Y-m-d H:i:s', $model->updated_at);
}
],
],
]);
?>

78
backend/modules/shop/views/delivery/logistics.php

@ -1,37 +1,51 @@
<?php <?php
use yii\helpers\Html;
use yii\widgets\DetailView; use yii\widgets\DetailView;
use backend\modules\shop\models\ars\Delivery;
/* @var $this yii\web\View */
/* @var $model common\models\ars\Delivery */
/* @var $logistics*/
?> ?>
<?=
DetailView::widget([
'model' => $model,
'attributes' => [
'id',
'order_id',
'shipping_name',
'shipping_id',
[
'attribute' => 'type',
'value' => function ($model) {
return Delivery::dropDown('type', $model->type);
}
],
'status',
'decription:ntext',
[
'attribute' => 'created_at',
'value' => function ($model) {
return date('Y-m-d H:i:s', $model->created_at);
}
],
[
'attribute' => 'updated_at',
'value' => function ($model) {
return date('Y-m-d H:i:s', $model->updated_at);
}
],
],
]);
?>
<p>
<?= Html::a('返回发货列表', ['index'], ['class' => 'btn btn-success']) ?>
</p>
<div class="delivery-view">
<div>
<label for="">物流单位:</label>
<?= isset($logistics->expTextName) ? $logistics->expTextName : ''?>
</div>
<div>
<label for="">运单号:</label>
<?= isset($logistics->mailNo) ? $logistics->mailNo : ''?>
</div>
<div>
<label for="">物流电话:</label>
<?= isset($logistics->tel) ? $logistics->tel : ''?>
</div>
<div>
<label for="">更新时间:</label>
<?= isset($logistics->updateStr) ? $logistics->updateStr : '' ?>
</div>
<div style="margin-top:20px">
<?php
if (isset($logistics->data)) {
foreach ($logistics->data as $value) {
echo '<div style="margin:20px 0">' .
"<div> $value->time </div>" .
"<div> $value->context </div>" .
'</div>';
}
}
?>
</div>
</div>

11
backend/modules/shop/views/delivery/view.php

@ -5,12 +5,14 @@ use kartik\tabs\TabsX;
/* @var $this yii\web\View */ /* @var $this yii\web\View */
/* @var $model backend\modules\shop\models\ars\Delivery */ /* @var $model backend\modules\shop\models\ars\Delivery */
/* @var $logistics */
$this->title = $model->id; $this->title = $model->id;
$this->params['breadcrumbs'][] = ['label' => 'Deliveries', 'url' => ['index']]; $this->params['breadcrumbs'][] = ['label' => 'Deliveries', 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title; $this->params['breadcrumbs'][] = $this->title;
Yii::$app->params['bsVersion'] = '4.x'; Yii::$app->params['bsVersion'] = '4.x';
\yii\web\YiiAsset::register($this); \yii\web\YiiAsset::register($this);
?> ?>
<div class="delivery-view"> <div class="delivery-view">
@ -23,9 +25,16 @@ Yii::$app->params['bsVersion'] = '4.x';
'bordered' => true, 'bordered' => true,
'items' => [ 'items' => [
[ [
'label' => '<i class="fas fa-user"></i> 物流信息',
'label' => '<i class="fas fa-user"></i> 发货信息',
'content' => $this->render('info', [
'model' => $model,
]),
],
[
'label' => '<i class="fas fa-shopping-cart "></i> 物流信息',
'content' => $this->render('logistics', [ 'content' => $this->render('logistics', [
'model' => $model, 'model' => $model,
'logistics' => $logistics
]), ]),
], ],
[ [

11
backend/modules/shop/views/order/logistics.php

@ -1,9 +1,14 @@
<?php
/**
* @var \backend\modules\shop\models\ars\Delivery $delivery
*/
?>
<div class="logistics"> <div class="logistics">
<?= $form->field($delivery, 'shipping_name')->textInput(['maxlength' => true]) ?>
<?= $form->field($delivery, 'shipping_id')->textInput(['maxlength' => true]) ?>
<?= $form->field($delivery, 'shipping_id')->dropDownList(Yii::$app->params['logistics']) ?>
<?= $form->field($delivery, 'status')->textInput() ?>
<?= $form->field($delivery, 'invoice_sn')->textInput(['maxlength' => true]) ?>
<?= $form->field($delivery, 'decription')->textarea(['rows' => 6]) ?> <?= $form->field($delivery, 'decription')->textarea(['rows' => 6]) ?>
</div> </div>

1
backend/modules/shop/views/order/refund.php

@ -8,6 +8,7 @@ use kartik\form\ActiveForm;
$this->title = '退款订单:' . $model->order_id; $this->title = '退款订单:' . $model->order_id;
$this->params['breadcrumbs'][] = ['label' => '订单列表', 'url' => ['index']]; $this->params['breadcrumbs'][] = ['label' => '订单列表', 'url' => ['index']];
Yii::$app->params['bsVersion'] = '4.x';
?> ?>
<div class="refund-log-form"> <div class="refund-log-form">

5
common/config/params.php

@ -5,4 +5,9 @@ return [
'senderEmail' => 'noreply@example.com', 'senderEmail' => 'noreply@example.com',
'senderName' => 'Example.com mailer', 'senderName' => 'Example.com mailer',
'user.passwordResetTokenExpire' => 3600, 'user.passwordResetTokenExpire' => 3600,
'logistics_config' => [
'logistics_appid' => '73887',
'logistics_secret' => '85e067c34f094c9f8be1278557889c5e',
],
]; ];
Loading…
Cancel
Save