mzeros a6216a09e1 feat(channel): 添加渠道 IP 白名单功能
- 新增 IP白名单设置页面和相关逻辑- 实现 IP 白名单的添加和修改功能
- 与宝塔防火墙进行集成,自动同步 IP 白名单
- 卡卡云渠道支持
- 自动选择最优价格
- 修改完成订单迁移bug
- 提单错误异常捕捉提醒
- 飞之度推单支持
2024-12-13 16:17:54 +08:00

765 lines
26 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\channel\service\system;
use app\gateway\service\CurlService;
use app\gateway\service\AuthService;
use app\gateway\service\RedisService;
use app\channel\service\ProductService;
use app\channel\service\ChannelService;
use app\channel\service\ShopService;
use app\merchant\service\OrderService;
use app\merchant\service\OrderAutoService;
use app\merchant\service\MerchantService;
use app\merchant\service\ProductService as MerchantProductService;
use dever\Log;
/**
* 系统核心
* Class Core
* @package app\channel\service
*/
class Core
{
protected $async = false;
protected $data = array();
protected $channel = array();
protected $product = array();
protected $host = '';
protected $mid = '';
protected $token = '';
protected $card_id = '';
public function __construct($async = false, $data = array(), $channel = array(), $product = array())
{
$this->async = $async;
$this->data = $data;
$this->channel = $channel;
$this->product = $product;
if ($this->channel) {
if (isset($this->channel['host']) && $this->channel['host']) {
$this->host = $this->channel['host'];
}
if (isset($this->channel['mid']) && $this->channel['mid']) {
$this->mid = $this->channel['mid'];
}
if (isset($this->channel['token']) && $this->channel['token']) {
$this->token = $this->channel['token'];
}
}
}
# 通用的充值
public function def($method, $param)
{
if (strstr($method, '_')) {
$temp = explode('_', $method);
$method = $temp[1];
return $this->$method($param);
} else {
return false;
}
}
# 检测传入数据
protected function param($param, $check)
{
if (empty($param['order'])) {
return 'order';
}
if (!isset($param['cash'])) {
$param['cash'] = 0;
}
$detail = array();
if ($check) {
foreach ($check as $k => $v) {
if (!isset($param[$k])) {
return $k;
} elseif (empty($param[$k]) && $param[$k] != 0) {
return $k;
} else {
$detail[$v] = $param[$k];
}
}
}
# 获取订单号
$merchant_order = $param['order'];
$param['order'] = $this->createOrder($param['order']);
$account = '';
if (isset($param['card']) && $param['card']) {
$account = $param['card'];
} elseif (isset($param['mobile']) && $param['mobile']) {
$account = $param['mobile'];
} elseif (isset($param['qq']) && $param['qq']) {
$account = $param['qq'];
} elseif (isset($param['account']) && $param['account']) {
$account = $param['account'];
}
$parent_order_id = '';
if (isset($param['parent_order_id']) && $param['parent_order_id']) {
$parent_order_id = $param['parent_order_id'];
}
$response = array();
if ($this->async) {
$this->data['order'] = $param['order'];
$log['type'] = 'merchant_request';
$log['config'] = $this->data;
$this->log($log);
# 下单
$status = 1;
if (isset($this->data['product_key']) && ($this->data['product_key'] != 'zshcz1')) {
} elseif ($account) {
$account_state = OrderService::instance()->countByAccount($account);
if (!$account_state) {
return '已达到最大下单次数';
}
# 检测该账号是否最近5分钟之内下单如果有就延迟下单
$account_order = OrderService::instance()->getByAccount($account, $param['cash']);
if ($account_order) {
$this->data['time'] = time() + 900;
$status = -1;
} else {
/*
# 检测汇天下当前石化渠道是否同时有80个处理中
if ($this->product && isset($this->product['limit_order']) && $this->product['limit_order'] > 0) {
$total = OrderService::instance()->getNum($this->data['product']);
if ($total >= $this->product['limit_order']) {
$status = -3;
}
}
*/
}
}
if (isset($param['s']) && $param['s']) {
$this->channel['order_limit'] = 0;
unset($param['s']);
}
if (isset($this->channel['order_limit']) && $this->channel['order_limit'] > 0) {
$status = -3;
}
$state = $this->create($param['order'], '', $merchant_order, $param['cash'], '', $this->data, $response, $status, $account, $parent_order_id, $this->card_id, false);
if (!$state) {
return '余额不足下单失败';
}
if ($status == 1) {
$this->queue('submit', $this->data);
}
return array('order' => $param['order'], 'account' => $account, 'status' => 1);
} elseif ($this->data['merchant'] == 6000 && $account) {
# 检测该账号是否最近5分钟之内下单如果有就延迟下单
$account_order = OrderService::instance()->getByAccount($account, $param['cash']);
if ($account_order) {
$this->data['order'] = $param['order'];
$this->data['time'] = time() + 300;
$status = -1;
$this->create($param['order'], '', $merchant_order, $param['cash'], '', $this->data, $response, $status, $account, $parent_order_id, $this->card_id, false);
return 'order';
}
} elseif (!$this->data['use_product']) {
$order = OrderService::instance()->get($param['order']);
if ($order && $order['status'] > 1 && $order['status'] != 5) {
return 'order';
}
}
/*
if (!$this->async) {
$merchant_info = MerchantService::instance()->get($this->data['merchant']);
if ($merchant_info['account_surplus'] < $param['cash'] && $merchant_info['credit_surplus'] < $param['cash']) {
$response['msg'] = 'error';
$response['data'] = '余额不足下单失败';
$this->create($param['order'], '', $merchant_order, $param['cash'], '', $this->data, $response, 3, $account);
return $response['data'];
}
}
*/
return array
(
'order' => $param['order'],
'merchant_order' => $merchant_order,
'cash' => $param['cash'],
'detail' => $detail,
'account' => $account,
'status' => -1
);
}
# 获取店铺id
protected function shop()
{
if ($this->data) {
$product = $this->product ? $this->product : ProductService::instance()->get($this->data['merchant'], $this->data['product']);
if ($product && $product['shop_id'] > 0) {
$shop = ShopService::instance()->get($product['shop_id']);
if ($shop && $shop['shop_id']) {
return $shop['shop_id'];
}
}
}
return 0;
}
protected function getUrl($url,$version = 3)
{
$host = sysconf('system_host');
$api_host = sysconf('api_notify_host');
if($api_host && $version == 4){
$host = $api_host;
}
if (!$host) {
if (isset($_SERVER['SERVER_PORT']) && isset($_SERVER['HTTP_HOST'])) {
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
$host = $protocol.$_SERVER['HTTP_HOST'];
} else {
$host = 'https://reapi.gcdat.com';
}
}
return $host . $url;
}
# 通知回调地址
protected function getNotify($order, $type, $api_id = false): string
{
if ($api_id) {
$type .= '&s_api_id=' . $api_id;
}
$api_host = sysconf('api_notify_host');
if($api_host){
$url = $this->getUrl('/call?s_order=' . $order . '&s_type=' . $type.'&version=v4.0',4);
}else{
$url = $this->getUrl('/gateway/api.notify/call?s_order=' . $order . '&s_type=' . $type);#修改原为v3.0之前版本
}
return $url;
}
# 创建订单
protected function create($order_id, $channel_order_id, $merchant_order_id, $cash, $url, $request, &$response, $status = 1, $account = '', $parent_order_id = '', $card_id = false, $kou = true)
{
$response_data = '';
if ($response) {
if ($response['msg'] == 'success') {
# 直接成功
$status = 2;
} elseif ($response['msg'] == 'ok') {
$status = 4;
} elseif ($response['msg'] == 'yescard') {
$status = 7;
} else {
$status = 3;
}
$response_data = $response['data'];
}
if ($this->data) {
$product = $this->product ? $this->product : ProductService::instance()->get($this->data['merchant'], $this->data['product']);
if ($product) {
if ($card_id == -1) {
$status = 3;
}
$channel = ChannelService::instance()->get($product['cid']);
if ($channel && $product['cid'] == $channel['id']) {
if (isset($this->data['project_id']) && $this->data['project_id']) {
$project_id = $this->data['project_id'];
} else {
$project_id = false;
}
$order = OrderService::instance()->up($this->data['merchant'], $channel['id'], $product['id'], $product['key'], $order_id, $channel_order_id, $merchant_order_id, $cash, $url, $request, $response_data, $status, $account, $parent_order_id, $project_id, $card_id, $this->data['param'], $kou);
if (!$order) {
return false;
}
# 商户后续操作
$merchant = MerchantService::instance()->get($this->data['merchant']);
if ($status == 3 && $merchant['id'] == 2 && isset($this->data['product_key']) && $channel['id'] == '2' && isset($request['szProductId']) && strstr($request['szProductId'], '3000')) {
# 如果直接下单失败就获取另外一个相同key的商品吧
$use_product = array();
if (empty($this->data['use_product'])) {
$this->data['use_product'] = array();
}
$this->data['use_product'][] = $product['id'];
$isp = false;
if (isset($order['isp']) && $order['isp']) {
$isp_config = \app\merchant\service\PercentService::instance()->getIsp();
$isp = $isp_config[$order['isp']];
}
$new_product = $this->getProduct($this->data['merchant'], $this->data['product_key'], $this->data['use_product'], $cash, $isp);
if ($new_product) {
# 记录失败的订单信息
OrderAutoService::instance()->up($order['id']);
$response['msg'] = ChannelService::instance()->use($this->data['merchant'], $new_product, $this->data['param'], false, $order_id, $this->data['use_product']);
return true;
}
}
if ($status == 3 && isset($merchant['stop']) && ($merchant['stop'] == 2 || $merchant['stop'] == 4)) {
# 是否暂停
$num = 1;
$order_id = $order['order_id'] . '_' . $num;
# 该渠道下单失败,重新下单,并暂停,等待手动启动
# 记录失败的订单信息
OrderAutoService::instance()->up($order['id']);
# 修改状态
OrderService::instance()->upStatus($order['order_id'], -5, '', false, $num);
return true;
} elseif ($status == 3 && $merchant['order_auto'] == 2 && isset($this->data['product_key'])) {
# 如果直接下单失败就获取另外一个相同key的商品吧
$use_product = array();
if (empty($this->data['use_product'])) {
$this->data['use_product'] = array();
}
$this->data['use_product'][] = $product['id'];
$isp = false;
if (isset($order['isp']) && $order['isp']) {
$isp_config = \app\merchant\service\PercentService::instance()->getIsp();
$isp = $isp_config[$order['isp']];
}
$new_product = $this->getProduct($this->data['merchant'], $this->data['product_key'], $this->data['use_product'], $cash, $isp);
if ($new_product) {
# 记录失败的订单信息
OrderAutoService::instance()->up($order['id']);
$response['msg'] = ChannelService::instance()->use($this->data['merchant'], $new_product, $this->data['param'], false, $order_id, $this->data['use_product']);
return true;
}
}
/*
if ($status == 3 && $product['id'] == 13 && isset($response['array']['status_code']) && $response['array']['status_code'] == '40000' && isset($response['array']['status_msg']) && strstr($response['array']['status_msg'], '账户余额')) {
# 如果是八达通失败,并且账户余额不足,就不通知用户失败,直接延迟到失败表中
$this->data['order'] = $order_id;
OrderService::instance()->upStatus($order_id, -2, $this->data);
return true;
}
*/
$result = array();
$result['project_id'] = $project_id;
$result['order_id'] = $order_id;
$result['merchant_order_id'] = $order['merchant_order_id'];
$result['status'] = $order['status'];
$result['notify_num'] = $order['merchant_callback_num'];
$result['cash'] = $order['cash'];
$result['account'] = $order['account'];
if (isset($order['isp']) && $order['isp']) {
$result['isp'] = $order['isp'];
}
$result['order'] = $order;
if (isset($this->data['param']['notify']) && $this->data['param']['notify']) {
$result['notify'] = $this->data['param']['notify'];
}
MerchantService::instance()->up($order['id'], $merchant, $product['id'], $product['key'], $result);
return true;
}
}
}
return false;
}
private function getProduct($mid, $product, $use_product, $cash, $isp = false)
{
$product_data = MerchantProductService::instance()->getAll($mid, $product);
if ($product_data) {
foreach ($product_data as $k => $v) {
if ($v['cash']) {
if ($isp && (strstr($v['cash'], '联通') || strstr($v['cash'], '移动') || strstr($v['cash'], '电信'))) {
$cash = $isp;
}
$temp = explode(',', $v['cash']);
if (!in_array($cash, $temp)) {
continue;
}
}
if (!in_array($v['pid'], $use_product)) {
return $v['pid'];
}
}
}
return false;
}
# 创建订单号
protected function createOrder($order)
{
if (isset($this->data['order']) && $this->data['order']) {
return $this->data['order'];
}
$order = $this->createOrderId();
$info = OrderService::instance()->get($order);
if (!$info) {
return $order;
} else {
return $this->createOrder($order);
}
//return sha1($this->data['merchant'] . '_' . $this->data['product'] . '_' . $this->createOrderId() . '_' . $order);
}
# 生成订单号
protected function createOrderId()
{
$prefix = sysconf('system_order_perfix');
if (!$prefix) {
$prefix = 'Q';
}
return $prefix . date('Ymd').substr(time(), -5) . substr(microtime(), 2, 5) . sprintf('%02d', mt_rand(100000, 999999));
// if (function_exists('session_create_id')) {
// return strtoupper(session_create_id());
// } else {
// $charid = strtoupper(md5(uniqid(mt_rand(), true)));
// return substr($charid, 0, 8) . substr($charid, 8, 4) . substr($charid, 12, 4) . substr($charid, 16, 4) . substr($charid, 20, 12);
// }
}
# 队列
protected function queue($key, $value = false)
{
$redis = RedisService::getInstance();
if (!$value) {
return $redis->pop($key);
} else {
return $redis->push($key, $value);
}
}
# 请求数据
protected function curl($method, $url, $param = array(), $json = false, $header = false)
{
$log['type'] = 'request';
$log['url'] = $url;
$log['param'] = $param;
$log['config'] = $this->data;
$this->log($log);
$data = CurlService::getInstance($url, $param, $method, $json, $header)->result();
if (!$data) {
$data = CurlService::getInstance($url, $param, $method, $json, $header)->result();
}
return $data;
}
public function http_send($type,$url,$data,$json = false)
{
$header = 'Content-type: application/x-www-form-urlencoded';
$payload = $data;
if($json){
$payload = json_encode($data); // 将数据编码为JSON
$header = 'Content-type: application/json';
}
$options = array(
'http' => array(
'header' => $header,
'method' => $type,
'content' => $payload,
),
);
$context = stream_context_create($options); // 创建流上下文
$result = file_get_contents($url, false, $context); // 发送请求
if ($result === FALSE) {
return 'Error';
}
return $result; // 解码JSON响应
// return $this->async;
}
# 通用签名方法,如果有渠道不同的签名方式,需要单独实现
protected function sign($request, $type = 'sha1', $suffix = '', $empty = true)
{
return AuthService::signature($request, $type, $suffix, $empty);
}
protected function getParam($param, $token, $sign_type = 1)
{
return AuthService::get($param, $token, $sign_type);
}
protected function timestamp()
{
return AuthService::timestamp();
}
# 记录日志
protected function log($data)
{
Log::write('gateway', 'log', $data);
}
protected function json_decode($data)
{
$array = json_decode($data, true);
if (!is_array($array) || is_null($array) || json_last_error() != JSON_ERROR_NONE) {
return false;
}
return $array;
}
# 获取手机号
protected function getMobile($card, $n = 1)
{
$info = \app\merchant\service\MobileService::instance()->get($card);
if ($info && $info['mobile']) {
return $info['mobile'];
}
$tel_arr = array(
'130','131','132','133','134','135','136','137','138','139','144','147','150','151','152','153','155','156','157','158','159','176','177','178','180','181','182','183','184','185','186','187','188','189','199',
);
for($i = 0; $i < $n; $i++) {
$tmp[] = $tel_arr[array_rand($tel_arr)].mt_rand(1000,9999).mt_rand(1000,9999);
// $tmp[] = $tel_arr[array_rand($tel_arr)].'xxxx'.mt_rand(1000,9999);
}
$tem = array_unique($tmp);
if (isset($tem[0])) {
$mobile = $tem[0];
\app\merchant\service\MobileService::instance()->up($card, $mobile);
} else {
$mobile = '13800138000';
}
return $mobile;
}
# 获取手机运营商
protected function isp($mobile)
{
return ChannelService::instance()->isp($mobile);
}
# 获取产品id
protected function getGid($cash)
{
$info = $this->product;
if ($info) {
if (isset($info['gid_rule']) && $info['gid_rule']) {
$rule = json_decode($info['gid_rule'], true);
if (isset($rule[$cash]) && $rule[$cash]) {
$gid = $rule[$cash];
return $gid;
}
}
if (isset($info['gid']) && $info['gid']) {
return $info['gid'];
}
}
return -1;
}
//随机生成n条手机号
protected function randomMobile($n = 1)
{
$tel_arr = array(
'130','131','132','133','134','135','136','137','138','139','144','147','150','151','152','153','155','156','157','158','159','176','177','178','180','181','182','183','184','185','186','187','188','189',
);
for($i = 0; $i < $n; $i++) {
$tmp[] = $tel_arr[array_rand($tel_arr)].$this->rands().$this->rands();
}
return array_unique($tmp);
}
//随机生成身份证号
protected function randomShenfen()
{
$city = array(11,12,13,14,15,21,22,23,31,32,33,34,35,36,37,41,42,43,44,45,46,50,51,52,53,54,61,62,63,64,65,71,81,82,91);
//校验位
$parity = array('1','0','X','9','8','7','6','5','4','3','2');
// $a = array('a','b','c');
$arr = array(0,1,2,3,4,5);
$str = '';
// echo $city[array_rand($city)];
//前两位
$str .=$city[array_rand($city)];
//地区位后四位
for($i=0;$i<4;$i++){
$str .=$arr[array_rand($arr)];
}
//出生年 随机20世纪
$str .= '19'.mt_rand(0,9).mt_rand(0,9);
//月份
$month = array('01','02','03','04','05','06','07','08','09','10','11','12');
$str .=$month[array_rand($month)];
//天
$day = mt_rand(0,3);
if($day==3){
$str .=$day.mt_rand(0,1);
}else{
$str .=$day.mt_rand(0,9);
}
//顺序码
for($i=0;$i<3;$i++){
$str .=mt_rand(0,9);
}
//计算加权因子
for($i=18;$i>1;$i--){
$factor[] = fmod(pow(2,$i-1),11);
}
//将加权因子和身份证号对应相乘,再求和
$sum = 0;
for($i=0;$i<count($factor);$i++){
$sum +=$factor[$i]*$str[$i];
}
//将sum对11求余
$mod = fmod($sum,11);
$str .=$parity[$mod];
return $str;
}
# 随机生成姓名
protected function randomName()
{
$name=new rndChinaName();
return $name->getName();
}
protected function rands()
{
$s = mt_rand(1000,9999);
if ($s == '1234') {
return $this->rands();
} elseif ($s == '2345') {
return $this->rands();
} elseif ($s == '3456') {
return $this->rands();
} elseif ($s == '4567') {
return $this->rands();
} elseif ($s == '5678') {
return $this->rands();
} elseif ($s == '6789') {
return $this->rands();
}
return $s;
}
protected function maketime($v)
{
if (!$v) {
return '';
}
if (is_numeric($v)) {
return $v;
}
if (is_array($v)) {
$v = $v[1];
}
if (strstr($v, ' ')) {
$t = explode(' ', $v);
$v = $t[0];
$s = explode(':', $t[1]);
} else {
$s = array(0, 0, 0);
}
if (!isset($s[1])) {
$s[1] = 0;
}
if (!isset($s[2])) {
$s[2] = 0;
}
if (strstr($v, '-')) {
$t = explode('-', $v);
} elseif (strstr($v, '/')) {
$u = explode('/', $v);
$t[0] = $u[2];
$t[1] = $u[0];
$t[2] = $u[1];
}
if (!isset($t)) {
$t = array(0, 0, 0);
}
if (!isset($t[1])) {
$t[1] = 0;
}
if (!isset($t[2])) {
$t[2] = 0;
}
$v = mktime($s[0], $s[1], $s[2], $t[1], $t[2], $t[0]);
return $v;
}
# 获取手机运营商
protected function getMobileInfo($mobile)
{
//git clone https://github.com/shitoudev/phone-location /www/wwwroot/api/extend/phone
# 此处不同服务器需改动的绝对路径
// $file = '/www/wwwroot/bao111/extend/phone/src/PhoneLocation.php';
$file = dirname(__DIR__, 4).'/extend/phone/src/PhoneLocation.php';
include $file;
$pl = new \Shitoudev\Phone\PhoneLocation();
$info = $pl->find($mobile);
return $info;
}
public function diy_send_post($notify_url, $post_data, $type): mixed
{
$postdate = http_build_query($post_data);
$options = array(
'http' => array(
'method' => $type,
'header' => 'Content-type:application/x-www-form-urlencoded',
'content' => $postdate,
'timeout' => 15 * 60 // 超时时间(单位:s
)
);
$context = stream_context_create($options);
return file_get_contents($notify_url, false, $context);
}
}