Files
FlowPilot/gopay-utils.js
2026-05-29 10:09:26 +08:00

344 lines
12 KiB
JavaScript
Raw Permalink 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.
(function attachGoPayUtils(root, factory) {
root.GoPayUtils = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createGoPayUtils() {
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
const PLUS_PAYMENT_METHOD_PAYPAL_HOSTED = 'paypal-hosted';
const PLUS_PAYMENT_METHOD_NONE = 'none';
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
const DEFAULT_GPC_BASE_URL = 'https://gpc.qlhazycoder.top';
const ALLOWED_GPC_REMOTE_HOST = 'gpc.qlhazycoder.top';
function normalizePlusPaymentMethod(value = '') {
const normalized = String(value || '').trim().toLowerCase();
if (normalized === PLUS_PAYMENT_METHOD_NONE || normalized === 'no-payment' || normalized === 'skip-payment') {
return PLUS_PAYMENT_METHOD_NONE;
}
if (normalized === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED || normalized === 'paypal_direct' || normalized === 'paypal-direct') {
return PLUS_PAYMENT_METHOD_PAYPAL_HOSTED;
}
if (normalized === PLUS_PAYMENT_METHOD_GPC_HELPER) {
return PLUS_PAYMENT_METHOD_GPC_HELPER;
}
return normalized === PLUS_PAYMENT_METHOD_GOPAY ? PLUS_PAYMENT_METHOD_GOPAY : PLUS_PAYMENT_METHOD_PAYPAL;
}
const DEFAULT_GOPAY_COUNTRY_CODE = '+86';
function normalizeGoPayCountryCode(value = '') {
const normalized = String(value || '').trim().replace(/[^\d+]/g, '');
const digits = normalized.replace(/\D/g, '');
return digits ? `+${digits}` : DEFAULT_GOPAY_COUNTRY_CODE;
}
function normalizeGoPayPhone(value = '') {
return String(value || '').trim().replace(/[^\d+]/g, '');
}
function normalizeGoPayPhoneForCountry(value = '', countryCode = DEFAULT_GOPAY_COUNTRY_CODE) {
const normalizedPhone = normalizeGoPayPhone(value);
const normalizedCountryCode = normalizeGoPayCountryCode(countryCode);
const countryDigits = normalizedCountryCode.replace(/\D/g, '');
let nationalNumber = normalizedPhone.replace(/\D/g, '');
if (countryDigits && nationalNumber.startsWith(countryDigits)) {
nationalNumber = nationalNumber.slice(countryDigits.length);
}
return nationalNumber;
}
function normalizeGoPayPin(value = '') {
return String(value || '').trim().replace(/[^\d]/g, '');
}
function normalizeGoPayOtp(value = '') {
return String(value || '').trim().replace(/[^\d]/g, '');
}
function normalizeGpcRemainingUses(value) {
if (value === undefined || value === null || String(value).trim() === '') {
return null;
}
const numeric = Number(value);
return Number.isFinite(numeric) ? Math.max(0, Math.floor(numeric)) : null;
}
function normalizeGpcCardKey(value = '') {
return String(value || '').trim().toUpperCase();
}
function isGpcCardKeyFormat(value = '') {
return /^GPC-[A-F0-9]{8}-[A-F0-9]{8}-[A-F0-9]{8}$/.test(normalizeGpcCardKey(value));
}
function unwrapGpcBalancePayload(payload = {}) {
const data = unwrapGpcResponse(payload);
if (!data || typeof data !== 'object' || Array.isArray(data)) {
return data;
}
const hasBalanceFields = [
'remaining_uses',
'remainingUses',
'balance',
'remaining',
'uses',
'available_uses',
'availableUses',
'status',
'card_status',
'cardStatus',
'card_type',
'cardType',
'expires_at',
'expiresAt',
].some((key) => Object.prototype.hasOwnProperty.call(data, key));
if (!hasBalanceFields && data.data && typeof data.data === 'object' && !Array.isArray(data.data)) {
return data.data;
}
return data;
}
function getGpcBalanceRemainingUses(payload = {}) {
const data = unwrapGpcBalancePayload(payload);
if (!data || typeof data !== 'object') {
return null;
}
return normalizeGpcRemainingUses(
data.remaining_uses
?? data.remainingUses
?? data.balance
?? data.remaining
?? data.uses
?? data.available_uses
?? data.availableUses
);
}
function getGpcCardStatus(payload = {}) {
const data = unwrapGpcBalancePayload(payload);
if (!data || typeof data !== 'object') {
return '';
}
return String(data.status || data.card_status || data.cardStatus || '').trim();
}
function normalizeGpcBaseUrl(apiUrl = '') {
let normalized = String(apiUrl || DEFAULT_GPC_BASE_URL).trim();
if (!normalized) {
return DEFAULT_GPC_BASE_URL;
}
normalized = normalized.replace(/\/+$/g, '');
normalized = normalized.replace(/\/api\/checkout\/start$/i, '');
normalized = normalized.replace(/\/api\/web\/card\/balance(?:\?.*)?$/i, '');
normalized = normalized.replace(/\/api\/card\/balance(?:\?.*)?$/i, '');
try {
const parsed = new URL(normalized);
const hostname = parsed.hostname.toLowerCase();
if (hostname === ALLOWED_GPC_REMOTE_HOST || hostname === 'localhost' || hostname === '127.0.0.1') {
return normalized || DEFAULT_GPC_BASE_URL;
}
return DEFAULT_GPC_BASE_URL;
} catch {
return DEFAULT_GPC_BASE_URL;
}
}
function buildGpcApiUrl(apiUrl = '', path = '') {
const baseUrl = normalizeGpcBaseUrl(apiUrl);
if (!baseUrl) {
return '';
}
const normalizedPath = String(path || '').startsWith('/') ? String(path || '') : `/${String(path || '')}`;
return `${baseUrl}${normalizedPath}`;
}
function buildGpcCardBalanceUrl(apiUrl = '', cardKey = '') {
const baseUrl = buildGpcApiUrl(apiUrl, '/api/web/card/balance');
const normalizedCardKey = normalizeGpcCardKey(cardKey);
if (!baseUrl || !normalizedCardKey) {
return baseUrl;
}
return `${baseUrl}?card_key=${encodeURIComponent(normalizedCardKey)}`;
}
function unwrapGpcResponse(payload = {}) {
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
return payload;
}
const hasUnifiedShape = Object.prototype.hasOwnProperty.call(payload, 'data')
&& (
Object.prototype.hasOwnProperty.call(payload, 'code')
|| Object.prototype.hasOwnProperty.call(payload, 'message')
);
return hasUnifiedShape ? (payload.data ?? {}) : payload;
}
function isGpcUnifiedResponseOk(payload = {}) {
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
return true;
}
if (!Object.prototype.hasOwnProperty.call(payload, 'code')) {
return payload.ok !== false;
}
const code = Number(payload.code);
if (Number.isFinite(code)) {
return code >= 200 && code < 300;
}
return String(payload.code || '').trim() === '200';
}
function formatGpcErrorField(field) {
if (field === undefined || field === null) {
return '';
}
if (typeof field === 'string') {
return field.trim();
}
if (typeof field !== 'object') {
return String(field).trim();
}
const key = Array.isArray(field.loc)
? field.loc.join('.')
: String(field.field || field.path || field.name || field.param || '').trim();
const message = String(field.msg || field.message || field.error || field.detail || field.reason || '').trim();
return [key, message].filter(Boolean).join(': ') || JSON.stringify(field);
}
function extractGpcResponseErrorDetail(payload = {}, status = 0) {
if (!payload || typeof payload !== 'object') {
return status ? `HTTP ${status}` : '未知错误';
}
const payloadText = JSON.stringify(payload).toLowerCase();
if (/account\s+already\s+linked/i.test(payloadText)) {
return 'GOPAY已经绑了订阅,需要手动解绑';
}
const data = payload.data;
if (data && typeof data === 'object' && !Array.isArray(data)) {
const nestedDetail = data.detail ?? data.error ?? data.reason;
if (nestedDetail !== undefined && nestedDetail !== null && String(nestedDetail).trim()) {
const nestedText = String(nestedDetail).trim();
return /account\s+already\s+linked/i.test(nestedText)
? 'GOPAY已经绑了订阅,需要手动解绑'
: nestedText;
}
const fields = data.fields ?? data.errors;
if (Array.isArray(fields) && fields.length > 0) {
const formatted = fields
.map(formatGpcErrorField)
.filter(Boolean)
.join('; ');
if (formatted) {
return formatted;
}
}
}
const direct = payload.detail
?? payload.message
?? payload.error
?? payload.error_description
?? payload.reason;
if (direct !== undefined && direct !== null && String(direct).trim()) {
const directText = String(direct).trim();
return /account\s+already\s+linked/i.test(directText)
? 'GOPAY已经绑了订阅,需要手动解绑'
: directText;
}
const errorMessages = payload.error_messages ?? payload.errorMessages;
if (Array.isArray(errorMessages) && errorMessages.length > 0) {
const firstMessage = String(errorMessages[0] || '').trim();
if (/account\s+already\s+linked/i.test(firstMessage)) {
return 'GOPAY已经绑了订阅,需要手动解绑';
}
if (firstMessage) {
return firstMessage;
}
}
const errors = payload.errors;
if (Array.isArray(errors) && errors.length > 0) {
const first = errors[0];
if (typeof first === 'string') {
return first.trim() || (status ? `HTTP ${status}` : '未知错误');
}
if (first && typeof first === 'object') {
const field = Array.isArray(first.loc) ? first.loc.join('.') : String(first.field || first.path || '').trim();
const message = String(first.msg || first.message || first.error || '').trim();
return [field, message].filter(Boolean).join(': ') || JSON.stringify(first);
}
}
return status ? `HTTP ${status}` : '未知错误';
}
function formatGpcBalancePayload(payload = {}) {
const data = unwrapGpcBalancePayload(payload);
if (!data || typeof data !== 'object') {
return '';
}
const candidates = [
data.remaining_uses,
data.remainingUses,
data.balance,
data.remaining,
data.uses,
data.available_uses,
data.availableUses,
];
const firstValue = candidates.find((value) => value !== undefined && value !== null && String(value).trim() !== '');
const totalUses = data.total_uses ?? data.totalUses;
const usedUses = data.used_uses ?? data.usedUses;
const status = String(data.status || data.card_status || data.cardStatus || '').trim();
const flowId = String(data.flow_id || data.flowId || '').trim();
const parts = [];
if (firstValue !== undefined) {
parts.push(totalUses !== undefined && totalUses !== null && String(totalUses).trim() !== ''
? `余额 ${firstValue}/${totalUses}`
: `余额 ${firstValue}`);
}
if (usedUses !== undefined && usedUses !== null && String(usedUses).trim() !== '') {
parts.push(`已用 ${usedUses}`);
}
if (status) {
parts.push(`状态 ${status}`);
}
if (flowId) {
parts.push(`flow_id ${flowId}`);
}
return parts.join('');
}
return {
DEFAULT_GOPAY_COUNTRY_CODE,
DEFAULT_GPC_BASE_URL,
PLUS_PAYMENT_METHOD_GPC_HELPER,
PLUS_PAYMENT_METHOD_GOPAY,
PLUS_PAYMENT_METHOD_NONE,
PLUS_PAYMENT_METHOD_PAYPAL,
PLUS_PAYMENT_METHOD_PAYPAL_HOSTED,
buildGpcCardBalanceUrl,
buildGpcApiUrl,
extractGpcResponseErrorDetail,
formatGpcBalancePayload,
getGpcCardStatus,
getGpcBalanceRemainingUses,
isGpcUnifiedResponseOk,
isGpcCardKeyFormat,
normalizeGpcBaseUrl,
normalizeGpcCardKey,
normalizeGpcRemainingUses,
normalizeGoPayCountryCode,
normalizeGoPayPhone,
normalizeGoPayPhoneForCountry,
normalizeGoPayOtp,
normalizeGoPayPin,
normalizePlusPaymentMethod,
unwrapGpcBalancePayload,
unwrapGpcResponse,
};
});