<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Arijit's Blog]]></title><description><![CDATA[Arijit's Blog]]></description><link>https://arijitsblog.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Mon, 07 Sep 2026 20:04:28 GMT</lastBuildDate><atom:link href="https://arijitsblog.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building ThrottleSync: A Social Ride Tracking App with Real-World GPS Challenges]]></title><description><![CDATA[When I started building ThrottleSync, the idea was straightforward:

Build a mobile app where riders can track rides, measure distance and speed, compete on a leaderboard, and connect socially with ot]]></description><link>https://arijitsblog.hashnode.dev/building-throttlesync-a-social-ride-tracking-app-with-real-world-gps-challenges</link><guid isPermaLink="true">https://arijitsblog.hashnode.dev/building-throttlesync-a-social-ride-tracking-app-with-real-world-gps-challenges</guid><dc:creator><![CDATA[Arijit Das]]></dc:creator><pubDate>Fri, 10 Jul 2026 03:10:34 GMT</pubDate><content:encoded><![CDATA[<p>When I started building <strong>ThrottleSync</strong>, the idea was straightforward:</p>
<blockquote>
<p>Build a mobile app where riders can track rides, measure distance and speed, compete on a leaderboard, and connect socially with other riders.</p>
</blockquote>
<p>But as the project evolved, I realized something important:</p>
<p>Building a ride tracking app is not just about saving GPS coordinates or drawing a route on a map.</p>
<p>The real challenge is deciding <strong>which data can be trusted</strong>.</p>
<p>GPS can be noisy. Leaderboards can become unfair. Cached data can confuse users. And without a good admin panel, debugging real user issues becomes guesswork.</p>
<p>This blog is a breakdown of how I built ThrottleSync, the problems I faced, and the engineering decisions that made the app more reliable.</p>
<hr />
<h2>What Is ThrottleSync?</h2>
<p><strong>ThrottleSync</strong> is a ride tracking and social leaderboard app for riders who want to track their progress and compare performance with others.</p>
<p>At its core, the app supports:</p>
<ul>
<li><p>GPS-based ride tracking</p>
</li>
<li><p>Distance, average speed, and top speed calculation</p>
</li>
<li><p>User profiles and ride statistics</p>
</li>
<li><p>Social leaderboard</p>
</li>
<li><p>Friends and shared-ride logic</p>
</li>
<li><p>Admin panel for monitoring users, rides, and data quality</p>
</li>
</ul>
<p>The goal was to keep the mobile experience simple while making the backend reliable enough to handle real-world ride data.</p>
<hr />
<h2>The Architecture</h2>
<p>ThrottleSync uses a practical full-stack setup:</p>
<ul>
<li><p><strong>React Native</strong> for the mobile app</p>
</li>
<li><p><strong>Node.js / Express</strong> for the backend API</p>
</li>
<li><p><strong>MongoDB</strong> for users, rides, leaderboard data, and ride credits</p>
</li>
<li><p><strong>Custom admin panel</strong> for monitoring live database activity</p>
</li>
</ul>
<p>The mobile app records ride data and sends it to the backend.</p>
<p>The backend processes ride credits, updates user statistics, and serves leaderboard data.</p>
<p>The admin panel connects to MongoDB and gives visibility into what is happening inside the system.</p>
<p>One important lesson I learned early:</p>
<blockquote>
<p>If real users are generating real data, you need visibility. Without admin tools, debugging becomes slow and uncertain.</p>
</blockquote>
<hr />
<h2>The Hardest Part: GPS Is Not Always Trustworthy</h2>
<p>One of the biggest issues I faced was around <strong>top speed</strong>.</p>
<p>In theory, calculating top speed sounds simple.</p>
<p>You receive GPS points, calculate speed between them, and store the maximum value.</p>
<p>In reality, GPS data can be unreliable.</p>
<p>At one point, a user's top speed went up to around <strong>260 km/h</strong>, even though they were only walking.</p>
<p>That immediately exposed a serious problem:</p>
<blockquote>
<p>Raw GPS speed should not be trusted blindly.</p>
</blockquote>
<p>A GPS spike can happen because of:</p>
<ul>
<li><p>Poor GPS accuracy</p>
</li>
<li><p>Sudden location jumps</p>
</li>
<li><p>Weak signal areas</p>
</li>
<li><p>Device sensor issues</p>
</li>
<li><p>Background location inconsistencies</p>
</li>
<li><p>Bad points returned by the operating system</p>
</li>
</ul>
<p>If the app simply accepts the highest speed value from the device, user stats and leaderboard rankings can become meaningless.</p>
<p>So I made the top speed logic stricter.</p>
<p>Instead of trusting one sudden high-speed reading, the app now checks more context around the movement.</p>
<p>It considers things like:</p>
<ul>
<li><p>GPS accuracy</p>
</li>
<li><p>Segment distance</p>
</li>
<li><p>Sudden location jumps</p>
</li>
<li><p>Whether high speed is supported by nearby movement data</p>
</li>
<li><p>Whether the reading is realistic for the ride pattern</p>
</li>
</ul>
<p>The principle became simple:</p>
<blockquote>
<p>A top speed should come from reliable movement, not from a single bad GPS point.</p>
</blockquote>
<p>This change made ride statistics more realistic and reduced the chance of impossible speeds appearing in user profiles.</p>
<hr />
<h2>Building a Fair Leaderboard</h2>
<p>Another important part of ThrottleSync is the leaderboard.</p>
<p>Initially, leaderboard data could become confusing because different parts of the system were not always showing the same updated values.</p>
<p>For example, after updating user names in MongoDB, the changes appeared in the overview and users pages of the admin panel, but not immediately in the socials page.</p>
<p>That made the admin panel feel inconsistent.</p>
<p>The issue came down to stale data.</p>
<p>Some leaderboard views were relying on older snapshot-style data instead of reading the latest user records directly.</p>
<p>For an admin panel, that is not acceptable.</p>
<p>If the admin panel has direct access to the database, refreshing the page should show the actual data currently present in MongoDB.</p>
<p>So I updated the leaderboard flow to use live user data.</p>
<p>Now, when the admin panel or mobile app requests leaderboard data, it reads the current values from the database instead of relying on stale snapshots.</p>
<p>I also added no-cache behavior so refreshes actually reload fresh data.</p>
<p>This was another useful lesson:</p>
<blockquote>
<p>Caching improves performance, but correctness matters more when users and admins expect live data.</p>
</blockquote>
<hr />
<h2>Showing Only the Most Relevant Leaderboard Data</h2>
<p>A full leaderboard can become overwhelming, especially on mobile.</p>
<p>So I changed the mobile leaderboard to show only the <strong>top 5 users</strong> by default.</p>
<p>But there was one more user experience detail to solve.</p>
<p>If the current user is not in the top 5, they should still know where they stand.</p>
<p>So the leaderboard now works like this:</p>
<ul>
<li><p>Show the top 5 users</p>
</li>
<li><p>If the current user is outside the top 5, show their rank separately below</p>
</li>
<li><p>Keep the screen clean while still making the ranking personally useful</p>
</li>
</ul>
<p>This makes the leaderboard easier to scan and more meaningful for every user.</p>
<p>The goal was not just to show data.</p>
<p>The goal was to show the right amount of data.</p>
<hr />
<h2>Why the Admin Panel Became Important</h2>
<p>The admin panel started as a simple way to inspect MongoDB data.</p>
<p>Over time, it became one of the most important parts of the project.</p>
<p>It helps monitor:</p>
<ul>
<li><p>Total users</p>
</li>
<li><p>Active rides</p>
</li>
<li><p>Completed rides</p>
</li>
<li><p>Ride records</p>
</li>
<li><p>Leaderboard data</p>
</li>
<li><p>Social connections</p>
</li>
<li><p>User activity status</p>
</li>
<li><p>Data quality issues</p>
</li>
<li><p>Suspicious ride statistics</p>
</li>
</ul>
<p>This became especially useful while debugging problems like impossible top speeds and stale leaderboard values.</p>
<p>I also improved the admin panel so that it does not show cached data after refresh.</p>
<p>For the Social page, I added a clear indication that the leaderboard is coming from live user data and when it was loaded.</p>
<p>That small detail makes debugging much easier.</p>
<p>Admin tools are often treated as secondary, but for this kind of project, they are part of the product's reliability.</p>
<blockquote>
<p>If you cannot inspect your data clearly, you cannot support your users properly.</p>
</blockquote>
<hr />
<h2>User Status: Online, Offline, or Riding</h2>
<p>Another small but important feature was user status.</p>
<p>At first, a simple <strong>ON/OFF</strong> status seemed enough.</p>
<p>But that can be misleading.</p>
<p>What does "ON" actually mean?</p>
<p>Is the user currently riding? Did they open the app recently? Are they inactive but still shown as online?</p>
<p>So I made the status logic more specific.</p>
<p>The admin panel now treats status based on clearer signals:</p>
<ul>
<li><p><strong>Riding</strong> if the user has an active ride</p>
</li>
<li><p><strong>Online</strong> if the user was seen recently</p>
</li>
<li><p><strong>Offline or stale</strong> if there has been no recent activity</p>
</li>
</ul>
<p>This makes the admin panel more useful because it reflects user activity more accurately.</p>
<hr />
<h2>Lessons Learned</h2>
<p>Building ThrottleSync taught me several important lessons.</p>
<h3>1. Never blindly trust GPS data</h3>
<p>GPS data can be noisy, delayed, or completely wrong.</p>
<p>If GPS values affect user stats or leaderboard rankings, they need validation.</p>
<h3>2. Leaderboards need fairness</h3>
<p>A leaderboard should not reward bad data, stale data, or impossible values.</p>
<p>For a social app, fairness directly affects user trust.</p>
<h3>3. Admin panels are part of the product</h3>
<p>A good admin panel helps you understand the system, debug faster, and make better decisions.</p>
<p>It is not just an internal dashboard.</p>
<h3>4. Cache carefully</h3>
<p>Caching can improve performance, but it can also create confusion.</p>
<p>For admin views and live leaderboard data, freshness often matters more than speed.</p>
<h3>5. Small UI decisions matter</h3>
<p>Showing only the top 5 users while still showing the current user's own rank made the leaderboard cleaner and more useful.</p>
<p>Simple changes can improve the experience a lot.</p>
<hr />
<h2>What Is Next</h2>
<p>There are several features I would like to add next:</p>
<ul>
<li><p>Weekly and monthly leaderboards</p>
</li>
<li><p>Ride verification scores</p>
</li>
<li><p>Badges and milestones</p>
</li>
<li><p>Friend activity feed</p>
</li>
<li><p>Better suspicious-speed detection</p>
</li>
<li><p>More detailed ride insights</p>
</li>
<li><p>Improved data quality tools in the admin panel</p>
</li>
</ul>
<p>ThrottleSync started as a ride tracking app, but it became a much deeper project involving GPS reliability, social ranking, backend consistency, and admin observability.</p>
<p>The biggest takeaway for me is this:</p>
<blockquote>
<p>Real-world apps are not just about building features. They are about handling messy data, edge cases, and user trust.</p>
</blockquote>
<p>And for a ride tracking app, trust starts with the data.</p>
<hr />
]]></content:encoded></item><item><title><![CDATA[Seamless Transactions: A Comprehensive Guide to PhonePe Payment Gateway API Integration with Node.js]]></title><description><![CDATA[In the ever-evolving landscape of online transactions, seamless payment experiences are crucial for the success of any web application. Integrating a reliable payment gateway is a fundamental step towards achieving this, and PhonePe, with its robust ...]]></description><link>https://arijitsblog.hashnode.dev/seamless-transactions-a-comprehensive-guide-to-phonepe-payment-gateway-api-integration-with-nodejs</link><guid isPermaLink="true">https://arijitsblog.hashnode.dev/seamless-transactions-a-comprehensive-guide-to-phonepe-payment-gateway-api-integration-with-nodejs</guid><category><![CDATA[Payment Gateway integrations on website]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[PhonePe]]></category><category><![CDATA[website]]></category><category><![CDATA[payment gateway]]></category><dc:creator><![CDATA[Arijit Das]]></dc:creator><pubDate>Fri, 24 Nov 2023 16:13:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1700892986751/eda0dd8e-8fa5-4daa-8c76-fd45f1e9d615.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the ever-evolving landscape of online transactions, seamless payment experiences are crucial for the success of any web application. Integrating a reliable payment gateway is a fundamental step towards achieving this, and PhonePe, with its robust API, offers an excellent solution. In this guide, we'll walk you through the process of integrating the PhonePe Payment Gateway API with Node.js, enabling you to enhance your web application's payment capabilities.</p>
<p>Firstly, create a PhonePe merchant account and store the MerchantId, key Index and salt key. We will use this during making requests on PhonePe APIs.</p>
<p>Proceeding by the assumption that we have already created an account (<mark>PhonePe merchant account</mark>), we need to first understand the working of the PhonePe APIs and a brief overview of it.</p>
<p>Let's create our express server to begin the work with.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>)
<span class="hljs-keyword">const</span> app = express()
<span class="hljs-keyword">const</span> cors = <span class="hljs-built_in">require</span>(<span class="hljs-string">'cors'</span>)
<span class="hljs-keyword">const</span> dotenv = <span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>)
dotenv.config({ <span class="hljs-attr">path</span>: <span class="hljs-string">"config.env"</span> })

app.use(cors());
app.use(express.json())

<span class="hljs-keyword">const</span> PORT = <span class="hljs-number">5000</span>
app.listen(PORT, <span class="hljs-function">() =&gt;</span>
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server started in development mode on port <span class="hljs-subst">${PORT}</span>`</span>)
)
</code></pre>
<p>Here, we have created a server using Express and hosted it on <code>port</code> 5000.</p>
<p>In the <code>config.env</code> (.env file), specify the <code>SALT KEY</code> and the <code>MERCHANT ID</code>. We will use it in our further PhonePe API functions without revealing the actual value every time in the code.</p>
<pre><code class="lang-javascript">SALT_KEY = <span class="hljs-string">"********-****-****-****-************"</span>
MERCHANT_ID = <span class="hljs-string">"*******************"</span>
</code></pre>
<p>For a successful payment approval, we will use PhonePe's two APIs; one for submitting the payment request and the next API for checking the Payment status with the merchant transaction ID value.</p>
<p>The <strong><em>newPayment</em></strong> function:</p>
<ul>
<li><p>The fields we will need to request the payment initiation API are already stated in the JSON variable <code>data</code> in the function.</p>
</li>
<li><p>Next, we have to encode the API link to <mark>base64</mark> and add the <code>key index</code> value with the <mark>hashed sha256</mark> value to create the checksum value for the <code>X-Verify</code> field.</p>
</li>
<li><p>Make the API request to PhonePe and retrieve the response from it. From the response, we will get the redirection link to the <strong><em>Payment Gateway page</em></strong> from where we can initiate our payment.</p>
</li>
<li><p>API to call: <code>https://api.phonepe.com/apis/hermes/pg/v1/pay</code></p>
</li>
<li><p>A sample of the response is provided below. Upon successful initiation, we will redirect to the URL specified in the <code>data.instrumentResponse.redirectInfo.url.</code></p>
</li>
<li><pre><code class="lang-javascript">  {
    <span class="hljs-string">"success"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-string">"code"</span>: <span class="hljs-string">"PAYMENT_INITIATED"</span>,
    <span class="hljs-string">"message"</span>: <span class="hljs-string">"Payment Iniiated"</span>,
    <span class="hljs-string">"data"</span>: {
      <span class="hljs-string">"merchantId"</span>: <span class="hljs-string">"*********"</span>,
      <span class="hljs-string">"merchantTransactionId"</span>: <span class="hljs-string">"M**********"</span>,
      <span class="hljs-string">"instrumentResponse"</span>: {
        <span class="hljs-string">"type"</span>: <span class="hljs-string">"PAY_PAGE"</span>,
        <span class="hljs-string">"redirectInfo"</span>: {
          <span class="hljs-string">"url"</span>: <span class="hljs-string">"https://mercury-uat.phonepe.com/transact?token=*********"</span>,
          <span class="hljs-string">"method"</span>: <span class="hljs-string">"GET"</span>
        }
      }
    }
  }
</code></pre>
</li>
</ul>
<p>The <strong><em>checkStatus</em></strong> function:</p>
<ul>
<li><p>This API is used for checking the status of an existing transaction.</p>
</li>
<li><p>The payment status can be <code>Success</code>, <code>Failed</code> or <code>Pending</code>. When <code>Pending</code>, merchants should retry until the status changes to <code>Success</code> or <code>Failed</code>.</p>
</li>
<li><p>API to call: <a target="_blank" href="https://api.phonepe.com/apis/hermes/pg/v1/status/${merchantId}/${merchantTransactionId}"><code>https://api.preprod.phonepe.com/apis/hermes/pg/v1/status/{merchantId}/{merchantTransactionId}</code></a></p>
</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> crypto =  <span class="hljs-built_in">require</span>(<span class="hljs-string">'crypto'</span>);
<span class="hljs-keyword">const</span> axios = <span class="hljs-built_in">require</span>(<span class="hljs-string">'axios'</span>);
<span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config();

<span class="hljs-keyword">const</span> newPayment = <span class="hljs-keyword">async</span> (req, res) =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> merchantTransactionId = <span class="hljs-string">'M'</span> + <span class="hljs-built_in">Date</span>.now();
        <span class="hljs-keyword">const</span> {user_id, price, phone, name} = req.body;
        <span class="hljs-keyword">const</span> data = {
            <span class="hljs-attr">merchantId</span>: process.env.MERCHANT_ID,
            <span class="hljs-attr">merchantTransactionId</span>: merchantTransactionId,
            <span class="hljs-attr">merchantUserId</span>: <span class="hljs-string">'MUID'</span> + user_id,
            <span class="hljs-attr">name</span>: name,
            <span class="hljs-attr">amount</span>: price * <span class="hljs-number">100</span>,
            <span class="hljs-attr">redirectUrl</span>: <span class="hljs-string">`http://localhost:3001/api/v1/status/<span class="hljs-subst">${merchantTransactionId}</span>`</span>,
            <span class="hljs-attr">redirectMode</span>: <span class="hljs-string">'POST'</span>,
            <span class="hljs-attr">mobileNumber</span>: phone,
            <span class="hljs-attr">paymentInstrument</span>: {
                <span class="hljs-attr">type</span>: <span class="hljs-string">'PAY_PAGE'</span>
            }
        };
        <span class="hljs-keyword">const</span> payload = <span class="hljs-built_in">JSON</span>.stringify(data);
        <span class="hljs-keyword">const</span> payloadMain = Buffer.from(payload).toString(<span class="hljs-string">'base64'</span>);
        <span class="hljs-keyword">const</span> keyIndex = <span class="hljs-number">2</span>;
        <span class="hljs-keyword">const</span> string = payloadMain + <span class="hljs-string">'/pg/v1/pay'</span> + process.env.SALT_KEY;
        <span class="hljs-keyword">const</span> sha256 = crypto.createHash(<span class="hljs-string">'sha256'</span>).update(string).digest(<span class="hljs-string">'hex'</span>);
        <span class="hljs-keyword">const</span> checksum = sha256 + <span class="hljs-string">'###'</span> + keyIndex;

        <span class="hljs-keyword">const</span> prod_URL = <span class="hljs-string">"https://api.phonepe.com/apis/hermes/pg/v1/pay"</span>
        <span class="hljs-keyword">const</span> options = {
            <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
            <span class="hljs-attr">url</span>: prod_URL,
            <span class="hljs-attr">headers</span>: {
                <span class="hljs-attr">accept</span>: <span class="hljs-string">'application/json'</span>,
                <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
                <span class="hljs-string">'X-VERIFY'</span>: checksum
            },
            <span class="hljs-attr">data</span>: {
                <span class="hljs-attr">request</span>: payloadMain
            }
        };

        axios.request(options).then(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">response</span>) </span>{
            <span class="hljs-keyword">return</span> res.redirect(response.data.data.instrumentResponse.redirectInfo.url)
        })
        .catch(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">error</span>) </span>{
            <span class="hljs-built_in">console</span>.error(error);
        });

    } <span class="hljs-keyword">catch</span> (error) {
        res.status(<span class="hljs-number">500</span>).send({
            <span class="hljs-attr">message</span>: error.message,
            <span class="hljs-attr">success</span>: <span class="hljs-literal">false</span>
        })
    }
}

<span class="hljs-keyword">const</span> checkStatus = <span class="hljs-keyword">async</span>(req, res) =&gt; {
    <span class="hljs-keyword">const</span> merchantTransactionId = req.params[<span class="hljs-string">'txnId'</span>]
    <span class="hljs-keyword">const</span> merchantId = process.env.MERCHANT_ID
    <span class="hljs-keyword">const</span> keyIndex = <span class="hljs-number">2</span>;
    <span class="hljs-keyword">const</span> string = <span class="hljs-string">`/pg/v1/status/<span class="hljs-subst">${merchantId}</span>/<span class="hljs-subst">${merchantTransactionId}</span>`</span> + process.env.SALT_KEY;
    <span class="hljs-keyword">const</span> sha256 = crypto.createHash(<span class="hljs-string">'sha256'</span>).update(string).digest(<span class="hljs-string">'hex'</span>);
    <span class="hljs-keyword">const</span> checksum = sha256 + <span class="hljs-string">"###"</span> + keyIndex;

    <span class="hljs-keyword">const</span> options = {
    <span class="hljs-attr">method</span>: <span class="hljs-string">'GET'</span>,
    <span class="hljs-attr">url</span>: <span class="hljs-string">`https://api.phonepe.com/apis/hermes/pg/v1/status/<span class="hljs-subst">${merchantId}</span>/<span class="hljs-subst">${merchantTransactionId}</span>`</span>,
    <span class="hljs-attr">headers</span>: {
        <span class="hljs-attr">accept</span>: <span class="hljs-string">'application/json'</span>,
        <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
        <span class="hljs-string">'X-VERIFY'</span>: checksum,
        <span class="hljs-string">'X-MERCHANT-ID'</span>: <span class="hljs-string">`<span class="hljs-subst">${merchantId}</span>`</span>
    }
    };

    <span class="hljs-comment">// CHECK PAYMENT STATUS</span>
    axios.request(options).then(<span class="hljs-keyword">async</span>(response) =&gt; {
        <span class="hljs-keyword">if</span> (response.data.success === <span class="hljs-literal">true</span>) {
            <span class="hljs-built_in">console</span>.log(response.data)
            <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">200</span>).send({<span class="hljs-attr">success</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">message</span>:<span class="hljs-string">"Payment Success"</span>});
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).send({<span class="hljs-attr">success</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">message</span>:<span class="hljs-string">"Payment Failure"</span>});
        }
    })
    .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
        <span class="hljs-built_in">console</span>.error(err);
        res.status(<span class="hljs-number">500</span>).send({<span class="hljs-attr">msg</span>: err.message});
    });
};

<span class="hljs-built_in">module</span>.exports = {
    newPayment,
    checkStatus
}
</code></pre>
<p>In the route file, call the two created functions from these two APIs. The first API calls the new payment function which starts the payment initiation. The second API calls the status check function which verifies the checksum value with the appropriate merchant transaction id which is provided as txnId as a parameter.</p>
<pre><code class="lang-javascript">router.post(<span class="hljs-string">'/payment'</span>, newPayment);
router.post(<span class="hljs-string">'/status/:txnId'</span>, checkStatus);
</code></pre>
]]></content:encoded></item></channel></rss>