Coinland Payمستندات
مفاهیم

جلسه‌های پرداخت

چرخه حیات جلسه پرداخت، ایدمپوتنسی بر پایه reference_id، و اینکه چرا در هر ارزی که می‌پذیرید قیمت می‌گذارید

جلسه پرداخت یک پیشنهاد است به یک مشتری: این سفارش، با این قیمت‌ها، تا این مهلت. شما می‌سازیدش، مشتری پرداختش می‌کند، و دقیقاً در یکی از سه وضعیت پایان می‌یابد.

همه چیز درباره یک جلسه پرداخت در لحظه ساخت تعیین می‌شود. فراخوانی برای ویرایش وجود ندارد — اگر قیمت عوض شد، جلسه را لغو کنید و یکی تازه با reference_id جدید بسازید.

چرخه حیات

              ┌──▶  completed

   open ──────┼──▶  expired

              └──▶  canceled
  • open تنها وضعیتی است که ویجت در آن پرداخت می‌گیرد. مشتری می‌تواند در طول این مدت هر چند بار که بخواهد صفحه را باز کند و رهایش کند.
  • completed پایانی است، و جلسه پرداخت از آن پس شیء payment خود را همراه دارد. پولی که جابه‌جا شده جابه‌جا می‌مانَد: در این ریل نه لغو وجود دارد، نه بازگشت، نه دریافت جزئی. اگر لازم است پول را برگردانید، برای مشتری یک انتقال بفرستید.
  • expired خودش در expires_at رخ می‌دهد. چیزی رزرو نشده و چیزی جابه‌جا نشده، پس یک جلسه منقضی برای هیچ‌کس هزینه‌ای ندارد.
  • canceled یعنی شما تصمیم گرفته‌اید سفارش پیش از پرداخت منتفی است. لغو ایدمپوتنت است: لغو یک جلسه لغوشده همان جلسه را بدون تغییر برمی‌گرداند و خطا نمی‌دهد. لغو یک جلسه تکمیل‌شده با PAY_SESSION_STATE (۴۰۹) رد می‌شود.

هرگز لازم نیست برای انقضا استعلام دوره‌ای بزنید. کوین‌لند وب‌هوک session.expired را می‌فرستد و GET /api/pay/v1/sessions/{id} همیشه وضعیت فعلی را گزارش می‌کند.

مدت اعتبار جلسه

مدت اعتبار پیش‌فرض از کنسول کسب‌وکار شما می‌آید. برای هر جلسه می‌توانید با ttl_minutes بین ۵ و ۱۴۴۰ (۲۴ ساعت) آن را بازنویسی کنید.

آن را متناسب با چیزی که می‌فروشید انتخاب کنید. TTL کوتاه برای وقتی درست است که کالا کم‌یاب است یا قیمتش به یک بازار متغیر گره خورده، چون قیمتی که اعلام کرده‌اید بعد از مدتی دیگر قیمتی نیست که اعلام می‌کردید — کوین‌لند هیچ تبدیلی انجام نمی‌دهد، پس قیمت بیت‌کوین یک‌ساعت‌پیش، دقیقاً قیمت بیت‌کوین یک‌ساعت‌پیش است. TTL بلند برای فاکتوری درست است که انتظار دارید فردا پرداخت شود. هر دو منطقی‌اند؛ آنچه مشکل می‌سازد انتخاب بی‌فکر است.

ایدمپوتنسی

reference_id شناسه سفارش شماست و در همان حال کلید ایدمپوتنسی شماست. این API هدر جداگانه Idempotency-Key ندارد.

آنچه می‌فرستیدآنچه می‌گیرید
یک reference_id تازهیک جلسه پرداخت تازه (۲۰۱)
همان reference_id با محتوای یکسانهمان جلسه اصلی، بدون تغییر
همان reference_id با محتوای متفاوتPAY_DUPLICATE_REFERENCE (۴۰۹)

این کل قاعده است، و همین است که تلاش مجدد را ایمن می‌کند. وقتی POST /sessions با وقفه زمانی شکست می‌خورد، هیچ چیزی به شما نمی‌گوید که جلسه ساخته شده یا نه، پس پاسخ درست ارسال دوباره همان درخواست است — نه ساختن یک شناسه تازه، که همان کاری است که یک سفارش را به دو جلسه پرداخت و در نهایت به دو پرداخت تبدیل می‌کند.

هرگز برای تلاش مجدد شناسه تازه نسازید

خطای ۴۰۹ روی محتوای تغییریافته یک ویژگی است: حالتی را می‌گیرد که شناسه یک سفارش را برای سفارشی دیگر دوباره استفاده کرده‌اید. اگر واقعاً به قیمت‌های متفاوت برای همان سبد نیاز دارید، آن در سیستم خودتان یک سفارش تازه است و reference_id تازه می‌گیرد.

reference_id در تمام عمر حساب شما یگانه است، نه فقط میان جلسه‌های باز. همچنین یک کلید جست‌وجو است: GET /api/pay/v1/sessions/{id} هم شناسه یکتای جلسه را می‌پذیرد و هم reference_id خودتان را، پس می‌توانید یک جلسه را بخوانید بدون آنکه چیزی از آنچه ما تولید کرده‌ایم ذخیره کرده باشید.

قیمت‌گذاری در چند ارز

amounts فهرستی از جفت‌های {currency, amount} است، حداکثر ده تا، و مشتری دقیقاً یکی را انتخاب می‌کند.

"amounts": [
  { "currency": "usdt", "amount": "24.90" },
  { "currency": "btc",  "amount": "0.00027" },
  { "currency": "eth",  "amount": "0.0069" }
]

کوین‌لند هیچ تبدیلی انجام نمی‌دهد. هر گزینه یک قیمت مستقل برای همان سفارش است، و هر کدام را که مشتری انتخاب کند، همان مبلغ دقیقاً از او کسر می‌شود. این ریل هیچ نرخ لحظه‌ای اعلام نمی‌کند، هیچ اسپردی اعمال نمی‌کند، و یک ارز را به ارز دیگر تبدیل نمی‌کند.

پیامدش این است که هم‌خوانی قیمت‌ها بین ارزها کار شماست. اگر یک قیمت تتری و یک قیمت بیت‌کوینی اعلام کنید، شما بوده‌اید که تصمیم گرفته‌اید این دو ارزش یکسانی دارند، و شما هستید که ریسک حرکت بازار پیش از پرداخت جلسه را می‌پذیرید. دو راه برای مدیریت آن:

  • در لحظه ساخت جلسه از یک نرخ زنده قیمت بگیرید و ttl_minutes کوتاهی بگذارید، تا بازه‌ای که بازار می‌تواند به زیان شما حرکت کند کوچک باشد.
  • فقط یک ارز اعلام کنید. یک amounts تک‌گزینه‌ای کاملاً عادی است، و پرسش ریسک را از بین می‌برد.

تومان پذیرفته نمی‌شود

کوین‌لند پی فقط از ارزهای دیجیتال پشتیبانی می‌کند؛ پرداخت تومانی در این سرویس ارائه نمی‌شود. تومان (IRT) به‌عنوان ارز پذیرفته‌شده یک کسب‌وکار قابل انتخاب نیست و قرار دادن آن در amounts با PAY_CURRENCY_NOT_ACCEPTED رد می‌شود.

قواعدی که به آن‌ها برمی‌خورید:

  • هر currency باید در accepted_currencies شما از GET /api/pay/v1/me باشد، وگرنه کل درخواست با PAY_CURRENCY_NOT_ACCEPTED (۴۲۲) رد می‌شود. آن فهرست را در زمان راه‌اندازی بخوانید و ارزها را در کد ثابت نکنید، چون در کنسول و بدون انتشار نسخه جدید از سمت شما تغییر می‌کند.
  • amount یک رشته اعشاری است: "24.90"، نه 24.9. عددهای اعشاری شناور نمی‌توانند هر مبلغ ده‌دهی را نمایش دهند، و خطای گردکردن در اینجا یعنی خطای گردکردن در آنچه دریافت می‌کنید.
  • مبالغ باید مثبت و در محدوده دقت آن ارز باشند. PAY_AMOUNT_INVALID (۴۲۲) هم مبلغ غیرمثبت را پوشش می‌دهد، هم تعداد ارقام اعشار بیش از حد آن ارز، و هم هر چیزی بیرون از محدوده‌های پلتفرم.
  • هر ارز یک بار می‌آید. دو گزینه برای یک ارز یعنی یک باگ در کد قیمت‌گذاری شما، نه انتخابی میان دو قیمت.

قیمت‌گذاری به دلار

به‌جای amounts می‌توانید یک price_usd بفرستید و کوین‌لند آن را در همه ارزهایی که می‌پذیرید با نرخ زنده، در همان لحظه ساخت جلسه، قیمت می‌گذارد.

{
  "reference_id": "order-10492",
  "title": "Order 10492",
  "price_usd": "24.90"
}

یا amounts بفرستید یا price_usd — نه هر دو، نه هیچ‌کدام. هر دو اشتباه با PAY_AMOUNT_INVALID (۴۲۲) رد می‌شوند.

پاسخ جلسه با pricing_mode: "usd"، همان price_usd که فرستاده‌اید، و آرایه amounts از قیمت‌های گرفته‌شده برمی‌گردد که هرکدام usd_value مبنای خود را همراه دارند. از آن به بعد دقیقاً مثل یک جلسه با amounts رفتار می‌کند: مشتری یک ارز را انتخاب می‌کند و همان عدد را می‌پردازد.

همین قیمت‌ها قفلِ نرخ هستند

تایمر جداگانه‌ای برای قفل نرخ وجود ندارد. قیمت‌ها یک بار و در لحظه ساخت گرفته می‌شوند، و مهلت خودِ جلسه (expires_at) همان بازه‌ای است که این قیمت‌ها در آن معتبرند. مشتری همان مبلغی را می‌پردازد که به او نشان داده شده. اگر بیش از حد طول بکشد، جلسه منقضی می‌شود و شما یکی تازه با نرخ همان لحظه می‌سازید.

ارزی که نرخ زنده نداشته باشد بی‌صدا کنار گذاشته می‌شود. بقیه ارزهای پذیرفته‌شده شما کار می‌کنند و مشتری فقط گزینه‌های کمتری می‌بیند. تنها اگر هیچ ارزی قابل قیمت‌گذاری نباشد درخواست رد می‌شود، با PAY_RATE_UNAVAILABLE (۵۰۳) — درخواستی که شکل درستی دارد و در سمت ما شکست خورده، پس دوباره تلاش کنید نه اینکه تغییرش دهید.

ایدمپوتنسی روی عدد دلاری است، نه روی قیمت‌ها

ارسال دوباره همان reference_id با همان price_usd جلسه اصلی را با همان قیمت‌ها برمی‌گرداند، حتی اگر نرخ زنده از آن زمان جابه‌جا شده باشد و قیمت‌های تازه فرق کنند. اثر انگشت ایدمپوتنسی روی عدد دلاری‌ای که فرستاده‌اید گرفته می‌شود، نه روی مبالغ ارزی مشتق‌شده از آن. همین است که تلاش مجدد را ایمن می‌کند: همان پیشنهادی را می‌گیرید که مشتری همین حالا جلوی چشمش است، نه یک قیمت‌گذاری تازه.

price_usd متفاوت با همان reference_id همچنان یک تعارض است و همچنان PAY_DUPLICATE_REFERENCE (۴۰۹) می‌گیرد.

متادیتا

metadata یک شیء JSON آزاد است که در هر خواندن جلسه پرداخت و روی پرداخت به شما بازگردانده می‌شود. جای درست شناسه سبد، کانال فروش و کمپین شماست — هر چیزی که وگرنه باید جداگانه جست‌وجو کنید.

فقط شما آن را می‌بینید، اما ذخیره می‌شود، پس اطلاعات محرمانه و داده شخصی را در آن نگذارید. محتوای موردنظر همان شناسه‌های خودتان است.

خواندن یک جلسه پرداخت

const API = "https://my.coinlandexchange.com";
const auth = { Authorization: `Bearer ${process.env.COINLAND_PAY_KEY}` };

// Either the session UUID or your own reference_id works as the id.
export async function getSession(id: string) {
  const res = await fetch(`${API}/api/pay/v1/sessions/${encodeURIComponent(id)}`, {
    headers: auth,
  });
  if (!res.ok) throw new Error(`coinland pay ${res.status}`);
  return res.json();
}

// Idempotent: cancelling a cancelled session returns it unchanged.
export async function cancelSession(id: string) {
  const res = await fetch(
    `${API}/api/pay/v1/sessions/${encodeURIComponent(id)}/cancel`,
    { method: "POST", headers: auth },
  );
  if (!res.ok) throw new Error(`coinland pay ${res.status}`);
  return res.json();
}

این وضعیت معتبر است. وقتی status برابر completed باشد، پرداخت کامل در پاسخ جای‌گذاری شده است، پس یک فراخوانی هم به «آیا پرداخت کرد» جواب می‌دهد و هم به «دقیقاً چه چیزی پرداخت کرد».

یک شناسه ناشناس — یا شناسه‌ای که به کسب‌وکار دیگری تعلق دارد — با PAY_SESSION_NOT_FOUND (۴۰۴) پاسخ می‌گیرد. این دو حالت عامدانه از هم قابل تشخیص نیستند: پاسخ متفاوت برای «وجود دارد اما مال شما نیست» به هر کسی که یک کلید دارد اجازه می‌داد سفارش‌های کسب‌وکارهای دیگر را شمارش کند.

در این صفحه