মূল কন্টেন্টে যান

দুই বছর পরেও যে EMI সিস্টেমের হিসাব মেলে

ঋণ ব্যবস্থাপনার সফটওয়্যার নীরবে ব্যর্থ হয় — এখানে একটি রাউন্ডিং ভুল, ওখানে বদলে যাওয়া ব্যালান্স। সমাধান আরও ভ্যালিডেশন নয়, বরং অপরিবর্তনীয় লেজার ও হিসাব করে পাওয়া ব্যালান্স।

Infymerce Engineering3 মিনিটের পড়া

ঋণ ব্যবস্থাপনার প্ল্যাটফর্ম খুব কমই শব্দ করে ভাঙে। ভাঙে দুই বছর পরে, যখন ফিনান্স টিম ব্যাখ্যা করতে পারে না কেন সিস্টেম বলছে গ্রাহকের দেনা ৳১২,৪৩৮ অথচ হাতে হিসাব করলে আসে ৳১২,৪৪১।

এই সমস্যার প্রায় প্রতিটি রূপের মূল কারণ একটাই: ব্যালান্স হিসাব করে বের করার বদলে সংরক্ষণ ও সম্পাদনা করা হয়।

ভুলটি: একটি ব্যালান্স কলাম

সহজ নকশাটি ঋণের রেকর্ডেই চলমান ব্যালান্স রাখে:

-- যে নকশা একদিন গরমিল করবেই
CREATE TABLE loans (
  id              uuid PRIMARY KEY,
  principal       numeric(14,2) NOT NULL,
  outstanding     numeric(14,2) NOT NULL,  -- প্রতি পেমেন্টে বদলায়
  last_payment_at timestamptz
);

প্রতিটি পেমেন্টে চলে UPDATE loans SET outstanding = outstanding - ?। যতক্ষণ না:

  • webhook পুনরায় চেষ্টা করায় একটি পেমেন্ট দুবার লেখা হয়
  • স্বাভাবিক প্রক্রিয়ার বাইরে একটি রিফান্ড হয়
  • পুনর্গঠনে কেউ ব্যালান্স হাতে বদলায়, কারণ লিখে রাখে না
  • দুটি পেমেন্ট একসঙ্গে আসে এবং একটি অন্যটির পড়া মান মুছে দেয়

এর কোনোটিই কোনো এরর দেখায় না। সংখ্যাটি কেবল ওই মুহূর্ত থেকে ভুল হয়ে যায়, আর কখন ভুল হলো তার কোনো রেকর্ডও থাকে না।

সমাধান: শুধু যোগ হয় এমন লেজার

অবস্থা নয়, ঘটনা সংরক্ষণ করুন। কিছুই কখনো আপডেট বা ডিলিট হয় না।

CREATE TABLE ledger_entries (
  id             bigserial PRIMARY KEY,
  loan_id        uuid NOT NULL REFERENCES loans(id),
  entry_type     text NOT NULL,   -- disbursement | instalment_due | payment
                                  -- | penalty | waiver | reversal
  amount         numeric(14,2) NOT NULL,  -- ধনাত্মক বা ঋণাত্মক
  effective_date date NOT NULL,
  reference      text,            -- গেটওয়ে ট্রানজেকশন আইডি, idempotency-র জন্য
  reverses_id    bigint REFERENCES ledger_entries(id),
  created_at     timestamptz NOT NULL DEFAULT now(),
  created_by     uuid NOT NULL
);
 
CREATE UNIQUE INDEX ledger_reference_idx
  ON ledger_entries (reference) WHERE reference IS NOT NULL;

বকেয়া ব্যালান্স হয়ে যায় একটি কোয়েরি:

SELECT COALESCE(SUM(amount), 0) AS outstanding
FROM ledger_entries
WHERE loan_id = $1 AND effective_date <= $2;

এতে একসঙ্গে চারটি সুবিধা মেলে:

১. Idempotency। reference-এ ইউনিক ইনডেক্স থাকায় গেটওয়ের পুনরাবৃত্ত webhook দ্বিগুণ ক্রেডিট না করে নিষ্ক্রিয়ভাবে উপেক্ষিত হয়। ২. নিরীক্ষাযোগ্যতা। প্রতিটি সংখ্যা এমন এন্ট্রিতে গিয়ে ঠেকে যার লেখক ও সময় নথিভুক্ত। ৩. সংশোধনযোগ্যতা। ভুল ঠিক হয় নতুন রিভার্সাল যোগ করে, ইতিহাস বদলে নয় — তাই সংশোধনটিও নিরীক্ষাযোগ্য থাকে। ৪. সময়ভ্রমণ। effective_date <= ? দিয়ে যেকোনো পুরোনো তারিখের ব্যালান্স হুবহু পুনর্গঠন করা যায় — নিরীক্ষক ঠিক এটিই চাইবেন।

ভলিউম বাড়লে এই যোগফলের কোয়েরি ধীর হলে একটি ম্যাটেরিয়ালাইজড স্ন্যাপশট যোগ করুন — তবে লেজারটিকেই সত্যের উৎস রাখুন এবং স্ন্যাপশট সেখান থেকেই তৈরি করুন।

পূর্ণসংখ্যা বা নির্ভুল দশমিক ব্যবহার করুন, float নয়

// ভুল — বাইনারি ফ্লোটিং পয়েন্ট এগুলো হুবহু প্রকাশ করতে পারে না
0.1 + 0.2                 // 0.30000000000000004
12438.55 * 3              // 37315.649999999994

ডেটাবেসে numeric / DECIMAL এবং অ্যাপ্লিকেশন কোডে নির্ভুল ডেসিমাল টাইপ ব্যবহার করুন। টাকার বদলে পয়সা হিসেবে পূর্ণসংখ্যায় রাখাও চলে, যদি সিস্টেমের প্রতিটি সীমানা একই নিয়ম মানে।

রাউন্ডিংয়ের নিয়ম একবার ঠিক করুন, আর লিখে রাখুন

৩ মাসে ৳১০,০০০ কিস্তি মানে তিনবার ৳৩,৩৩৩.৩৩ — যোগ করলে হয় ৳৯,৯৯৯.৯৯। হারিয়ে যাওয়া পয়সাটিকে কোথাও যেতে হবে।

আমরা যে নিয়ম ব্যবহার করি: প্রতিটি কিস্তি ২ দশমিক ঘরে রাউন্ড করে হিসাব করুন, তারপর অবশিষ্টটুকু শেষ কিস্তিতে বসান। শেষ পেমেন্ট পার্থক্যটি শুষে নেয়, আর সময়সূচির যোগফল সবসময় মোট প্রদেয়র সমান হয়।

যে নিয়মই বাছুন, সেটিকে একটিমাত্র ফাংশনে রাখুন, প্রান্তিক ক্ষেত্রগুলোর জন্য টেস্ট লিখুন, এবং নিশ্চিত করুন গ্রাহককে দেখানো সময়সূচি ও লেজার দুটিই একই নিয়ম মানছে।

যেসব ঘটনা বাস্তবে ঘটে, সেগুলোই পরীক্ষা করুন

একটি ঋণ ব্যবস্থাপনা সিস্টেমের টেস্ট স্যুটে অন্তত থাকা উচিত:

  • কিস্তির চেয়ে কম আংশিক পরিশোধ
  • অতিরিক্ত পরিশোধ, যা পরের কিস্তি ও তার পরেরটির অংশ মেটায়
  • অনুপার্জিত সুদে ছাড়সহ আগাম নিষ্পত্তি
  • গ্রেস পিরিয়ডের ঠিক সীমানায় আসা পেমেন্ট — একদিনের হেরফের
  • মেয়াদের মাঝপথে পুনর্গঠন এবং তার ফলে সময়সূচি নতুন করে তৈরি
  • পরে আরেকটি পেমেন্ট প্রয়োগের পরে আগের একটি পেমেন্ট বাতিল করা
  • দৈনিক সুদের পণ্যে অধিবর্ষের ফেব্রুয়ারি

শেষেরটি প্রোডাকশনে যত সমস্যা তৈরি করেছে, তার কোনো যৌক্তিকতা নেই।

বাস্তবে এটি কেমন দাঁড়ায়

এই নকশায় হিসাব মেলানো একটি তদন্ত নয়, একটি কোয়েরি হয়ে যায়। ফিনান্স টিম যখন জিজ্ঞেস করে কেন ব্যালান্স এমন, আপনি দুই বছরের পরিবর্তন পুনর্গঠনের চেষ্টা না করে সরাসরি সেই এন্ট্রিগুলো দেখাতে পারেন যেগুলো থেকে সংখ্যাটি এসেছে — তারিখ, লেখক ও গেটওয়ে রেফারেন্সসহ।

আসল ডেলিভারেবল এটাই। পেমেন্ট কাজ করলেই সফটওয়্যার শেষ হয় না; শেষ হয় যখন কেউ প্রমাণ করতে পারে যে পেমেন্ট ঠিকভাবে কাজ করেছে।


আমরা ঠিক এই ভিত্তির উপরেই EMI ও লোন ম্যানেজমেন্ট সিস্টেম তৈরি করি — বিকাশ ও নগদের হিসাব মেলানো অন্তর্ভুক্ত। আপনার পোর্টফোলিও নিয়ে আমাদের সঙ্গে কথা বলুন।

বিষয়

অসাধারণ কিছু তৈরি করতে প্রস্তুত?

যেসব শীর্ষস্থানীয় প্রতিষ্ঠান ও স্টার্টআপ তাদের সবচেয়ে গুরুত্বপূর্ণ প্রজেক্টে ইনফাইমার্সের উপর আস্থা রাখে, তাদের সঙ্গে যুক্ত হোন।