- خانه
- کلاینت و آداپتورها
مفاهیم پایه
کلاینت و آداپتورها
هر چیزی که سیستم تایپ درباره درگاههای شما میداند از یک آبجکت لیترال میآید. شما هرگز جنریک را annotate نمیکنید؛ فقط آداپتورها را مینویسید و استنتاج بقیه را انجام میدهد.
استنتاج، گام به گام
کلاس بهصورت UnoPay<TGateways extends Record<string, GatewayAdapter>> اعلام شده است. وقتی new UnoPay({…}) را بدون آرگومان تایپ صدا میزنید، TypeScript مقدار TGateways را از شکل آبجکتی که دادهاید استنتاج میکند.
const unopay = new UnoPay({
zarinpal: new ZarinpalAdapter({ merchantId: 'abc' }),
});
// TGateways is inferred as:
// { zarinpal: ZarinpalAdapter }
//
// so keyof TGateways is the literal type 'zarinpal'.
هر دو متد کلاینت با K extends keyof TGateways اعلام شدهاند. همین چیزی است که نام درگاه را به بررسی زمان کامپایل تبدیل میکند، نه یک استعلام زمان اجرا که تصادفاً کار کند.
| آنچه مینویسید | آنچه کامپایلر استنتاج میکند | اگر اشتباه باشد |
|---|---|---|
new UnoPay({ zarinpal: … }) |
{ zarinpal: ZarinpalAdapter } |
A misspelled key is just a new key — no error, but no adapter either. |
createPayment('zarinpl', …) |
K = 'zarinpl' |
Compile error. 'zarinpl' is not assignable to keyof TGateways.
|
verifyCallback('zarinpal', …) |
K = 'zarinpal' |
Compiles. The adapter is looked up at runtime. |
| An adapter that is missing a method | Constraint check against GatewayAdapter |
Compile error at the new UnoPay(…) call, not when the payment fails.
|
README پکیج SDK تبلیغ میکند که هیچ تایپ any در کار نیست. دقیقاً یکی هست: CallbackRequest.body بهصورت Record<string, any> در src/types.ts:17. هر فیلد دیگری در سطح عمومی بهطور کامل استنتاج میشود. آداپتور هم دو بار request<any>(…) صدا میزند که داخلی است و برای شما نامرئی.
هزینه: کلاینت تغییرناپذیر است
هر دو فیلد سازنده private هستند، پس نه addAdapter() وجود دارد و نه راهی برای دستکاری نگاشت. این یک معاوضه آگاهانه است: اتحاد provider دقیق میماند، و بهایش ساخت کلاینت جدید هنگام تغییر مجموعه درگاههاست.
// Build the map once from config, so adding a gateway is a config change.
import { UnoPay, ZarinpalAdapter, type GatewayAdapter } from 'unopay';
function buildClient(enabled: string[]) {
const adapters: Record<string, GatewayAdapter> = {};
if (enabled.includes('zarinpal')) {
adapters.zarinpal = new ZarinpalAdapter({
merchantId: process.env.ZARINPAL_MERCHANT_ID!,
sandbox: process.env.NODE_ENV !== 'production',
});
}
return new UnoPay(adapters);
}
کمکتابع بالا باید نگاشت محلی را annotate کند، و Record<string, GatewayAdapter> دوباره هر کلیدی را میپذیرد. اگر میخواهید بررسی provider در زمان کامپایل حفظ شود، کلاینت را بدون annotate بگذارید و اجازه دهید استنتاج آن را باریک نگه دارد.
نوشتن آداپتور خودتان
GatewayAdapter دو متد دارد. UnoPay هر آبجکتی را که آن را برآورده کند، هرجا آداپتور داخلی پذیرفته میشود میپذیرد — و تایپ provider از کلید شما پیروی میکند، نه از یک فهرست ثابت.
import {
UnoPay, PaymentValidationError,
type GatewayAdapter, type PaymentRequest, type PaymentResult,
type CallbackRequest, type VerificationResult,
} from 'unopay';
class IdpayAdapter implements GatewayAdapter {
constructor(private config: { apiKey: string; sandbox: boolean }) {
if (!config.apiKey) throw new PaymentValidationError('idpay requires apiKey');
}
async createPayment(req: PaymentRequest): Promise<PaymentResult> {
// headers: X-API-KEY, and X-SANDBOX: 1 in sandbox
// the gateway returns a 32-character hex id, not a URL
const id = 'a1b2…';
return {
redirectUrl: 'https://sandbox.idpay.ir/start/' + id,
transactionId: id,
providerToken: id,
};
}
async verifyCallback(req: CallbackRequest): Promise<VerificationResult> {
const id = req.query.id ?? String(req.body.id ?? '');
const status = Number(req.query.status ?? req.body.status ?? 0);
// 1 unpaid · 10 pending · 100 verified · 101 already verified
const ok = status === 100 || status === 101;
return {
isSuccessful: ok,
transactionId: id,
settledAmount: Number(req.query.amount ?? req.body.amount ?? 0),
errorReason: ok ? undefined : 'status ' + status,
};
}
}
const unopay = new UnoPay({ idpay: new IdpayAdapter({ apiKey, sandbox: true }) });
// provider is now 'idpay' and nothing else
لازم نیست خطاها را بپیچید. هر چیزی که PaymentError نباشد توسط کلاینت به GatewayProviderError تبدیل میشود. قرارداد کامل در صفحه GatewayAdapter.