Add SMS templates, prepaid IRT billing, and advertising shortcode 9000590009.
Charge ceil(len/64)*400 IRT per recipient across OTP, customer, and partner SMS; seed new businesses with 500k IRT; default partner sends to the advertising line. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
4623061a4f
commit
4a1d60dbef
+59
-1
@@ -21,7 +21,7 @@ export type SmsSendOptions = {
|
||||
};
|
||||
|
||||
/** Default Meshkee advertising shortcode when a business has no own SMS number. */
|
||||
export const DEFAULT_ADVERTISING_SMS_SOURCE = '5000990009';
|
||||
export const DEFAULT_ADVERTISING_SMS_SOURCE = '9000590009';
|
||||
|
||||
/** Gama batch size limit is undocumented; keep chunks conservative. */
|
||||
const GAMA_BULK_CHUNK_SIZE = 100;
|
||||
@@ -58,6 +58,64 @@ export class SmsService {
|
||||
);
|
||||
}
|
||||
|
||||
/** Meshkee service shortcode (OTP + verified SMS templates). */
|
||||
getServiceSource(): string {
|
||||
return (
|
||||
this.config.get<string>('SMS_GAMA_SOURCE_SERVICE')?.trim() ||
|
||||
'5000110005'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort account credit from Gama. Returns null when the provider
|
||||
* does not expose credit over REST (check the Gama panel instead).
|
||||
*/
|
||||
async getAccountCredit(): Promise<{ balance: number; currency?: string } | null> {
|
||||
if (!this.isEnabled() || this.isProxyConfigured()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const username = this.config.get<string>('SMS_GAMA_USERNAME')?.trim();
|
||||
const password = this.config.get<string>('SMS_GAMA_PASSWORD')?.trim();
|
||||
const baseUrl = (
|
||||
this.config.get<string>('SMS_GAMA_BASE_URL') ?? 'https://sms.igama.ir/api/v1'
|
||||
).replace(/\/$/, '');
|
||||
|
||||
if (!username || !password) return null;
|
||||
|
||||
// Gama REST docs do not document a credit endpoint; try common paths quietly.
|
||||
for (const path of ['/account/credit', '/credit', '/user/credit']) {
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}${path}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ username, password }),
|
||||
signal: AbortSignal.timeout(8_000),
|
||||
});
|
||||
if (!response.ok) continue;
|
||||
const payload = (await response.json()) as {
|
||||
success?: boolean;
|
||||
body?: number | { credit?: number; balance?: number };
|
||||
};
|
||||
if (!payload.success) continue;
|
||||
const raw =
|
||||
typeof payload.body === 'number'
|
||||
? payload.body
|
||||
: payload.body?.credit ?? payload.body?.balance;
|
||||
if (typeof raw === 'number' && Number.isFinite(raw)) {
|
||||
return { balance: raw };
|
||||
}
|
||||
} catch {
|
||||
// ignore and try next path
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async sendVerificationCode(
|
||||
cellNumber: string,
|
||||
code: string,
|
||||
|
||||
Reference in New Issue
Block a user