feat(recharge): add V5Pay for Yumi and Aslan
This commit is contained in:
parent
b96b5d934b
commit
f457803881
143
common/api.js
143
common/api.js
@ -633,7 +633,7 @@ var default_api = 'https://api.global-interaction.com/';
|
|||||||
};
|
};
|
||||||
|
|
||||||
// H5 充值 API 只做路径和参数名封装;token、app_code、env、错误 envelope 仍由 request 统一处理。
|
// H5 充值 API 只做路径和参数名封装;token、app_code、env、错误 envelope 仍由 request 统一处理。
|
||||||
// 金额、金币数、普通/币商钱包、MiFaPay 参数和 USDT 链上校验都以后端订单快照为准。
|
// 金额、金币数、普通/币商钱包、三方支付参数和 USDT 链上校验都以后端订单快照为准。
|
||||||
function isAslanWebPay() {
|
function isAslanWebPay() {
|
||||||
return activeAppConfig && activeAppConfig.adapter === 'aslan-web-pay';
|
return activeAppConfig && activeAppConfig.adapter === 'aslan-web-pay';
|
||||||
}
|
}
|
||||||
@ -701,52 +701,92 @@ var default_api = 'https://api.global-interaction.com/';
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
function aslanPaymentMethods(options) {
|
function legacyProviderPaymentMethods(options) {
|
||||||
var providers = (activeAppConfig && activeAppConfig.providers) || {};
|
var providers = (activeAppConfig && activeAppConfig.providers) || {};
|
||||||
var mifapay = providers.mifapay || {};
|
|
||||||
var countries = mifapay.countries || [];
|
|
||||||
var countryCode = String(
|
var countryCode = String(
|
||||||
options.country_code || options.countryCode || ''
|
options.country_code || options.countryCode || ''
|
||||||
).toUpperCase();
|
).toUpperCase();
|
||||||
var matchedCountries = countries.filter(function (country) {
|
var matchedMethods = [];
|
||||||
return countryMatchesCode(country, countryCode);
|
var globalMethods = [];
|
||||||
|
var allProviderMethods = [];
|
||||||
|
|
||||||
|
// Aslan/Yumi 的 app JSON 是旧支付网关的前端控制面;这里必须逐 provider 收集,
|
||||||
|
// 否则新增 V5Pay 后仍只读取 providers.mifapay,会出现配置存在但页面永远不展示的假开通。
|
||||||
|
Object.keys(providers).forEach(function (providerKey) {
|
||||||
|
var provider = providers[providerKey] || {};
|
||||||
|
var countries = Array.isArray(provider.countries)
|
||||||
|
? provider.countries
|
||||||
|
: [];
|
||||||
|
var matchedCountries = countries.filter(function (country) {
|
||||||
|
return countryMatchesCode(country, countryCode);
|
||||||
|
});
|
||||||
|
var globalCountries = countries.filter(function (country) {
|
||||||
|
return countryMatchesCode(country, 'GLOBAL');
|
||||||
|
});
|
||||||
|
matchedMethods = matchedMethods.concat(
|
||||||
|
collectLegacyProviderMethods(
|
||||||
|
matchedCountries,
|
||||||
|
provider,
|
||||||
|
providerKey,
|
||||||
|
options,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
);
|
||||||
|
globalMethods = globalMethods.concat(
|
||||||
|
collectLegacyProviderMethods(
|
||||||
|
globalCountries,
|
||||||
|
provider,
|
||||||
|
providerKey,
|
||||||
|
options,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
);
|
||||||
|
allProviderMethods = allProviderMethods.concat(
|
||||||
|
collectLegacyProviderMethods(
|
||||||
|
countries,
|
||||||
|
provider,
|
||||||
|
providerKey,
|
||||||
|
options,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
var globalCountries = countries.filter(function (country) {
|
|
||||||
return countryMatchesCode(country, 'GLOBAL');
|
var methods = matchedMethods.concat(globalMethods);
|
||||||
});
|
|
||||||
var matchedMethods = collectMifaPayMethods(
|
|
||||||
matchedCountries,
|
|
||||||
mifapay,
|
|
||||||
options,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
var methods = matchedMethods.concat(
|
|
||||||
collectMifaPayMethods(globalCountries, mifapay, options, false)
|
|
||||||
);
|
|
||||||
if (!matchedMethods.length && countryCode) {
|
if (!matchedMethods.length && countryCode) {
|
||||||
// 用户国家在 app JSON 里没有具体支付方式时,商品仍按用户国家查询,支付方式降级展示 JSON 中全部方式。
|
// 用户国家在 app JSON 里没有具体支付方式时,商品仍按用户国家查询,支付方式降级展示 JSON 中全部方式。
|
||||||
// 兜底方式只影响前端展示和用户选择,下单仍带当前用户国家的 payCountryId 供后端校验金额和币种。
|
// 兜底方式只影响前端展示和用户选择,下单仍带当前用户国家的 payCountryId 供后端校验金额和币种。
|
||||||
methods = collectMifaPayMethods(countries, mifapay, options, true);
|
methods = allProviderMethods;
|
||||||
}
|
}
|
||||||
return methods;
|
return methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectMifaPayMethods(countries, mifapay, options, fallbackAll) {
|
function collectLegacyProviderMethods(
|
||||||
|
countries,
|
||||||
|
provider,
|
||||||
|
providerKey,
|
||||||
|
options,
|
||||||
|
fallbackAll
|
||||||
|
) {
|
||||||
var methods = [];
|
var methods = [];
|
||||||
|
var providerCode = provider.provider_code || providerKey;
|
||||||
|
var providerName = provider.provider_name || providerCode;
|
||||||
(countries || []).forEach(function (country) {
|
(countries || []).forEach(function (country) {
|
||||||
var countryCode = primaryCountryCode(country);
|
var countryCode = primaryCountryCode(country);
|
||||||
(country.methods || []).forEach(function (method, index) {
|
(country.methods || []).forEach(function (method, index) {
|
||||||
methods.push(
|
methods.push(
|
||||||
Object.assign({}, method, {
|
Object.assign({}, method, {
|
||||||
|
// method_id 只用于同页稳定区分卡片,不能复制 Lalu 数据库 method_id;
|
||||||
|
// 旧网关真正下单依赖 provider/payWay/payType/channelCode,而不是该前端合成值。
|
||||||
method_id: [
|
method_id: [
|
||||||
mifapay.provider_code || 'mifapay',
|
providerCode,
|
||||||
countryCode,
|
countryCode,
|
||||||
method.pay_way,
|
method.channel_code || method.pay_way,
|
||||||
method.pay_type,
|
method.pay_type || method.channel_code,
|
||||||
index,
|
index,
|
||||||
].join(':'),
|
].join(':'),
|
||||||
provider_code: mifapay.provider_code || 'mifapay',
|
provider_code: providerCode,
|
||||||
provider_name: mifapay.provider_name || 'MiFaPay',
|
provider_name: providerName,
|
||||||
country_code: countryCode,
|
country_code: countryCode,
|
||||||
country_name: country.country_name,
|
country_name: country.country_name,
|
||||||
currency_code:
|
currency_code:
|
||||||
@ -761,7 +801,7 @@ var default_api = 'https://api.global-interaction.com/';
|
|||||||
options.pay_country_id ||
|
options.pay_country_id ||
|
||||||
options.payCountryId ||
|
options.payCountryId ||
|
||||||
'',
|
'',
|
||||||
status: mifapay.status || 'active',
|
status: provider.status || 'active',
|
||||||
fallback_all_countries: !!fallbackAll,
|
fallback_all_countries: !!fallbackAll,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -1058,28 +1098,48 @@ var default_api = 'https://api.global-interaction.com/';
|
|||||||
channel.channel_code,
|
channel.channel_code,
|
||||||
details.channelCode,
|
details.channelCode,
|
||||||
details.channel_code,
|
details.channel_code,
|
||||||
|
details.factoryChannel,
|
||||||
|
details.factory_channel,
|
||||||
].forEach(function (code) {
|
].forEach(function (code) {
|
||||||
if (code) supported[String(code)] = item;
|
if (code) supported[String(code).toUpperCase()] = item;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return aslanPaymentMethods(options).filter(function (method) {
|
return legacyProviderPaymentMethods(options).filter(function (method) {
|
||||||
|
if (method.provider_code === 'v5pay') {
|
||||||
|
// Yumi 的旧 options 接口只返回数据库渠道明细;V5Pay 由服务端在下单时根据
|
||||||
|
// provider/payType 合成受控快照,因此不能拿 MiFaPay 的旧渠道集合误删 V5Pay 入口。
|
||||||
|
return true;
|
||||||
|
}
|
||||||
var channelCode =
|
var channelCode =
|
||||||
method.channel_code ||
|
method.channel_code ||
|
||||||
[method.pay_way, method.pay_type].filter(Boolean).join('_');
|
[method.pay_way, method.pay_type].filter(Boolean).join('_');
|
||||||
if (!Object.keys(supported).length) return true;
|
if (!Object.keys(supported).length) return true;
|
||||||
return !!supported[channelCode];
|
return !!supported[String(channelCode || '').toUpperCase()];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function providerCodeFromFactory(factoryCode) {
|
||||||
|
// Java 旧网关返回 factory code,而 H5 状态机统一消费小写 provider code;
|
||||||
|
// 显式映射能避免 V5_PAY 被当成未知的重定向渠道并以大写值持久化。
|
||||||
|
if (factoryCode === 'MIFA_PAY' || factoryCode === 'MIFAPAY')
|
||||||
|
return 'mifapay';
|
||||||
|
if (factoryCode === 'V5_PAY' || factoryCode === 'V5PAY') return 'v5pay';
|
||||||
|
return factoryCode;
|
||||||
|
}
|
||||||
|
|
||||||
function mapYumiOrder(order) {
|
function mapYumiOrder(order) {
|
||||||
order = order || {};
|
order = order || {};
|
||||||
var factoryCode = String(
|
var factoryCode = String(
|
||||||
order.factoryCode || order.factory_code || ''
|
order.factoryCode ||
|
||||||
|
order.factory_code ||
|
||||||
|
order.providerCode ||
|
||||||
|
order.provider_code ||
|
||||||
|
''
|
||||||
).toUpperCase();
|
).toUpperCase();
|
||||||
return {
|
return {
|
||||||
order_id: order.orderId || order.order_id || '',
|
order_id: order.orderId || order.order_id || '',
|
||||||
trade_no: order.tradeNo || order.trade_no || '',
|
trade_no: order.tradeNo || order.trade_no || '',
|
||||||
provider_code: factoryCode === 'MIFA_PAY' ? 'mifapay' : factoryCode,
|
provider_code: providerCodeFromFactory(factoryCode),
|
||||||
factory_code: factoryCode,
|
factory_code: factoryCode,
|
||||||
pay_url:
|
pay_url:
|
||||||
order.requestUrl || order.request_url || order.pay_url || '',
|
order.requestUrl || order.request_url || order.pay_url || '',
|
||||||
@ -1101,12 +1161,16 @@ var default_api = 'https://api.global-interaction.com/';
|
|||||||
? 'failed'
|
? 'failed'
|
||||||
: 'pending';
|
: 'pending';
|
||||||
var factoryCode = String(
|
var factoryCode = String(
|
||||||
order.factoryCode || order.factory_code || ''
|
order.factoryCode ||
|
||||||
|
order.factory_code ||
|
||||||
|
order.providerCode ||
|
||||||
|
order.provider_code ||
|
||||||
|
''
|
||||||
).toUpperCase();
|
).toUpperCase();
|
||||||
return {
|
return {
|
||||||
order_id: order.orderId || order.order_id || '',
|
order_id: order.orderId || order.order_id || '',
|
||||||
trade_no: order.tradeNo || order.trade_no || '',
|
trade_no: order.tradeNo || order.trade_no || '',
|
||||||
provider_code: factoryCode === 'MIFA_PAY' ? 'mifapay' : factoryCode,
|
provider_code: providerCodeFromFactory(factoryCode),
|
||||||
factory_code: factoryCode,
|
factory_code: factoryCode,
|
||||||
currency_code: order.currency || order.currency_code || '',
|
currency_code: order.currency || order.currency_code || '',
|
||||||
provider_amount_minor: decimalToMinor(order.amount),
|
provider_amount_minor: decimalToMinor(order.amount),
|
||||||
@ -1176,7 +1240,8 @@ var default_api = 'https://api.global-interaction.com/';
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
options.payment_methods = aslanPaymentMethods(options);
|
options.payment_methods =
|
||||||
|
legacyProviderPaymentMethods(options);
|
||||||
options.usdt_trc20_enabled = false;
|
options.usdt_trc20_enabled = false;
|
||||||
return options;
|
return options;
|
||||||
});
|
});
|
||||||
@ -1219,7 +1284,17 @@ var default_api = 'https://api.global-interaction.com/';
|
|||||||
goodsId: payload && payload.product_id,
|
goodsId: payload && payload.product_id,
|
||||||
payCountryId: payload && payload.pay_country_id,
|
payCountryId: payload && payload.pay_country_id,
|
||||||
userId: payload && payload.user_id,
|
userId: payload && payload.user_id,
|
||||||
|
// Yumi 旧接口以前只收到 channelCode,无法区分同一渠道下的 MiFaPay/V5Pay。
|
||||||
|
// provider 与 V5Pay productType/productMode 必须原样透传,让后端策略选择和签名字段都以用户选择为准。
|
||||||
|
providerCode: payload && payload.provider_code,
|
||||||
|
payWay: payload && payload.pay_way,
|
||||||
|
payType: payload && payload.pay_type,
|
||||||
channelCode: payload && payload.channel_code,
|
channelCode: payload && payload.channel_code,
|
||||||
|
countryCode: payload && payload.country_code,
|
||||||
|
currencyCode: payload && payload.currency_code,
|
||||||
|
returnUrl: payload && payload.return_url,
|
||||||
|
commandId: payload && payload.command_id,
|
||||||
|
language: readLanguageQuery() || 'en',
|
||||||
newVersion: true,
|
newVersion: true,
|
||||||
appVersion: 'h5',
|
appVersion: 'h5',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
"dev:manager-center": "python3 scripts/h5_dev_server.py --host 127.0.0.1 --port 8788 --directory .",
|
"dev:manager-center": "python3 scripts/h5_dev_server.py --host 127.0.0.1 --port 8788 --directory .",
|
||||||
"aslan:webview": "node scripts/aslan_flutter_webview.js",
|
"aslan:webview": "node scripts/aslan_flutter_webview.js",
|
||||||
"test:manager-center": "node scripts/test_manager_center_resource_pagination.js",
|
"test:manager-center": "node scripts/test_manager_center_resource_pagination.js",
|
||||||
|
"test:recharge-v5pay": "node scripts/test_recharge_v5pay.js",
|
||||||
"format": "prettier --write \"**/*.{js,html,css,json,md}\"",
|
"format": "prettier --write \"**/*.{js,html,css,json,md}\"",
|
||||||
"format:check": "prettier --check \"**/*.{js,html,css,json,md}\""
|
"format:check": "prettier --check \"**/*.{js,html,css,json,md}\""
|
||||||
},
|
},
|
||||||
|
|||||||
@ -361,6 +361,236 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"v5pay": {
|
||||||
|
"provider_code": "v5pay",
|
||||||
|
"provider_name": "V5Pay",
|
||||||
|
"status": "active",
|
||||||
|
"countries": [
|
||||||
|
{
|
||||||
|
"country_code": "EG",
|
||||||
|
"country_codes": ["EG", "EGY"],
|
||||||
|
"country_name": "Egypt",
|
||||||
|
"currency_code": "EGP",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"pay_way": "Cash",
|
||||||
|
"pay_type": "FAWRY",
|
||||||
|
"channel_code": "FAWRY",
|
||||||
|
"method_name": "Fawry",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "Ewallet",
|
||||||
|
"pay_type": "VODAFONE",
|
||||||
|
"channel_code": "VODAFONE",
|
||||||
|
"method_name": "Vodafone",
|
||||||
|
"logo_url": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"country_code": "IN",
|
||||||
|
"country_codes": ["IN", "IND"],
|
||||||
|
"country_name": "India",
|
||||||
|
"currency_code": "INR",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "UPI",
|
||||||
|
"channel_code": "UPI",
|
||||||
|
"method_name": "UPI",
|
||||||
|
"logo_url": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"country_code": "PH",
|
||||||
|
"country_codes": ["PH", "PHL"],
|
||||||
|
"country_name": "Philippines",
|
||||||
|
"currency_code": "PHP",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "GCASH_ONLINE",
|
||||||
|
"channel_code": "GCASH_ONLINE",
|
||||||
|
"method_name": "GCash",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "COINS_ONLINE",
|
||||||
|
"channel_code": "COINS_ONLINE",
|
||||||
|
"method_name": "Coins.ph",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "FIXED_CODE",
|
||||||
|
"pay_type": "INSTAPAY_QR_STATIC",
|
||||||
|
"channel_code": "INSTAPAY_QR_STATIC",
|
||||||
|
"method_name": "QR Ph",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "DYNAMIC_CODE",
|
||||||
|
"pay_type": "INSTAPAY_QR",
|
||||||
|
"channel_code": "INSTAPAY_QR",
|
||||||
|
"method_name": "QR Ph",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "QR",
|
||||||
|
"pay_type": "QRPH_GCASH",
|
||||||
|
"channel_code": "QRPH_GCASH",
|
||||||
|
"method_name": "QR Ph (GCash)",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "PESONET",
|
||||||
|
"channel_code": "PESONET",
|
||||||
|
"method_name": "PESONet",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "UB_ONLINE",
|
||||||
|
"channel_code": "UB_ONLINE",
|
||||||
|
"method_name": "UnionBank",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "BPI_ONLINE",
|
||||||
|
"channel_code": "BPI_ONLINE",
|
||||||
|
"method_name": "BPI",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "RCBC_ONLINE",
|
||||||
|
"channel_code": "RCBC_ONLINE",
|
||||||
|
"method_name": "RCBC",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "FIXED_CODE",
|
||||||
|
"pay_type": "BANKVA",
|
||||||
|
"channel_code": "BANKVA",
|
||||||
|
"method_name": "Bank Transfer",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "FIXED_CODE",
|
||||||
|
"pay_type": "BILL",
|
||||||
|
"channel_code": "BILL",
|
||||||
|
"method_name": "Paybills",
|
||||||
|
"logo_url": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"country_code": "PK",
|
||||||
|
"country_codes": ["PK", "PAK"],
|
||||||
|
"country_name": "Pakistan",
|
||||||
|
"currency_code": "PKR",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "JAZZCASH",
|
||||||
|
"channel_code": "JAZZCASH",
|
||||||
|
"method_name": "JazzCash",
|
||||||
|
"logo_url": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"country_code": "TR",
|
||||||
|
"country_codes": ["TR", "TUR"],
|
||||||
|
"country_name": "Turkey",
|
||||||
|
"currency_code": "TRY",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "GPAY",
|
||||||
|
"channel_code": "GPAY",
|
||||||
|
"method_name": "GPay",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "BKM_EXPRESS",
|
||||||
|
"channel_code": "BKM_EXPRESS",
|
||||||
|
"method_name": "BKM Express",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "BANK_TRANSFER",
|
||||||
|
"channel_code": "BANK_TRANSFER",
|
||||||
|
"method_name": "Bank Transfer",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "CREDIT_CARD",
|
||||||
|
"channel_code": "CREDIT_CARD",
|
||||||
|
"method_name": "Credit Card",
|
||||||
|
"logo_url": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"country_code": "BR",
|
||||||
|
"country_codes": ["BR", "BRA"],
|
||||||
|
"country_name": "Brazil",
|
||||||
|
"currency_code": "BRL",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "PICPAY",
|
||||||
|
"channel_code": "PICPAY",
|
||||||
|
"method_name": "PicPay",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "PIX",
|
||||||
|
"channel_code": "PIX",
|
||||||
|
"method_name": "PIX",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "BOLETO",
|
||||||
|
"channel_code": "BOLETO",
|
||||||
|
"method_name": "Boleto",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "BANK_TRANSFER",
|
||||||
|
"pay_type": "BANK_TRANSFER",
|
||||||
|
"channel_code": "BANK_TRANSFER",
|
||||||
|
"method_name": "Transferência Bancária",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "CREDIT_CARD",
|
||||||
|
"channel_code": "CREDIT_CARD",
|
||||||
|
"method_name": "Credit Card",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "BINANCE",
|
||||||
|
"channel_code": "BINANCE",
|
||||||
|
"method_name": "Binance Pay",
|
||||||
|
"logo_url": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -362,6 +362,258 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"v5pay": {
|
||||||
|
"provider_code": "v5pay",
|
||||||
|
"provider_name": "V5Pay",
|
||||||
|
"status": "active",
|
||||||
|
"countries": [
|
||||||
|
{
|
||||||
|
"country_code": "EG",
|
||||||
|
"country_codes": ["EG", "EGY"],
|
||||||
|
"country_name": "Egypt",
|
||||||
|
"currency_code": "EGP",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"pay_way": "Cash",
|
||||||
|
"pay_type": "FAWRY",
|
||||||
|
"channel_code": "FAWRY",
|
||||||
|
"method_name": "Fawry",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "Ewallet",
|
||||||
|
"pay_type": "VODAFONE",
|
||||||
|
"channel_code": "VODAFONE",
|
||||||
|
"method_name": "Vodafone",
|
||||||
|
"logo_url": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"country_code": "IN",
|
||||||
|
"country_codes": ["IN", "IND"],
|
||||||
|
"country_name": "India",
|
||||||
|
"currency_code": "INR",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "UPI",
|
||||||
|
"channel_code": "UPI",
|
||||||
|
"method_name": "UPI",
|
||||||
|
"logo_url": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"country_code": "PH",
|
||||||
|
"country_codes": ["PH", "PHL"],
|
||||||
|
"country_name": "Philippines",
|
||||||
|
"currency_code": "PHP",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "GCASH_ONLINE",
|
||||||
|
"channel_code": "GCASH_ONLINE",
|
||||||
|
"method_name": "GCash",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "COINS_ONLINE",
|
||||||
|
"channel_code": "COINS_ONLINE",
|
||||||
|
"method_name": "Coins.ph",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "FIXED_CODE",
|
||||||
|
"pay_type": "INSTAPAY_QR_STATIC",
|
||||||
|
"channel_code": "INSTAPAY_QR_STATIC",
|
||||||
|
"method_name": "QR Ph",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "DYNAMIC_CODE",
|
||||||
|
"pay_type": "INSTAPAY_QR",
|
||||||
|
"channel_code": "INSTAPAY_QR",
|
||||||
|
"method_name": "QR Ph",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "QR",
|
||||||
|
"pay_type": "QRPH_GCASH",
|
||||||
|
"channel_code": "QRPH_GCASH",
|
||||||
|
"method_name": "QR Ph (GCash)",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "PESONET",
|
||||||
|
"channel_code": "PESONET",
|
||||||
|
"method_name": "PESONet",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "UB_ONLINE",
|
||||||
|
"channel_code": "UB_ONLINE",
|
||||||
|
"method_name": "UnionBank",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "BPI_ONLINE",
|
||||||
|
"channel_code": "BPI_ONLINE",
|
||||||
|
"method_name": "BPI",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "RCBC_ONLINE",
|
||||||
|
"channel_code": "RCBC_ONLINE",
|
||||||
|
"method_name": "RCBC",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "FIXED_CODE",
|
||||||
|
"pay_type": "BANKVA",
|
||||||
|
"channel_code": "BANKVA",
|
||||||
|
"method_name": "Bank Transfer",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "FIXED_CODE",
|
||||||
|
"pay_type": "BILL",
|
||||||
|
"channel_code": "BILL",
|
||||||
|
"method_name": "Paybills",
|
||||||
|
"logo_url": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"country_code": "PK",
|
||||||
|
"country_codes": ["PK", "PAK"],
|
||||||
|
"country_name": "Pakistan",
|
||||||
|
"currency_code": "PKR",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "JAZZCASH",
|
||||||
|
"channel_code": "JAZZCASH",
|
||||||
|
"method_name": "JazzCash",
|
||||||
|
"logo_url": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"country_code": "VN",
|
||||||
|
"country_codes": ["VN", "VNM"],
|
||||||
|
"country_name": "Vietnam",
|
||||||
|
"currency_code": "VND",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "BANK_TRANSFER",
|
||||||
|
"channel_code": "BANK_TRANSFER",
|
||||||
|
"method_name": "Bank Transfer",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "QR",
|
||||||
|
"channel_code": "QR",
|
||||||
|
"method_name": "VietQR",
|
||||||
|
"logo_url": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"country_code": "TR",
|
||||||
|
"country_codes": ["TR", "TUR", "T2", "TU2"],
|
||||||
|
"country_name": "Turkey",
|
||||||
|
"currency_code": "TRY",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "GPAY",
|
||||||
|
"channel_code": "GPAY",
|
||||||
|
"method_name": "GPay",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "BKM_EXPRESS",
|
||||||
|
"channel_code": "BKM_EXPRESS",
|
||||||
|
"method_name": "BKM Express",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "BANK_TRANSFER",
|
||||||
|
"channel_code": "BANK_TRANSFER",
|
||||||
|
"method_name": "Bank Transfer",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "CREDIT_CARD",
|
||||||
|
"channel_code": "CREDIT_CARD",
|
||||||
|
"method_name": "Credit Card",
|
||||||
|
"logo_url": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"country_code": "BR",
|
||||||
|
"country_codes": ["BR", "BRA"],
|
||||||
|
"country_name": "Brazil",
|
||||||
|
"currency_code": "BRL",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "PICPAY",
|
||||||
|
"channel_code": "PICPAY",
|
||||||
|
"method_name": "PicPay",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "PIX",
|
||||||
|
"channel_code": "PIX",
|
||||||
|
"method_name": "PIX",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "BOLETO",
|
||||||
|
"channel_code": "BOLETO",
|
||||||
|
"method_name": "Boleto",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "BANK_TRANSFER",
|
||||||
|
"pay_type": "BANK_TRANSFER",
|
||||||
|
"channel_code": "BANK_TRANSFER",
|
||||||
|
"method_name": "Transferência Bancária",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "CREDIT_CARD",
|
||||||
|
"channel_code": "CREDIT_CARD",
|
||||||
|
"method_name": "Credit Card",
|
||||||
|
"logo_url": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pay_way": "WEB_ONLINE",
|
||||||
|
"pay_type": "BINANCE",
|
||||||
|
"channel_code": "BINANCE",
|
||||||
|
"method_name": "Binance Pay",
|
||||||
|
"logo_url": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1252,7 +1252,7 @@
|
|||||||
var selectedPayCountry =
|
var selectedPayCountry =
|
||||||
selectPayCountryFromConfig(context);
|
selectPayCountryFromConfig(context);
|
||||||
if (
|
if (
|
||||||
configuredMifaPayCountries().length &&
|
configuredProviderCountries().length &&
|
||||||
!selectedPayCountry
|
!selectedPayCountry
|
||||||
) {
|
) {
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
@ -2341,7 +2341,7 @@
|
|||||||
return configured;
|
return configured;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configuredMifaPayCountries().length) {
|
if (configuredProviderCountries().length) {
|
||||||
// Aslan H5 只按接口返回的用户国家匹配本地支付 JSON;没有对应国家时不兜底到其它国家。
|
// Aslan H5 只按接口返回的用户国家匹配本地支付 JSON;没有对应国家时不兜底到其它国家。
|
||||||
state.selectedPayCountryID = '';
|
state.selectedPayCountryID = '';
|
||||||
state.selectedCountryCode =
|
state.selectedCountryCode =
|
||||||
@ -2382,7 +2382,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function selectedConfiguredPayCountry(context) {
|
function selectedConfiguredPayCountry(context) {
|
||||||
var countries = configuredMifaPayCountries();
|
var countries = configuredProviderCountries();
|
||||||
if (!countries.length) return null;
|
if (!countries.length) return null;
|
||||||
var userCountryCode = userCountryCodeFromContext(context);
|
var userCountryCode = userCountryCodeFromContext(context);
|
||||||
if (!userCountryCode) return null;
|
if (!userCountryCode) return null;
|
||||||
@ -2396,13 +2396,22 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function configuredMifaPayCountries() {
|
function configuredProviderCountries() {
|
||||||
var config = state.appConfig || {};
|
var config = state.appConfig || {};
|
||||||
var providers = config.providers || {};
|
var providers = config.providers || {};
|
||||||
var mifapay = providers.mifapay || {};
|
// 国家开关属于整个 legacy provider 配置,而不是 MiFaPay 私有配置。
|
||||||
return Array.isArray(mifapay.countries)
|
// 合并所有 provider 后,TR/MX/BR 等仅由 V5Pay 开通的国家才能继续请求商品和支付方式。
|
||||||
? mifapay.countries
|
return Object.keys(providers).reduce(function (
|
||||||
: [];
|
countries,
|
||||||
|
providerKey
|
||||||
|
) {
|
||||||
|
var provider = providers[providerKey] || {};
|
||||||
|
return countries.concat(
|
||||||
|
Array.isArray(provider.countries)
|
||||||
|
? provider.countries
|
||||||
|
: []
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
function primaryConfiguredCountryCode(country) {
|
function primaryConfiguredCountryCode(country) {
|
||||||
|
|||||||
366
scripts/test_recharge_v5pay.js
Normal file
366
scripts/test_recharge_v5pay.js
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
const vm = require('node:vm');
|
||||||
|
|
||||||
|
const root = path.join(__dirname, '..');
|
||||||
|
const configPaths = {
|
||||||
|
aslan: path.join(root, 'recharge', 'config', 'aslan.json'),
|
||||||
|
yumi: path.join(root, 'recharge', 'config', 'yumi.json'),
|
||||||
|
};
|
||||||
|
const currentRuntimeTypes = {
|
||||||
|
EG: ['FAWRY', 'VODAFONE'],
|
||||||
|
IN: ['UPI'],
|
||||||
|
PH: [
|
||||||
|
'GCASH_ONLINE',
|
||||||
|
'COINS_ONLINE',
|
||||||
|
'INSTAPAY_QR_STATIC',
|
||||||
|
'INSTAPAY_QR',
|
||||||
|
'QRPH_GCASH',
|
||||||
|
'PESONET',
|
||||||
|
'UB_ONLINE',
|
||||||
|
'BPI_ONLINE',
|
||||||
|
'RCBC_ONLINE',
|
||||||
|
'BANKVA',
|
||||||
|
'BILL',
|
||||||
|
],
|
||||||
|
PK: ['JAZZCASH'],
|
||||||
|
VN: ['BANK_TRANSFER', 'QR'],
|
||||||
|
TR: ['GPAY', 'BKM_EXPRESS', 'BANK_TRANSFER', 'CREDIT_CARD'],
|
||||||
|
MX: ['SPEI', 'CODI', 'SEVEN_ELEVEN', 'OXXO', 'CREDIT_CARD'],
|
||||||
|
BR: ['PICPAY', 'PIX', 'BOLETO', 'BANK_TRANSFER', 'CREDIT_CARD', 'BINANCE'],
|
||||||
|
};
|
||||||
|
// V5Pay 商户虽已开放 8 个国家,但各应用只有同时存在有效币种汇率和上架商品时才可展示;
|
||||||
|
// 这里锁定线上真实交集,防止前端把无法生成金额快照的空入口误放给用户。
|
||||||
|
const expectedTypesByApp = {
|
||||||
|
aslan: Object.fromEntries(
|
||||||
|
['EG', 'IN', 'PH', 'PK', 'TR', 'BR'].map((code) => [
|
||||||
|
code,
|
||||||
|
currentRuntimeTypes[code],
|
||||||
|
])
|
||||||
|
),
|
||||||
|
yumi: Object.fromEntries(
|
||||||
|
['EG', 'IN', 'PH', 'PK', 'VN', 'TR', 'BR'].map((code) => [
|
||||||
|
code,
|
||||||
|
currentRuntimeTypes[code],
|
||||||
|
])
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
function response(payload) {
|
||||||
|
return {
|
||||||
|
headers: { get: () => '' },
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
statusText: 'OK',
|
||||||
|
text: () =>
|
||||||
|
Promise.resolve(
|
||||||
|
JSON.stringify({ code: 'OK', message: 'ok', data: payload })
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAPIHarness() {
|
||||||
|
const requests = [];
|
||||||
|
const storage = new Map();
|
||||||
|
let nextPayload = {};
|
||||||
|
const window = {
|
||||||
|
document: { documentElement: { setAttribute: () => {} } },
|
||||||
|
fetch: (rawURL, options = {}) => {
|
||||||
|
// 每次调用都记录真实序列化后的 URL/body,测试的是浏览器最终发出的契约,而不是内部 helper 的中间对象。
|
||||||
|
requests.push({
|
||||||
|
url: new URL(rawURL),
|
||||||
|
options,
|
||||||
|
body: options.body ? JSON.parse(options.body) : null,
|
||||||
|
});
|
||||||
|
return Promise.resolve(response(nextPayload));
|
||||||
|
},
|
||||||
|
localStorage: {
|
||||||
|
getItem: (key) => storage.get(key) || null,
|
||||||
|
removeItem: (key) => storage.delete(key),
|
||||||
|
setItem: (key, value) => storage.set(key, String(value)),
|
||||||
|
},
|
||||||
|
location: {
|
||||||
|
hash: '',
|
||||||
|
hostname: 'h5.global-interaction.com',
|
||||||
|
href: 'https://h5.global-interaction.com/recharge/index.html',
|
||||||
|
origin: 'https://h5.global-interaction.com',
|
||||||
|
pathname: '/recharge/index.html',
|
||||||
|
search: '?language=tr',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const context = vm.createContext({
|
||||||
|
FormData,
|
||||||
|
JSON,
|
||||||
|
Math,
|
||||||
|
Number,
|
||||||
|
Object,
|
||||||
|
Promise,
|
||||||
|
String,
|
||||||
|
URL,
|
||||||
|
URLSearchParams,
|
||||||
|
Uint32Array,
|
||||||
|
window,
|
||||||
|
});
|
||||||
|
vm.runInContext(
|
||||||
|
fs.readFileSync(path.join(root, 'common', 'api.js'), 'utf8'),
|
||||||
|
context,
|
||||||
|
{ filename: 'common/api.js' }
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
api: window.HyAppAPI,
|
||||||
|
requests,
|
||||||
|
respondWith(payload) {
|
||||||
|
nextPayload = payload;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readConfigs() {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(configPaths).map(([app, file]) => [
|
||||||
|
app,
|
||||||
|
JSON.parse(fs.readFileSync(file, 'utf8')),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertRuntimeConfig(config, app) {
|
||||||
|
const provider = config.providers && config.providers.v5pay;
|
||||||
|
assert.ok(provider, `${app} should configure V5Pay`);
|
||||||
|
assert.equal(provider.provider_code, 'v5pay');
|
||||||
|
assert.equal(provider.provider_name, 'V5Pay');
|
||||||
|
assert.equal(provider.status, 'active');
|
||||||
|
assert.deepEqual(
|
||||||
|
Object.fromEntries(
|
||||||
|
provider.countries.map((country) => [
|
||||||
|
country.country_code,
|
||||||
|
country.methods.map((method) => method.pay_type),
|
||||||
|
])
|
||||||
|
),
|
||||||
|
expectedTypesByApp[app],
|
||||||
|
`${app} should expose only V5Pay countries backed by current app products`
|
||||||
|
);
|
||||||
|
provider.countries.forEach((country) => {
|
||||||
|
country.methods.forEach((method) => {
|
||||||
|
assert.equal(method.channel_code, method.pay_type);
|
||||||
|
assert.equal(method.logo_url, '');
|
||||||
|
assert.ok(method.pay_way, `${country.country_code} needs pay_way`);
|
||||||
|
assert.ok(method.method_name, `${method.pay_type} needs a name`);
|
||||||
|
// Lalu 的 method_id 和商户密钥属于后端数据,复制到 app JSON 会造成跨应用错误绑定或敏感信息泄露。
|
||||||
|
assert.equal(Object.hasOwn(method, 'method_id'), false);
|
||||||
|
assert.equal(Object.hasOwn(method, 'secret_key'), false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function assertProviderParsing(harness, config, app) {
|
||||||
|
harness.api.setAppConfig(config);
|
||||||
|
const currencies = Object.fromEntries(
|
||||||
|
config.providers.v5pay.countries.map((country) => [
|
||||||
|
country.country_code,
|
||||||
|
country.currency_code,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const [countryCode, types] of Object.entries(
|
||||||
|
expectedTypesByApp[app]
|
||||||
|
)) {
|
||||||
|
if (app === 'aslan') {
|
||||||
|
harness.respondWith({
|
||||||
|
country_code: countryCode,
|
||||||
|
currency_code: currencies[countryCode],
|
||||||
|
products: [{ product_id: 'legacy-product' }],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
harness.respondWith({
|
||||||
|
countryCode,
|
||||||
|
currencyCode: currencies[countryCode],
|
||||||
|
payCountryId: 'pay-country-1',
|
||||||
|
commodity: [
|
||||||
|
{
|
||||||
|
id: 'legacy-product',
|
||||||
|
amountUsd: 1,
|
||||||
|
amount: 1,
|
||||||
|
currency: currencies[countryCode],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 生产旧接口当前只返回 MiFaPay 数据库渠道。V5Pay 使用下单时的安全合成快照,
|
||||||
|
// 必须证明它不会被一个完全无关的旧渠道集合误过滤掉。
|
||||||
|
channels: [
|
||||||
|
{
|
||||||
|
channel: { channelCode: 'MIFA_ONLY_CHANNEL' },
|
||||||
|
details: { factoryChannel: 'MIFA_ONLY_TYPE' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const options = await harness.api.recharge.options('100000001', {
|
||||||
|
country_code: countryCode,
|
||||||
|
currency_code: currencies[countryCode],
|
||||||
|
pay_country_id: 'pay-country-1',
|
||||||
|
user_id: '100000001',
|
||||||
|
});
|
||||||
|
const v5Methods = Array.from(options.payment_methods).filter(
|
||||||
|
(method) => method.provider_code === 'v5pay'
|
||||||
|
);
|
||||||
|
const v5Types = v5Methods.map((method) => method.pay_type);
|
||||||
|
assert.deepEqual(
|
||||||
|
v5Types,
|
||||||
|
types,
|
||||||
|
`${app} should parse ${countryCode} V5Pay methods`
|
||||||
|
);
|
||||||
|
v5Methods.forEach((method) => {
|
||||||
|
assert.equal(method.provider_name, 'V5Pay');
|
||||||
|
assert.equal(method.channel_code, method.pay_type);
|
||||||
|
assert.match(method.method_id, /^v5pay:/);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function assertLegacyOrderPayloads(harness, configs) {
|
||||||
|
const legacyProductID = '90071992547409931234';
|
||||||
|
for (const app of ['aslan', 'yumi']) {
|
||||||
|
harness.api.setAppConfig(configs[app]);
|
||||||
|
harness.respondWith({
|
||||||
|
orderId: `${app}-order-1`,
|
||||||
|
factoryCode: app === 'yumi' ? 'V5_PAY' : undefined,
|
||||||
|
providerCode: app === 'aslan' ? 'v5pay' : undefined,
|
||||||
|
requestUrl: `https://pay.example/${app}-order-1`,
|
||||||
|
});
|
||||||
|
const order = await harness.api.recharge.createOrder({
|
||||||
|
product_id: legacyProductID,
|
||||||
|
pay_country_id: 'pay-country-1',
|
||||||
|
user_id: '100000001',
|
||||||
|
provider_code: 'v5pay',
|
||||||
|
pay_way: 'Cash',
|
||||||
|
pay_type: 'FAWRY',
|
||||||
|
channel_code: 'FAWRY',
|
||||||
|
country_code: 'EG',
|
||||||
|
currency_code: 'EGP',
|
||||||
|
command_id: `${app}-command-1`,
|
||||||
|
return_url: 'https://h5.global-interaction.com/recharge/index.html',
|
||||||
|
});
|
||||||
|
const body = harness.requests.at(-1).body;
|
||||||
|
assert.equal(body.goodsId, legacyProductID);
|
||||||
|
assert.equal(typeof body.goodsId, 'string');
|
||||||
|
assert.equal(body.providerCode, 'v5pay');
|
||||||
|
assert.equal(body.payWay, 'Cash');
|
||||||
|
assert.equal(body.payType, 'FAWRY');
|
||||||
|
assert.equal(body.channelCode, 'FAWRY');
|
||||||
|
if (app === 'yumi') {
|
||||||
|
assert.equal(body.countryCode, 'EG');
|
||||||
|
assert.equal(body.currencyCode, 'EGP');
|
||||||
|
assert.equal(body.commandId, 'yumi-command-1');
|
||||||
|
assert.equal(body.language, 'tr');
|
||||||
|
assert.equal(order.provider_code, 'v5pay');
|
||||||
|
|
||||||
|
harness.respondWith({
|
||||||
|
orderId: 'yumi-order-1',
|
||||||
|
factoryCode: 'V5PAY',
|
||||||
|
status: 'SUCCESS',
|
||||||
|
amount: '2.00',
|
||||||
|
});
|
||||||
|
const status = await harness.api.recharge.getOrder(
|
||||||
|
'yumi-order-1',
|
||||||
|
'100000001'
|
||||||
|
);
|
||||||
|
assert.equal(status.provider_code, 'v5pay');
|
||||||
|
assert.equal(status.status, 'credited');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function assertLaluNumericPayload(harness) {
|
||||||
|
harness.api.setAppConfig(null);
|
||||||
|
harness.respondWith({ order_id: 'lalu-order-1' });
|
||||||
|
await harness.api.recharge.createOrder({
|
||||||
|
product_id: 123456,
|
||||||
|
provider_code: 'v5pay',
|
||||||
|
});
|
||||||
|
const body = harness.requests.at(-1).body;
|
||||||
|
assert.equal(body.product_id, 123456);
|
||||||
|
assert.equal(typeof body.product_id, 'number');
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractPageFunction(source, name) {
|
||||||
|
const start = source.indexOf(`function ${name}(`);
|
||||||
|
assert.notEqual(start, -1, `missing recharge page function ${name}`);
|
||||||
|
const next = source.indexOf('\n function ', start + 1);
|
||||||
|
return source.slice(start, next === -1 ? source.length : next);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertPageProductIDCompatibility() {
|
||||||
|
const pageSource = fs.readFileSync(
|
||||||
|
path.join(root, 'recharge', 'index.html'),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
const source = [
|
||||||
|
extractPageFunction(pageSource, 'productID'),
|
||||||
|
extractPageFunction(pageSource, 'createOrderProductID'),
|
||||||
|
extractPageFunction(pageSource, 'isLegacyWebPayApp'),
|
||||||
|
].join('\n');
|
||||||
|
const context = vm.createContext({ isFinite, Number, String, state: {} });
|
||||||
|
vm.runInContext(source, context, { filename: 'recharge/index.html' });
|
||||||
|
|
||||||
|
context.state.appConfig = { adapter: 'yumi-web-pay' };
|
||||||
|
assert.equal(
|
||||||
|
context.createOrderProductID({ product_id: '90071992547409931234' }),
|
||||||
|
'90071992547409931234'
|
||||||
|
);
|
||||||
|
context.state.appConfig = { adapter: 'aslan-web-pay' };
|
||||||
|
assert.equal(
|
||||||
|
context.createOrderProductID({ product_id: '123456' }),
|
||||||
|
'123456'
|
||||||
|
);
|
||||||
|
context.state.appConfig = null;
|
||||||
|
assert.equal(
|
||||||
|
context.createOrderProductID({ product_id: '123456' }),
|
||||||
|
123456
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertPageProviderCountries(configs) {
|
||||||
|
const pageSource = fs.readFileSync(
|
||||||
|
path.join(root, 'recharge', 'index.html'),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
const source = extractPageFunction(
|
||||||
|
pageSource,
|
||||||
|
'configuredProviderCountries'
|
||||||
|
);
|
||||||
|
const context = vm.createContext({ Object, Array, state: {} });
|
||||||
|
vm.runInContext(source, context, { filename: 'recharge/index.html' });
|
||||||
|
Object.entries(configs).forEach(([app, config]) => {
|
||||||
|
context.state.appConfig = config;
|
||||||
|
const codes = Array.from(context.configuredProviderCountries()).map(
|
||||||
|
(country) => country.country_code
|
||||||
|
);
|
||||||
|
Object.keys(expectedTypesByApp[app]).forEach((countryCode) => {
|
||||||
|
assert.ok(
|
||||||
|
codes.includes(countryCode),
|
||||||
|
`${app} page country selection should include ${countryCode}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const configs = readConfigs();
|
||||||
|
Object.entries(configs).forEach(([app, config]) =>
|
||||||
|
assertRuntimeConfig(config, app)
|
||||||
|
);
|
||||||
|
const harness = createAPIHarness();
|
||||||
|
await assertProviderParsing(harness, configs.aslan, 'aslan');
|
||||||
|
await assertProviderParsing(harness, configs.yumi, 'yumi');
|
||||||
|
await assertLegacyOrderPayloads(harness, configs);
|
||||||
|
await assertLaluNumericPayload(harness);
|
||||||
|
assertPageProductIDCompatibility();
|
||||||
|
assertPageProviderCountries(configs);
|
||||||
|
console.log('recharge V5Pay compatibility tests passed');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user