চারটা বেমানান বাংলাদেশি পেমেন্ট গেটওয়েকে একটা interface-এর পেছনে একত্র করা একটা Flutter e-commerce অ্যাপ, সাথে local-first কার্ট আর সারাদেশের মিড-রেঞ্জ ডিভাইসের জন্য বানানো connectivity-aware UX।
eBangladesh-এর সবচেয়ে কঠিন ইঞ্জিনিয়ারিং সমস্যা shopping flow ছিল না — ছিল পেমেন্ট। চারটা বাংলাদেশি গেটওয়ে (bKash, Nagad, uPay, aamarPay), চারটা বেমানান integration প্যাটার্ন, চারটা আলাদা failure vocabulary, আর একটা checkout স্ক্রিন, যেটাকে জানতেই দেওয়া যাবে না কাস্টমার কোনটা বেছে নিয়েছে। এর উপর: target userbase-এর একটা বড় অংশ থাকে মিড-রেঞ্জ Android ডিভাইসে, অস্থির connectivity নিয়ে, তাই নেটওয়ার্ক না থাকলেও অ্যাপটাকে ব্যবহারযোগ্য থাকতে হতো — browsing, cart, checkout warning সবকিছুতেই।
আমি Codeboxr-এ Flutter মোবাইল অ্যাপটা বানিয়েছি — payment abstraction layer, offline-tolerant কার্ট, আর connectivity-aware UX ডিজাইন করেছি, যা অ্যাপটাকে শুধু ভালো সিগন্যালের এলাকায় না, পুরো দেশ জুড়েই ব্যবহারযোগ্য করেছে।
সমস্যাটা কী ছিল
ঢাকার বাইরে বাংলাদেশে e-commerce-এর বিস্তার প্রচলিত মার্কেটপ্লেসের চেয়ে আলাদা পদ্ধতি দাবি করত। আধা-শহুরে আর গ্রামীণ এলাকার কাস্টমারদের দরকার ছিল বিশ্বাসযোগ্য লোকাল কোঅর্ডিনেটর, তাদের আগে থেকেই ব্যবহার করা লোকাল পেমেন্ট মেথড (bKash, Nagad), আর একটা অ্যাপ যেটা variable connectivity নিয়েও মিড-রেঞ্জ ডিভাইসে নির্ভরযোগ্যভাবে কাজ করে।
প্রতি বর্গমাইলে একজন ফিজিক্যাল কোঅর্ডিনেটর রাখার eBangladesh-এর মডেলের দরকার ছিল এমন একটা মোবাইল অ্যাপ, যা দেশ জুড়ে ব্যবহারকারীদের জন্য browsing আর কেনাকাটা দুটোই ঘর্ষণহীন করে তোলে।
আমার ভূমিকা
আমি Codeboxr-এ Flutter মোবাইল অ্যাপটা বানিয়েছি — architecture, implementation, আর delivery-র দায়িত্বে ছিলাম:
একটা payment gateway abstraction layer ডিজাইন করেছি — bKash, Nagad, uPay, আর aamarPay-কে একটা কমন PaymentGateway interface-এর পেছনে একত্র করেছি, একটাই pay(amount, orderId) কন্ট্র্যাক্টসহ। প্রতিটা গেটওয়ের WebView redirect, token management, আর error normalization ওই interface-এর পেছনে সিল করা; checkout স্ক্রিন কখনো সরাসরি কোনো গেটওয়ে ছোঁয় না।
Bloc বা Riverpod-এর বদলে Provider বেছে নিয়েছি — অ্যাপের state graph তুলনামূলক flat (cart, auth, connectivity), আর Codeboxr-এর টিম কনটেক্সট Provider-এর সরলতাকেই বেশি পছন্দ করত। এই tradeoff মেনে নেওয়া হয়েছিল: 15+ স্ক্রিন জুড়ে দ্রুত onboarding আর কম boilerplate ফাইলের বিনিময়ে কম compile-time safety।
shared_preferences-এর উপর একটা local-first কার্ট ইমপ্লিমেন্ট করেছি — ইউজাররা পুরোপুরি অফলাইনে browse করতে, কার্টে যোগ করতে, আর wishlist ম্যানেজ করতে পারে। Authentication ছাড়াই কার্ট state সেশন জুড়ে persist করে, যা প্রথমবার আসা ইউজারদের জন্য checkout-এর ঘর্ষণ কমায়।
connectivity_plus দিয়ে connectivity-aware UX বানিয়েছি — নেটওয়ার্ক চলে গেলে অ্যাপ gracefully degrade করে: cache করা প্রোডাক্ট ডেটা browse করা যায়, কার্ট অপারেশন অফলাইনে কাজ করে, আর কানেকশন ছাড়া পেমেন্টে যাওয়ার আগে ইউজারকে সতর্ক করা হয়।
মিড-রেঞ্জ Android ডিভাইসের জন্য অপ্টিমাইজ করেছি (2GB RAM, Android 8+) — পুরো ডেভেলপমেন্ট জুড়ে আসল ডিভাইসে profile করেছি। সবখানে ListView.builder ব্যবহার করেছি, widget tree shallow রেখেছি, addPostFrameCallback দিয়ে জরুরি না এমন initialization পিছিয়ে দিয়েছি, আর fixed dimension আর shimmer placeholder সহ cached_network_image দিয়ে image loading স্ট্যান্ডার্ডাইজ করেছি।
কারিগরি সিদ্ধান্ত
Payment Gateway Abstraction
চারটা গেটওয়ে, চারটা integration প্যাটার্ন — bKash আর Nagad WebView redirect ব্যবহার করে, uPay একটা কাস্টম SDK ব্যবহার করে, aamarPay সরাসরি একটা card form দেয়। প্রতিটা আলাদা response object আর error code রিটার্ন করে। checkout flow জুড়ে গেটওয়ে type অনুযায়ী branch করার বদলে, আমি একটা কমন কন্ট্র্যাক্ট ডিফাইন করেছি:
abstract class PaymentGateway { Future<PaymentResult> pay(double amount, String orderId);}
প্রতিটা গেটওয়ে implementation তার নিজের redirect flow, token lifecycle, আর error normalization ওই একটা method-এর পেছনে encapsulate করে। checkout স্ক্রিন শুধু interface-এর উপর নির্ভর করে — পঞ্চম গেটওয়ে যোগ করা মানে একটা নতুন ক্লাস লেখা, checkout-এর কোনো কোড না ছোঁয়া।
Local-First Cart Without an Account
সাধারণত e-commerce অ্যাপে কার্টে যোগ করার আগে লগইন লাগে। কার্ট আর wishlist-এর জন্য আমি shared_preferences বেছে নিয়েছি (SQLite না), কারণ ডেটা ছোট (প্রোডাক্ট আইডি + quantity-র একটা list), সেশন-স্কোপড, আর querying-এর দরকার নেই। এতে ইউজাররা sign-up করার আগেই একটা কার্ট বানাতে পারে — এটা একটা ইচ্ছাকৃত প্রোডাক্ট সিদ্ধান্ত, এমন একটা মার্কেটে abandonment কমাতে যেখানে ইউজাররা credential শেয়ার করতে সতর্ক থাকে।
Connectivity-Aware Architecture
প্রতিটা network call try-catch-এ মোড়ানোর বদলে, connectivity_plus একটা singleton stream Provider-এ ফিড করে। স্ক্রিনগুলো connectivity state সাবস্ক্রাইব করে আর সেই অনুযায়ী UI অ্যাডজাস্ট করে: প্রোডাক্ট গ্রিড cache থেকে পুরোপুরি browse করা যায়, checkout বাটন অফলাইনে একটা warning banner দেখায়, আর cart badge আপডেট হতেই থাকে। এটা connectivity logic-কে business logic থেকে decouple করে — কোনো স্ক্রিনের জানার দরকার নেই connectivity কীভাবে detect হয়।
কারিগরি স্ট্যাক
Package
Purpose
dio
Network requests with interceptors
provider
State management
shared_preferences
Local persistence (cart, wishlist, session)
connectivity_plus
Network state monitoring
cached_network_image
Image loading and caching
carousel_slider
Product image galleries
image_picker
Profile photo selection
flutter_svg
SVG asset rendering
pin_code_fields
OTP verification UI
syncfusion_flutter_pdfviewer
Invoice and document viewing
share_plus
Share products via platform share sheet
চ্যালেঞ্জ ও সমাধান
চ্যালেঞ্জ: চারটা আলাদা পেমেন্ট গেটওয়ে, প্রতিটার নিজস্ব integration প্যাটার্ন, error code vocabulary, আর sandbox behaviour। ইউজার যেই গেটওয়েই বেছে নিক না কেন, checkout flow সামঞ্জস্যপূর্ণ রাখা জটিল ছিল।
সমাধান: একটা payment gateway abstraction layer বানিয়েছি — একটা কমন PaymentGateway interface, pay(amount, orderId) method সহ। প্রতিটা গেটওয়ে implementation তার নিজের redirect flow, token management, আর error normalization ওই interface-এর পেছনে হ্যান্ডেল করে। checkout স্ক্রিন শুধু interface সম্পর্কে জানে, গেটওয়েগুলো সম্পর্কে না।
চ্যালেঞ্জ: ভেন্ডর সিস্টেম থেকে আসা প্রোডাক্ট ইমেজ সাইজ আর কোয়ালিটিতে অসামঞ্জস্যপূর্ণ ছিল, যার ফলে ইমেজ লোড হওয়ার সময় layout jank হতো।
সমাধান: fixed width আর height constraint আর একটা shimmer placeholder সহ cached_network_image দিয়ে ইমেজ dimension স্ট্যান্ডার্ডাইজ করেছি। ইমেজগুলো ListView.builder দিয়ে lazily লোড হয়, তাই শুধু দৃশ্যমান আইটেমই network request ট্রিগার করে।
চ্যালেঞ্জ: ঢাকার বাইরে সাধারণ এমন বাজেট Android ডিভাইসে (2GB RAM, পুরনো Android ভার্সন) অ্যাপটাকে কাজ করতে হতো।
সমাধান: পুরো ডেভেলপমেন্ট জুড়ে একটা আসল মিড-রেঞ্জ ডিভাইসে profile করেছি। সবখানে ListView.builder ব্যবহার করেছি, widget tree shallow রেখেছি, আর addPostFrameCallback দিয়ে জরুরি না এমন সার্ভিসের initialization পিছিয়ে দিয়েছি।
ফলাফল
একটাই PaymentGateway interface চারটা গেটওয়ের সব খুঁটিনাটি শুষে নিয়েছে — পঞ্চমটা যোগ করা মানে একটা নতুন ক্লাস, checkout কোডে শূন্য পরিবর্তন
Local-first কার্ট আর connectivity-aware UX অ্যাপটাকে ঢাকার বাইরে সাধারণ connectivity গ্যাপ জুড়েও পুরোপুরি browse-যোগ্য আর বেশিরভাগ ব্যবহারযোগ্য রেখেছে
Rendering আর initialization-এর কাজ বিশেষভাবে 2GB-RAM, Android 8+ ডিভাইসের জন্য টিউন করা, emulator না, আসল হার্ডওয়্যারে profile করা
Launch-পরবর্তী মনিটরিং লক্ষ্যভিত্তিক ফিক্সের দিকে নিয়ে গেছে, যা crash rate উল্লেখযোগ্যভাবে কমিয়েছে