پیادهسازی خرید حضوری
سوپر اپلیکیشن مهراد
این سند راهنمای پیادهسازی خرید حضوری برای توسعهدهندگان پذیرنده است؛ حالتی که مشتری به صورت فیزیکی در فروشگاه حاضر است و با اپلیکیشن مهراد پرداخت میکند.
معرفی خرید حضوری
در جریان آنلاین (بخش مستندات سفارش) مشتری در سایت پذیرنده سبد خرید میسازد و برای پرداخت به مهراد هدایت میشود.
در خرید حضوری سبد خرید و مرورگری در کار نیست؛ مشتری روبهروی صندوق ایستاده است. بنابراین:
- پذیرنده اقلام خرید را ثبت میکند و یک خرید حضوری (Order Draft) میسازد.
- مهراد یک کد پیگیری ۸ رقمی و یک لینک پرداخت برمیگرداند که پذیرنده آن را به شکل QR یا عدد به مشتری نشان میدهد.
- مشتری در اپلیکیشن مهراد کد را وارد یا QR را اسکن میکند، خرید را باز میکند و از داراییهای خود پرداخت میکند.
- پذیرنده پرداخت را تأیید (Verify) میکند و مبلغ برای او نهایی میشود.
تفاوت کلیدی با جریان آنلاین: در خرید حضوری، پذیرنده هیچگاه authority (ID تلاش پرداخت) را دریافت نمیکند، چون پرداخت در دستگاه مشتری انجام میشود. به همین دلیل تأیید پرداخت با order_id انجام میشود، نه با authority.
مرحلهٔ ۳ کاملاً توسط اپلیکیشن مهراد انجام میشود و پیادهسازی آن بر عهدهٔ پذیرنده نیست.
روشهای استفاده
خرید حضوری به دو روش قابل استفاده است:
| روش | کاربرد | نیاز به پیادهسازی |
|---|---|---|
| پنل پذیرندهٔ مهراد | ساخت خرید، نمایش QR و تأیید پرداخت از طریق رابط کاربری پنل | ندارد |
| سیستم فروشگاهی پذیرنده (صندوق یا اپلیکیشن فروشگاهی) | ساخت خرید و تأیید پرداخت از سرور پذیرنده | دارد؛ طبق ادامهٔ همین سند |
استفاده از پنل پذیرنده
در پنل پذیرنده، سرویس، اقلام خرید، هزینههای جانبی، تخفیف و در صورت نیاز توضیح سفارش وارد میشود. مبلغ نهایی و ID سفارش بهصورت خودکار توسط پنل محاسبه و تولید میشوند. پس از ساخت خرید، پنل QR، کد پیگیری ۸ رقمی و زمان انقضای خرید را نمایش میدهد. پس از پرداخت مشتری، تأیید پرداخت از صفحهٔ جزئیات همان سفارش در بخش سفارشهای حضوری پنل انجام میشود.
این روش به هیچ پیادهسازی API نیاز ندارد؛ قواعد مبلغ، مهلتها و چرخهٔ عمر سفارش که در ادامه آمده است در این روش نیز برقرار است.
یکپارچهسازی با سیستم فروشگاهی پذیرنده
ادامهٔ این سند برای تیمهایی است که ساخت خرید و تأیید پرداخت را از سیستم فروشگاهی خود انجام میدهند. این ارتباط سرور-به-سرور است و با token اپلیکیشن پذیرنده انجام میشود.
| endpoint | کاربرد |
|---|---|
POST /gateway/order/draft/request | ساخت خرید حضوری |
POST /gateway/order/payment/verify | تأیید پرداخت با order_id |
هر دو endpoint با token اپلیکیشن پذیرنده در header Authorization فراخوانی میشوند؛ نحوهٔ دریافت token در مستندات ارتباط پذیرندگان آمده است.
پیشنیازها
برای یکپارچهسازی با سیستم فروشگاهی، موارد زیر لازم است:
- حساب پذیرنده فعال در مهراد.
client_idوclient_secretاپلیکیشن پذیرنده برای دریافت token.service_idیکی از سرویسهای فعال حساب پذیرنده؛ این مقدار برای ساخت خرید الزامی است و باید از اطلاعات پیکربندی یکپارچهسازی دریافت شود.- امکان تولید و نمایش QR یا نمایش کد ۸ رقمی به مشتری.
- یک ID یکتا برای هر سفارش در سیستم پذیرنده (
external_order_id).
مرحله ۱ — ساخت خرید حضوری
/gateway/order/draft/requestAuthentication: token اپلیکیشن پذیرنده در header Authorization.
مقادیری که توسط پذیرنده تولید میشوند
| فیلد | توضیح |
|---|---|
external_order_id | ID سفارش در سیستم پذیرنده؛ باید برای هر سفارش یکتا باشد |
amount | مبلغ نهایی که بر اساس اقلام محاسبه میشود |
shipments / services / discount_amount | مرسولهها و اقلام، هزینههای جانبی و تخفیف |
مقادیری که API تولید میکند
draft_id، tracking_code، expires_at و draft_url را API میسازد؛ این مقادیر نباید ارسال شوند.
ورودی (Request Body)
service_ iduuidالزامیamountintegerالزامیexternal_ order_ idstringالزامیshipmentsarray of objectsالزامیفیلدهای هر عضو لیست shipments3 فیلد
idstringالزامیshipping_ feeintegerپیشفرض: 0
itemsarray of objectsالزامیفیلدهای هر عضو لیست items5 فیلد
productobjectالزامیفیلدهای product6 فیلد
idstringالزامیtitlestringالزامیurlstringالزامیis_ domesticbooleanimageobjectالزامیفیلدهای image2 فیلد
urlstringالزامیthumbnail_ urlstringالزامیvariantobjectالزامیفیلدهای variant2 فیلد
idstringالزامیtitlestringالزامیpriceintegerالزامیquantityintegerپیشفرض: 1
categorystringالزامیextra_ dataobjectservicesarray of objectsفیلدهای هر عضو لیست services2 فیلد
titlestringالزامیamountintegerالزامیdiscount_ amountintegerdescriptionstringorder_ urlstringexternal_ payment_ idstringcallback_ urlstringاین endpoint از فیلد items (ساختار منسوخ جریان آنلاین) پشتیبانی نمیکند و اقلام تنها از طریق shipments ثبت میشوند؛ فیلد items در صورت ارسال نادیده گرفته میشود. اگر shipments ارسال نشود، response آن با کد ۴۲۲ و خطای VALIDATION_ERROR برگردانده میشود.
در صورت وجود دو قلم با ID کالا (product.id) و تنوع (variant.id) یکسان در یک request، حتی در مرسولههای متفاوت، کل request با خطای DUPLICATE_ORDER_ITEM رد میشود.
توجه:
servicesباservice_idیکی نیست.service_idسرویس پذیرنده است وservicesردیفهای هزینهٔ جانبی همین سفارش است.
قاعدهٔ مبلغ
مقدار amount باید دقیقاً برابر باشد با:
amount = SUM(price × quantity همهٔ اقلام) + SUM(shipments.shipping_fee) + SUM(services.amount) - discount_amountاگر این تساوی برقرار نباشد request با INVALID_ORDER_AMOUNT رد میشود و اگر حاصل صفر یا منفی شود با ORDER_AMOUNT_NOT_POSITIVE.
نمونه request
{
"service_id": "2f1b7d6e-2f4e-4a1a-9c22-6f0c0a1d5b31",
"amount": 3100000,
"external_order_id": "POS-2026-000184",
"description": "خرید از شعبه مرکزی",
"shipments": [
{
"id": "S1",
"shipping_fee": 0,
"items": [
{
"product": {
"id": "SKU-7781",
"title": "هدفون بیسیم",
"url": "https://shop.example.com/product/SKU-7781",
"image": {
"url": "https://shop.example.com/media/SKU-7781.webp",
"thumbnail_url": "https://shop.example.com/media/SKU-7781-thumb.webp"
},
"variant": {
"id": "SKU-7781-black",
"title": "مشکی"
}
},
"price": 1250000,
"quantity": 2,
"category": "لوازم جانبی"
},
{
"product": {
"id": "SKU-1042",
"title": "کابل USB-C یک متری",
"url": "https://shop.example.com/product/SKU-1042",
"image": {
"url": "https://shop.example.com/media/SKU-1042.webp",
"thumbnail_url": "https://shop.example.com/media/SKU-1042-thumb.webp"
},
"variant": {
"id": "SKU-1042-white",
"title": "سفید"
}
},
"price": 150000,
"quantity": 3,
"category": "لوازم جانبی"
}
]
}
],
"services": [
{
"title": "نصب",
"amount": 200000
},
{
"title": "بستهبندی",
"amount": 50000
}
],
"discount_amount": 100000
}محاسبهٔ مبلغ این نمونه:
(1250000 × 2) + (150000 × 3) + 0 + (200000 + 50000) - 100000 = 3100000Response
draft_ iduuidtracking_ codestringexpires_ atdatetimedraft_ urlstring{
"success": true,
"data": {
"draft_id": "9b1c4f52-1f2a-4f33-8a77-2c0b9b0d51aa",
"tracking_code": "48219307",
"expires_at": "2026-09-20T10:35:12.480000+00:00",
"draft_url": "https://pwa.mehrad.app/purchase/9b1c4f52-1f2a-4f33-8a77-2c0b9b0d51aa"
},
"meta": null
}مقادیر قابل ذخیره و نمایش
مقادیر لازم برای ذخیره: draft_id (برای تأیید پرداخت در مرحلهٔ ۳ لازم است) و expires_at.
موارد قابل نمایش به مشتری: مبلغ نهایی، تصویر QR ساختهشده از draft_url، کد پیگیری ۸ رقمی به عنوان روش جایگزین، و زمان باقیمانده تا انقضا.
خرید ساختهشده ۵ دقیقه اعتبار دارد. تا وقتی مشتری آن را باز نکرده باشد، هیچ سفارشی ثبت نشده است و پس از انقضا باید خرید جدیدی ساخته شود.
مرحله ۲ — باز کردن خرید و پرداخت توسط مشتری
این مرحله کاملاً در اپلیکیشن مهراد انجام میشود و به پیادهسازی توسط پذیرنده نیاز ندارد. آنچه پذیرنده باید از این مرحله بداند:
- مشتری با اسکن QR یا وارد کردن کد ۸ رقمی، محتوای خرید (اقلام، خدمات، تخفیف، مبلغ و نام پذیرنده) را میبیند.
- با باز شدن خرید توسط مشتری، سفارش با
channel = IN_PERSONساخته میشود وorder_idآن همانdraft_idاست. در همین لحظه یک تلاش پرداخت با وضعیتPENDINGایجاد میشود. - هر خرید فقط یکبار قابل باز شدن است؛ پس از باز شدن یا انقضا، همان کد یا QR دیگر قابل استفاده نیست.
- با پرداخت موفق، مبلغ از حساب مشتری برداشت و نگهداری میشود و وضعیت تلاش پرداخت به
PAIDتغییر میکند. مبلغ تا تأیید پذیرنده برای او نهایی نمیشود. callback_urlاختیاری است. اگر ارسال شود، پس از پرداخت یا لغو مشتری به آن هدایت میشود. اگر ارسال نشود، مشتری در اپلیکیشن میماند.
مرحله ۳ — تأیید پرداخت
/gateway/order/payment/verifyAuthentication: token اپلیکیشن پذیرنده در header Authorization.
تأیید پرداخت مرحلهٔ نهایی و الزامی است؛ بدون آن مبلغ برای پذیرنده نهایی نمیشود. در خرید حضوری، تأیید با order_id انجام میشود.
ورودی
order_ iduuidالزامیamountintegerالزامی{
"order_id": "9b1c4f52-1f2a-4f33-8a77-2c0b9b0d51aa",
"amount": 3100000
}Response
{
"success": true,
"data": {
"ref_id": "5f7d1a30-1c77-4a2b-b6d1-0f3f41f9c5e2",
"status": "VERIFY_OK",
"fee": 15500
},
"meta": null
}statusبرابرVERIFY_OKیعنی تأیید موفق بوده است.ref_id، ID تراکنش نهایی است و برای پیگیری و تطبیق حساب باید ذخیره شود.feeکارمزد سامانه برای این تراکنش است.
پس از تأیید موفق: وضعیت سفارش SUCCESS میشود، مبلغ برای پذیرنده نهایی میگردد و پیامک تأیید خرید برای مشتری ارسال میشود.
تشخیص انجامشدن پرداخت
authority در اختیار پذیرنده نیست؛ وضعیت پرداخت از همین endpoint تأیید مشخص میشود:
| response | معنی | اقدام لازم |
|---|---|---|
ORDER_NOT_FOUND | مشتری هنوز خرید را باز نکرده است | تکرار request پس از چند لحظه؛ پس از انقضای خرید، ساخت خرید جدید |
ORDER_ATTEMPT_NOT_PAID | خرید باز شده ولی پرداخت انجام نشده است | تکرار request پس از چند لحظه |
status = VERIFY_OK | پرداخت انجام و تأیید شده است | تحویل کالا و ثبت ref_id |
ORDER_ALREADY_PAID | پرداخت قبلاً تأیید شده است | پرداخت موفق تلقی میشود؛ تأیید دوباره لازم نیست |
مبلغ تأیید
مبلغ ارسالی با مبلغ پرداختشده مقایسه میشود. اگر این دو برابر نباشند، تأیید با ORDER_AMOUNT_NOT_MATCHED رد میشود و تلاش پرداخت باطل میشود؛ یعنی دیگر امکان تأیید همان پرداخت وجود ندارد.
اگر سفارش متعلق به پذیرندهٔ دیگری باشد، request با ORDER_MERCHANT_NOT_MATCHED رد میشود و پرداخت دستنخورده باقی میماند.
محدودیت زمانی تأیید
تأیید باید حداکثر تا ۳۰ دقیقه پس از ایجاد تلاش پرداخت انجام شود. پس از آن پرداخت به صورت خودکار بسته و مبلغ به مشتری بازگردانده میشود و وضعیت تلاش پرداخت VERIFY_TIMEOUT خواهد شد.
مرحله ۴ — مدیریت نتیجهٔ نهایی
| سناریو | نحوهٔ تشخیص | اقدام لازم |
|---|---|---|
| پرداخت موفق و تأییدشده | response تأیید با status = VERIFY_OK | تحویل کالا، ثبت ref_id، نمایش «پرداخت موفق» |
| پرداخت انجام نشده | ORDER_ATTEMPT_NOT_PAID | مشتری باید پرداخت را در اپلیکیشن کامل کند |
| قبلاً تأیید شده | ORDER_ALREADY_PAID | پرداخت موفق تلقی میشود؛ تأیید دوباره لازم نیست |
| خرید باز نشده یا منقضی شده | ORDER_NOT_FOUND | پس از انقضای ۵ دقیقهای، ساخت خرید جدید و ارائهٔ کد تازه به مشتری |
| مبلغ سفارش با مبلغ پرداختشده برابر نیست | ORDER_AMOUNT_NOT_MATCHED | پرداخت باطل شده است؛ خرید جدید باید ساخته شود |
| سفارش متعلق به پذیرندهٔ دیگری است | ORDER_MERCHANT_NOT_MATCHED | بررسی order_id؛ پرداخت دستنخورده باقی مانده است |
| مهلت تأیید گذشته | ORDER_ATTEMPT_NOT_PAID پس از ۳۰ دقیقه از ایجاد تلاش پرداخت | مبلغ به مشتری بازگشته است؛ خرید جدید باید ساخته شود |
وضعیتها
وضعیت سفارش
| وضعیت | معنی |
|---|---|
PENDING | سفارش ساخته شده ولی هنوز تأیید نشده است |
SUCCESS | پرداخت تأیید شده است |
FAILED | پرداخت انجام یا تأیید نشده و سفارش بسته شده است |
وضعیت تلاش پرداخت
| وضعیت | معنی |
|---|---|
PENDING | خرید باز شده ولی مشتری هنوز پرداخت نکرده است |
PAID | پرداخت انجام شده و منتظر تأیید پذیرنده است |
VERIFY_OK | پرداخت تأیید شده است |
VERIFY_FAILED | تأیید با مبلغ نادرست انجام شده و پرداخت باطل شده است |
VERIFY_TIMEOUT | مهلت تأیید گذشته و مبلغ به مشتری بازگردانده شده است |
CANCELED | پرداخت انجامنشده پس از انقضا لغو شده است |
USER_CANCELED | لغو توسط مشتری |
سفارشهای این جریان همیشه channel = IN_PERSON دارند و در پنل پذیرنده در بخش سفارشهای حضوری نمایش داده میشوند.
قواعد اعتبارسنجی و محدودیتها
قواعدی که API اعمال میکند
| قاعده | خطا در صورت نقض |
|---|---|
amount باید بزرگتر از صفر باشد | ORDER_AMOUNT_NOT_POSITIVE |
amount باید با اقلام، خدمات و تخفیف همخوان باشد | INVALID_ORDER_AMOUNT |
| کالاهای تکراری (همان ID کالا + همان تنوع) در یک request نباشند | DUPLICATE_ORDER_ITEM |
| سرویس باید متعلق به پذیرنده و فعال باشد | SERVICE_NOT_OFFERED_BY_MERCHANT |
| مبلغ تأیید باید با مبلغ پرداختشده برابر باشد | ORDER_AMOUNT_NOT_MATCHED |
| سفارش باید متعلق به همین پذیرنده باشد | ORDER_MERCHANT_NOT_MATCHED |
| سفارش باید پرداخت انجامشده داشته باشد | ORDER_ATTEMPT_NOT_PAID |
محدودیتهای زمانی
| مورد | مقدار |
|---|---|
| اعتبار خرید ساختهشده | ۵ دقیقه |
| مهلت تأیید پرداخت | ۳۰ دقیقه پس از ایجاد تلاش پرداخت |
قواعد اعتبارسنجی
در صورت رعایت نشدن هر یک از این موارد، request رد میشود:
shipmentsارسال شده باشد.- مبلغ نهایی با اقلام،
shipping_feeمرسولهها، خدمات و تخفیف برابر و بزرگتر از صفر باشد. - قیمت هر قلم و مبلغ هر خدمت بزرگتر از صفر باشد.
- تعداد هر قلم عدد صحیح و حداقل ۱ باشد.
shipping_feeو تخفیف منفی نباشد.- یک کالا با ID و تنوع یکسان بیش از یکبار نیامده باشد.
- طول فیلدهای متنی از حداکثر مجاز بیشتر نباشد.
external_order_id هنگام ساخت خرید برای یکتا بودن بررسی نمیشود. اثر آن در لحظهٔ باز کردن خرید است: اگر این مقدار به سفارشی پرداختشده تعلق داشته باشد، خرید باز نمیشود و اگر به سفارشی پرداختنشده تعلق داشته باشد، پرداخت به همان سفارش قبلی وصل میشود و draft_id جدید سفارش تازهای نخواهد ساخت. اگر سفارش پرداختنشده متعلق به مشتری دیگری باشد، خرید باز نمیشود.
فهرست خطاها
اگر request با خطا مواجه شود، response آن کدی غیر از ۲xx دارد و success در آن false است. کد خطا در فیلد error.code و متن قابل نمایش در error.message برگردانده میشود. ساختار کامل خطا در بخش مقدمه آمده است. خطاهای مشترک همهٔ endpointها (VALIDATION_ERROR، FINANCIAL_OPERATIONS_FROZEN و نبود header Authorization) در همان بخش آمدهاند.
ساخت خرید حضوری
/gateway/order/draft/request| کد خطا | HTTP | توضیح و اقدام پیشنهادی |
|---|---|---|
| MERCHANT_APP_TOKEN_EXPIRED_OR_INVALID | 401 | token اپلیکیشن پذیرنده نامعتبر یا منقضی است؛ token تازه باید دریافت شود. |
| MERCHANT_APP_IP_ADDRESS_NOT_ALLOWED | 403 | آیپی request در لیست سفید اپلیکیشن پذیرنده نیست. |
| VALIDATION_ERROR | 422 | ساختار request نامعتبر است؛ برای مثال ارسال نکردن shipments، قیمت یا مبلغ خدمت صفر یا منفی، تعداد کمتر از ۱، یا shipping_fee یا تخفیف منفی. |
| SERVICE_NOT_OFFERED_BY_MERCHANT | 400 | service_id متعلق به پذیرنده نیست یا غیرفعال است. |
| ORDER_AMOUNT_NOT_POSITIVE | 400 | مبلغ نهایی صفر یا منفی است؛ معمولاً تخفیف بیش از حد. |
| DUPLICATE_ORDER_ITEM | 400 | یک کالا با همان ID و همان تنوع بیش از یکبار در shipments آمده. |
| INVALID_ORDER_AMOUNT | 400 | amount با اقلام، خدمات و تخفیف همخوان نیست؛ محاسبه باید اصلاح شود. |
{
"success": false,
"error": {
"code": "SERVICE_NOT_OFFERED_BY_MERCHANT",
"message": "برای این پذیرنده، سرویس مورد نظر وجود ندارد.",
"details": null
}
}تأیید پرداخت
/gateway/order/payment/verify| کد خطا | HTTP | توضیح و اقدام پیشنهادی |
|---|---|---|
| HTTP_EXCEPTION | 401 | header Authorization ارسال نشده یا token اپلیکیشن پذیرنده نامعتبر یا منقضی است. message در این حالت Unauthorized است. یکبار token تازه باید دریافت و request تکرار شود. |
| VALIDATION_ERROR | 422 | order_id یا amount ارسال نشده، order_id قالب UUID ندارد، یا order_id و authority با هم ارسال شدهاند. |
| ORDER_NOT_FOUND | 404 | سفارشی با این ID وجود ندارد؛ مشتری هنوز خرید را باز نکرده یا خرید منقضی شده است. |
| ORDER_MERCHANT_NOT_MATCHED | 400 | سفارش متعلق به پذیرندهٔ دیگری است؛ پرداخت تغییری نمیکند. |
| ORDER_ATTEMPT_NOT_PAID | 400 | پرداختی برای تأیید وجود ندارد؛ تأیید باید پس از انجام پرداخت توسط مشتری انجام شود. |
| ORDER_ALREADY_PAID | 400 | این سفارش قبلاً تأیید شده است؛ تأیید دوباره لازم نیست. |
| ORDER_AMOUNT_NOT_MATCHED | 400 | مبلغ سفارش با مبلغ پرداختشده برابر نیست و پرداخت باطل شده است. |
{
"success": false,
"error": {
"code": "ORDER_ATTEMPT_NOT_PAID",
"message": "برای این سفارش پرداختی انجام نشده است.",
"details": null
}
}سایر خطاهای token اپلیکیشن پذیرنده در مستندات ارتباط پذیرندگان آمده است.
چکلیست پیادهسازی
- دریافت token اپلیکیشن پذیرنده و مدیریت انقضای آن
- نگهداری
service_idسرویس فعال پذیرنده در تنظیمات سیستم فروشگاهی - تولید
external_order_idیکتا برای هر سفارش - ساخت اقلام، خدمات و تخفیف و محاسبهٔ
amountطبق قاعدهٔ مبلغ - اعتبارسنجی مقادیر پیش از ارسال (تعداد، قیمت، تخفیف)
- فراخوانی
POST /gateway/order/draft/request - ذخیرهٔ
draft_idوexpires_atاز response - نمایش QR (از
draft_url)، کد پیگیری و مبلغ به مشتری - نمایش زمان باقیمانده و ساخت خرید جدید پس از انقضای ۵ دقیقهای
- فراخوانی
POST /gateway/order/payment/verifyباdraft_idبه عنوانorder_idو مبلغ دقیق - ذخیرهٔ
ref_idو نمایش نتیجهٔ نهایی به مشتری - مدیریت خطاهای تأیید (
ORDER_NOT_FOUND،ORDER_ATTEMPT_NOT_PAID،ORDER_AMOUNT_NOT_MATCHED،ORDER_ALREADY_PAID) - رعایت مهلت ۳۰ دقیقهای تأیید و مدیریت
VERIFY_TIMEOUT