Skip to main content
POST
/
api
/
subscription
/
verify
Verify Payment and Activate Subscription
curl --request POST \
  --url https://api.example.com/api/subscription/verify \
  --header 'Content-Type: application/json' \
  --data '
{
  "razorpay_payment_id": "<string>",
  "razorpay_order_id": "<string>",
  "razorpay_signature": "<string>"
}
'
{
  "success": true,
  "userSubscribe": {},
  "projectId": "<string>",
  "message": "<string>"
}
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

razorpay_payment_id
string
required
The payment ID returned by Razorpay after successful payment
razorpay_order_id
string
required
The order ID created in the init endpoint
razorpay_signature
string
required
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

success
boolean
Indicates successful payment verification
userSubscribe
object
User’s subscription status (if available)
projectId
string
The ID of the newly created project
message
string
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.
  1. Signature Verification: Uses HMAC-SHA256 with your Razorpay secret key
  2. Duplicate Prevention: Checks if order is already processed
  3. User Authentication: Requires valid JWT token
  4. Order Ownership: Verifies order belongs to authenticated user
  5. 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:
  1. Missing payment details (400): Returns error if any required field is missing
  2. Invalid user (400): User must be authenticated
  3. Order not found (400): Order must exist in PendingOrders
  4. Already processed (400): Prevents duplicate subscription activation
  5. Signature mismatch (400): Payment verification failed
  6. Server errors (500): Database or Razorpay API errors

Best Practices

  1. Idempotency: The endpoint checks for already-processed orders
  2. Atomic Operations: Uses database transactions where possible
  3. Background Jobs: Email notifications are queued, not blocking
  4. Error Logging: Errors are logged to console for debugging
  5. 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.