diff --git a/app/listener/AfterRefund.php b/app/listener/AfterRefund.php
index 66315b74..2e1dcf7e 100644
--- a/app/listener/AfterRefund.php
+++ b/app/listener/AfterRefund.php
@@ -23,6 +23,10 @@ class AfterRefund
     {
         Log::info('refundCommissionStart');
         $this->refundOrder = $event['res'];
+        if ($this->refundOrder['refund_price'] != $this->refundOrder->order['total_price']) {
+            Log::info('refundCommissionEnd, not full refund');
+            return true;
+        }
         $financialRecords = FinancialRecord::getInstance()->where('order_id', $this->refundOrder['order_id'])->select();
         Log::info('refundCommissionCount:' . count($financialRecords));
         foreach ($financialRecords as $financialRecord) {
diff --git a/app/listener/paySuccessOrder.php b/app/listener/paySuccessOrder.php
index 4649c99f..a2b8be11 100644
--- a/app/listener/paySuccessOrder.php
+++ b/app/listener/paySuccessOrder.php
@@ -8,6 +8,7 @@ use app\common\dao\system\merchant\MerchantDao;
 use app\common\model\system\merchant\Merchant;
 use app\common\repositories\system\merchant\FinancialRecordRepository;
 use app\common\repositories\system\merchant\MerchantRepository;
+use crmeb\utils\DingTalk;
 use think\facade\Db;
 use think\facade\Log;
 
@@ -126,7 +127,8 @@ class paySuccessOrder
             } else {
                 Db::rollback();
             }
-            Log::error('', ['code' => $e->getCode(), 'message' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
+            Log::error('订单分润出错', ['code' => $e->getCode(), 'message' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
+            DingTalk::exception($e, '订单分润出错');
         }
     }
 
diff --git a/crmeb/utils/Curl.php b/crmeb/utils/Curl.php
new file mode 100644
index 00000000..507803a2
--- /dev/null
+++ b/crmeb/utils/Curl.php
@@ -0,0 +1,330 @@
+<?php
+
+namespace crmeb\utils;
+
+class Curl
+{
+    // Default options from config.php
+    public $options = array();
+
+    // request specific options - valid only for single request
+    public $request_options = array();
+
+
+    private $header;
+    private $headerMap;
+    private $error;
+    private $status;
+    private $info;
+
+    // default config
+    private $config = array(
+        CURLOPT_RETURNTRANSFER => true,
+        CURLOPT_FOLLOWLOCATION => true,
+        CURLOPT_HEADER         => false,
+        CURLOPT_VERBOSE        => true,
+        CURLOPT_AUTOREFERER    => true,
+        CURLOPT_CONNECTTIMEOUT => 30,
+        CURLOPT_TIMEOUT        => 30,
+        CURLOPT_SSL_VERIFYPEER => false,
+        CURLOPT_SSL_VERIFYHOST => false,
+        CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'
+    );
+
+    public static function mergeArray()
+    {
+        $args = func_get_args();
+        $res = array_shift($args);
+        while (!empty($args)) {
+            $next = array_shift($args);
+            foreach ($next as $k => $v) {
+                if (is_array($v) && isset($res[$k]) && is_array($res[$k])) {
+                    $res[$k] = self::mergeArray($res[$k], $v);
+                } elseif (is_numeric($k)) {
+                    isset($res[$k]) ? $res[] = $v : $res[$k] = $v;
+                } else {
+                    $res[$k] = $v;
+                }
+            }
+        }
+        return $res;
+    }
+
+    public function getOptions()
+    {
+        $options = self::mergeArray($this->request_options, $this->options, $this->config);
+        return $options;
+    }
+
+    public function setOption($key, $value, $default = false)
+    {
+        if ($default) {
+            $this->options[$key] = $value;
+        } else {
+            $this->request_options[$key] = $value;
+        }
+
+        return $this;
+    }
+
+    /**
+     * Clears Options
+     * This will clear only the request specific options. Default options cannot be cleared.
+     */
+
+    public function resetOptions()
+    {
+        $this->request_options = array();
+        return $this;
+    }
+
+    /**
+     * Resets the Option to Default option
+     * @param $key
+     * @return $this
+     */
+    public function resetOption($key)
+    {
+        if (isset($this->request_options[$key])) {
+            unset($this->request_options[$key]);
+        }
+        return $this;
+    }
+
+    public function setOptions($options, $default = false)
+    {
+        if ($default) {
+            $this->options = $options + $this->request_options;
+        } else {
+            $this->request_options = $options + $this->request_options;
+        }
+
+        return $this;
+    }
+
+    public function buildUrl($url, $data = array())
+    {
+        $parsed = parse_url($url);
+
+        isset($parsed['query']) ? parse_str($parsed['query'], $parsed['query']) : $parsed['query'] = array();
+
+        $params = isset($parsed['query']) ? $data + $parsed['query'] : $data;
+        $parsed['query'] = ($params) ? '?' . http_build_query($params) : '';
+        if (!isset($parsed['path'])) {
+            $parsed['path']='/';
+        }
+
+        $parsed['port'] = isset($parsed['port'])?':'.$parsed['port']:'';
+        return $parsed['scheme'].'://'.$parsed['host'].$parsed['port'].$parsed['path'].$parsed['query'];
+    }
+
+    public function exec($url, $options, $debug = false)
+    {
+        $this->error = null;
+        $this->header = null;
+        $this->headerMap = null;
+        $this->info = null;
+        $this->status = null;
+
+        $ch = curl_init($url);
+        curl_setopt_array($ch, $options);
+
+        $output = curl_exec($ch);
+
+        $this->status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+
+        if (!$output) {
+            $this->error = curl_error($ch);
+            $this->info = curl_getinfo($ch);
+        } elseif ($debug) {
+            $this->info = curl_getinfo($ch);
+        }
+
+        if (@$options[CURLOPT_HEADER] == true) {
+            list($header, $output) = $this->processHeader($output, curl_getinfo($ch, CURLINFO_HEADER_SIZE));
+            $this->header = $header;
+        }
+        curl_close($ch);
+
+        return $output;
+    }
+
+    public function processHeader($response, $header_size)
+    {
+        return array(substr($response, 0, $header_size), substr($response, $header_size));
+    }
+
+    public function get($url, $params = array(), $debug = false)
+    {
+        $exec_url = $this->buildUrl($url, $params);
+        $options = $this->getOptions();
+        return $this->exec($exec_url, $options, $debug);
+    }
+
+    public function post($url, $data, $params = array(), $debug = false, $flag = false)
+    {
+        $url = $this->buildUrl($url, $params);
+//        if ($flag) {
+//            $this->setOption(CURLOPT_HTTPHEADER, Yii::app()->params['host']);
+//        }
+        $options = $this->getOptions();
+        $options[CURLOPT_POST] = true;
+        $options[CURLOPT_POSTFIELDS] = $data;
+
+        return $this->exec($url, $options, $debug);
+    }
+
+    public function postJson($url, $data, $params = array(), $debug = false, $flag = false)
+    {
+        $url = $this->buildUrl($url, $params);
+//        if ($flag) {
+//            $this->setOption(CURLOPT_HTTPHEADER, Yii::app()->params['host']);
+//        }
+        $options = $this->getOptions();
+        $options[CURLOPT_POST] = true;
+        $options[CURLOPT_POSTFIELDS] = $data;
+        $options[CURLOPT_HEADER] = 0;
+        $options[CURLOPT_HTTPHEADER] =
+            array('Content-Type: application/json; charset=utf-8', 'Content-Length:' . strlen($data));
+
+        return $this->exec($url, $options, $debug);
+    }
+
+    public function put($url, $data = null, $params = array(), $debug = false)
+    {
+        $url = $this->buildUrl($url, $params);
+
+        $f = fopen('php://temp', 'rw+');
+        fwrite($f, $data);
+        rewind($f);
+
+        $options =  $this->getOptions();
+        $options[CURLOPT_PUT] = true;
+        $options[CURLOPT_INFILE] = $f;
+        $options[CURLOPT_INFILESIZE] = strlen($data);
+
+        return $this->exec($url, $options, $debug);
+    }
+
+    public function patch($url, $data = array(), $params = array(), $debug = false)
+    {
+        $url = $this->buildUrl($url, $params);
+
+        $options = $this->getOptions();
+        $options[CURLOPT_CUSTOMREQUEST] = 'PATCH';
+        $options[CURLOPT_POSTFIELDS] = $data;
+
+        return $this->exec($url, $options, $debug);
+    }
+
+    public function delete($url, $params = array(), $debug = false)
+    {
+        $url = $this->buildUrl($url, $params);
+
+        $options = $this->getOptions();
+        $options[CURLOPT_CUSTOMREQUEST] = 'DELETE';
+
+        return $this->exec($url, $options, $debug);
+    }
+
+    /**
+     * Gets header of the last curl call if header was enabled
+     */
+    public function getHeaders()
+    {
+        if (!$this->header) {
+            return array();
+        }
+
+        if (!$this->headerMap) {
+            $headers = explode("\r\n", trim($this->header));
+            $output = array();
+            $output['http_status'] = array_shift($headers);
+
+            foreach ($headers as $line) {
+                $params = explode(':', $line, 2);
+
+                if (!isset($params[1])) {
+                    $output['http_status'] = $params[0];
+                } else {
+                    $output[trim($params[0])] = trim($params[1]);
+                }
+            }
+
+            $this->headerMap = $output;
+        }
+        return $this->headerMap;
+    }
+
+    public function addHeader($header = array())
+    {
+        $h = isset($this->request_options[CURLOPT_HTTPHEADER]) ? $this->request_options[CURLOPT_HTTPHEADER] : array();
+        foreach ($header as $k => $v) {
+            $h[] = $k . ': ' . $v;
+        }
+
+        $this->setHeaders($h);
+        return $this;
+    }
+
+    public function getHeader($key)
+    {
+        $headers = array_change_key_case($this->getHeaders(), CASE_LOWER);
+        $key = strtolower($key);
+
+        return @$headers[$key];
+    }
+
+    public function setHeaders($header = array(), $default = false)
+    {
+        if ($this->isAssoc($header)) {
+            $out = array();
+            foreach ($header as $k => $v) {
+                $out[] = $k .': '.$v;
+            }
+            $header = $out;
+        }
+
+        $this->setOption(CURLOPT_HTTPHEADER, $header, $default);
+        return $this;
+    }
+
+    private function isAssoc($arr)
+    {
+        return array_keys($arr) !== range(0, count($arr) - 1);
+    }
+
+    public function getError()
+    {
+        return $this->error;
+    }
+
+    public function getInfo()
+    {
+        return $this->info;
+    }
+
+    public function getStatus()
+    {
+        return $this->status;
+    }
+
+    public function init()
+    {
+        return;
+    }
+
+    public function download($fileUrl, $path)
+    {
+        $localFileName = $path . DIRECTORY_SEPARATOR . md5($fileUrl);
+        $ch = curl_init($fileUrl);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+        $result = curl_exec($ch);
+        curl_close($ch);
+        $fp = fopen($localFileName, 'a');
+        fwrite($fp, $result);
+        fclose($fp);
+
+        return $localFileName;
+    }
+}
diff --git a/crmeb/utils/DingTalk.php b/crmeb/utils/DingTalk.php
new file mode 100644
index 00000000..0dfe7333
--- /dev/null
+++ b/crmeb/utils/DingTalk.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace crmeb\utils;
+
+class DingTalk
+{
+
+    public static $url = 'https://oapi.dingtalk.com/robot/send?access_token=%s';
+
+    /**
+     * @param $url
+     * @param $text
+     * @param $at array
+     * @return mixed
+     */
+    public static function send($text, $url = null, array $at = [])
+    {
+        $tokenArray = [
+            'production' => '64f863c5f4415e715c8b9c925b6d58837553335e42059b6fba579f0a301287e9',
+            'test' => 'f3d3bb7cf3c46073c6f657c021113d74fabd59742d33af5d8601ec8c38f18310',
+        ];
+        $env = env('APP_ENV', 'test');
+//        if ($env == 'test') {
+//            return true;
+//        }
+        $token = $tokenArray[$env];
+        if(empty($url)){
+            $url = self::$url;
+        }
+        $url = sprintf($url, $token);
+        $data = array (
+            'msgtype' => 'text',
+            'text' => [
+                'content' => $text
+            ],
+            'at' => [
+                'atMobiles' => 'all' == $at ? [] : $at,
+                'isAtAll' => 'all' == $at,
+            ],
+        );
+        $data_string = json_encode($data);
+        $curl = new Curl();
+        $result = $curl->postJson($url, $data_string);
+        if ($result) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 发送钉钉异常告警
+     * @param \Throwable $exception
+     * @param $msg
+     * @param null $request
+     * @return bool|mixed
+     */
+    public static function exception(\Throwable $exception, $msg, $request = null)
+    {
+        $message = [
+            "业务异常: {$msg}",
+            'code:' . $exception->getCode(),
+            'message:' . $exception->getMessage(),
+            'file:' . $exception->getFile(),
+            'line:' . $exception->getLine(),
+        ];
+        if ($request !== null) {
+            $message[] = 'method:' . $request->method();
+            $message[] = 'route:' . $request->rule()->getRule();
+            $message[] = 'params:' . json_encode($request->rule()->getVars());
+        }
+        if (is_callable([$exception, 'errors'])) {
+            $message[] = 'errors:' . json_encode($exception->errors(), JSON_UNESCAPED_UNICODE);
+        }
+        $message = implode(PHP_EOL, $message);
+        return self::send($message);
+    }
+
+}