Files
wordpress-theme-aksrt/inc/classes/payment/shipping-traits/trait-aksrt-shipping-part-1.php
T
kate522 ce1c13b947 refactor(classes): organize inc/classes into domain folders
Move ~94 root class shells and root *-traits into domain directories
(core, auth, user, credits, content, payment/gateways, forum, shop,
sms, ai, messages, extensions, widgets, settings, admin). Update
bootstrap require paths, co-locate traits with owners, fix forum/user
relative requires, drop duplicate shop controller requires, and wire
home-modules load. Path-only; class names and init order unchanged.
2026-07-17 11:11:57 +08:00

366 lines
14 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
defined('ABSPATH') || exit;
trait AKSRT_Shipping_Part_1 {
private function send_shipping_notification(string $order_number, string $carrier, string $tracking_no, string $note = ''): void {
if (!class_exists('AKSRT_Order_Repository')) {
return;
}
$order = AKSRT_Order_Repository::get_instance()->get($order_number);
if (!$order) {
return;
}
$user_id = (int) ($order['user_id'] ?? 0);
if ($user_id <= 0) {
return;
}
$user = get_userdata($user_id);
if (!$user || empty($user->user_email)) {
return;
}
$carrier_label = $this->get_carrier_label($carrier);
$site_name = get_bloginfo('name');
$subject = sprintf(__('[%s] 您的订单已发货', 'aksrt'), $site_name);
$body = sprintf(
__('您好 %1$s,\n\n您的订单 %2$s 已发货。\n\n物流公司:%3$s\n运单号:%4$s\n%5$s\n\n如有疑问请联系客服。\n\n%s', 'aksrt'),
$user->display_name,
$order_number,
$carrier_label,
$tracking_no,
$note !== '' ? sprintf(__('备注:%s', 'aksrt'), $note) : '',
$site_name
);
wp_mail($user->user_email, $subject, $body);
}
private function decorate_record(array $record): array {
$record['id'] = (int) ($record['id'] ?? 0);
$record['operator_id'] = isset($record['operator_id']) ? (int) $record['operator_id'] : 0;
$record['status_label'] = $this->get_status_label((string) ($record['status'] ?? ''));
$record['carrier_label'] = $this->get_carrier_label((string) ($record['carrier'] ?? ''));
$record['operator'] = $this->get_user_summary((int) ($record['operator_id'] ?? 0));
return $record;
}
private function get_carrier_label(string $carrier): string {
foreach (self::get_carrier_options() as $item) {
if (($item['value'] ?? '') === $carrier) {
return (string) ($item['label'] ?? $carrier);
}
}
return $carrier;
}
private function get_status_label(string $status): string {
foreach (self::get_status_options() as $item) {
if (($item['value'] ?? '') === $status) {
return (string) ($item['label'] ?? $status);
}
}
return $status;
}
private function get_user_summary(int $user_id): array {
if ($user_id <= 0) {
return [];
}
$user = get_userdata($user_id);
if (!$user) {
return [];
}
return [
'id' => $user_id,
'display_name' => (string) $user->display_name,
'user_login' => (string) $user->user_login,
];
}
private function build_summary_item(array $record): array {
return [
'id' => (int) ($record['id'] ?? 0),
'status' => (string) ($record['status'] ?? ''),
'status_label' => (string) ($record['status_label'] ?? ''),
'carrier' => (string) ($record['carrier'] ?? ''),
'carrier_label' => (string) ($record['carrier_label'] ?? ''),
'tracking_no' => (string) ($record['tracking_no'] ?? ''),
'created_at' => (string) ($record['created_at'] ?? ''),
'updated_at' => (string) ($record['updated_at'] ?? ''),
'delivered_at' => (string) ($record['delivered_at'] ?? ''),
];
}
private function can_transition_status(string $current_status, string $new_status): bool {
$map = [
self::STATUS_SHIPPED => [self::STATUS_IN_TRANSIT, self::STATUS_DELIVERED, self::STATUS_RETURNED],
self::STATUS_IN_TRANSIT => [self::STATUS_DELIVERED, self::STATUS_RETURNED],
self::STATUS_DELIVERED => [],
self::STATUS_RETURNED => [],
];
return in_array($new_status, $map[$current_status] ?? [], true);
}
private function sync_order_status(string $order_number, string $shipping_status, string $note = ''): void {
if ($order_number === '' || !class_exists('AKSRT_Order_Repository') || !class_exists('AKSRT_Order')) {
return;
}
if (!in_array($shipping_status, [self::STATUS_SHIPPED, self::STATUS_IN_TRANSIT, self::STATUS_DELIVERED], true)) {
return;
}
AKSRT_Order_Repository::get_instance()->update_status($order_number, AKSRT_Order::STATUS_DELIVERED, [
'note' => $note,
]);
}
/* ============================================================
* Express Query API (§6.6 快递查询)
*
* Supports kuaidi100, aliyun, kdniao.
* Traces cached in order_shipping.express_data JSON.
* Refresh interval from shop settings.
* ============================================================ */
public function query_express(int $shipping_id, bool $force = false): array {
global $wpdb;
$record = $this->get($shipping_id);
if (!$record) {
return ['success' => false, 'message' => __('发货记录不存在', 'aksrt')];
}
$tracking_no = (string) ($record['tracking_no'] ?? '');
$carrier = (string) ($record['carrier'] ?? '');
if ($tracking_no === '' || $carrier === '') {
return ['success' => false, 'message' => __('缺少运单号或物流公司', 'aksrt')];
}
$cached = $this->get_express_cache($shipping_id);
if (!$force && !empty($cached) && !$this->should_refresh_cache($cached)) {
return ['success' => true, 'data' => $cached];
}
if (!empty($cached['signed'])) {
return ['success' => true, 'data' => $cached];
}
$shop_settings = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_shop_settings() : [];
$api_sdk = sanitize_key((string) ($shop_settings['express']['api_sdk'] ?? 'kuaidi100'));
$result = $this->call_express_api($api_sdk, $carrier, $tracking_no, $shop_settings);
if ($result['success']) {
$express_data = [
'query_time' => current_time('mysql'),
'traces' => $result['traces'] ?? [],
'state' => $result['state'] ?? 'unknown',
'signed' => !empty($result['signed']),
'api' => $api_sdk,
];
$wpdb->update(
$this->table(),
['note' => wp_json_encode($express_data, JSON_UNESCAPED_UNICODE)],
['id' => $shipping_id],
['%s'],
['%d']
);
if (!empty($result['signed']) && ($record['status'] ?? '') !== self::STATUS_DELIVERED) {
$this->update_status($shipping_id, self::STATUS_DELIVERED, __('快递已签收(API检测)', 'aksrt'));
}
return ['success' => true, 'data' => $express_data];
}
return $result;
}
private function get_express_cache(int $shipping_id): array {
global $wpdb;
$note = $wpdb->get_var($wpdb->prepare(
"SELECT note FROM {$this->table()} WHERE id = %d",
$shipping_id
));
if (!$note) return [];
$data = json_decode($note, true);
return is_array($data) && isset($data['traces']) ? $data : [];
}
private function should_refresh_cache(array $cached): bool {
if (empty($cached['query_time'])) return true;
$shop = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_shop_settings() : [];
$interval = max(30, min(240, (int) ($shop['express']['query_interval'] ?? 60)));
$last = strtotime($cached['query_time']);
return (time() - $last) > ($interval * 60);
}
private function call_express_api(string $sdk, string $carrier, string $tracking_no, array $settings): array {
$carrier_code = $this->map_carrier_code($sdk, $carrier);
if ($sdk === 'kuaidi100') {
return $this->query_kuaidi100($carrier_code, $tracking_no, $settings);
}
if ($sdk === 'kdniao') {
return $this->query_kdniao($carrier_code, $tracking_no, $settings);
}
if ($sdk === 'aliyun') {
return $this->query_aliyun($carrier_code, $tracking_no, $settings);
}
return ['success' => false, 'message' => __('未配置快递查询接口', 'aksrt')];
}
private function query_kuaidi100(string $com, string $num, array $settings): array {
$key = sanitize_text_field((string) ($settings['express']['kuaidi100_key'] ?? ''));
$customer = sanitize_text_field((string) ($settings['express']['kuaidi100_customer'] ?? ''));
if ($key === '' || $customer === '') {
return ['success' => false, 'message' => __('快递100 API 未配置', 'aksrt')];
}
$param = wp_json_encode(['com' => $com, 'num' => $num]);
$sign = strtoupper(md5($param . $key . $customer));
$response = wp_remote_post('https://poll.kuaidi100.com/poll/query.do', [
'body' => ['customer' => $customer, 'sign' => $sign, 'param' => $param],
'timeout' => 10,
]);
if (is_wp_error($response)) {
return ['success' => false, 'message' => $response->get_error_message()];
}
$body = json_decode(wp_remote_retrieve_body($response), true);
if (!is_array($body) || (int) ($body['status'] ?? 0) !== 200) {
return ['success' => false, 'message' => (string) ($body['message'] ?? __('查询失败', 'aksrt'))];
}
$state = (string) ($body['state'] ?? '0');
$signed = $state === '3';
$traces = [];
foreach ((array) ($body['data'] ?? []) as $item) {
$traces[] = [
'time' => (string) ($item['ftime'] ?? $item['time'] ?? ''),
'context' => (string) ($item['context'] ?? ''),
];
}
return ['success' => true, 'traces' => $traces, 'state' => $state, 'signed' => $signed];
}
private function query_kdniao(string $code, string $num, array $settings): array {
$app_id = sanitize_text_field((string) ($settings['express']['kdniao_app_id'] ?? ''));
$app_key = sanitize_text_field((string) ($settings['express']['kdniao_app_key'] ?? ''));
if ($app_id === '' || $app_key === '') {
return ['success' => false, 'message' => __('快递鸟 API 未配置', 'aksrt')];
}
$request_data = wp_json_encode(['ShipperCode' => $code, 'LogisticCode' => $num]);
$data_sign = urlencode(base64_encode(md5($request_data . $app_key)));
$response = wp_remote_post('https://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx', [
'body' => [
'RequestData' => urlencode($request_data),
'EBusinessID' => $app_id,
'RequestType' => '1002',
'DataSign' => $data_sign,
'DataType' => '2',
],
'timeout' => 10,
]);
if (is_wp_error($response)) {
return ['success' => false, 'message' => $response->get_error_message()];
}
$body = json_decode(wp_remote_retrieve_body($response), true);
if (!is_array($body) || !($body['Success'] ?? false)) {
return ['success' => false, 'message' => (string) ($body['Reason'] ?? __('查询失败', 'aksrt'))];
}
$state = (string) ($body['State'] ?? '0');
$signed = $state === '3';
$traces = [];
foreach ((array) ($body['Traces'] ?? []) as $item) {
$traces[] = [
'time' => (string) ($item['AcceptTime'] ?? ''),
'context' => (string) ($item['AcceptStation'] ?? ''),
];
}
return ['success' => true, 'traces' => $traces, 'state' => $state, 'signed' => $signed];
}
private function query_aliyun(string $code, string $num, array $settings): array {
$app_code = sanitize_text_field((string) ($settings['express']['aliyun_app_code'] ?? ''));
if ($app_code === '') {
return ['success' => false, 'message' => __('阿里云快递 API 未配置', 'aksrt')];
}
$url = add_query_arg(['no' => $num, 'type' => $code], 'https://wuliu.market.alicloudapi.com/kdi');
$response = wp_remote_get($url, [
'headers' => ['Authorization' => 'APPCODE ' . $app_code],
'timeout' => 10,
]);
if (is_wp_error($response)) {
return ['success' => false, 'message' => $response->get_error_message()];
}
$body = json_decode(wp_remote_retrieve_body($response), true);
if (!is_array($body) || (int) ($body['status'] ?? -1) !== 0) {
return ['success' => false, 'message' => (string) ($body['msg'] ?? __('查询失败', 'aksrt'))];
}
$result_data = $body['result'] ?? [];
$state = (string) ($result_data['deliverystatus'] ?? '0');
$signed = $state === '3';
$traces = [];
foreach ((array) ($result_data['list'] ?? []) as $item) {
$traces[] = [
'time' => (string) ($item['time'] ?? ''),
'context' => (string) ($item['status'] ?? ''),
];
}
return ['success' => true, 'traces' => $traces, 'state' => $state, 'signed' => $signed];
}
private function map_carrier_code(string $sdk, string $carrier): string {
$kuaidi100_map = [
'sf' => 'shunfeng', 'yd' => 'yunda', 'zt' => 'zhongtong', 'yt' => 'yuantong',
'sto' => 'shentong', 'jd' => 'jd', 'ems' => 'ems', 'db' => 'debangkuaidi',
'yunda' => 'yunda',
];
$kdniao_map = [
'sf' => 'SF', 'yd' => 'YD', 'zt' => 'ZTO', 'yt' => 'YTO',
'sto' => 'STO', 'jd' => 'JD', 'ems' => 'EMS', 'db' => 'DBL',
'yunda' => 'YD',
];
if ($sdk === 'kuaidi100') {
return $kuaidi100_map[$carrier] ?? $carrier;
}
if ($sdk === 'kdniao') {
return $kdniao_map[$carrier] ?? strtoupper($carrier);
}
return $carrier;
}
}