1.修复index.vue表格翻译错误
2.限制统一订单类型审核 3.统一订单手动推送报错PlayX 接口未配置
This commit is contained in:
@@ -166,7 +166,7 @@ class Order extends Backend
|
|||||||
if ($order->status !== MallOrder::STATUS_PENDING) {
|
if ($order->status !== MallOrder::STATUS_PENDING) {
|
||||||
return $this->error(__('Order status must be PENDING'));
|
return $this->error(__('Order status must be PENDING'));
|
||||||
}
|
}
|
||||||
if ($order->type === MallOrder::TYPE_PHYSICAL) {
|
if ($order->type !== MallOrder::TYPE_WITHDRAW) {
|
||||||
return $this->error(__('Order type not supported'));
|
return $this->error(__('Order type not supported'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,6 +214,9 @@ class Order extends Backend
|
|||||||
if ($order->status !== MallOrder::STATUS_PENDING) {
|
if ($order->status !== MallOrder::STATUS_PENDING) {
|
||||||
return $this->error(__('Order status must be PENDING'));
|
return $this->error(__('Order status must be PENDING'));
|
||||||
}
|
}
|
||||||
|
if (!in_array($order->type, [MallOrder::TYPE_PHYSICAL, MallOrder::TYPE_WITHDRAW], true)) {
|
||||||
|
return $this->error(__('Order type not supported'));
|
||||||
|
}
|
||||||
|
|
||||||
if ($order->type === MallOrder::TYPE_PHYSICAL && $rejectReason === '') {
|
if ($order->type === MallOrder::TYPE_PHYSICAL && $rejectReason === '') {
|
||||||
return $this->error(__('Missing required fields'));
|
return $this->error(__('Missing required fields'));
|
||||||
@@ -291,7 +294,7 @@ class Order extends Backend
|
|||||||
return $this->error(__('Current grant status cannot be manually pushed'));
|
return $this->error(__('Current grant status cannot be manually pushed'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strval(config('playx.api.base_url', '')) === '') {
|
if (strval(config('playx.angpow_import.base_url', '')) === '') {
|
||||||
return $this->error(__('PlayX API not configured'));
|
return $this->error(__('PlayX API not configured'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace app\common\library;
|
|||||||
|
|
||||||
use app\common\model\MallItem;
|
use app\common\model\MallItem;
|
||||||
use app\common\model\MallOrder;
|
use app\common\model\MallOrder;
|
||||||
|
use app\common\model\MallUserAsset;
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,60 +19,117 @@ final class MallBonusGrantPush
|
|||||||
*/
|
*/
|
||||||
public static function push(MallOrder $order): array
|
public static function push(MallOrder $order): array
|
||||||
{
|
{
|
||||||
$baseUrl = rtrim(strval(config('playx.api.base_url', '')), '/');
|
$conf = config('playx.angpow_import');
|
||||||
if ($baseUrl === '') {
|
if (!is_array($conf)) {
|
||||||
return [
|
return [
|
||||||
'ok' => false,
|
'ok' => false,
|
||||||
'message' => 'PlayX base_url not configured',
|
'message' => 'PlayX angpow_import not configured',
|
||||||
|
'playx_transaction_id' => '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$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',
|
||||||
'playx_transaction_id' => '',
|
'playx_transaction_id' => '',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$path = strval(config('playx.api.bonus_grant_url', '/api/v1/bonus/grant'));
|
|
||||||
$url = $baseUrl . $path;
|
$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',
|
||||||
|
'playx_transaction_id' => '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
$item = MallItem::where('id', $order->mall_item_id)->find();
|
$item = MallItem::where('id', $order->mall_item_id)->find();
|
||||||
$rewardName = $item ? strval($item->title) : '';
|
if (!$item) {
|
||||||
$category = $item ? strval($item->category) : 'daily';
|
return [
|
||||||
$categoryTitle = $item ? strval($item->category_title) : '';
|
'ok' => false,
|
||||||
|
'message' => 'Item not found',
|
||||||
|
'playx_transaction_id' => '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$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',
|
||||||
|
'playx_transaction_id' => '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$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);
|
$multiplier = intval($order->multiplier ?? 0);
|
||||||
if ($multiplier <= 0) {
|
if ($multiplier <= 0) {
|
||||||
$multiplier = 1;
|
$multiplier = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$payload = [
|
||||||
|
'merchant_code' => $merchantCode,
|
||||||
|
'report_date' => $reportDate,
|
||||||
|
'angpow' => [
|
||||||
|
[
|
||||||
|
'member_login' => strval($asset->playx_user_id),
|
||||||
|
'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([
|
$client = new Client([
|
||||||
'timeout' => 20,
|
'timeout' => 20,
|
||||||
'http_errors' => false,
|
'http_errors' => false,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$requestId = 'mall_bonus_' . uniqid();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$res = $client->post($url, [
|
$res = $client->post($url, [
|
||||||
'json' => [
|
'headers' => [
|
||||||
'request_id' => $requestId,
|
'Content-Type' => 'application/json',
|
||||||
'externalTransactionId' => $order->external_transaction_id,
|
'X-Request-Signature' => $signature,
|
||||||
'user_id' => $order->user_id,
|
|
||||||
'amount' => $order->amount,
|
|
||||||
'rewardName' => $rewardName,
|
|
||||||
'category' => $category,
|
|
||||||
'categoryTitle' => $categoryTitle,
|
|
||||||
'multiplier' => $multiplier,
|
|
||||||
],
|
],
|
||||||
|
'json' => $payload,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$data = json_decode(strval($res->getBody()), true) ?? [];
|
$data = json_decode(strval($res->getBody()), true) ?? [];
|
||||||
if ($res->getStatusCode() === 200 && ($data['status'] ?? '') === 'accepted') {
|
if (($data['code'] ?? null) === '0' || ($data['code'] ?? null) === 0) {
|
||||||
return [
|
return [
|
||||||
'ok' => true,
|
'ok' => true,
|
||||||
'message' => '',
|
'message' => '',
|
||||||
'playx_transaction_id' => strval($data['playx_transaction_id'] ?? ''),
|
'playx_transaction_id' => '',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'ok' => false,
|
'ok' => false,
|
||||||
'message' => strval($data['message'] ?? 'PlayX bonus grant not accepted'),
|
'message' => strval($data['message'] ?? 'PlayX angpow import not accepted'),
|
||||||
'playx_transaction_id' => '',
|
'playx_transaction_id' => '',
|
||||||
];
|
];
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
@@ -82,4 +140,35 @@ final class MallBonusGrantPush
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,8 +247,8 @@ class AngpowImportJobs
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
$start = gmdate('Y-m-d H:i:s', strtotime($order->start_time));
|
$start = gmdate('Y-m-d\TH:i:s\Z', strtotime($order->start_time));
|
||||||
$end = gmdate('Y-m-d H:i:s', strtotime($order->end_time));
|
$end = gmdate('Y-m-d\TH:i:s\Z', strtotime($order->end_time));
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'member_login' => strval($asset->playx_user_id),
|
'member_login' => strval($asset->playx_user_id),
|
||||||
|
|||||||
@@ -11,4 +11,8 @@ export default {
|
|||||||
[adminBaseRoutePath + '/user/rule']: ['./backend/${lang}/auth/rule.ts'],
|
[adminBaseRoutePath + '/user/rule']: ['./backend/${lang}/auth/rule.ts'],
|
||||||
[adminBaseRoutePath + '/user/scoreLog']: ['./backend/${lang}/user/moneyLog.ts'],
|
[adminBaseRoutePath + '/user/scoreLog']: ['./backend/${lang}/user/moneyLog.ts'],
|
||||||
[adminBaseRoutePath + '/crud/crud']: ['./backend/${lang}/crud/log.ts', './backend/${lang}/crud/state.ts'],
|
[adminBaseRoutePath + '/crud/crud']: ['./backend/${lang}/crud/log.ts', './backend/${lang}/crud/state.ts'],
|
||||||
|
[adminBaseRoutePath + '/mall/item']: ['./backend/${lang}/mall/item.ts'],
|
||||||
|
[adminBaseRoutePath + '/mall/item/index']: ['./backend/${lang}/mall/item.ts'],
|
||||||
|
[adminBaseRoutePath + '/mall.Item']: ['./backend/${lang}/mall/item.ts'],
|
||||||
|
[adminBaseRoutePath + '/mall.Item/index']: ['./backend/${lang}/mall/item.ts'],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
export default {
|
export default {
|
||||||
id: 'id',
|
id: 'ID',
|
||||||
title: 'title',
|
title: 'Title',
|
||||||
description: 'description',
|
description: 'Description',
|
||||||
remark: 'remark',
|
remark: 'Remark',
|
||||||
score: 'score',
|
score: 'Points',
|
||||||
type: 'type',
|
type: 'Type',
|
||||||
'type 1': 'type 1',
|
'type 1': 'Bonus',
|
||||||
'type 2': 'type 2',
|
'type 2': 'Physical',
|
||||||
'type 3': 'type 3',
|
'type 3': 'Withdraw',
|
||||||
amount: 'amount',
|
amount: 'Cash amount',
|
||||||
multiplier: 'multiplier',
|
multiplier: 'Turnover multiplier',
|
||||||
category: 'category',
|
category: 'Category',
|
||||||
category_title: 'category_title',
|
category_title: 'Category title',
|
||||||
admin_id: 'admin_id',
|
admin_id: 'Created by',
|
||||||
admin__username: 'username',
|
admin__username: 'Created by',
|
||||||
image: 'show image',
|
image: 'Image',
|
||||||
stock: 'stock',
|
stock: 'Stock',
|
||||||
sort: 'sort',
|
sort: 'Sort',
|
||||||
create_time: 'create_time',
|
status: 'Status',
|
||||||
update_time: 'update_time',
|
'status 0': 'Disabled',
|
||||||
'quick Search Fields': 'id',
|
'status 1': 'Enabled',
|
||||||
|
create_time: 'Created at',
|
||||||
|
update_time: 'Updated at',
|
||||||
|
'quick Search Fields': 'ID',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const optButtons: OptButton[] = defaultOptButtons(['edit', 'delete']).map((btn)
|
|||||||
type: 'primary',
|
type: 'primary',
|
||||||
class: 'table-row-edit',
|
class: 'table-row-edit',
|
||||||
icon: 'fa fa-check',
|
icon: 'fa fa-check',
|
||||||
|
display: (row: TableRow) => ['PHYSICAL', 'WITHDRAW'].includes(String(row.type)) && row.status === 'PENDING',
|
||||||
}
|
}
|
||||||
: btn
|
: btn
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -85,7 +85,7 @@
|
|||||||
:placeholder="t('Please input field', { field: t('mall.order.reject_reason') })"
|
:placeholder="t('Please input field', { field: t('mall.order.reject_reason') })"
|
||||||
/>
|
/>
|
||||||
<el-alert v-else type="info" :closable="false" show-icon>
|
<el-alert v-else type="info" :closable="false" show-icon>
|
||||||
确认后将驳回该订单并退回积分(红利/提现订单无需填写驳回原因)。
|
确认后将驳回该订单并退回积分(提现订单无需填写驳回原因)。
|
||||||
</el-alert>
|
</el-alert>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@@ -182,9 +182,10 @@ const { t } = useI18n()
|
|||||||
|
|
||||||
const isEdit = computed(() => baTable.form.operate === 'Edit')
|
const isEdit = computed(() => baTable.form.operate === 'Edit')
|
||||||
const isPending = computed(() => isEdit.value && baTable.form.items?.status === 'PENDING')
|
const isPending = computed(() => isEdit.value && baTable.form.items?.status === 'PENDING')
|
||||||
|
const needsReviewType = computed(() => ['PHYSICAL', 'WITHDRAW'].includes(String(baTable.form.items?.type || '')))
|
||||||
const isPhysical = computed(() => baTable.form.items?.type === 'PHYSICAL')
|
const isPhysical = computed(() => baTable.form.items?.type === 'PHYSICAL')
|
||||||
const canApprove = computed(() => isPending.value)
|
const canApprove = computed(() => isPending.value && needsReviewType.value)
|
||||||
const usePagedActions = computed(() => isPending.value)
|
const usePagedActions = computed(() => isPending.value && needsReviewType.value)
|
||||||
|
|
||||||
const page = ref<1 | 2>(1)
|
const page = ref<1 | 2>(1)
|
||||||
const action = ref<'approveShip' | 'reject' | null>(null)
|
const action = ref<'approveShip' | 'reject' | null>(null)
|
||||||
|
|||||||
Reference in New Issue
Block a user