Skip to content
Sudip KC writing notes in a journal at a desk
SK.
← All articles

Nepal Tech · · 5 min read

नेपाली एपमा अनलाइन भुक्तानी: के-के चुनौती छन्?

  • Nepal
  • नेपाली
  • Security
  • Django
  • Architecture

eSewa वा Khalti को button राख्न एक दिन लाग्छ। तर पैसा काटियो, order बनेन, अनि ग्राहक फोन गरेर रिसाउँछ भने त्यो समस्या सुल्झाउन हप्ता लाग्छ। भुक्तानीको असली काम button पछाडि हुन्छ।

नेपालमा अहिले eSewa, Khalti, Fonepay र ConnectIPS जस्ता विकल्पहरू छन्, र धेरैजसो ग्राहकसँग कम्तीमा एउटा wallet वा mobile banking छ। त्यसैले आजकल लगभग हरेक client ले "अनलाइन भुक्तानी पनि राखिदिनुस् न" भन्छन्। हरेक gateway को आफ्नै documentation र प्रक्रिया हुन्छ, र म यहाँ कुनै एउटाको exact API बारे लेख्दिनँ। त्यसका लागि सम्बन्धित gateway को आधिकारिक documentation नै पढ्नुपर्छ। यहाँ म ती सिद्धान्तहरूको कुरा गर्छु जुन जुनसुकै gateway मा लागू हुन्छन्, र जुन मैले कहिलेकाहीं गल्ती गरेर सिकेँ।

सामान्य flow कस्तो हुन्छ

धेरैजसो gateway को flow मोटामोटी यस्तै हुन्छ:

  1. ग्राहकले तपाईंको एपमा "भुक्तानी गर्नुहोस्" थिच्छ।
  2. तपाईंको server ले एउटा order वा payment record बनाउँछ, स्थिति pending राखेर।
  3. ग्राहक gateway को page वा app मा जान्छ र भुक्तानी गर्छ।
  4. Gateway ले ग्राहकलाई तपाईंको एपमा फिर्ता पठाउँछ, साथमा केही जानकारी (callback)।
  5. तपाईंको server ले gateway सँग सिधै सोधेर भुक्तानी साँच्चै भएको हो कि होइन भनेर verify गर्छ।
  6. Verify भएपछि मात्र order लाई paid बनाइन्छ।

कागजमा सजिलो देखिन्छ। समस्या चरण ४ र ५ बीचमा हुन्छ।

Client को callback मा कहिल्यै भरोसा नगर्नुहोस्

यो सबैभन्दा महत्त्वपूर्ण नियम हो। जब gateway ले ग्राहकलाई तपाईंको success URL मा फिर्ता पठाउँछ, त्यो URL मा भएका query parameter हरू ग्राहकको browser बाट आउँछन्। ब्राउजरबाट आउने कुरा जसले पनि बदल्न सक्छ। कसैले success URL कपी गरेर आफैं खोल्यो भने के हुन्छ? यदि तपाईंको frontend ले "success page मा आयो, त्यसैले पैसा तिरेको हो" भनेर मान्छ भने, तपाईंले मुफ्तमा सामान बेच्नुभयो।

त्यसैले सही तरिका यो हो:

  • Frontend ले केवल सूचना दिन्छ: "म फर्केर आएँ, यो transaction जाँच्नुहोस्।"
  • Server ले verify गर्छ: आफ्नो secret key प्रयोग गरेर gateway को verification API मा सिधै सोध्छ।
  • रकम मिलाउनुहोस्: Gateway ले भनेको रकम तपाईंको database को order रकमसँग मेल खान्छ कि खाँदैन, जाँच्नुहोस्।
  • Secret key server मै राख्नुहोस्: Frontend को JavaScript वा Flutter app भित्र कहिल्यै होइन।
Browser ले भनेको "success" एउटा अनुरोध मात्र हो। पैसा आयो भन्ने प्रमाण gateway सँग server ले गरेको कुराकानीबाट मात्र आउँछ।

एउटै भुक्तानी दुईपटक प्रक्रिया नहोस्

नेपालको इन्टरनेटमा request बीचमै अड्किनु सामान्य कुरा हो। ग्राहकले success page reload गर्छ, browser ले request फेरि पठाउँछ, वा gateway ले एउटै सूचना दुईपटक पठाउँछ। यदि तपाईंको code ले हरेकपटक "order paid भयो, stock घटाऊ, receipt पठाऊ" गर्छ भने stock दुईपटक घट्छ र ग्राहकलाई दुईवटा receipt जान्छ।

यसलाई idempotency भनिन्छ: एउटै काम जतिपटक गरे पनि नतिजा एउटै हुनुपर्छ। Django मा म सामान्यतया यस्तो ढाँचा प्रयोग गर्छु:

from django.db import transaction

def confirm_payment(payment_id, gateway_ref):
    with transaction.atomic():
        payment = Payment.objects.select_for_update().get(id=payment_id)

        # पहिल्यै पूरा भइसकेको छ भने केही नगर्ने
        if payment.status == Payment.Status.PAID:
            return payment

        result = verify_with_gateway(gateway_ref)  # server बाट gateway लाई सोध्ने
        if not result.ok or result.amount != payment.amount:
            payment.status = Payment.Status.FAILED
            payment.save(update_fields=["status"])
            return payment

        payment.status = Payment.Status.PAID
        payment.gateway_ref = gateway_ref
        payment.save(update_fields=["status", "gateway_ref"])
        payment.order.mark_paid()  # stock, receipt आदि यहीँबाट एकपटक मात्र
    return payment

यहाँ select_for_update ले एउटै record मा दुईवटा request एकैचोटि आए पनि एउटाले अर्कोलाई पर्खाउँछ। अनि सुरुमै PAID जाँच्दा दोस्रो request ले केही गर्दैन। gateway_ref लाई database मा unique राख्नु पनि राम्रो अभ्यास हो, ताकि एउटै transaction दुईवटा order मा जोडिन नसकोस्। Django को transaction documentation एकपटक पढ्नलायक छ।

Pending र failed अवस्था पनि डिजाइन गर्नुहोस्

धेरै developer ले success र failure मात्र सोच्छन्। तर वास्तविकतामा तेस्रो अवस्था सबैभन्दा झन्झटिलो हुन्छ: थाहा छैन। ग्राहकको पैसा काटियो जस्तो देखिन्छ, तर callback आएन किनकि बीचमै इन्टरनेट गयो वा ग्राहकले app बन्द गर्‍यो।

यस्तो अवस्थाका लागि:

  • Payment record मा स्पष्ट status राख्नुहोस्: pending, paid, failed, expired जस्ता।
  • पृष्ठभूमिमा फेरि जाँच्नुहोस्: केही समयसम्म pending रहेका भुक्तानीहरूलाई एउटा scheduled task ले gateway सँग फेरि verify गरोस्।
  • Expiry राख्नुहोस्: निश्चित समयपछि पनि पुष्टि नभए expired बनाउनुहोस्, ताकि stock सधैं अड्किएर नबसोस्।
  • Admin ले हेर्न मिल्ने बनाउनुहोस्: ग्राहकले फोन गर्दा support टिमले transaction को इतिहास तुरुन्तै हेर्न सकोस्।

NovaRestro जस्तो restaurant system मा यो कुरा झन् महत्त्वपूर्ण हुन्छ, किनकि त्यहाँ ग्राहक काउन्टरमै उभिएको हुन्छ। "भुक्तानी जाँच हुँदैछ" भन्ने स्पष्ट अवस्था नभए कर्मचारी र ग्राहक दुवै अन्योलमा पर्छन्।

ग्राहकको अनुभव: retry सजिलो, तर सुरक्षित

UX मा सबैभन्दा ठूलो गल्ती भनेको भुक्तानी असफल हुँदा "Something went wrong" मात्र देखाउनु हो। ग्राहकलाई सबैभन्दा पहिले यो जान्न मन लाग्छ: "मेरो पैसा काटियो कि काटिएन?"

मैले Handling Loading, Error and Empty States Properly मा लेखेझैं, हरेक अवस्थाको सन्देश स्पष्ट हुनुपर्छ। भुक्तानीका लागि म यी कुरा ध्यान दिन्छु:

  • Pending मा: "तपाईंको भुक्तानी जाँच हुँदैछ, फेरि भुक्तानी नगर्नुहोस्" भनेर स्पष्ट लेख्ने।
  • Failed मा: "भुक्तानी पूरा भएन, तपाईंको खाताबाट रकम काटिएको छैन" (यदि verify गरेर थाहा छ भने मात्र)।
  • Retry button: उही order का लागि नयाँ payment attempt बनाउने, नयाँ order होइन।
  • Double tap रोक्ने: Button थिचेपछि loading अवस्थामा राखेर disable गर्ने।

ढिलो नेटवर्कमा यी कुरा झन् जरुरी हुन्छन्। Designing Apps for Slow Internet Connections मा मैले भनेझैं, नेपाली प्रयोगकर्ताको नेटवर्क कहिलेकाहीं बीचमै जान्छ भन्ने मानेर नै डिजाइन गर्नुपर्छ।

परीक्षण र रेकर्ड

धेरैजसो gateway ले test वा sandbox वातावरण दिन्छन्। Live मा जानुअघि त्यहाँ सफल भुक्तानी मात्र होइन, बीचमै रद्द गरेको, browser बन्द गरेको र success URL दुईपटक खोलेको अवस्था पनि जाँच्नुहोस्।

साथै gateway बाट आएको हरेक response को log राख्नुहोस्। पछि ग्राहकसँग विवाद भयो भने त्यो log नै तपाईंको प्रमाण हुन्छ। तर log मा संवेदनशील जानकारी नराख्न ध्यान दिनुहोस्।

मैले सिकेको मुख्य कुरा

भुक्तानी integration लाई "एउटा feature" होइन, सानो accounting system जस्तो सोच्नुहोस्। Client लाई कहिल्यै भरोसा नगर्ने, server बाट verify गर्ने, एउटै भुक्तानी दुईपटक प्रक्रिया हुन नदिने, र pending अवस्थालाई गम्भीरतापूर्वक लिने। यी चार कुरा ठीक भए gateway जुनसुकै भए पनि तपाईंको system बलियो हुन्छ। बाँकी कुरा आधिकारिक documentation ले सिकाउँछ।