UnoPayv1.0.0
  1. خانه
  2. کلاینت و آداپتورها

مفاهیم پایه

کلاینت و آداپتورها

هر چیزی که سیستم تایپ درباره درگاه‌های شما می‌داند از یک آبجکت لیترال می‌آید. شما هرگز جنریک را annotate نمی‌کنید؛ فقط آداپتورها را می‌نویسید و استنتاج بقیه را انجام می‌دهد.

استنتاج، گام به گام

کلاس به‌صورت UnoPay<TGateways extends Record<string, GatewayAdapter>> اعلام شده است. وقتی new UnoPay({…}) را بدون آرگومان تایپ صدا می‌زنید، TypeScript مقدار TGateways را از شکل آبجکتی که داده‌اید استنتاج می‌کند.

inference.ts ts
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.
تنها جایی که any ظاهر می‌شود

README پکیج SDK تبلیغ می‌کند که هیچ تایپ any در کار نیست. دقیقاً یکی هست: CallbackRequest.body به‌صورت Record<string, any> در src/types.ts:17. هر فیلد دیگری در سطح عمومی به‌طور کامل استنتاج می‌شود. آداپتور هم دو بار request<any>(…) صدا می‌زند که داخلی است و برای شما نامرئی.

هزینه: کلاینت تغییرناپذیر است

هر دو فیلد سازنده private هستند، پس نه addAdapter() وجود دارد و نه راهی برای دستکاری نگاشت. این یک معاوضه آگاهانه است: اتحاد provider دقیق می‌ماند، و بهایش ساخت کلاینت جدید هنگام تغییر مجموعه درگاه‌هاست.

config.ts ts
// 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 از کلید شما پیروی می‌کند، نه از یک فهرست ثابت.

idpay.ts ts
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.