TypeScript উদাহরণ সহ সম্পূর্ণ শেষ বিন্দু উল্লেখ। সমস্ত উদাহরণ ব্যবহার করে handypay থেকে সাহায্যকারী দ্রুত শুরুটুলিং ডাউনলোড করতে পারে OpenAPI 3.1 চুক্তি.
বেস URL
https://api.handypay.me/api/v1API সংস্করণঃ 2025-01-01 (ফিরে এসেছে X-API-Version প্রতিটি প্রতিক্রিয়ায় হেড)।
প্রমাণীকরণ
সমস্ত অনুরোধের জন্য একটি বাহক টোকেন প্রয়োজন Authorization হেডার।
Authorization: Bearer hp_live_your_api_key_here- কীগুলি এর সাথে যুক্ত করা হয়েছে
hp_live_উৎপাদনের জন্য। - কীগুলি এর সাথে যুক্ত করা হয়েছে
hp_test_sandbox/testing-এর জন্য। - থেকে কী তৈরি করুন এবং পরিচালনা করুন মার্চেন্ট পোর্টাল.
টেস্ট মোড সম্পূর্ণরূপে বিচ্ছিন্ন
প্রত্যেকটি hp_test_ অনুরোধ একটি নিবেদিত পরীক্ষার অ্যাকাউন্ট ব্যবহার করে। পণ্য, গ্রাহক, অর্থ প্রদান, সদস্যপদ এবং ওয়েবহুক এন্ডপয়েন্টগুলি কখনই সরাসরি ব্যবসায়িক কার্যকলাপে প্রদর্শিত হয় না। টেস্ট মোড কর্মক্ষেত্র.
curl https://api.handypay.me/api/v1/test-payments \
-H "Authorization: Bearer hp_test_your_api_key_here"হারের সীমা
API কী-তে প্রতি ঘন্টায় 1,000 অনুরোধ। সীমা অতিক্রম করে ফেরত 429 সঙ্গে একটি Retry-After হেডার।
প্রতিক্রিয়া বিন্যাস
প্রতিটি প্রতিক্রিয়া একটি সাধারণ খামের মধ্যে মোড়ানো থাকে।
সাফল্য
{
"success": true,
"data": { ... },
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}ত্রুটি
{
"success": false,
"error": {
"code": "validation_error",
"message": "Name is required"
},
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}পৃষ্ঠাংকন
সমস্ত তালিকা শেষ বিন্দুগুলি কার্সার-ভিত্তিক পৃষ্ঠাটি ব্যবহার করে।
| মাঠ | টাইপ করুন | প্রয়োজন। | বর্ণনা |
|---|---|---|---|
| limit | number | না। | প্রতি পৃষ্ঠায় আইটেম (1-100, ডিফল্ট 10) |
| starting_after | string | না। | পূর্ববর্তী পৃষ্ঠার শেষ আইটেমের ID |
প্রতিক্রিয়া অন্তর্ভুক্ত has_more: true যখন অতিরিক্ত পৃষ্ঠা থাকে।
পণ্য
এককালীন ক্রয়ের জন্য পণ্য তৈরি করুন এবং পরিচালনা করুন।
| পদ্ধতি | পথ। | বর্ণনা |
|---|---|---|
| POST | /v1/products | একটি পণ্য তৈরি করুন |
| GET | /v1/products | পণ্যের তালিকা করুন |
| GET | /v1/products/:id | একটি পণ্য পান |
| PUT | /v1/products/:id | একটি পণ্য আপডেট করুন |
| DELETE | /v1/products/:id | একটি পণ্য সংরক্ষণ করুন |
একটি পণ্য তৈরি করুন
const product = await handypay("/products", {
method: "POST",
body: JSON.stringify({
name: "Premium Plan",
description: "Access to all features",
price: {
amount: 2999,
currency: "usd",
},
}),
});
console.log(product.id); // "prod_abc123"cURL উদাহরণ
curl -X POST https://api.handypay.me/api/v1/products \
-H "Authorization: Bearer hp_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Premium Plan",
"description": "Access to all features",
"price": {
"amount": 2999,
"currency": "usd"
}
}'অনুরোধ সংস্থা
| মাঠ | টাইপ করুন | প্রয়োজন। | বর্ণনা |
|---|---|---|---|
| name | string | হ্যাঁ। | পণ্যের নাম |
| description | string | না। | পণ্যের বিবরণ |
| images | string[] | না। | 8 পর্যন্ত ছবি URLs |
| metadata | object | না। | কাস্টম কী-ভ্যালু জোড়া (50 পর্যন্ত) |
| active | boolean | না। | ডিফল্ট সত্য |
| url | string | না। | আপনার সাইটে পণ্য পৃষ্ঠা URL |
| shippable | boolean | না। | পণ্যের পাঠানোর প্রয়োজন আছে কি না |
| unit_label | string | না। | প্রতি-ইউনিট লেবেল (যেমন "আসন", "লাইসেন্স") |
| statement_descriptor | string | না। | ব্যাঙ্ক স্টেটমেন্ট টেক্সট (সর্বোচ্চ 22 অক্ষর) |
| tax_code | string | না। | Stripe কর কোড |
| price.amount | number | না। | সর্বনিম্ন মুদ্রা একক (সেন্ট)-এ মূল্য |
| price.currency | string | না। | ISO 4217 মুদ্রা কোড (যেমন "ইউ. এস. ডি", "জে. এম. ডি") |
| price.tax_behavior | string | না। | অন্তর্ভুক্তিমূলক, একচেটিয়া বা অনির্দিষ্ট |
গ্রাহকরা
পুনরাবৃত্তি ক্রয় এবং সাবস্ক্রিপশনের জন্য গ্রাহকের রেকর্ড পরিচালনা করুন।
| পদ্ধতি | পথ। | বর্ণনা |
|---|---|---|
| POST | /v1/customers | একজন গ্রাহক তৈরি করুন |
| GET | /v1/customers | গ্রাহকদের তালিকা করুন |
| GET | /v1/customers/:id | একজন গ্রাহক পান। |
| PUT | /v1/customers/:id | একজন গ্রাহককে আপডেট করুন |
| DELETE | /v1/customers/:id | একজন গ্রাহককে মুছে ফেলুন |
একজন গ্রাহক তৈরি করুন
const customer = await handypay("/customers", {
method: "POST",
body: JSON.stringify({
email: "customer@example.com",
name: "Jane Doe",
}),
});
console.log(customer.id); // "cus_abc123"cURL উদাহরণ
curl -X POST https://api.handypay.me/api/v1/customers \
-H "Authorization: Bearer hp_live_..." \
-H "Content-Type: application/json" \
-d '{
"email": "customer@example.com",
"name": "Jane Doe"
}'অনুরোধ সংস্থা
| মাঠ | টাইপ করুন | প্রয়োজন। | বর্ণনা |
|---|---|---|---|
| string | হ্যাঁ। | গ্রাহকের ইমেল | |
| name | string | না। | গ্রাহকের নাম |
| phone | string | না। | গ্রাহকের ফোন নম্বর |
| metadata | object | না। | কাস্টম কী-মান জোড়া |
পেমেন্ট সেশন
এককালীন অর্থপ্রদানের জন্য হোস্ট করা চেকআউট সেশন তৈরি করুন। স্ট্যান্ডার্ড HandyPay মূল্য প্রযোজ্যঃ 4.9% + US $0.40 বিনামূল্যে পরিকল্পনায় প্রতি লেনদেন, অথবা 4.2% + US $0.40 প্রো-তে। উপরে কোনও অতিরিক্ত API বা প্ল্যাটফর্ম ফি নেই।
| পদ্ধতি | পথ। | বর্ণনা |
|---|---|---|
| POST | /v1/payment-sessions | একটি পেমেন্ট সেশন তৈরি করুন |
| GET | /v1/payment-sessions/:id | অধিবেশনের অবস্থা পান |
| GET | /v1/test-payments | পরীক্ষার অর্থপ্রদানের তালিকা করুন (শুধুমাত্র hp_test_) |
একটি পেমেন্ট সেশন তৈরি করুন (বিদ্যমান মূল্য সহ)
const session = await handypay("/payment-sessions", {
method: "POST",
body: JSON.stringify({
line_items: [{ price_id: "price_abc123", quantity: 1 }],
success_url: "https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url: "https://yoursite.com/cancel",
}),
});
// Redirect customer to checkout
window.location.href = session.url;cURL উদাহরণ
curl -X POST https://api.handypay.me/api/v1/payment-sessions \
-H "Authorization: Bearer hp_live_..." \
-H "Content-Type: application/json" \
-d '{
"line_items": [{ "price_id": "price_abc123", "quantity": 1 }],
"success_url": "https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}",
"cancel_url": "https://yoursite.com/cancel"
}'একটি পেমেন্ট সেশন তৈরি করুন (কাস্টম পরিমাণ)
const session = await handypay("/payment-sessions", {
method: "POST",
body: JSON.stringify({
line_items: [{
amount: 5000,
currency: "usd",
name: "Custom Order",
quantity: 1,
}],
success_url: "https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url: "https://yoursite.com/cancel",
}),
});cURL উদাহরণ
curl -X POST https://api.handypay.me/api/v1/payment-sessions \
-H "Authorization: Bearer hp_live_..." \
-H "Content-Type: application/json" \
-d '{
"line_items": [{
"amount": 5000,
"currency": "usd",
"name": "Custom Order",
"quantity": 1
}],
"success_url": "https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}",
"cancel_url": "https://yoursite.com/cancel"
}'অনুরোধ সংস্থা
| মাঠ | টাইপ করুন | প্রয়োজন। | বর্ণনা |
|---|---|---|---|
| line_items | array | হ্যাঁ। | অন্তত একটি লাইনের জিনিস |
| success_url | string | হ্যাঁ। | সফল অর্থপ্রদানের পর URL পুনর্নির্দেশ করুন |
| cancel_url | string | হ্যাঁ। | গ্রাহক বাতিল করলে URL পুনর্নির্দেশ করুন |
| customer_id | string | না। | বিদ্যমান গ্রাহক ID |
| customer_email | string | না। | ইমেল আগে থেকে পূরণ করুন (যদি না customer_id হয়) |
| pass_fees_to_customer | boolean | না। | গ্রাহকের মোট প্রক্রিয়াকরণ এবং পরিষেবা ফি যোগ করুন |
| metadata | object | না। | কাস্টম কী-মান জোড়া |
| collect_shipping_address | boolean | না। | একটি পাঠানোর ঠিকানা সংগ্রহ করুন |
| billing_address_collection | string | না। | স্বয়ংক্রিয় বা প্রয়োজনীয় |
| shipping_countries | string[] | না। | অনুমোদিত ISO-2 গন্তব্য দেশ |
| shipping_options | array | না। | ক্ষুদ্রতম মুদ্রা ইউনিটে লেবেল এবং পরিমাণ প্রেরণ করা হয় |
লাইন আইটেম ক্ষেত্র
| মাঠ | টাইপ করুন | প্রয়োজন। | বর্ণনা |
|---|---|---|---|
| price_id | string | না। | বিদ্যমান Stripe মূল্য ID |
| amount | number | না। | কাস্টম পরিমাণ সেন্ট |
| currency | string | না। | পরিমাণের সঙ্গে প্রয়োজনীয় |
| name | string | না। | পরিমাণের সঙ্গে প্রয়োজনীয় |
| quantity | number | হ্যাঁ। | পরিমাণ |
দুটোই দিন। price_id অথবা amount+currency+name প্রতি লাইন আইটেম।
{CHECKOUT_SESSION_ID} URL-এর সাফল্যে। জিজ্ঞাসা করুন যে ID একই বণিক এবং একই live/test কী মোডের সাথে যা এটি তৈরি করেছে। অধিবেশনটি শেষ হওয়ার পরেও প্রশ্নবিদ্ধ থাকে, তবে আপনার স্বাক্ষরিত ওয়েবহুকটি এখনও চালান পরিপূর্ণতা চালাতে হবে।অন্তর্নির্মিত অর্থপ্রদান
আপনার নিজের চেকআউট পেজে Stripe উপাদানগুলি রেন্ডার করতে চাইলে আপনার সার্ভার থেকে একটি PaymentIntent তৈরি করুন। আপনার HandyPay API কী সার্ভারের পাশে থাকে; শুধুমাত্র ফেরত দেওয়া প্রকাশযোগ্য কী, সংযুক্ত অ্যাকাউন্ট ID এবং স্বল্পস্থায়ী ক্লায়েন্ট গোপন ব্রাউজারে পাঠান।
| পদ্ধতি | পথ। | বর্ণনা |
|---|---|---|
| POST | /v1/payment-intents | একটি এম্বেডেড PaymentIntent তৈরি করুন |
const intent = await handypay("/payment-intents", {
method: "POST",
body: JSON.stringify({
amount: 5000,
currency: "ttd",
description: "Order #1042",
customer_email: "buyer@example.com",
pass_fees_to_customer: true,
metadata: { order_id: "1042" },
}),
});
// Pass these values to Stripe.js/Elements. Never pass HANDYPAY_API_KEY.
return {
clientSecret: intent.client_secret,
publishableKey: intent.publishable_key,
stripeAccount: intent.stripe_account,
};| মাঠ | টাইপ করুন | প্রয়োজন। | বর্ণনা |
|---|---|---|---|
| amount | number | হ্যাঁ। | ক্ষুদ্রতম মুদ্রা একক-এ ধনাত্মক পূর্ণসংখ্যা |
| currency | string | হ্যাঁ। | ISO 4217 তিন অক্ষরের মুদ্রা কোড |
| description | string | না। | অর্থপ্রদানের বিবরণ |
| customer_email | string | না। | রসিদ এবং পুনর্মিলনের জন্য গ্রাহকের ইমেল |
| pass_fees_to_customer | boolean | না। | অর্থের পরিমাণ বাড়িয়ে দিন যাতে গ্রাহক অর্থমূল্য বহন করতে পারেন। |
| metadata | object | না। | আপনার অর্ডার বা চালান শনাক্তকারী |
টাকা ফেরত
প্রমিত বণিকের মালিকানাধীন অর্থ ফেরত দিন। HandyPay অর্থপ্রদানের মালিকানা এবং উপলব্ধ ব্যালেন্স যাচাই করে। বাদ দিন amount পূর্ণ অর্থ ফেরতের জন্য।
| পদ্ধতি | পথ। | বর্ণনা |
|---|---|---|
| POST | /v1/refunds | একটি পূর্ণ বা আংশিক ফেরত তৈরি করুন |
const refund = await handypay("/refunds", {
method: "POST",
body: JSON.stringify({
session_id: "cs_live_...",
amount: 2500,
reason: "requested_by_customer",
}),
});
console.log(refund.id, refund.status);| মাঠ | টাইপ করুন | প্রয়োজন। | বর্ণনা |
|---|---|---|---|
| session_id | string | শর্তসাপেক্ষ। | চেকআউট সেশন ID (সি. এস...)। এটি বা payment_intent ব্যবহার করুন। |
| payment_intent | string | শর্তসাপেক্ষ। | PaymentIntent ID (পাই...)। এটি বা session_id ব্যবহার করুন। |
| amount | number | না। | ক্ষুদ্রতম মুদ্রা ইউনিটে আংশিক অর্থ ফেরতের পরিমাণ |
| reason | string | না। | নকল, প্রতারণামূলক, বা requested_by_customer |
একটি বিতর্কিত, সম্পূর্ণরূপে ফেরত, আন্তঃ-বাণিজ্য বা অপর্যাপ্ত-ভারসাম্য প্রদান ফেরত তৈরি না করেই প্রত্যাখ্যান করা হয়।
বিবাদ।
সংযুক্ত বণিকের জন্য চার্জব্যাক পর্যালোচনা করুন এবং নির্ধারিত তারিখের আগে প্রমাণ প্রদান করুন।
| পদ্ধতি | পথ। | বর্ণনা |
|---|---|---|
| GET | /v1/disputes | বিবাদের তালিকা তৈরি করুন |
| GET | /v1/disputes/:id | একটি বিতর্ক এবং তার প্রমাণের অবস্থা পান |
| POST | /v1/disputes/:id/evidence | প্রমাণ আপডেট করুন বা জমা দিন |
const dispute = await handypay("/disputes/dp_123/evidence", {
method: "POST",
body: JSON.stringify({
evidence: {
customer_email_address: "buyer@example.com",
product_description: "Annual software subscription",
customer_communication: "https://files.example.com/evidence/1042.pdf",
},
submit: false,
}),
});| মাঠ | টাইপ করুন | প্রয়োজন। | বর্ণনা |
|---|---|---|---|
| evidence | object | না। | Stripe স্ট্রিং মান হিসাবে বিরোধ প্রমাণ ক্ষেত্র |
| submit | boolean | না। | প্রমাণ সম্পূর্ণ হলে এবং পর্যালোচনার জন্য প্রস্তুত থাকলেই সত্য নির্ধারণ করুন। |
সাবস্ক্রিপশন
পুনরাবৃত্ত পণ্য তৈরি করুন এবং সাবস্ক্রিপশন পরিচালনা করুন।
সাবস্ক্রিপশন পণ্য
| পদ্ধতি | পথ। | বর্ণনা |
|---|---|---|
| POST | /v1/subscription-products | একটি সাবস্ক্রিপশন পণ্য তৈরি করুন |
| GET | /v1/subscription-products | সাবস্ক্রিপশন পণ্যের তালিকা করুন |
সাবস্ক্রিপশন সেশন ও ব্যবস্থাপনা
| পদ্ধতি | পথ। | বর্ণনা |
|---|---|---|
| POST | /v1/subscription-sessions | সাবস্ক্রিপশন চেকআউট তৈরি করুন |
| GET | /v1/subscriptions | সক্রিয় সদস্যপদগুলির তালিকা করুন |
| PATCH | /v1/subscriptions/:id/quantity | স্পষ্টভাবে আসন পরিবর্তন করুন |
| POST | /v1/subscriptions/:id/cancel | বিলিং সময়কাল শেষে বাতিল করুন |
সমর্থিত বিলিং ব্যবধান
একটি সাবস্ক্রিপশন পণ্য তৈরি করুন
const subProduct = await handypay("/subscription-products", {
method: "POST",
body: JSON.stringify({
name: "Pro Plan",
description: "Monthly pro access",
amount: 1999,
currency: "usd",
interval: "monthly",
trial_period_days: 14,
}),
});
console.log(subProduct.price.id); // Use this price_id for subscription sessionscURL উদাহরণ
curl -X POST https://api.handypay.me/api/v1/subscription-products \
-H "Authorization: Bearer hp_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Pro Plan",
"description": "Monthly pro access",
"amount": 1999,
"currency": "usd",
"interval": "monthly",
"trial_period_days": 14
}'অনুরোধ সংস্থা
| মাঠ | টাইপ করুন | প্রয়োজন। | বর্ণনা |
|---|---|---|---|
| name | string | হ্যাঁ। | পণ্যের নাম |
| description | string | না। | পণ্যের বিবরণ |
| amount | number | হ্যাঁ। | সর্বনিম্ন মুদ্রা ইউনিটে মূল্য |
| currency | string | হ্যাঁ। | ISO 4217 মুদ্রা কোড |
| interval | string | হ্যাঁ। | সমর্থিত ব্যবধানগুলির মধ্যে একটি |
| trial_period_days | number | না। | কয়েক দিনের মধ্যে বিনামূল্যে ট্রায়াল |
| metadata | object | না। | কাস্টম কী-মান জোড়া |
একাধিক আসন সহ চেকআউট তৈরি করুন
const session = await handypay("/subscription-sessions", {
method: "POST",
body: JSON.stringify({
price_id: subProduct.price.id,
quantity: 5,
customer_email: "buyer@example.com",
success_url: "https://example.com/success",
cancel_url: "https://example.com/plans",
}),
});সক্রিয় সদস্যপদের আসন পরিবর্তন করুন
পরিমাণ অবশ্যই 1 থেকে 1,000 পর্যন্ত একটি পূর্ণসংখ্যা হতে হবে। বিলিং সমন্বয়টি একটি অন্তর্নিহিত ডিফল্টের উপর নির্ভর করার পরিবর্তে কীভাবে পরিচালনা করা হয় তা বেছে নিন।
const updated = await handypay("/subscriptions/sub_123/quantity", {
method: "PATCH",
body: JSON.stringify({
quantity: 8,
proration_behavior: "create_prorations",
}),
});| মাঠ | টাইপ করুন | প্রয়োজন। | বর্ণনা |
|---|---|---|---|
| quantity | number | হ্যাঁ। | নতুন আসন গণনা 1 থেকে 1,000 পর্যন্ত |
| proration_behavior | string | না। | create_prorations, always_invoice, অথবা কিছুই নয় |
| item_id | string | না। | নির্দিষ্ট সাবস্ক্রিপশন আইটেম যখন একটি সাবস্ক্রিপশনে একাধিক পণ্য থাকে |
Webhooks
আপনার শেষ পয়েন্টগুলিতে HTTP POST-এর মাধ্যমে রিয়েল-টাইম ইভেন্ট বিজ্ঞপ্তিগুলি পান।
| পদ্ধতি | পথ। | বর্ণনা |
|---|---|---|
| POST | /v1/webhook-endpoints | একটি শেষ বিন্দু নিবন্ধিত করুন |
| GET | /v1/webhook-endpoints | শেষ পয়েন্টের তালিকা করুন |
| DELETE | /v1/webhook-endpoints/:id | একটি শেষ বিন্দু নিষ্ক্রিয় করুন |
- শেষ পয়েন্টগুলিকে অবশ্যই HTTPS ব্যবহার করতে হবে।
- Webhook শেষ পয়েন্টগুলি একটি নিবন্ধিত
hp_test_কী শুধুমাত্র পরীক্ষার ইভেন্টগুলি গ্রহণ করে। লাইভ এবং পরীক্ষার গন্তব্যগুলি আলাদাভাবে সংরক্ষণ করা হয়। - একটি ইভেন্টে সাবস্ক্রাইব করা প্রতিটি সক্রিয় এন্ডপয়েন্ট সেই এন্ডপয়েন্টের নিজস্ব গোপনীয়তার সাথে স্বাক্ষরিত একটি স্বাধীন ডেলিভারি পায়। একটি এন্ডপয়েন্টের গোপন বিষয়টিকে অন্যটির জন্য পুনরায় ব্যবহার করবেন না।
- 10 সেকেন্ডের মধ্যে 2xx প্রতিক্রিয়া প্রদান করুন। প্রতিটি ইভেন্ট সংরক্ষণ করুন।
idপার্শ্বপ্রতিক্রিয়ার আগে তাই ডুপ্লিকেট ডেলিভারি নিরাপদ। - 10 পরপর ডেলিভারি ব্যর্থতার পর, একটি শেষ বিন্দু স্বয়ংক্রিয়ভাবে নিষ্ক্রিয় হয়ে যায়।
সমর্থিত অনুষ্ঠানের প্রকার
- payment_intent.succeeded
- payment_intent.payment_failed
- checkout.session.completed
- checkout.session.expired
- checkout.session.async_payment_succeeded
- checkout.session.async_payment_failed
- customer.subscription.created
- customer.subscription.updated
- customer.subscription.deleted
- charge.refunded
- charge.dispute.created
- charge.dispute.closed
payment_intent.payment_failed ঘটনা PaymentIntent কে চিহ্নিত করে data.idআপনার মেটাডেটা ব্যবহার করুন (উদাহরণস্বরূপ) order_id) এর মধ্যে সমন্বয় করতে। checkout.session.expired বিলম্বিত অর্থপ্রদান পদ্ধতির জন্য অধিবেশন মেয়াদ শেষ এবং অ্যাসিঙ্ক ইভেন্টগুলির জন্য।ওয়েবহুকে স্বাক্ষর যাচাই করা হচ্ছে
প্রতিটি ডেলিভারিতে একটি X-HandyPay-Signature ফরম্যাটে হেডার sha256={hex}আপনার শেষ বিন্দুর স্বাক্ষরের গোপন তথ্য ব্যবহার করে র অনুরোধ সংস্থার HMAC-SHA256 গণনা করে যাচাই করুনঃ
import { createHmac, timingSafeEqual } from "node:crypto";
function verifySignature(
payload: string | Buffer,
secret: string,
signature: string
): boolean {
const prefix = "sha256=";
if (!signature.startsWith(prefix)) return false;
const hex = signature.slice(prefix.length);
if (!/^[0-9a-f]{64}$/i.test(hex)) return false;
const expected = createHmac("sha256", secret).update(payload).digest();
const received = Buffer.from(hex, "hex");
return received.length === expected.length && timingSafeEqual(received, expected);
}Next.js API রুট হ্যান্ডলার
import { NextRequest, NextResponse } from "next/server";
import { createHmac, timingSafeEqual } from "node:crypto";
export const runtime = "nodejs";
const SECRET = process.env.HANDYPAY_WEBHOOK_SECRET;
if (!SECRET) throw new Error("HANDYPAY_WEBHOOK_SECRET is not configured");
function hasValidSignature(payload: string, signature: string): boolean {
const prefix = "sha256=";
if (!signature.startsWith(prefix)) return false;
const hex = signature.slice(prefix.length);
if (!/^[0-9a-f]{64}$/i.test(hex)) return false;
const expected = createHmac("sha256", SECRET).update(payload).digest();
const received = Buffer.from(hex, "hex");
return received.length === expected.length && timingSafeEqual(received, expected);
}
export async function POST(req: NextRequest) {
const body = await req.text();
const signature = req.headers.get("x-handypay-signature") ?? "";
if (!hasValidSignature(body, signature)) {
return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
}
let event: { id: string; type: string; data: unknown };
try {
event = JSON.parse(body);
} catch {
return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
}
// Persist event.id before side effects so redeliveries can be ignored safely.
switch (event.type) {
case "checkout.session.completed":
// Fulfill an immediate payment.
break;
case "checkout.session.async_payment_succeeded":
// Fulfill a delayed payment.
break;
case "charge.refunded":
// Mark the matching order as refunded.
break;
}
return NextResponse.json({ received: true });
}Express.js হ্যান্ডলার
// Register this route BEFORE app.use(express.json()).
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";
const router = express.Router();
const SECRET = process.env.HANDYPAY_WEBHOOK_SECRET;
if (!SECRET) throw new Error("HANDYPAY_WEBHOOK_SECRET is not configured");
function hasValidSignature(payload: Buffer, signature: string): boolean {
const prefix = "sha256=";
if (!signature.startsWith(prefix)) return false;
const hex = signature.slice(prefix.length);
if (!/^[0-9a-f]{64}$/i.test(hex)) return false;
const expected = createHmac("sha256", SECRET).update(payload).digest();
const received = Buffer.from(hex, "hex");
return received.length === expected.length && timingSafeEqual(received, expected);
}
router.post(
"/handypay",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = String(req.headers["x-handypay-signature"] ?? "");
if (!hasValidSignature(req.body, signature)) {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(req.body.toString("utf8"));
// Persist event.id before side effects so redeliveries are idempotent.
console.log("Verified HandyPay event", event.id, event.type);
return res.json({ received: true });
}
);
export default router;Webhook পেলোড বিন্যাস
{
"id": "evt_abc123",
"type": "payment_intent.succeeded",
"created": 1706745600,
"data": { ... }
}অ্যাকাউন্ট
সংযুক্ত বণিকের চার্জ এবং পেআউট প্রস্তুতির পাশাপাশি ডিফল্ট পেআউট ব্যাঙ্ক অ্যাকাউন্ট পড়ুন। ব্যাঙ্ক এবং রাউটিং নম্বরগুলি মুখোশযুক্ত; শুধুমাত্র তাদের শেষ চারটি অঙ্ক ফেরত দেওয়া হয়।
| পদ্ধতি | পথ। | বর্ণনা |
|---|---|---|
| GET | /v1/account | সংযুক্ত অ্যাকাউন্ট এবং ছদ্মবেশী অর্থপ্রদানের বিবরণ পান |
const account = await handypay("/account");
console.log({
chargesEnabled: account.chargesEnabled,
payoutsEnabled: account.payoutsEnabled,
bank: account.bankAccount?.bankName,
last4: account.bankAccount?.last4,
});ত্রুটি কোড
| কোড | HTTP | বর্ণনা |
|---|---|---|
| unauthorized | 401 | API কী অনুপস্থিত বা অবৈধ |
| key_revoked | 401 | API কী বাতিল করা হয়েছে। |
| key_expired | 401 | API কী-এর মেয়াদ শেষ হয়ে গেছে। |
| rate_limit_exceeded | 429 | অনেক অনুরোধ |
| validation_error | 400 | অনুরোধের মূল বৈধতা ব্যর্থ হয়েছে |
| invalid_url | 400 | অবৈধ success_url বা cancel_url |
| invalid_interval | 400 | অসমর্থিত বিলিং ব্যবধান |
| product_not_found | 404 | পণ্যের অস্তিত্ব নেই |
| customer_not_found | 404 | গ্রাহক নেই |
| session_not_found | 404 | চেকআউট অধিবেশন বিদ্যমান নেই |
| payment_not_found | 404 | এই বণিকের অর্থের অভাব রয়েছে বা তার মালিকানা নেই। |
| subscription_not_found | 404 | সাবস্ক্রিপশন বিদ্যমান নেই |
| refund_not_allowed | 400 | অর্থ প্রদান বিতর্কিত এবং তা ফেরত দেওয়া যাবে না। |
| already_refunded | 400 | ইতিমধ্যেই সম্পূর্ণ টাকা ফেরত দেওয়া হয়েছে। |
| refund_amount_too_large | 400 | অবশিষ্ট ফেরতযোগ্য ব্যালেন্সের চেয়ে বেশি |
| insufficient_balance | 400 | উপলব্ধ ব্যালেন্স অর্থ ফেরত দিতে পারে না। |
| balance_verification_failed | 400 | ভারসাম্য মালিকানা বা প্রাপ্যতা যাচাই করা যায়নি |
| endpoint_not_found | 404 | অজানা API শেষ বিন্দু |
| stripe_error | 502 | Stripe API একটি ত্রুটি প্রদান করেছে |
| internal_error | 500 | সার্ভারে অপ্রত্যাশিত ত্রুটি |
| payload_too_large | 413 | অনুরোধের পরিমাণ 1 এম. বি অতিক্রম করেছে |
| prohibited_content | 400 | বিষয়বস্তু গ্রহণযোগ্য ব্যবহার নীতি লঙ্ঘন করে |
| webhook_url_must_be_https | 400 | Webhook URL-কে অবশ্যই HTTPS ব্যবহার করতে হবে। |
| key_creation_rate_exceeded | 429 | টাইম উইন্ডোতে অনেক বেশি কী তৈরি করা হয়েছে |
| max_keys_reached | 400 | সর্বাধিক সক্রিয় API কী পৌঁছয় (25) |
নিরাপত্তা
- রাখুন।
hp_live_এবংhp_test_আপনার সার্ভারে কীগুলি রাখুন। এগুলি কখনই ব্রাউজার JavaScript, মোবাইল অ্যাপ, URLs, লগ বা সোর্স কন্ট্রোলে রাখবেন না। - সমস্ত প্রতিক্রিয়ার মধ্যে রয়েছে
X-Content-Type-Options: nosniff,X-Frame-Options: DENY, এবংStrict-Transport-Securityহেডার। - অনুরোধের মূল সীমাঃ 1 MiB, স্ট্রিম বা চ্যাঙ্কড অনুরোধ সহ।
- Webhook শেষ বিন্দুগুলিকে অবশ্যই HTTPS ব্যবহার করতে হবে।
- সঠিক কাঁচা বাইটের বিপরীতে ওয়েবহুক স্বাক্ষরগুলি যাচাই করুন এবং স্থির সময়ে পরিপাকের তুলনা করুন।
- পণ্যের নাম এবং বিবরণ একটি নিষিদ্ধ বিষয়বস্তু ব্লকলিস্টের বিরুদ্ধে প্রদর্শিত হয়।
- 5 + বিষয়বস্তু লঙ্ঘন 24 ঘন্টার মধ্যে আপনার API কী স্থগিত করে দেবে।
API কী-এর সীমা
- প্রতি 10 মিনিটের উইন্ডোতে সর্বোচ্চ 3 কী তৈরি করা হয়েছে।
- প্রতি 1-ঘন্টা উইন্ডোতে সর্বোচ্চ 10 কী তৈরি করা হয়েছে।
- প্রতি বণিক সর্বোচ্চ 25 সক্রিয় কী।