fix: align SMS Bower provider with documented API
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
})(typeof self !== 'undefined' ? self : globalThis, function createSmsBowerProviderModule() {
|
})(typeof self !== 'undefined' ? self : globalThis, function createSmsBowerProviderModule() {
|
||||||
const PROVIDER_ID = 'sms-bower';
|
const PROVIDER_ID = 'sms-bower';
|
||||||
const DEFAULT_BASE_URL = 'https://smsbower.page/stubs/handler_api.php';
|
const DEFAULT_BASE_URL = 'https://smsbower.page/stubs/handler_api.php';
|
||||||
const DEFAULT_SERVICE_CODE = 'ot';
|
const DEFAULT_SERVICE_CODE = 'dr';
|
||||||
const DEFAULT_SERVICE_LABEL = 'OpenAI';
|
const DEFAULT_SERVICE_LABEL = 'OpenAI';
|
||||||
const DEFAULT_COUNTRY_ID = 6;
|
const DEFAULT_COUNTRY_ID = 6;
|
||||||
const DEFAULT_COUNTRY_LABEL = 'Indonesia';
|
const DEFAULT_COUNTRY_LABEL = 'Indonesia';
|
||||||
@@ -160,8 +160,9 @@
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
const message = describePayload(payload);
|
const message = describePayload(payload);
|
||||||
if (/^(BAD_KEY|BAD_ACTION|BAD_SERVICE|BAD_STATUS|NO_ACTIVATION|EARLY_CANCEL_DENIED|BAD_COUNTRY)\b/i.test(message)) {
|
const apiStatus = payload && typeof payload === 'object' && !Array.isArray(payload) ? Number(payload.status) : null;
|
||||||
const error = new Error(`${actionLabel}失败:${message}`);
|
if (apiStatus === 0 || /^(BAD_KEY|BAD_ACTION|BAD_SERVICE|BAD_STATUS|NO_ACTIVATION|EARLY_CANCEL_DENIED|BAD_COUNTRY)\b/i.test(message)) {
|
||||||
|
const error = new Error(`${actionLabel}失败:${message || 'SMS Bower API 返回失败状态'}`);
|
||||||
error.payload = payload;
|
error.payload = payload;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -220,7 +221,7 @@
|
|||||||
async function fetchPrices(state = {}, countryConfig = {}, deps = {}) {
|
async function fetchPrices(state = {}, countryConfig = {}, deps = {}) {
|
||||||
const config = resolveConfig(state, deps);
|
const config = resolveConfig(state, deps);
|
||||||
return fetchPayload(config, {
|
return fetchPayload(config, {
|
||||||
action: 'getPrices',
|
action: 'getPricesV3',
|
||||||
service: config.serviceCode,
|
service: config.serviceCode,
|
||||||
country: normalizeSmsBowerCountryId(countryConfig?.id ?? state.smsBowerCountryId, DEFAULT_COUNTRY_ID),
|
country: normalizeSmsBowerCountryId(countryConfig?.id ?? state.smsBowerCountryId, DEFAULT_COUNTRY_ID),
|
||||||
}, 'SMS Bower 查询价格');
|
}, 'SMS Bower 查询价格');
|
||||||
@@ -232,12 +233,21 @@
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
if (!payload || typeof payload !== 'object') return entries;
|
if (!payload || typeof payload !== 'object') return entries;
|
||||||
const cost = Number(payload.cost ?? payload.price ?? payload.Price);
|
const directCount = Number(payload.count ?? payload.qty ?? payload.Qty);
|
||||||
if (Number.isFinite(cost) && cost >= 0) {
|
const directPrice = Number(payload.cost ?? payload.price ?? payload.Price);
|
||||||
const count = Number(payload.count ?? payload.qty ?? payload.Qty);
|
if (Number.isFinite(directPrice) && directPrice >= 0) {
|
||||||
entries.push({ cost, count: Number.isFinite(count) ? count : 0, inStock: !Number.isFinite(count) || count > 0 });
|
entries.push({ cost: directPrice, count: Number.isFinite(directCount) ? directCount : 0, inStock: !Number.isFinite(directCount) || directCount > 0 });
|
||||||
}
|
}
|
||||||
Object.values(payload).forEach((entry) => collectPriceEntries(entry, entries));
|
Object.entries(payload).forEach(([key, entry]) => {
|
||||||
|
if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
|
||||||
|
const keyedPrice = Number(key);
|
||||||
|
const keyedCount = Number(entry.count ?? entry.qty ?? entry.Qty);
|
||||||
|
if (/^\d+(?:\.\d+)?$/.test(String(key)) && String(key).includes('.') && Number.isFinite(keyedPrice) && keyedPrice >= 0 && !Number.isFinite(Number(entry.cost ?? entry.price ?? entry.Price))) {
|
||||||
|
entries.push({ cost: keyedPrice, count: Number.isFinite(keyedCount) ? keyedCount : 0, inStock: !Number.isFinite(keyedCount) || keyedCount > 0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
collectPriceEntries(entry, entries);
|
||||||
|
});
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,10 +259,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const source = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload : {};
|
const source = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload : {};
|
||||||
const activationId = normalizeText(source.activationId ?? source.id ?? fallback.activationId);
|
const activationId = normalizeText(source.activationId ?? source.activation_id ?? source.id ?? fallback.activationId);
|
||||||
const phoneNumber = normalizeText(source.phoneNumber ?? source.phone ?? fallback.phoneNumber);
|
const phoneNumber = normalizeText(source.phoneNumber ?? source.phone_number ?? source.phone ?? fallback.phoneNumber);
|
||||||
if (!activationId || !phoneNumber) return null;
|
if (!activationId || !phoneNumber) return null;
|
||||||
const countryId = normalizeSmsBowerCountryId(source.countryId ?? source.country ?? fallback.countryId, DEFAULT_COUNTRY_ID);
|
const countryId = normalizeSmsBowerCountryId(source.countryId ?? source.country_id ?? source.countryCode ?? source.country ?? fallback.countryId, DEFAULT_COUNTRY_ID);
|
||||||
return {
|
return {
|
||||||
activationId,
|
activationId,
|
||||||
phoneNumber,
|
phoneNumber,
|
||||||
@@ -287,7 +297,7 @@
|
|||||||
throw new Error(`SMS Bower 价格区间无效:最低购买价 ${range.minPriceLimit} 高于价格上限 ${range.maxPriceLimit}。`);
|
throw new Error(`SMS Bower 价格区间无效:最低购买价 ${range.minPriceLimit} 高于价格上限 ${range.maxPriceLimit}。`);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
action: 'getNumber',
|
action: 'getNumberV2',
|
||||||
service: config.serviceCode,
|
service: config.serviceCode,
|
||||||
country: normalizeSmsBowerCountryId(countryConfig.id, DEFAULT_COUNTRY_ID),
|
country: normalizeSmsBowerCountryId(countryConfig.id, DEFAULT_COUNTRY_ID),
|
||||||
maxPrice: range.maxPriceLimit,
|
maxPrice: range.maxPriceLimit,
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ function createTextResponse(payload, ok = true, status = ok ? 200 : 400, statusT
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
test('SMS Bower provider defaults to Indonesia when no country is configured', async () => {
|
test('SMS Bower provider defaults to Indonesia and documented OpenAI service code', async () => {
|
||||||
const requests = [];
|
const requests = [];
|
||||||
const provider = api.createProvider({
|
const provider = api.createProvider({
|
||||||
fetchImpl: async (url, options = {}) => {
|
fetchImpl: async (url, options = {}) => {
|
||||||
const parsed = new URL(url);
|
const parsed = new URL(url);
|
||||||
requests.push({ url: parsed, options });
|
requests.push({ url: parsed, options });
|
||||||
if (parsed.searchParams.get('action') === 'getNumber') {
|
if (parsed.searchParams.get('action') === 'getNumberV2') {
|
||||||
return createTextResponse('ACCESS_NUMBER:67890:6281234567890');
|
return createTextResponse('ACCESS_NUMBER:67890:6281234567890');
|
||||||
}
|
}
|
||||||
throw new Error(`unexpected ${parsed.toString()}`);
|
throw new Error(`unexpected ${parsed.toString()}`);
|
||||||
@@ -30,11 +30,12 @@ test('SMS Bower provider defaults to Indonesia when no country is configured', a
|
|||||||
const activation = await provider.requestActivation({ smsBowerApiKey: 'demo-key' });
|
const activation = await provider.requestActivation({ smsBowerApiKey: 'demo-key' });
|
||||||
|
|
||||||
assert.equal(requests[0].url.searchParams.get('country'), '6');
|
assert.equal(requests[0].url.searchParams.get('country'), '6');
|
||||||
|
assert.equal(requests[0].url.searchParams.get('service'), 'dr');
|
||||||
assert.equal(activation.countryId, 6);
|
assert.equal(activation.countryId, 6);
|
||||||
assert.equal(activation.countryLabel, 'Indonesia');
|
assert.equal(activation.countryLabel, 'Indonesia');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('SMS Bower provider uses handler API key query for balance and price catalog', async () => {
|
test('SMS Bower provider uses documented handler API methods for balance and price catalog', async () => {
|
||||||
const requests = [];
|
const requests = [];
|
||||||
const provider = api.createProvider({
|
const provider = api.createProvider({
|
||||||
fetchImpl: async (url, options = {}) => {
|
fetchImpl: async (url, options = {}) => {
|
||||||
@@ -43,8 +44,8 @@ test('SMS Bower provider uses handler API key query for balance and price catalo
|
|||||||
if (parsed.searchParams.get('action') === 'getBalance') {
|
if (parsed.searchParams.get('action') === 'getBalance') {
|
||||||
return createTextResponse('ACCESS_BALANCE:12.34');
|
return createTextResponse('ACCESS_BALANCE:12.34');
|
||||||
}
|
}
|
||||||
if (parsed.searchParams.get('action') === 'getPrices') {
|
if (parsed.searchParams.get('action') === 'getPricesV3') {
|
||||||
return createTextResponse({ 52: { ot: { cost: 0.21, count: 7 } } });
|
return createTextResponse({ 52: { dr: { 3170: { price: 0.21, count: 7 } } } });
|
||||||
}
|
}
|
||||||
throw new Error(`unexpected ${parsed.toString()}`);
|
throw new Error(`unexpected ${parsed.toString()}`);
|
||||||
},
|
},
|
||||||
@@ -58,21 +59,32 @@ test('SMS Bower provider uses handler API key query for balance and price catalo
|
|||||||
assert.equal(requests[0].url.searchParams.get('api_key'), 'demo-key');
|
assert.equal(requests[0].url.searchParams.get('api_key'), 'demo-key');
|
||||||
assert.equal(requests[0].url.searchParams.get('action'), 'getBalance');
|
assert.equal(requests[0].url.searchParams.get('action'), 'getBalance');
|
||||||
assert.equal(balance.balance, 12.34);
|
assert.equal(balance.balance, 12.34);
|
||||||
assert.equal(requests[1].url.searchParams.get('action'), 'getPrices');
|
assert.equal(requests[1].url.searchParams.get('action'), 'getPricesV3');
|
||||||
assert.equal(requests[1].url.searchParams.get('service'), 'ot');
|
assert.equal(requests[1].url.searchParams.get('service'), 'dr');
|
||||||
assert.equal(requests[1].url.searchParams.get('country'), '52');
|
assert.equal(requests[1].url.searchParams.get('country'), '52');
|
||||||
assert.deepStrictEqual(entries, [{ cost: 0.21, count: 7, inStock: true }]);
|
assert.deepStrictEqual(entries, [{ cost: 0.21, count: 7, inStock: true }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('SMS Bower provider acquires number, polls code, and updates activation statuses', async () => {
|
test('SMS Bower provider treats JSON status 0 responses as API errors even when HTTP is ok', async () => {
|
||||||
|
const provider = api.createProvider({
|
||||||
|
fetchImpl: async () => createTextResponse({ status: 0, message: 'No access', data: [] }, true, 200),
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => provider.fetchBalance({ smsBowerApiKey: 'bad-key' }),
|
||||||
|
/SMS Bower 查询余额失败:No access/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SMS Bower provider acquires number with getNumberV2, polls code, and updates activation statuses', async () => {
|
||||||
const requests = [];
|
const requests = [];
|
||||||
const provider = api.createProvider({
|
const provider = api.createProvider({
|
||||||
fetchImpl: async (url, options = {}) => {
|
fetchImpl: async (url, options = {}) => {
|
||||||
const parsed = new URL(url);
|
const parsed = new URL(url);
|
||||||
const action = parsed.searchParams.get('action');
|
const action = parsed.searchParams.get('action');
|
||||||
requests.push({ url: parsed, options });
|
requests.push({ url: parsed, options });
|
||||||
if (action === 'getNumber') {
|
if (action === 'getNumberV2') {
|
||||||
return createTextResponse('ACCESS_NUMBER:12345:447700900123');
|
return createTextResponse({ activationId: '12345', phoneNumber: '447700900123', activationCost: 0.3, countryCode: 16 });
|
||||||
}
|
}
|
||||||
if (action === 'getStatus') {
|
if (action === 'getStatus') {
|
||||||
return createTextResponse('STATUS_OK:Your OpenAI code is 654321');
|
return createTextResponse('STATUS_OK:Your OpenAI code is 654321');
|
||||||
@@ -88,7 +100,7 @@ test('SMS Bower provider acquires number, polls code, and updates activation sta
|
|||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
smsBowerApiKey: 'demo-key',
|
smsBowerApiKey: 'demo-key',
|
||||||
smsBowerServiceCode: 'ot',
|
smsBowerServiceCode: 'dr',
|
||||||
smsBowerCountryId: 16,
|
smsBowerCountryId: 16,
|
||||||
smsBowerCountryLabel: 'United Kingdom',
|
smsBowerCountryLabel: 'United Kingdom',
|
||||||
smsBowerMinPrice: '0.1',
|
smsBowerMinPrice: '0.1',
|
||||||
@@ -110,8 +122,8 @@ test('SMS Bower provider acquires number, polls code, and updates activation sta
|
|||||||
assert.equal(reused.activationId, '12345');
|
assert.equal(reused.activationId, '12345');
|
||||||
|
|
||||||
const acquireUrl = requests[0].url;
|
const acquireUrl = requests[0].url;
|
||||||
assert.equal(acquireUrl.searchParams.get('action'), 'getNumber');
|
assert.equal(acquireUrl.searchParams.get('action'), 'getNumberV2');
|
||||||
assert.equal(acquireUrl.searchParams.get('service'), 'ot');
|
assert.equal(acquireUrl.searchParams.get('service'), 'dr');
|
||||||
assert.equal(acquireUrl.searchParams.get('country'), '16');
|
assert.equal(acquireUrl.searchParams.get('country'), '16');
|
||||||
assert.equal(acquireUrl.searchParams.get('maxPrice'), '0.3');
|
assert.equal(acquireUrl.searchParams.get('maxPrice'), '0.3');
|
||||||
assert.equal(acquireUrl.searchParams.get('minPrice'), '0.1');
|
assert.equal(acquireUrl.searchParams.get('minPrice'), '0.1');
|
||||||
@@ -121,7 +133,7 @@ test('SMS Bower provider acquires number, polls code, and updates activation sta
|
|||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
requests.map((entry) => [entry.url.searchParams.get('action'), entry.url.searchParams.get('status')]),
|
requests.map((entry) => [entry.url.searchParams.get('action'), entry.url.searchParams.get('status')]),
|
||||||
[
|
[
|
||||||
['getNumber', null],
|
['getNumberV2', null],
|
||||||
['getStatus', null],
|
['getStatus', null],
|
||||||
['setStatus', '6'],
|
['setStatus', '6'],
|
||||||
['setStatus', '8'],
|
['setStatus', '8'],
|
||||||
|
|||||||
Reference in New Issue
Block a user