API v1

ডেভেলপার ডকুমেন্টেশন

একটা POST দিয়ে পেমেন্ট বানান, কাস্টমারকে চেকআউটে পাঠান, ওয়েবহুকে ফলাফল নিন। সিক্রেট কী কখনো ব্রাউজার বা মোবাইল অ্যাপে রাখবেন না।

অথেন্টিকেশন

ড্যাশবোর্ড → ডেভেলপার থেকে কী নিন। প্রতিটা রিকোয়েস্টে Authorization: Bearer <secret key> হেডার দিন। বেস URL:

https://oxypay.lovable.app/api/public/v1

টেস্ট মোড

oxp_test_sk_ দিয়ে শুরু হওয়া কী দিয়ে বানানো পেমেন্টে আসল টাকা লাগে না। চেকআউট পেজে "সফল" বা "ব্যর্থ" বাটন থাকে, চাপলেই পেমেন্টের অবস্থা বদলায় আর আপনার টেস্ট ওয়েবহুকে খবর যায়। উত্তরে livemode: false থাকে। টেস্ট আর লাইভ পেমেন্ট কখনো মেশে না।

পেমেন্ট তৈরি

একই order_id আবার পাঠালে আগের পেমেন্টটাই ফেরত আসে (200)।

curl -X POST https://oxypay.lovable.app/api/public/v1/payments \
  -H "Authorization: Bearer oxp_test_sk_..." \
  -H "Idempotency-Key: order-1042-try1" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 2450,
    "order_id": "1042",
    "description": "Order #1042",
    "return_url": "https://shop.example.bd/orders/1042"
  }'

# 201 Created
{
  "payment_id": "6f1c…",
  "order_id": "1042",
  "amount": 2450,
  "currency": "BDT",
  "status": "pending",
  "verified": false,
  "livemode": false,
  "checkout_url": "https://oxypay.lovable.app/pay/ab12cd34ef",
  "expires_at": "…"
}

Idempotency-Key

নেটওয়ার্ক কেটে গেলে নিশ্চিন্তে আবার পাঠাতে Idempotency-Key হেডার দিন। ২৪ ঘণ্টার মধ্যে একই কী আর একই বডি একই পেমেন্ট ফেরত দেয় (Idempotent-Replayed: true হেডার সহ)। একই কী অন্য বডিতে দিলে 409 idempotency_conflict।

রিকোয়েস্ট সাইন করা

প্রতিটা কী-র সাথে একটা সাইনিং সিক্রেট পাবেন। সাইন করা ঐচ্ছিক; ড্যাশবোর্ডে "শুধু সাইন করা" চালু করলে সাইন ছাড়া রিকোয়েস্ট 401 signature_required পাবে। টাইমস্ট্যাম্প ৫ মিনিটের বেশি আগে-পরে হলে বাতিল।

OxyPay-Signature: v1=hex(HMAC-SHA256(signing_secret, "{ts}.{METHOD}.{path}.{rawBody}"))
// Node.js — sign a request (optional, or required if you turn on "Signed only")
import crypto from "node:crypto";

const ts = Math.floor(Date.now() / 1000);
const body = JSON.stringify({ amount: 2450, order_id: "1042" });
const toSign = `${ts}.POST./api/public/v1/payments.${body}`;   // GET: `${ts}.GET./api/public/v1/payments/1042.`
const sig = crypto.createHmac("sha256", process.env.OXYPAY_SIGNING_SECRET).update(toSign).digest("hex");

await fetch("https://oxypay.lovable.app/api/public/v1/payments", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.OXYPAY_SECRET_KEY}`,
    "Content-Type": "application/json",
    "OxyPay-Timestamp": String(ts),
    "OxyPay-Signature": "v1=" + sig,
  },
  body,   // send exactly the bytes you signed
});

ওয়েবহুক

ড্যাশবোর্ড → ওয়েবহুক-এ https ঠিকানা দিন। পেমেন্টের অবস্থা বদলালেই আমরা POST পাঠাই। ১০ সেকেন্ডের মধ্যে 2xx না পেলে আবার পাঠাই (5m, 15m, 30m, 1h, 3h, 6h, 12h পরে, মোট 8 বার)। একই id দুবার এলে একবারই কাজ করুন।

payment.under_reviewpayment.succeededpayment.failedpayment.refundedpayment.partially_refundedpayment.cancelledpayment.expiredwebhook.test

হেডার: OxyPay-Event-Id, OxyPay-Event-Type, OxyPay-Timestamp, OxyPay-Signature: v1=hex(HMAC-SHA256(webhook_secret, "{ts}.{rawBody}"))

// Node.js (Express) — verify an OxyPay webhook
import crypto from "node:crypto";

app.post("/oxypay/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const ts = req.get("OxyPay-Timestamp");
  const expected = "v1=" + crypto
    .createHmac("sha256", process.env.OXYPAY_WEBHOOK_SECRET)
    .update(`${ts}.${req.body.toString()}`)
    .digest("hex");
  const given = req.get("OxyPay-Signature") ?? "";
  const ok = given.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected)) &&
    Math.abs(Date.now() / 1000 - Number(ts)) < 300;
  if (!ok) return res.sendStatus(400);

  const event = JSON.parse(req.body);          // { id, type, livemode, data: { order_id, status, verified, ... } }
  if (alreadyProcessed(event.id)) return res.sendStatus(200);
  if (event.type === "payment.succeeded" && event.data.verified) confirmOrder(event.data.order_id);
  res.sendStatus(200);
});

পেমেন্টের অবস্থা

শুধু status "success" আর verified true হলে অর্ডার কনফার্ম করুন। নিচের পথের বাইরে অবস্থা বদলানো যায় না — ডেটাবেস নিজেই আটকায়।

created→pendingverifyingunder_reviewfailedexpiredcancelled
pending→verifyingunder_reviewsuccessfailedexpiredcancelled
verifying→under_reviewsuccessfailed
under_review→verifyingsuccessfailed
success→refundedpartially_refunded
partially_refunded→refunded
failed→শেষ অবস্থা
expired→শেষ অবস্থা
cancelled→শেষ অবস্থা
refunded→শেষ অবস্থা

সীমা আর এরর

প্রতি কী-তে মিনিটে ১২০টা রিকোয়েস্ট। উত্তরে X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset হেডার থাকে; সীমা পেরোলে 429 আর Retry-After।

401invalid_api_key
401signature_required
401invalid_signature
403merchant_suspended
404not_found
409idempotency_conflict
422validation_error
429rate_limited

এন্ডপয়েন্ট

POST
/paymentsপেমেন্ট তৈরি (চেকআউট লিংক পাবেন)
GET
/payments/{payment_id | order_id}পেমেন্টের অবস্থা
GET
/openapi.jsonOpenAPI স্পেক
POST
/device/pairফোন অ্যাপ যুক্ত করা (QR কোড)
POST
/device/eventsফোন থেকে টাকা আসার SMS