مطالعه موردی مهندسی
بازارچهٔ ساعت دستدوم
ساعت دستدوم یک محصول کاتالوگ نیست. صاحبش کس دیگری است، یک تکنسین باید بگوید چیست، و یک تکه به مبلغی خیلی بزرگ فروخته میشود. برای همین بازارچه را داخل فروشگاه یک خردهفروش ساعت و جواهر اول بهعنوان یک سیستم اعتماد ساختم: هر تکه دقیقاً در یک حالت است، و هیچچیزی که فروشنده تایپ میکند بدون تأیید دو نفر به صفحهٔ عمومی نمیرسد.
مسئله تجاری
خردهفروش میخواست مشتریها بتوانند ساعتشان را از طریق فروشگاه بفروشند. سادهترین ساخت یک فرم محصول با تیک «دستدوم» است و سر اولین اختلاف میشکند: توضیح فروشنده بیبررسی منتشر میشود، یک تقلبی کنار جنس اصل فروشگاه مینشیند، خریدار پول تکهای را میدهد که هنوز در کشوی فروشنده است، و هیچکس نمیتواند بگوید ساعت در هر لحظه دست چه کسی بوده. ثبت، بازبینی فنی، تأیید، درج، رزرو، امانت و تسویه حالتهای متفاوتی با مالکهای متفاوتاند و یکی کردنشان در یک ردیف CRUD همهشان را ناامن میکند.
مسئلهٔ میزبان هم بود. فروشگاه روی WooCommerce میچرخد و درگاههای بانکی مجوزدار افزونهٔ WooCommerce هستند، پس پرداخت باید از فروشگاه میگذشت. اما فروش بازارچه فروش فروشگاه نیست: نباید در گزارشهای فروشگاه بیاید، نباید ایمیلهای فروشگاه را بفرستد، و همانطور که فهمیدم، نباید سر راهش به سبد خرید واقعی مشتری دست بزند.
آنچه تحویل دادم
- مسیر پنجمرحلهای فروشنده: ثبت، بازبینی روی میز تعمیر توسط تکنسین، تأیید عکسها و رفرنس و هویت توسط اپراتور، احراز هویت فروشنده (KYC)، و بعد درج. هیچ راهی از فرم فروشنده تا صفحهٔ عمومی وجود ندارد که از میز تعمیر یا اپراتور رد نشود.
- امانتداری و تحویل بهشکل یک ماشین حالت صریح. ثبت، بازبینی، اصالتسنجی، درج، رزرو، امانت و تحویل حالتهای جدا هستند، گذارها اعتبارسنجی میشوند، و هر تکه در هر لحظه دقیقاً در یکی از آنهاست. حکم میز تعمیر جدا از وضعیت فعلی ثبت میشود، چون این دو واقعیت متفاوتاند.
- رد شدن روی میز تعمیر پرونده را با برچسبهای دلیل و یادداشت تکنسین به خود فروشنده برمیگرداند، نه به صف اپراتور. هر دلیل یک شکل کوتاه پیامکی هم دارد، چون توکنهای الگوی درگاه پیامک سقف سی نویسه دارند.
- شش مسیر پرداخت و تسویه از روی یک پل WooCommerce، با سفارشهای بازارچه بیرون از گزارشها و آنالیتیکس فروشگاه، ایمیلهای تراکنشی فروشگاه خاموش، و سفارشهای صرفاً کارمزدی که هرگز موجودی نمیگیرند. یک تکه فقط در برابر پرداخت تأییدشده رزرو میشود و پاسخی که درگاه برای تأیید میفرستد اگر دوباره برسد اثر دوباره ندارد، پس یک تکه دو بار رزرو نمیشود.
- نمای بازارچه در پنل تعمیرات: سه گروه در انتظار، تأییدشده و ردشده، از روی پروندههایی که میز تعمیر دربارهشان حکم داده، تا تکنسین حکمهای خودش را بیآنکه از ابزارش بیرون بیاید ببیند.
- کسب امتیاز باشگاه با یکدهم نرخ فروشگاه، با چهار قاعدهٔ انتخابی (فروشنده هنگام فروش، فروشنده هنگام تسویه، خریدار هنگام خرید، و یک اعطای ثابت برای امانت پذیرفتهشده که پیشفرض خاموش است) و یک رتبهٔ پیکربندیشدنی فروشنده بر مبنای مبلغ خالص تسویهشده که کنار سطح باشگاه در پنل حساب نشان داده میشود.
رویکرد فنی
- حالتها را پیش از پرداختها مدل کردم. مخزن عمومی نمایشی هم همینطور ساخته شده: اول طراحی حالت، مرزهای اعتماد و حالتهای خرابی، بعد حجم رابط کاربری. ماشین حالت سختگیر انعطاف را میگیرد و نکته همین است؛ گذارهای ناامن را حذف میکند، نه اینکه مستندشان کند.
- هویت از فروشگاه میزبان قرض گرفته میشود، مجوز نه. یک کد OTP درست ثابت میکند بازدیدکننده کیست؛ بعد ماژول نقش خریدار یا فروشندهٔ خودش را میچسباند و داشبورد درست را باز میکند. هر تلاش تأیید رزرو و هر کد درست مصرف میشود، بهشکل یک گذار اتمی وابسته به نسل همان کد؛ کش اشتراکی هیچوقت منبع حقیقت یک جریان چنددرخواستی نیست؛ و اگر ذخیرهگاه پایدار سهمیه نتواند جا رزرو کند، اصلاً به سرویسدهندهٔ پیامک زنگ زده نمیشود.
- مسیر رد شدن از یک خرابی زنده درآمد: اولین ردِ واقعی برای فروشنده دو پیام فرستاد و دلیلش وسط راه بریده شد. قاعدهای که از آن بیرون آمد این است: یک واقعیت، یک پیام؛ و حالا هر دلیلی به شکلی که در توکن جا شود وجود دارد.
- امتیاز هیچوقت مستقیم به دفتر نمیرسد. هر منبع، تعمیرات و بازارچه و پیشخوان، از یک سرویس پاداش زیر قفل بهازای هر مشتری میگذرد، و کلید یکتایی نام کار یا آگهی را میبرد، نه لحظه را؛ پس یک رویداد تکراری نمیتواند دو بار بپردازد. بازارچه تومان ذخیره میکند، ماژول تعمیرات ریال، و باشگاه تومان میشمارد؛ پس تبدیل در یک جا زندگی میکند نه در هر فراخوان.
- جدایی کامل از WooCommerce توافق شد و عمداً عقب افتاد: درگاههای بانکی مجوزدار افزونهٔ WooCommerce هستند، پس پل تا بعد از عرضهٔ عمومی میماند، و تداخلی که ایجاد کرده بود همین حالا رفع شده است.
نتیجه و شواهد
اینجا هیچ عدد کارایی ادعا نمیشود و مخزن عمومی هم ادعا نمیکند؛ چیزی که تأیید شده خود مسیر است. مسیر فروشنده در محیط زنده کار کرده، اولین ردِ واقعیاش با دلیل به فروشنده رسیده، و اصلاحاتی که رو کرد منتشر شدهاند. تغییرناپذیرها برقرارند: هر تکه در هر لحظه در یک حالت است، رزرو پرداخت تأییدشده میخواهد، سفارش صرفاً کارمزدی موجودی نمیگیرد، و فروش بازارچه با گزارشها، ایمیلها و سبد خرید فروشگاه کاری ندارد.
اهمیت برای کارفرما
خردهفروشی که میتواند ساعت مشتری را امانی بگیرد، اصالتش را تأیید کند و بفروشد، مشتریای را نگه میدارد که وگرنه سراغ خریدار شخصی میرفت، و این کار را بیآنکه یک تقلبی کنار جنس خودش بنشیند انجام میدهد. ماشین حالت همان چیزی است که صف اپراتور را قابلاعتماد و اختلاف را پاسخدادنی میکند: چه کسی تکه را در اختیار داشته، چه کسی درج را تأیید کرده و چه مبلغی بابتش پرداخت شده، واقعیتهای ثبتشدهاند نه خاطره.
خلاصه اجرایی خوانا
خلاصه_اجرایی {
پروژه: "بازارچهٔ ساعت دستدوم"
میزبان: "فروشگاه WordPress/WooCommerce یک خردهفروش ساعت و جواهر"
مسیر_فروشنده: "ثبت -> بازبینی میز تعمیر -> تأیید اپراتور
(عکس، رفرنس، هویت) -> KYC -> درج"
حالتها: "ثبت / بازبینی / اصالتسنجی / درج / رزرو / امانت /
تحویل؛ هر تکه در یک حالت"
رد_شدن: "با برچسب دلیل و یادداشت تکنسین به فروشنده برمیگردد،
هرگز به صف اپراتور"
پرداخت: "شش مسیر روی پل WooCommerce؛ رزرو فقط با پرداخت
تأییدشده؛ پاسخ تکراری درگاه بیاثر"
جداسازی: "بی گزارش، ایمیل یا سبد فروشگاه؛ سفارش صرفاً کارمزدی
موجودی نمیگیرد"
باشگاه: "یک سرویس پاداش، قفل بهازای مشتری؛ بازارچه یکدهم نرخ
فروشگاه؛ رتبهٔ فروشنده بر خالص تسویهشده"
وضعیت: "مسیر فروشنده زنده؛ هیچ عدد کارایی ادعا نمیشود"
}ارزش حرفهای این پروژه
مسیر رد شدن همان چیزی است که دوست دارم بازبین بخواند. بیشتر سیستمها پروندهٔ ردشده را برای کسی میفرستند که کاری از دستش برنمیآید؛ این یکی آن را با دلیل برای خود فروشنده میفرستد، و پیام بعد از اولین ردِ واقعی که بریده شد اصلاح شد.
شریک شدن در پشتهٔ پرداخت فروشگاه میزبان تصمیم درستی بود و روی هر شش مسیر یک بار گزیدم. نگه داشتن پل و حذف عوارض جانبیاش، بهجای بازنویسی درگاهها، معاملهٔ صادقانه بود.