This endpoint verifies the Razorpay payment signature using HMAC-SHA256, creates a project, and activates the user’s subscription with automated expiry notifications.
Authentication
Requires JWT authentication via the verifyJWT middleware.
Request Body
The payment ID returned by Razorpay after successful payment
The signature generated by Razorpay to verify payment authenticity
Signature Verification
The endpoint verifies payment authenticity using HMAC-SHA256:
const generated_signature = crypto
.createHmac("sha256", process.env.KEY_SECRET)
.update(razorpay_order_id + "|" + razorpay_payment_id)
.digest("hex");
if (generated_signature === razorpay_signature) {
// Payment verified
}
Implementation details in verify.controller.js:20.
The KEY_SECRET is your Razorpay API secret. Never expose this on the client side. Signature verification must always happen on the server.
Subscription Duration
The subscription end date is calculated based on the environment:
Production Environment
- Start Date: Current timestamp
- End Date: Current timestamp + (months × 30 days)
- Expiry Warning: 5 days before end date (months × 25 days)
Development/Test Environment
- Start Date: Current timestamp
- End Date: Current timestamp + 60 minutes
- Expiry Warning: 2 minutes before end
const subscriptionEnd = process.env.NODE_ENV === "production"
? new Date(now.getTime() + months * 30 * 24 * 60 * 60 * 1000)
: new Date(now.getTime() + 60 * 60 * 1000);
See implementation in verify.controller.js:54.
Response
Indicates successful payment verification
User’s subscription status (if available)
The ID of the newly created project
Success message: "Payment verified successfully!"
Activation Flow
On successful verification, the endpoint performs the following operations:
1. Verify Pending Order
- Fetches
PendingOrder matching user ID and order ID
- Validates order exists and status is not already
"completed"
- Prevents duplicate processing
2. Create Completed Order
Creates a CompletedOrder record with:
userid: User ID
orderid: Razorpay order ID
months: Subscription duration
amount: Payment amount in paise
plan: Subscription plan (“pro”)
status: "completed"
projectid: Reference to created project
3. Create Project
Creates a new Project with:
paymentId: Reference to CompletedOrder
plan: Subscription plan (“pro”)
startDate: Current timestamp
endDate: Calculated expiry date
owner: Authenticated user ID
4. Schedule Background Jobs
Three Bull queue jobs are scheduled:
Expiry Warning Notification
await isBeforeExpiryNotify.add(
"deployhub-isBeforeExpiryQueue",
{
userId: req.user._id,
email: req.user.email,
fullname: req.user.fullName
},
{
delay: isBeforeExpiryDate - Date.now(),
jobId: req.user._id.toString(),
removeOnComplete: true
}
);
Subscription Start Notification
await subscriptionStart.add("deployhub-subscriptionstart", {
fullName: req.user.fullName,
email: req.user.email,
price: orderfromdb.amount / 100,
duration: orderfromdb.months,
plan: orderfromdb.plan
});
Subscription Expiry Handler
await subscriptionExpire.add(
"deployhub-subscriptionend",
{ userId: req.user._id },
{
jobId: req.user._id.toString(),
delay: subscriptionend - Date.now()
}
);
See verify.controller.js:87.
5. Update Order Status
Marks PendingOrder status as "completed" to prevent reprocessing.
Request Example
curl -X POST https://api.deployhub.com/api/subscription/verify \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"razorpay_payment_id": "pay_KjQ5xU0vFJmJ4r",
"razorpay_order_id": "order_rcptid_0.8347562",
"razorpay_signature": "9b5e67890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}'
Response Examples
Success Response
{
"success": true,
"userSubscribe": {
"plan": "pro",
"status": "active"
},
"projectId": "507f1f77bcf86cd799439011",
"message": "Payment verified successfully!"
}
Missing Parameters
{
"message": "required details fro verify payment"
}
User Not Found
{
"success": false,
"message": "User not found!"
}
Order Not Found
{
"message": "something went wrong! PLease Contact your team"
}
Already Processed
{
"message": "Your Oder ALready PRocessed"
}
Invalid Signature
{
"success": false,
"message": "Payment verification failed!"
}
Server Error
{
"error": "Error verifying payment"
}
Security Considerations
Always verify the signature on the server side. Never trust payment verification from the client.
- Signature Verification: Uses HMAC-SHA256 with your Razorpay secret key
- Duplicate Prevention: Checks if order is already processed
- User Authentication: Requires valid JWT token
- Order Ownership: Verifies order belongs to authenticated user
- Expiry Protection: Pending orders auto-delete after 2 hours
Database Schema
CompletedOrder
{
userid: ObjectId, // Reference to User
orderid: String, // Razorpay order ID
months: Number, // Subscription duration
amount: Number, // Payment amount in paise
plan: String, // "pro"
status: String, // "completed"
projectid: ObjectId, // Reference to Project
createdAt: Date,
updatedAt: Date
}
Project Updates
{
paymentId: ObjectId, // Reference to CompletedOrder
plan: "pro", // Subscription plan
startDate: Date, // Subscription start
endDate: Date, // Subscription expiry
owner: ObjectId, // User ID
status: "pending" // Initial status
}
Integration Example
// Frontend Razorpay integration
const options = {
key: "rzp_test_...",
amount: order.amount,
currency: "INR",
name: "DeployHub",
description: "Pro Plan Subscription",
order_id: order.id,
handler: async function (response) {
// Send to verify endpoint
const result = await fetch('/api/subscription/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
razorpay_payment_id: response.razorpay_payment_id,
razorpay_order_id: response.razorpay_order_id,
razorpay_signature: response.razorpay_signature
})
});
const data = await result.json();
if (data.success) {
console.log('Project ID:', data.projectId);
// Redirect to project dashboard
}
}
};
const rzp = new Razorpay(options);
rzp.open();
Error Handling
The endpoint handles several error scenarios:
- Missing payment details (400): Returns error if any required field is missing
- Invalid user (400): User must be authenticated
- Order not found (400): Order must exist in PendingOrders
- Already processed (400): Prevents duplicate subscription activation
- Signature mismatch (400): Payment verification failed
- Server errors (500): Database or Razorpay API errors
Best Practices
- Idempotency: The endpoint checks for already-processed orders
- Atomic Operations: Uses database transactions where possible
- Background Jobs: Email notifications are queued, not blocking
- Error Logging: Errors are logged to console for debugging
- Webhook Integration: Consider adding Razorpay webhooks for redundancy
In production, implement webhook verification as a backup to handle edge cases where the user closes the browser before the verify call completes.