mChannelControl = new chctlex(); $this->mQuality = new quality_ploy(); $this->mPrices = new merchant_price(); $this->mStorageLocker = new rstorage(); $this->mGroupCtl = new rgroup_ctl(); $this->mRatioCtl = new mratio_controlex(); $this->mQuality->setRatioCtl($this->mRatioCtl); $this->mGlobalInterceptor = new interceptor(); $this->mMChannels = new mchannel(); $this->mThirdHelper = new third_helper(); $this->mChTimes = new ch_times(); $this->mChSpeed = new chspeed(); } public function load() { parent::load(); $opened_names = $this->mOpenedProviderNames; sort($opened_names); $opened_merchants = $this->opened_merchants(); $this->mChannelControl->load($opened_names); $this->mChannelControl->update_price($this); $this->mQuality->load(); $this->mPrices->load($opened_merchants); $turn_name = 'oil_amount_lock_turn'; $this->mAmountLockTurn = rkcache($turn_name); Log::record("AmountLockTurn = {$this->mAmountLockTurn}",Log::DEBUG); $this->mStorageLocker->load(); $this->mGroupCtl->load($opened_names,$opened_merchants); $this->mRatioCtl->load(); $this->mGlobalInterceptor->load(); $this->mMChannels->load(); $this->mThirdHelper->load(); $this->init_mavg_price(); $this->mChTimes->load(); $this->mChSpeed->load(); } public function get_times() { return $this->mChTimes; } private function init_mavg_price() { $specs_merger = function ($qspecs) { $q_keys = []; foreach ($qspecs as $quality => $specs) { foreach ($specs as $item) { [$card_type, $spec] = $item; $q_keys[$quality][] = "{$card_type}-{$spec}"; } } $sspecs = []; $first = true; foreach ($q_keys as $quality => $specs) { if ($first) { $sspecs = $specs; $first = false; } else { $sspecs = array_intersect($sspecs, $specs); } } $result = []; foreach ($sspecs as $item) { [$card_type, $spec] = explode('-',$item); $result[] = [intval($card_type), intval($spec)]; } return $result; }; $pQuality = $this->mQuality; $mchids = $pQuality->mechants_quality(); foreach ($mchids as $mchid => $mixed_quality) { $qualities = $pQuality->qualities($mixed_quality); $specs = []; foreach ($qualities as $quality) { $item = $this->mPrices->merchant_specs($mchid, $quality); if (empty($item)) continue; $specs[$quality] = $item; } $specs = $specs_merger($specs); foreach ($specs as $item) { [$card_type, $spec] = $item; if (!in_array($card_type, [4, 5, 6])) continue; $prices = []; $sale_price = $this->mPrices->price($mchid,$card_type,$spec,$mixed_quality,''); $prices[$mixed_quality] = round($sale_price,4); foreach ($qualities as $quality) { $inprice = $this->calc_maxprice($mchid, $quality, $card_type, $spec); $prices[$quality] = round($inprice,4); } $this->mRatioCtl->setMixedPrice($mchid, $mixed_quality, $card_type, $spec, $prices); } } } protected function calc_avgprice($mchid,$quality,$card_type,$spec) { $providers = parent::get_providers($mchid,$spec,$card_type,$quality,-1); $names = []; foreach ($providers as $provider) { $names[] = $provider->name(); } [$hasGroup,$can_others,$channels] = $this->mGroupCtl->find_providers($mchid, $spec, $card_type, $quality); if($hasGroup) { if(empty($channels)) { if(!$can_others) { return false; } } else { $ret = array_intersect($names, $channels); if (empty($ret)) { return false; } else { $names = $ret; } } } $name_provider = []; foreach ($providers as $provider) { $name = $provider->name(); $name_provider[$name] = $provider; } $total = 0.0; foreach ($names as $name) { $provider = $name_provider[$name]; [$goods_id, $price] = $provider->goods($quality, $spec, $card_type, -1, []); $total += $price; } return $total / count($names); } protected function calc_maxprice($mchid,$quality,$card_type,$spec) { $providers = parent::get_providers($mchid,$spec,$card_type,$quality,-1); $names = []; foreach ($providers as $provider) { $names[] = $provider->name(); } [$hasGroup,$can_others,$channels] = $this->mGroupCtl->find_providers($mchid, $spec, $card_type, $quality); if($hasGroup) { if(empty($channels)) { if(!$can_others) { return false; } } else { $ret = array_intersect($names, $channels); if (empty($ret)) { return false; } else { $names = $ret; } } } $name_provider = []; foreach ($providers as $provider) { $name = $provider->name(); $name_provider[$name] = $provider; } $max_price = 0.0; foreach ($names as $name) { $provider = $name_provider[$name]; [$goods_id, $price] = $provider->goods($quality, $spec, $card_type, -1, []); if($price > $max_price) $max_price = $price; } return $max_price; } private function opened_merchants() { $mchids = []; $i = 0; while (true) { $start = $i * 1000; $items = Model()->table('merchant')->field('*')->where(['merchant_state' => 1])->order('mchid asc')->limit("{$start},1000")->select(); if(empty($items)) { break; } $i++; foreach ($items as $item) { $mchids[] = intval($item['mchid']); } } sort($mchids); return $mchids; } public function find_providers(order $order): array { $mchid = $order->mchid(); $spec = $order->spec(); $card_type = $order->card_type(); $org_quality = $order->org_quality(); $cur_quality = $order->cur_quality(); $pcode = $order->pcode(); $regin_no = $order->region_no(); $order_time = $order->order_time(); $providers = parent::get_providers($mchid,$spec,$card_type,$cur_quality,$regin_no); if(empty($providers)) { return [$providers,false]; } $names = []; foreach ($providers as $provider) { $names[] = $provider->name(); } Log::record("ProviderManager::get_providers result=" . implode(',',$names),Log::DEBUG); //机构和通道,交叉设置过滤 [$card_paper, $channels] = $this->mMChannels->channels($mchid, $card_type, $cur_quality); if ($card_paper === mtopcard\OilCardPaper || $card_paper === mtopcard\PhoneCardPaper) { sort($names); $names = array_intersect($names, $channels); } Log::record("mchannel::channels result=" . implode(',',$names),Log::DEBUG); [$hasGroup,$can_others,$channels] = $this->mGroupCtl->find_providers($mchid, $spec, $card_type, $cur_quality); Log::record("GroupControl mchid={$mchid} spec={$spec} quality={$cur_quality} card_type={$card_type} first result=" . implode(',',$channels),Log::DEBUG); if($hasGroup) { if(empty($channels)) { if(!$can_others) { return [[],false]; } } else { $ret = array_intersect($names, $channels); if (empty($ret)) { return [[], false]; } else { $names = $ret; } } } Log::record("GroupControl second result=" . implode(',',$names),Log::DEBUG); if(PolicyUtil::mixed_quality($org_quality)) { $mixedQuality = $org_quality; } else { $mixedQuality = $cur_quality; } $price = $this->mPrices->price($mchid,$card_type,$spec,$mixedQuality,$pcode); if($price === false) { return [[],false]; } $extra_price = $this->mPrices->extra_price($mchid,$card_type,$spec,$mixedQuality,$pcode); $extra_price = $extra_price == false ? 0.00 : $extra_price; //以当前通道质量为准 $max_inprice = $this->mPrices->max_inprice($mchid,$card_type,$spec,$cur_quality,$pcode); Log::record("max_price={$max_inprice}",Log::DEBUG); global $config; $auto_find = $config['auto_find_channels']; $mobile_types = [mtopcard\ChinaMobileCard, mtopcard\ChinaUnicomCard, mtopcard\ChinaTelecomCard]; $qualities = [Quality::Normal, Quality::Quick, Quality::CardKey, Quality::ThirdShop]; if ($auto_find && in_array($card_type, $mobile_types, true) && in_array($cur_quality, $qualities, true)) { $names = $this->mChannelControl->auto_match($names, $spec, $card_type, $cur_quality, $price - $extra_price, $max_inprice, time() - $order_time); Log::record("policy::find_providers ChannelControl auto_match quality={$cur_quality} spec={$spec} card_type={$card_type} result=" . implode(',', $names), Log::DEBUG); } else { $names = $this->mChannelControl->match($names, $spec, $card_type, $cur_quality, $max_inprice); Log::record("policy::find_providers ChannelControl match quality={$cur_quality} spec={$spec} card_type={$card_type} result=" . implode(',', $names), Log::DEBUG); } $name_provider = []; foreach ($providers as $provider) { $name = $provider->name(); $name_provider[$name] = $provider; } $result = []; foreach ($names as $name) { if(array_key_exists($name,$name_provider)) { $result[] = $name_provider[$name]; } } return [$result,false]; } public function price($mchid,$spec,$card_type,$quality,$pcode) { return $this->mPrices->price($mchid,$card_type,$spec,$quality,$pcode); } public function max_inprice($mchid, $spec, $card_type, $quality, $pcode) { return $this->mPrices->max_inprice($mchid,$card_type,$spec,$quality,$pcode); } public function channeles(int $mchid, int $spec, int $card_type, int $quality, $regin_no) { $providers = parent::get_providers($mchid, $spec, $card_type, $quality, $regin_no); return count($providers); } public function find_quality(order $order, bool $skip_pre = false): array { $trace = new scope_trace(__METHOD__); $mchid = $order->mchid(); $spec = $order->spec(); $card_type = $order->card_type(); $cur_quality = $order->cur_quality(); $pcode = $order->pcode(); $regin_no = $order->region_no(); Log::record("spec=$spec,card_type=$card_type",Log::DEBUG); if($card_type == mtopcard\SinopecCard || $card_type == mtopcard\PetroChinaCard) { $caller = new times_caller($mchid,$spec,$card_type,-1,$this); } else { $caller = null; } [$org_quality, $qualities, $match] = $this->mQuality->find_quality($order, $caller); $order->set_match($match); if(empty($qualities)) { return [$org_quality,0]; } $namer = function ($providers) { $result = []; foreach ($providers as $provider) { $result[] = $provider->name(); } return $result; }; Log::record("skip_pre = $skip_pre",Log::DEBUG); $start = false; foreach ($qualities as $quality) { if(!$start && $skip_pre) { if($quality == $cur_quality) { $start = true; continue; } } $price = $this->mPrices->price($mchid,$card_type,$spec,$quality,$pcode); if($price === false) { Log::record("$mchid 没有协商 quality = $quality 价格",Log::DEBUG); continue; } $max_inprice = $this->mPrices->max_inprice($mchid, $card_type, $spec, $quality, $pcode); Log::record("quality = $quality max_price=$max_inprice",Log::DEBUG); $providers = parent::get_providers($mchid, $spec, $card_type, $quality, $regin_no); if(empty($providers)) { continue; } $names = $namer($providers); //机构和通道,交叉设置过滤 [$card_paper, $channels] = $this->mMChannels->channels($mchid, $card_type, $quality); if ($card_paper === mtopcard\OilCardPaper || $card_paper === mtopcard\PhoneCardPaper) { sort($names); $names = array_intersect($names, $channels); } Log::record(implode(',',$names),Log::DEBUG); $names = $this->mChannelControl->match($names, $spec, $card_type, $quality, $max_inprice); if (!empty($names)) { return [$org_quality, $quality]; } else { Log::record("Policy::find_quality fail: mchid=$mchid $quality-$spec-$card_type", Log::DEBUG); } } Log::record("Policy::find_quality fail: mchid=$mchid $spec-$card_type", Log::DEBUG); return [$org_quality,0]; } public function allow($mchid,$card_type,$amount,$quality) : bool { return $this->mStorageLocker->allow($mchid,$card_type,$amount); } private function allow_storge($mchid,$card_type,$amount,$quality) { $reader = function () { $cache = rcache("refill_able",'merchant-'); if(!empty($cache)) { $result = unserialize($cache['data']); } else { $result = []; } return $result; }; if(defined('MOBILE_SERVER') && MOBILE_SERVER === true) { if(StatesHelper::fetch_state('merchant')) { $this->mLimits = $reader(); } } else { $this->mLimits = $reader(); } $key = "{$mchid}-{$card_type}-{$amount}"; if(empty($this->mLimits)) { return true; } elseif(array_key_exists($key,$this->mLimits)) { return $this->mLimits[$key]; } else { return true; } } public function notify($order_info, $refill_info) : bool { $order_state = $order_info['order_state']; if ($order_state == ORDER_STATE_CANCEL) { $state = "CANCEL"; } else { $state = "SUCCESS"; } $mchid = $refill_info['mchid']; $mch_info = Model('merchant')->getMerchantInfo(['mchid' => $mchid]); [$params, $sign] = $this->body($state, $refill_info, $mch_info); $params['sign'] = $sign; $notify_url = $refill_info['notify_url']; //如果http请求内部,又发出回调自己的请求,在处理进程非动态扩容的情况下,容易造成阻塞. if ($this->is_url($notify_url)) { $resp = http_request($notify_url, $params, 'POST'); } else { $resp = RBridgeFactory::instance()->notify($notify_url, $params); } return $resp == "SUCCESS" || $resp == 'FAILED'; } private function body($state, $refill_info, $mch_info) { if($state == 'SUCCESS') { $err_msg = ''; } else { $err_msg = 'fail'; } $ctls = ['osn' => true, 'sms' => false]; util::onEventCallback($refill_info, $mch_info, $ctls); if($ctls['osn']) { $osn = $refill_info['official_sn'] ?? ""; } else { $osn = ""; } $params = [ "mchid" => $refill_info['mchid'], "order_sn" => $refill_info['mch_order'], "amount" => $refill_info['refill_amount'], "sale_price" => ncPriceFormat($refill_info['mch_amount']), "cardno" => $refill_info['card_no'], "trade_no" => $refill_info['order_sn'], "idcard" => $refill_info['idcard'] ?? "", "card_name" => $refill_info['card_name'] ?? "", 'official_sn' => $osn, 'message' => $err_msg, "state" => $state]; if($ctls['sms']) { [$has_sms,$sms] = $this->sms($refill_info); if($has_sms) { $params['sms'] = $sms; } } $secure_key = $mch_info['secure_key']; $sign = $this->sign($params, $secure_key); return [$params, $sign]; } public function notify_success($order_info, $refill_info) : bool { $gen_body = function ($state, $refill_info, $mch_info) { $err_msg = ''; $osn = ""; $params = [ "mchid" => $refill_info['mchid'], "order_sn" => $refill_info['mch_order'], "amount" => $refill_info['refill_amount'], "sale_price" => ncPriceFormat($refill_info['mch_amount']), "cardno" => $refill_info['card_no'], "trade_no" => $refill_info['order_sn'], "idcard" => $refill_info['idcard'] ?? "", "card_name" => $refill_info['card_name'] ?? "", 'official_sn' => $osn, 'message' => $err_msg, "state" => $state]; $secure_key = $mch_info['secure_key']; $sign = $this->sign($params, $secure_key); return [$params, $sign]; }; $state = "SUCCESS"; $mchid = $refill_info['mchid']; $mch_info = Model('merchant')->getMerchantInfo(['mchid' => $mchid]); [$params, $sign] = $gen_body($state, $refill_info, $mch_info); $params['sign'] = $sign; $notify_url = $refill_info['notify_url']; //如果http请求内部,又发出回调自己的请求,在处理进程非动态扩容的情况下,容易造成阻塞. if ($this->is_url($notify_url)) { $resp = http_request($notify_url, $params, 'POST'); } else { $resp = RBridgeFactory::instance()->notify($notify_url, $params); } return $resp == "SUCCESS" || $resp == 'FAILED'; } private function sms($refill_info) { $official_sn = $refill_info['official_sn'] ?? ""; $card_type = $refill_info['card_type']; $card_no = $refill_info['card_no']; $amount = intval($refill_info['refill_amount']); if(!empty($official_sn) && $card_type == mtopcard\SinopecCard && !empty($card_no)) { $short_no = substr($card_no,-6); $ret = preg_match('/\d{4}(?P\d{2})(?P\d{2})(?P\d{2})(?P\d{2})\d{4}/u', $official_sn, $matches); if($ret > 0) { $sms = "【中国石化】您尾号为{$short_no}的加油卡于{$matches['month']}月{$matches['day']}日 {$matches['hour']}时{$matches['min']}分充值成功,金额{$amount}元,订单号:{$official_sn}"; return [true,$sms]; } } return [false,'']; } private function sign($params, $key) { ksort($params); $body = ""; $i = 0; foreach ($params as $k => $v) { if (false === $this->check_empty($v) && "@" != substr($v, 0, 1)) { if ($i == 0) { $body .= "$k" . "=" . urlencode($v); } else { $body .= "&" . "$k" . "=" . urlencode($v); } $i++; } } $body .= "&key=$key"; Log::record("notify_body=$body", Log::DEBUG); return md5($body); } private function check_empty($value) { if (!isset($value)) return true; if ($value === null) return true; if (trim($value) === "") return true; return false; } private function is_url($url) { $checker = function ($haystack, $needle) { $length = strlen($needle); return (substr($haystack, 0, $length) === $needle); }; return $checker($url, "http://") || $checker($url, "https://"); } public function need_intercept($mchid,$card_type,$card_state,$is_transfer,$card_no) : bool { return $this->mRatioCtl->need_intercept($mchid,$card_type,$card_state,$is_transfer,$card_no); } public function region_intercept($quality,$card_type,$region_no) : bool { return $this->mGlobalInterceptor->isIntercept($quality,$card_type,$region_no); } public function update_mchratios($gross,$detail,$types) { $this->mRatioCtl->update($gross,$detail,$types); } public function update_chctl($params) { $this->mChannelControl->update_chctl($params); } public function update_maxspeeds($speeds) { $this->mChannelControl->update_maxspeeds($speeds); } public function update_chspeeds($speeds) { $this->mChSpeed->update_cur($speeds); } public function is_over_chspeed($chname) { return $this->mChSpeed->is_over_chspeed($chname); } public function third_retry(order $order): array { return $this->mThirdHelper->third_retry($order); } public function third_mixed($mchid,$pcode): bool { return $this->mThirdHelper->mixed($mchid,$pcode); } }