仰望星辰工作室

better-staridc-MNBT

Z zfhsh first commit 2 天前
1<?php
2/**
3 * qmzl_domain 插件 - 启明智联平台 API 客户端
4 *
5 * 上游:https://cloud.qimingidc.cn/console/v1(地址写死)
6 * 认证:Authorization: Bearer {jwt}(用户各自绑定账号,密码登录获取)
7 *
8 * 覆盖接口(参考 qmzl_domain/域名注册API对接文档.md 与 idcsmart-api.md):
9 * POST /login 登录(密码方式)
10 * GET /idcsmart_domain/config 域名配置
11 * GET /idcsmart_domain/domain_suffix 后缀列表
12 * GET /idcsmart_domain/check_domain 可用性
13 * GET /idcsmart_domain/get_price 价格
14 * GET /idcsmart_domain/info_template 模板列表
15 * POST/PUT /idcsmart_domain/info_template 创建/更新模板(multipart)
16 * DELETE /idcsmart_domain/info_template/{id} 删除模板
17 * POST /idcsmart_domain/info_template/{id}/certifications 提交实名认证
18 * GET /product/group/first | /product/group/second | /product 域名产品 ID
19 * GET /gateway 支付网关
20 * POST /cart 加入购物车
21 * POST /cart/settle 结算创建订单
22 * POST /pay 发起支付(返回三方 HTML)
23 * GET /pay/{id}/status 支付状态
24 * GET /host 已购产品(域名)
25 */
26if (!defined('IN_CRONLITE')) exit;
27
28/** 上游 API 地址(写死) */
29define('QMZL_API_BASE', 'https://cloud.qimingidc.cn/console/v1');
30/** 上游站点根(支付页跳转兜底用) */
31define('QMZL_SITE', 'https://cloud.qimingidc.cn');
32
33/* ============================================================
34 * 1. HTTP 基础
35 * ============================================================ */
36
37/**
38 * 统一解析上游响应
39 * @param array $res mnbt_http_* / curl 的结果数组
40 * @return array {ok,status,msg,data}
41 */
42function qmzl_parse_response($res)
43{
44 $out = ['ok' => false, 'status' => 0, 'msg' => '', 'data' => null, 'code' => null];
45 $body = trim((string)($res['body'] ?? ''));
46 $j = null;
47 if ($body !== '') {
48 $j = json_decode($body, true);
49 }
50 if (empty($res['ok'])) {
51 if (is_array($j) && isset($j['status'])) {
52 $out['status'] = (int)$j['status'];
53 $out['msg'] = (string)($j['msg'] ?? '');
54 $out['data'] = $j['data'] ?? null;
55 $out['code'] = $j['code'] ?? null;
56 $out['ok'] = $out['status'] === 200;
57 return $out;
58 }
59 $out['msg'] = $res['error'] ?: ('HTTP ' . $res['code']);
60 return $out;
61 }
62 if ($body === '') {
63 $out['msg'] = '上游返回空响应';
64 return $out;
65 }
66 if (!is_array($j)) {
67 $out['msg'] = '上游返回非 JSON 内容';
68 return $out;
69 }
70 $out['status'] = isset($j['status']) ? (int)$j['status'] : 0;
71 $out['msg'] = (string)($j['msg'] ?? '');
72 $out['data'] = $j['data'] ?? null;
73 $out['code'] = $j['code'] ?? null;
74 $out['ok'] = $out['status'] === 200;
75 return $out;
76}
77
78/**
79 * JSON 请求(GET/POST/PUT/DELETE)
80 */
81function qmzl_http_json($method, $path, $token = '', $body = null, $params = [])
82{
83 $url = QMZL_API_BASE . '/' . ltrim($path, '/');
84 if ($params) {
85 $url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($params);
86 }
87 $headers = [];
88 if ($token !== '') {
89 $headers[] = 'Authorization: Bearer ' . $token;
90 }
91 $method = strtoupper($method ?: 'GET');
92 if ($method === 'GET') {
93 $res = mnbt_http_get($url, ['timeout' => 25, 'headers' => $headers]);
94 } elseif ($method === 'POST') {
95 $res = mnbt_http_post($url, $body, ['timeout' => 25, 'headers' => $headers]);
96 } else {
97 // PUT / DELETE:手动 curl
98 $ch = curl_init($url);
99 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
100 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
101 curl_setopt($ch, CURLOPT_TIMEOUT, 25);
102 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
103 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
104 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
105 curl_setopt($ch, CURLOPT_USERAGENT, 'MNBT-Plugin/1.83');
106 if ($body !== null) {
107 $headers[] = 'Content-Type: application/json; charset=utf-8';
108 curl_setopt($ch, CURLOPT_POSTFIELDS, is_array($body) ? json_encode($body, JSON_UNESCAPED_UNICODE) : $body);
109 }
110 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
111 $resp = curl_exec($ch);
112 $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
113 $err = curl_error($ch);
114 curl_close($ch);
115 $res = ['ok' => $code >= 200 && $code < 300, 'code' => $code, 'body' => (string)$resp, 'error' => $err];
116 }
117 return qmzl_parse_response($res);
118}
119
120/**
121 * multipart/form-data 请求(模板创建/更新、提交实名认证需传证件照片)
122 */
123function qmzl_http_multipart($method, $path, $token, $fields = [], $files = [])
124{
125 $url = QMZL_API_BASE . '/' . ltrim($path, '/');
126 $post = $fields;
127 foreach ($files as $k => $f) {
128 if (!empty($f['tmp_name']) && is_file($f['tmp_name'])) {
129 $post[$k] = new CURLFile(
130 $f['tmp_name'],
131 ($f['type'] && $f['type'] !== 'application/octet-stream') ? $f['type'] : 'application/octet-stream',
132 $f['name']
133 );
134 }
135 }
136 $headers = [];
137 if ($token !== '') {
138 $headers[] = 'Authorization: Bearer ' . $token;
139 }
140 $ch = curl_init($url);
141 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
142 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($method ?: 'POST'));
143 curl_setopt($ch, CURLOPT_TIMEOUT, 90);
144 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
145 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
146 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
147 curl_setopt($ch, CURLOPT_USERAGENT, 'MNBT-Plugin/1.83');
148 curl_setopt($ch, CURLOPT_POSTFIELDS, $post); // 数组+文件 → 自动 multipart
149 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
150 $resp = curl_exec($ch);
151 $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
152 $err = curl_error($ch);
153 curl_close($ch);
154 return qmzl_parse_response(['ok' => $code >= 200 && $code < 300, 'code' => $code, 'body' => (string)$resp, 'error' => $err]);
155}
156
157/* ============================================================
158 * 2. 账号与 Token
159 * ============================================================ */
160
161/** 密码加密(与系统 authcode 保持一致,SYS_KEY 做密钥) */
162function qmzl_encrypt_pwd($pwd)
163{
164 if ($pwd === '') return '';
165 return authcode($pwd, 'ENCODE', SYS_KEY);
166}
167
168function qmzl_decrypt_pwd($enc)
169{
170 if ($enc === '') return '';
171 return authcode($enc, 'DECODE', SYS_KEY);
172}
173
174/**
175 * 上游登录密码加密(AES-128-CBC,与 qiming-web AuthView.vue 一致)
176 * key=idcsmart.finance iv=9311019310287172 输出 base64
177 */
178function qmzl_encrypt_upstream_password($password)
179{
180 if (!function_exists('openssl_encrypt')) {
181 return '';
182 }
183 $enc = openssl_encrypt((string)$password, 'aes-128-cbc', 'idcsmart.finance', OPENSSL_RAW_DATA, '9311019310287172');
184 return $enc === false ? '' : base64_encode($enc);
185}
186
187/** 解析 JWT 的 exp */
188function qmzl_jwt_exp($jwt)
189{
190 if (!is_string($jwt) || $jwt === '') return 0;
191 $parts = explode('.', $jwt);
192 if (count($parts) < 2) return 0;
193 $payload = strtr($parts[1], '-_', '+/');
194 $j = json_decode((string)base64_decode($payload), true);
195 if (is_array($j) && isset($j['exp'])) return (int)$j['exp'];
196 return 0;
197}
198
199/**
200 * 密码方式登录(上游登录需要人机验证时传入 $captcha/$captchaToken)
201 * @return array {ok,status,msg,data:{jwt,exp,client_id}}
202 */
203function qmzl_login($account, $password, $captcha = '', $captchaToken = '')
204{
205 $enc = qmzl_encrypt_upstream_password($password);
206 if ($enc === '') {
207 return ['ok' => false, 'status' => 0, 'msg' => '服务器不支持密码加密(缺少 openssl 扩展)', 'data' => null];
208 }
209 $body = [
210 'type' => 'password',
211 'account' => (string)$account,
212 'password' => $enc,
213 'remember_password' => '1',
214 ];
215 if ($captcha !== '' && $captchaToken !== '') {
216 $body['captcha'] = (string)$captcha;
217 $body['token'] = (string)$captchaToken;
218 }
219 $res = qmzl_http_json('POST', 'login', '', $body);
220 if (!$res['ok']) {
221 return ['ok' => false, 'status' => $res['status'], 'msg' => $res['msg'] ?: '登录失败', 'data' => null];
222 }
223 $jwt = is_array($res['data']) ? (string)($res['data']['jwt'] ?? '') : '';
224 if ($jwt === '') {
225 return ['ok' => false, 'status' => 0, 'msg' => '登录成功但未返回 token', 'data' => null];
226 }
227 $exp = qmzl_jwt_exp($jwt);
228 return [
229 'ok' => true,
230 'status' => 200,
231 'msg' => '登录成功',
232 'data' => [
233 'jwt' => $jwt,
234 'exp' => $exp ?: (time() + 7200),
235 'client_id' => (int)(is_array($res['data']) ? ($res['data']['client_id'] ?? 0) : 0),
236 ],
237 ];
238}
239
240/**
241 * 获取上游人机验证码图片(QmCaptcha 点选验证,路径在站点根,不在 /console/v1 下)。
242 * 使用浏览器头规避反爬,返回字段宽松解析(部分环境不返回 status)。
243 * @return array {ok,msg,data:{base64,captcha_icon,sign}}
244 */
245function qmzl_captcha_refresh()
246{
247 $url = rtrim(QMZL_SITE, '/') . '/captcha/qm_captcha/index/refresh';
248 $ch = curl_init($url);
249 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
250 curl_setopt($ch, CURLOPT_TIMEOUT, 25);
251 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
252 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
253 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
254 curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
255 curl_setopt($ch, CURLOPT_HTTPHEADER, [
256 'Accept: application/json',
257 'Referer: ' . rtrim(QMZL_SITE, '/') . '/',
258 ]);
259 $resp = curl_exec($ch);
260 $err = curl_error($ch);
261 curl_close($ch);
262
263 $j = json_decode(trim((string)$resp), true);
264 if (!is_array($j)) {
265 return ['ok' => false, 'msg' => $err ?: '获取验证码失败(接口返回异常)', 'data' => null];
266 }
267 $base64 = (string)($j['base64'] ?? '');
268 $sign = (string)($j['sign'] ?? '');
269 if ($base64 === '' || $sign === '') {
270 return ['ok' => false, 'msg' => (string)($j['msg'] ?? '获取验证码失败'), 'data' => null];
271 }
272 return ['ok' => true, 'msg' => '', 'data' => [
273 'base64' => $base64,
274 'captcha_icon' => (string)($j['captcha_icon'] ?? ''),
275 'sign' => $sign,
276 ]];
277}
278
279/** 校验人机验证码(点选坐标) */
280function qmzl_captcha_verify($captcha, $token)
281{
282 $url = rtrim(QMZL_SITE, '/') . '/captcha/qm_captcha/index/verify';
283 $ch = curl_init($url);
284 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
285 curl_setopt($ch, CURLOPT_POST, true);
286 curl_setopt($ch, CURLOPT_TIMEOUT, 25);
287 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
288 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
289 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
290 curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
291 curl_setopt($ch, CURLOPT_HTTPHEADER, [
292 'Accept: application/json',
293 'Referer: ' . rtrim(QMZL_SITE, '/') . '/',
294 ]);
295 curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
296 'captcha' => (string)$captcha,
297 'token' => (string)$token,
298 ]));
299 $resp = curl_exec($ch);
300 $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
301 $err = curl_error($ch);
302 curl_close($ch);
303 $j = json_decode(trim((string)$resp), true);
304 if (!is_array($j) || !isset($j['status'])) {
305 return ['ok' => false, 'msg' => $err ?: '验证失败', 'data' => null];
306 }
307 if ((int)$j['status'] !== 200) {
308 return ['ok' => false, 'msg' => (string)($j['msg'] ?? '验证失败'), 'data' => null];
309 }
310 return ['ok' => true, 'msg' => '验证通过', 'data' => $j];
311}
312
313/**
314 * 开放接口鉴权登录(管理员/代理商用,换取 JWT,无需人机验证)
315 * POST {site}/api/v1/auth
316 * 请求:{username: 注册邮箱或手机号, password: API密钥 token}
317 * 返回:data.jwt
318 * @return array {ok,status,msg,data:{jwt,exp}}
319 */
320function qmzl_openapi_auth($username, $apiToken)
321{
322 $url = rtrim(QMZL_SITE, '/') . '/api/v1/auth';
323 $res = mnbt_http_post($url, [
324 'username' => (string)$username,
325 'password' => (string)$apiToken,
326 ], ['timeout' => 25]);
327 $j = json_decode(trim((string)($res['body'] ?? '')), true);
328 $jwt = is_array($j) && isset($j['data']) && is_array($j['data']) ? (string)($j['data']['jwt'] ?? '') : '';
329 if ($jwt === '') {
330 return ['ok' => false, 'status' => (int)($j['status'] ?? 0), 'msg' => (string)($j['msg'] ?? '鉴权失败'), 'data' => null];
331 }
332 $exp = qmzl_jwt_exp($jwt);
333 return ['ok' => true, 'status' => 200, 'msg' => '鉴权成功', 'data' => ['jwt' => $jwt, 'exp' => $exp ?: (time() + 7200)]];
334}
335
336/**
337 * 获取当前 user_info 用户的有效 token,过期自动重新登录
338 * @return array {ok,status,msg,data:{jwt}}
339 */
340function qmzl_get_token($user_id)
341{
342 $row = qmzl_account_get($user_id);
343 if (!$row) {
344 return ['ok' => false, 'status' => 0, 'msg' => '请先绑定启明智联账号', 'data' => null];
345 }
346 $jwt = (string)($row['jwt'] ?? '');
347 $exp = (int)($row['jwt_expire'] ?? 0);
348 if ($jwt !== '' && $exp > time() + 300) {
349 return ['ok' => true, 'status' => 200, 'msg' => '', 'data' => ['jwt' => $jwt]];
350 }
351 // token 失效,用存储的密码重新登录
352 $password = qmzl_decrypt_pwd((string)$row['password']);
353 if ($password === '') {
354 return ['ok' => false, 'status' => 0, 'msg' => '账号密码缺失,请重新绑定', 'data' => null];
355 }
356 $login = qmzl_login($row['account'], $password);
357 if (!$login['ok']) {
358 // 上游登录需人机验证时自动刷新失败,提示重新绑定
359 $msg = $login['msg'];
360 if (mb_strpos($msg, '验证码') !== false || mb_strpos($msg, 'captcha') !== false || mb_strpos($msg, '人机') !== false) {
361 $msg = '登录需要人机验证,请到「云账号」页重新验证绑定';
362 }
363 qmzl_account_update_status($user_id, 'error', $msg);
364 $login['msg'] = $msg;
365 return $login;
366 }
367 qmzl_account_set_token($user_id, $login['data']['jwt'], $login['data']['exp']);
368 qmzl_account_update_status($user_id, 'ok', '');
369 return ['ok' => true, 'status' => 200, 'msg' => '', 'data' => ['jwt' => $login['data']['jwt']]];
370}
371
372/* ============================================================
373 * 3. 域名接口
374 * ============================================================ */
375
376/** 获取域名配置(协议链接 / 可搜索后缀 / 默认后缀) */
377function qmzl_config($token)
378{
379 return qmzl_http_json('GET', 'idcsmart_domain/config', $token);
380}
381
382/** 获取后缀列表 */
383function qmzl_suffixes($token)
384{
385 return qmzl_http_json('GET', 'idcsmart_domain/domain_suffix', $token);
386}
387
388/** 查询域名可用性,$suffix 形如 .com,可传数组 */
389function qmzl_check($token, $domain, $suffix = '.com')
390{
391 $suffix = is_array($suffix) ? $suffix[0] : $suffix;
392 return qmzl_http_json('GET', 'idcsmart_domain/check_domain', $token, null, [
393 'domain' => $domain,
394 'suffix' => $suffix,
395 'host_id' => 0,
396 ]);
397}
398
399/** 查询域名价格(各年限) */
400function qmzl_price($token, $name)
401{
402 return qmzl_http_json('GET', 'idcsmart_domain/get_price', $token, null, [
403 'name' => $name,
404 'host_id' => 0,
405 ]);
406}
407
408/** 信息模板列表 */
409function qmzl_templates($token)
410{
411 return qmzl_http_json('GET', 'idcsmart_domain/info_template', $token);
412}
413
414/** 创建模板($fields 表单字段,$files 证件照片) */
415function qmzl_template_create($token, $fields, $files)
416{
417 return qmzl_http_multipart('POST', 'idcsmart_domain/info_template', $token, $fields, $files);
418}
419
420/** 更新模板 */
421function qmzl_template_update($token, $id, $fields, $files)
422{
423 return qmzl_http_multipart('PUT', 'idcsmart_domain/info_template/' . (int)$id, $token, $fields, $files);
424}
425
426/** 删除模板 */
427function qmzl_template_delete($token, $id)
428{
429 return qmzl_http_json('DELETE', 'idcsmart_domain/info_template/' . (int)$id, $token);
430}
431
432/** 提交实名认证 */
433function qmzl_template_certify($token, $id, $files)
434{
435 return qmzl_http_multipart('POST', 'idcsmart_domain/info_template/' . (int)$id . '/certifications', $token, [], $files);
436}
437
438/** 查找域名产品 ID(遍历产品分组 + 关键词兜底) */
439function qmzl_product_id($token)
440{
441 // 1) 关键词搜索
442 $res = qmzl_http_json('GET', 'product', $token, null, ['keywords' => '域名']);
443 if ($res['ok'] && !empty($res['data']['list']) && is_array($res['data']['list'])) {
444 foreach ($res['data']['list'] as $p) {
445 $name = (string)($p['name'] ?? '');
446 if (mb_strpos($name, '域名') !== false || stripos($name, 'domain') !== false) {
447 return (int)$p['id'];
448 }
449 }
450 if (isset($res['data']['list'][0]['id'])) return (int)$res['data']['list'][0]['id'];
451 }
452 // 2) 一级分组遍历
453 $r1 = qmzl_http_json('GET', 'product/group/first', $token);
454 if ($r1['ok'] && !empty($r1['data']['list']) && is_array($r1['data']['list'])) {
455 foreach ($r1['data']['list'] as $g) {
456 $r2 = qmzl_http_json('GET', 'product/group/second', $token, null, ['id' => (int)$g['id']]);
457 if (!$r2['ok'] || empty($r2['data']['list'])) continue;
458 foreach ($r2['data']['list'] as $sg) {
459 $rp = qmzl_http_json('GET', 'product', $token, null, ['id' => (int)$sg['id']]);
460 if (!$rp['ok'] || empty($rp['data']['list'])) continue;
461 foreach ($rp['data']['list'] as $p) {
462 $name = (string)($p['name'] ?? '');
463 if (mb_strpos($name, '域名') !== false || stripos($name, 'domain') !== false) {
464 return (int)$p['id'];
465 }
466 }
467 }
468 }
469 }
470 return 0;
471}
472
473/** 支付网关列表 */
474function qmzl_gateways($token = '')
475{
476 return qmzl_http_json('GET', 'gateway', $token);
477}
478
479/** 加入购物车 */
480function qmzl_cart_add($token, $productId, $domain, $year)
481{
482 return qmzl_http_json('POST', 'cart', $token, [
483 'product_id' => (int)$productId,
484 'config_options' => [
485 'domain' => (string)$domain,
486 'year' => (int)$year,
487 ],
488 'qty' => 1,
489 'customfield' => ['is_domain' => 1],
490 ]);
491}
492
493/** 结算创建订单 */
494function qmzl_cart_settle($token, $templateId, $autoRenew = 1, $lockStatus = 1)
495{
496 return qmzl_http_json('POST', 'cart/settle', $token, [
497 'positions' => [0],
498 'customfield' => [
499 'auto_renew' => $autoRenew ? 1 : 0,
500 'lock_status' => $lockStatus ? 1 : 0,
501 'c_sysid' => (string)$templateId,
502 'host_id' => 0,
503 ],
504 ]);
505}
506
507/** 发起支付,返回三方 HTML */
508function qmzl_pay($token, $orderId, $gateway)
509{
510 $res = qmzl_http_json('POST', 'pay', $token, [
511 'id' => (int)$orderId,
512 'gateway' => (string)$gateway,
513 ]);
514 if (!$res['ok']) return $res;
515 $data = is_array($res['data']) ? $res['data'] : [];
516 $code = (string)($res['code'] ?? ($data['code'] ?? ''));
517 $res['paid'] = ($code === 'Paid');
518 $res['pay_html'] = (string)($data['html'] ?? '');
519 return $res;
520}
521
522/** 支付状态:返回 {ok,status,msg,data,paid} */
523function qmzl_pay_status($token, $orderId)
524{
525 $res = qmzl_http_json('GET', 'pay/' . (int)$orderId . '/status', $token);
526 if (!$res['ok']) return $res;
527 $code = (string)($res['code'] ?? '');
528 if ($code === '' && is_array($res['data'])) {
529 $code = (string)($res['data']['code'] ?? '');
530 }
531 $res['paid'] = ($code === 'Paid');
532 return $res;
533}
534
535/** 使用账户余额支付上游订单(agent 模式自动注册用) */
536function qmzl_credit_pay($token, $orderId)
537{
538 return qmzl_http_json('POST', 'credit', $token, [
539 'id' => (int)$orderId,
540 'use' => 1,
541 ]);
542}
543
544/** 已购产品列表(域名) */
545function qmzl_hosts($token, $page = 1, $limit = 100)
546{
547 return qmzl_http_json('GET', 'host', $token, null, ['page' => (int)$page, 'limit' => (int)$limit]);
548}
549
550/**
551 * 获取当前用户已购域名列表。
552 * client 模式:按用户自己的上游账号 /host 过滤 type=domain;
553 * agent 模式:用管理员代理商账号取全部域名,再过滤该用户本地已支付订单的域名。
554 * @param array $user user_info 用户数组
555 * @return array {ok,msg,list}
556 */
557function qmzl_my_domains($user)
558{
559 $agent = qmzl_mode() === 'agent';
560 $tok = $agent ? qmzl_agent_token() : qmzl_get_token((int)$user['id']);
561 if (!$tok['ok']) return ['ok' => false, 'msg' => $tok['msg'], 'list' => []];
562 $res = qmzl_hosts($tok['data']['jwt'], 1, 100);
563 if (!$res['ok']) return ['ok' => false, 'msg' => $res['msg'] ?: '获取失败', 'list' => []];
564 $all = (is_array($res['data'] ?? null)) ? ($res['data']['list'] ?? []) : [];
565 if (!is_array($all)) $all = [];
566
567 if ($agent) {
568 // 该用户本地已支付订单的域名集合
569 $orders = qmzl_order_list((int)$user['id'], 1, 200);
570 $mine = [];
571 foreach ($orders['rows'] as $o) {
572 if (($o['status'] ?? '') === 'Paid' && ($o['domain'] ?? '') !== '') {
573 $mine[strtolower($o['domain'])] = true;
574 }
575 }
576 $list = [];
577 foreach ($all as $h) {
578 $name = strtolower((string)($h['name'] ?? $h['domain'] ?? ''));
579 if ($name !== '' && isset($mine[$name])) {
580 $list[] = $h;
581 }
582 }
583 return ['ok' => true, 'msg' => '', 'list' => $list];
584 }
585
586 $list = [];
587 foreach ($all as $h) {
588 if (($h['type'] ?? '') === 'domain') {
589 $list[] = $h;
590 }
591 }
592 return ['ok' => true, 'msg' => '', 'list' => $list];
593}
594
595/**
596 * 代理商模式:支付成功后自动在上游注册域名(加入购物车 + 结算 + 余额支付)。
597 * @param int $orderId plg_qmzl_order.id
598 * @return array {ok,msg}
599 */
600function qmzl_agent_register_domain($orderId)
601{
602 $order = qmzl_order_get_by_id($orderId);
603 if (!$order) return ['ok' => false, 'msg' => '本地订单不存在'];
604
605 $tok = qmzl_agent_token();
606 if (!$tok['ok']) return ['ok' => false, 'msg' => $tok['msg']];
607 $jwt = $tok['data']['jwt'];
608
609 $domain = (string)$order['domain'];
610 $year = (int)$order['year'];
611 $tplId = (int)$order['template_id'];
612 if ($domain === '' || $year < 1 || $tplId <= 0) {
613 qmzl_order_update($order['id'], ['status' => 'Failed', 'remark' => '订单参数不完整']);
614 return ['ok' => false, 'msg' => '订单参数不完整'];
615 }
616
617 // 1) 域名产品 ID
618 $productId = qmzl_product_id($jwt);
619 if ($productId <= 0) {
620 qmzl_order_update($order['id'], ['status' => 'Failed', 'remark' => '未找到域名产品']);
621 return ['ok' => false, 'msg' => '未找到域名产品,请联系平台确认'];
622 }
623
624 // 2) 加入购物车
625 $cart = qmzl_cart_add($jwt, $productId, $domain, $year);
626 if (!$cart['ok']) {
627 qmzl_order_update($order['id'], ['status' => 'Failed', 'remark' => '加入购物车失败:' . $cart['msg']]);
628 return ['ok' => false, 'msg' => '加入购物车失败:' . $cart['msg']];
629 }
630
631 // 3) 结算创建上游订单
632 $settle = qmzl_cart_settle($jwt, $tplId, 1, 1);
633 if (!$settle['ok']) {
634 qmzl_order_update($order['id'], ['status' => 'Failed', 'remark' => '上游下单失败:' . $settle['msg']]);
635 return ['ok' => false, 'msg' => '上游下单失败:' . $settle['msg']];
636 }
637 $cloudOrderId = 0;
638 if (isset($settle['data']['order_id'])) {
639 $cloudOrderId = (int)$settle['data']['order_id'];
640 } elseif (!empty($settle['data']['ids']) && is_array($settle['data']['ids'])) {
641 $cloudOrderId = (int)$settle['data']['ids'][0];
642 }
643 if ($cloudOrderId <= 0) {
644 qmzl_order_update($order['id'], ['status' => 'Failed', 'remark' => '上游未返回订单号']);
645 return ['ok' => false, 'msg' => '上游未返回订单号'];
646 }
647
648 // 4) 用代理商账号余额支付
649 $credit = qmzl_credit_pay($jwt, $cloudOrderId);
650 if (!$credit['ok']) {
651 qmzl_order_update($order['id'], [
652 'cloud_order_id' => (string)$cloudOrderId,
653 'status' => 'Failed',
654 'remark' => '上游余额支付失败:' . $credit['msg'],
655 ]);
656 return ['ok' => false, 'msg' => '上游余额支付失败:' . $credit['msg'] . '(请检查代理商账号余额)'];
657 }
658
659 qmzl_order_update($order['id'], [
660 'cloud_order_id' => (string)$cloudOrderId,
661 'status' => 'Paid',
662 'remark' => '已注册(上游订单 ' . $cloudOrderId . ')',
663 ]);
664 return ['ok' => true, 'msg' => '域名注册成功'];
665}