Files
webman-buildadmin-mall/app/common/library/MallBonusGrantPush.php

247 lines
7.9 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
declare(strict_types=1);
namespace app\common\library;
use app\common\model\MallItem;
use app\common\model\MallOrder;
use app\common\model\MallUserAsset;
use GuzzleHttp\Client;
use support\Log;
/**
* 红利订单调用 PlayX bonus/grant与定时任务、后台手动推送共用
*/
final class MallBonusGrantPush
{
/**
* @return array{ok: bool, message: string}
*/
public static function push(MallOrder $order): array
{
$conf = config('playx.angpow_import');
if (!is_array($conf)) {
return [
'ok' => false,
'message' => 'playX angpow_import not configured',
];
}
$baseUrl = rtrim(strval($conf['base_url'] ?? ''), '/');
$path = strval($conf['path'] ?? '');
$merchantCode = strval($conf['merchant_code'] ?? '');
$authKey = strval($conf['auth_key'] ?? '');
if ($baseUrl === '' || $path === '' || $merchantCode === '' || $authKey === '') {
return [
'ok' => false,
'message' => 'playX Angpow Import API not configured',
];
}
$url = $baseUrl . $path;
$asset = MallUserAsset::where('playx_user_id', $order->user_id)->find();
if (!$asset || !is_string($asset->playx_user_id ?? null) || strval($asset->playx_user_id) === '') {
return [
'ok' => false,
'message' => 'User asset not found',
];
}
$memberLogin = trim(strval($asset->username ?? ''));
if ($memberLogin === '') {
return [
'ok' => false,
'message' => 'User username empty',
];
}
$item = MallItem::where('id', $order->mall_item_id)->find();
if (!$item) {
return [
'ok' => false,
'message' => 'Item not found',
];
}
$reportDate = strval(time());
$signatureInput = 'merchant_code=' . $merchantCode . '&report_date=' . $reportDate;
$signature = self::buildSignature($signatureInput, $authKey);
if ($signature === null) {
return [
'ok' => false,
'message' => 'Build signature failed',
];
}
$start = gmdate('Y-m-d\TH:i:s\Z', strtotime(strval($order->start_time)));
$end = gmdate('Y-m-d\TH:i:s\Z', strtotime(strval($order->end_time)));
$multiplier = intval($order->multiplier ?? 0);
if ($multiplier <= 0) {
$multiplier = 1;
}
$payload = [
'merchant_code' => $merchantCode,
'report_date' => $reportDate,
'angpow' => [
[
'member_login' => $memberLogin,
'start_time' => $start,
'end_time' => $end,
'amount' => $order->amount,
'reward_name' => strval($item->title ?? ''),
'description' => strval($item->description ?? ''),
'member_inbox_message' => 'Congratulations! You received an angpow.',
'category' => strval($item->category ?? ''),
'category_title' => strval($item->category_title ?? ''),
'one_time_turnover' => 'yes',
'multiplier' => $multiplier,
],
],
'currency_visual' => [
[
'currency' => strval($conf['currency'] ?? 'MYR'),
'visual_name' => strval($conf['visual_name'] ?? 'Angpow'),
],
],
];
$client = new Client([
'timeout' => 20,
'http_errors' => false,
]);
try {
$res = $client->post($url, [
'headers' => [
'Content-Type' => 'application/json',
'X-Request-Signature' => $signature,
],
'json' => $payload,
]);
$httpStatus = 0;
if (is_object($res) && method_exists($res, 'getStatusCode')) {
$sc = $res->getStatusCode();
if (is_int($sc)) {
$httpStatus = $sc;
}
}
$rawBody = strval($res->getBody());
$data = json_decode($rawBody, true);
if (!is_array($data)) {
$jsonDetail = 'root not JSON object';
if (json_last_error() !== JSON_ERROR_NONE) {
$jem = json_last_error_msg();
$jsonDetail = is_string($jem) && $jem !== '' ? $jem : 'JSON parse error';
}
Log::error(
'[MallBonusGrantPush] response not a JSON object | order_id=' . $order->id
. ' | http=' . $httpStatus . ' | ' . $jsonDetail . ' | body=' . self::truncateForLog($rawBody)
);
return [
'ok' => false,
'message' => 'Invalid response',
];
}
if (($data['code'] ?? null) === '0' || ($data['code'] ?? null) === 0) {
return [
'ok' => true,
'message' => '',
];
}
$remoteMsg = $data['message'] ?? $data['msg'] ?? '';
if (!is_string($remoteMsg)) {
$remoteMsg = '';
}
if ($remoteMsg === '') {
$remoteMsg = 'playX angpow import not accepted';
}
$flags = JSON_UNESCAPED_UNICODE;
if (defined('JSON_INVALID_UTF8_SUBSTITUTE')) {
$flags = $flags | JSON_INVALID_UTF8_SUBSTITUTE;
}
$encoded = json_encode($data, $flags);
if (!is_string($encoded)) {
$encoded = $rawBody;
}
Log::error(
'[MallBonusGrantPush] angpow import rejected | order_id=' . $order->id
. ' | http=' . $httpStatus . ' | parsed=' . self::truncateForLog($encoded)
);
return [
'ok' => false,
'message' => $remoteMsg,
];
} catch (\Throwable $e) {
Log::error('[MallBonusGrantPush] request exception | order_id=' . $order->id . ' | ' . $e->getMessage());
return [
'ok' => false,
'message' => $e->getMessage(),
];
}
}
private static function truncateForLog(string $text, int $maxLen = 8000): string
{
$s = trim($text);
if ($s === '') {
return '';
}
$oneLine = preg_replace('/\s+/', ' ', $s);
$snippet = is_string($oneLine) && $oneLine !== '' ? $oneLine : $s;
if (function_exists('mb_strlen') && function_exists('mb_substr')) {
if (mb_strlen($snippet, 'UTF-8') > $maxLen) {
return mb_substr($snippet, 0, $maxLen, 'UTF-8') . '…';
}
return $snippet;
}
if (strlen($snippet) > $maxLen) {
return substr($snippet, 0, $maxLen) . '…';
}
return $snippet;
}
private static function buildSignature(string $input, string $authKey): ?string
{
$keyBytes = null;
$maybeBase64 = base64_decode($authKey, true);
if ($maybeBase64 !== false && $maybeBase64 !== '') {
$keyBytes = $maybeBase64;
}
if ($keyBytes === null) {
$isHex = ctype_xdigit($authKey) && (strlen($authKey) % 2 === 0);
if ($isHex) {
$hex = hex2bin($authKey);
if ($hex !== false && $hex !== '') {
$keyBytes = $hex;
}
}
}
if ($keyBytes === null) {
$keyBytes = $authKey;
}
$raw = hash_hmac('sha1', $input, $keyBytes, true);
if (!is_string($raw) || $raw === '') {
return null;
}
return base64_encode($raw);
}
}