Skip to content
SDK preview. Packages are not publicly published yet — use REST / cURL, or the local development artifacts.

I only have an app

You have an Android, iOS, React Native or Flutter app and no server? You still must not put a PayTaka secret key in it — you need one tiny endpoint.

Text
Your app  ──▶  your small backend  ──(PayTaka secret key)──▶  PayTaka
   ▲                  │
   └──── checkoutUrl ─┘

The rule that keeps you safe: the server decides the price#

The app sends an order id. The server loads that order and decides the amount itself. Never let the app send the amount to forward to PayTaka — a tampered app could change ৳1,250 to ৳1.

Pick the stack you already know. Each example creates the payment for an order and returns only the checkout_url.

JavaScript
// The tiny trusted server a mobile app needs (Android, iOS, React Native, Flutter).
// The app calls THIS; only this server holds the PayTaka secret key.
//
// The app sends an ORDER ID. The server looks up the order and decides the amount itself —
// never trust an amount sent by the app (it could be changed to ৳1).
import { createServer } from "node:http";
import { PayTaka } from "@paytaka/sdk";

const paytaka = new PayTaka({
  secretKey: process.env.PAYTAKA_SECRET_KEY,
  baseUrl: process.env.PAYTAKA_API_URL, // @preview-only — not needed once the SDK ships with the production URL built in
});

// Stand-in for YOUR database of orders.
const orders = new Map([["order-1001", { total: "1250.00", status: "AWAITING_PAYMENT", paymentId: null }]]);

const send = (res, status, body) => res.writeHead(status, { "Content-Type": "application/json" }).end(JSON.stringify(body));

createServer(async (req, res) => {
  try {
    const pay = req.method === "POST" && req.url?.match(/^\/orders\/([\w-]+)\/pay$/);
    if (pay) {
      const order = orders.get(pay[1]);
      if (!order) return send(res, 404, { error: "order_not_found" });
      if (order.status === "PAID") return send(res, 409, { error: "already_paid" });

      const payment = await paytaka.payments.create(
        {
          amount: order.total, // decided by the server, from the order
          externalId: pay[1],
          returnUrl: "myapp://payment-return", // your app's link
        },
        { idempotencyKey: `pay-${pay[1]}` }, // the same order never creates two payments
      );
      order.paymentId = payment.id;
      return send(res, 200, { checkout_url: payment.checkoutUrl });
    }

    const status = req.method === "GET" && req.url?.match(/^\/orders\/([\w-]+)\/status$/);
    if (status) {
      const order = orders.get(status[1]);
      if (!order) return send(res, 404, { error: "order_not_found" });
      // The authoritative answer: ask PayTaka (a verified payment.paid webhook would update this too).
      if (order.paymentId) {
        const payment = await paytaka.payments.retrieve(order.paymentId);
        if (payment.status === "PAID") order.status = "PAID";
      }
      return send(res, 200, { status: order.status });
    }
    send(res, 404, { error: "not_found" });
  } catch (error) {
    console.error(error);
    send(res, 500, { error: "server_error" });
  }
}).listen(Number(process.env.PORT ?? 3000));

The Node.js example also shows the second endpoint every app needs: GET /orders/:id/status, which answers from your records after asking PayTaka. Deploy any of these where you like — a small server, a serverless function, or a Next.js route.

What the app does#

  1. Sends the order id to your backend and receives checkout_url.
  2. Opens it: PayTakaCheckout.open(...) on Android or PayTakaCheckout.present(...) on iOS; with the system browser on React Native and Flutter.
  3. Comes back through your return link, then asks your backend for the order's status.

Keep the order id in your app's saved state, so you can ask for the status even if the customer never returns through the link.