Skip to content

feat: add razorpay#15

Merged
deadlyjack merged 21 commits into
mainfrom
razorpay
May 11, 2026
Merged

feat: add razorpay#15
deadlyjack merged 21 commits into
mainfrom
razorpay

Conversation

@bajrangCoder
Copy link
Copy Markdown
Member

@bajrangCoder bajrangCoder commented Jan 2, 2026

ENV to be Updated before Merger.

  • PG_KEY_ID
  • PG_KEY_SECRET
  • PG_WEBHOOK_SECRET

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @bajrangCoder, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the platform's monetization capabilities by integrating Razorpay as a new payment gateway for web-based plugin purchases. It provides a complete end-to-end solution, from a user-friendly "Buy" button on the client-side to robust server-side APIs for secure transaction processing, payment verification, and webhook handling. The underlying database schema has been extended to support user-specific purchase records across different payment providers, ensuring that once a plugin is bought, it's permanently linked to the user's account for seamless access and download.

Highlights

  • Razorpay Integration: Introduced a new payment gateway for web-based plugin purchases.
  • Client-Side Purchase UI: Added a "BuyButton" component and logic to display purchase status (owned, buy, login) on plugin pages.
  • Server-Side Payment APIs: Implemented API endpoints for creating Razorpay orders, verifying payments, checking plugin ownership, and handling webhooks.
  • Database Enhancements: Updated the purchase_order schema to link purchases to user accounts and support multiple payment providers (Google Play, Razorpay).
  • Dynamic Plugin Download: Modified the plugin download API to recognize user-linked purchases from any provider, allowing access to paid plugins.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@bajrangCoder bajrangCoder requested review from a team and deadlyjack January 2, 2026 13:40
gemini-code-assist[bot]

This comment was marked as resolved.

gemini-code-assist[bot]

This comment was marked as resolved.

@bajrangCoder
Copy link
Copy Markdown
Member Author

@greptileai

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jan 11, 2026

Greptile Summary

This PR integrates Razorpay as a payment gateway for purchasing paid plugins and an "Acode Pro" subscription tier. It adds server-side order creation, signature-verified payment confirmation, webhook handling, and a 2-hour refund window for both products.

  • New payment flows: /api/razorpay/* routes cover create-order, verify, verify-pro, refund-pro, refund-plugin, my-purchases, and a webhook handler that serves as a fallback when the client-side verify call fails.
  • Schema changes: Two new migration scripts add acode_pro/pro_purchase_token/pro_purchased_at to the user table and user_id/provider to purchase_order; the purchase_order migration is missing its provider ALTER statement (see inline comment).
  • Client component: A new razorpayCheckout component dynamically loads the Razorpay SDK and handles the checkout flow for both plugins and Pro purchases.

Confidence Score: 3/5

Not safe to merge without fixing the migration script and the previously-flagged refund-window and double-refund bugs.

The purchase_order migration script will silently skip adding the provider column on existing databases, causing SQL errors at runtime on every download and purchase-history request. Combined with the already-reported refund-window bypass (double-Z NaN) and double-refund race in refund-plugin, the core payment and refund paths have multiple defects that need to land together before this ships.

server/migrations/add_purchase_order_columns.js (missing provider column), server/apis/razorpay.js (refund-window and race condition bugs noted in existing threads)

Important Files Changed

Filename Overview
server/apis/razorpay.js New 800-line payment API: handles plugin and Acode Pro purchases, webhook processing, and refunds. Previously-flagged issues (double-Z NaN bug in refund window checks, refund-plugin race condition) remain; see existing review threads.
server/migrations/add_purchase_order_columns.js Migration script is missing the provider column ALTER statement despite its own comment claiming to add it — existing databases will be left without the column, breaking all provider-aware queries.
server/migrations/add_acode_pro_columns.js Adds acode_pro, pro_purchase_token, and pro_purchased_at columns to the user table; handles duplicate-column errors gracefully.
server/entities/purchaseOrder.js Adds user_id and provider columns to the entity definition and table schema; minColumns updated to include provider.
server/apis/plugin.js Download endpoint extended to support user-linked Razorpay purchases alongside token-based Google Play flow; adds ownership filtering; verifyPurchase falls back to true on provider API errors to avoid blocking legitimate downloads.
server/main.js Adds CSRF middleware, raw-body middleware for the webhook path (placed before express.json), and a global error handler for interrupted uploads.
client/components/razorpayCheckout/index.js Client-side Razorpay checkout component for plugin and Pro purchases; dynamically loads the Razorpay SDK script; handles success, cancel, and failed-verification states.

Sequence Diagram

sequenceDiagram
    participant C as Client
    participant S as Server
    participant RZP as Razorpay
    participant DB as Database

    C->>S: "POST /api/razorpay/create-order {pluginId}"
    S->>DB: Check existing PURCHASED order
    S->>RZP: orders.create(amount, notes)
    RZP-->>S: orderId, amount, currency
    S-->>C: orderId + keyId

    C->>RZP: Open Razorpay Checkout UI
    RZP-->>C: payment success (order_id, payment_id, signature)

    C->>S: "POST /api/razorpay/verify {signature, pluginId}"
    S->>S: Verify HMAC signature (timingSafeEqual)
    S->>RZP: "orders.fetch(order_id) status=paid"
    S->>DB: INSERT purchase_order (STATE_PURCHASED)
    S-->>C: "{success: true}"

    Note over RZP,S: Webhook (fallback if verify missed)
    RZP->>S: POST /api/razorpay/webhook (payment.captured)
    S->>S: Verify webhook HMAC signature
    S->>DB: Upsert order STATE_PURCHASED
    S-->>RZP: 200 OK

    C->>S: "POST /api/razorpay/refund-plugin {orderId}"
    S->>S: Check elapsed 2h refund window
    S->>RZP: payments.refund(token)
    S->>DB: Update order STATE_CANCELED
    S-->>C: "{success: true}"
Loading

Reviews (3): Last reviewed commit: "refactor: conditionally enable Razorpay ..." | Re-trigger Greptile

greptile-apps[bot]

This comment was marked as resolved.

bajrangCoder and others added 7 commits January 11, 2026 18:56
- Removed the OAuth provider handling from `oauth.mjs` to streamline authentication flow.
- Added new `appConfig.js` entity to manage application configuration, including Acode Pro pricing.
- Updated `razorpay.js` to handle Acode Pro purchases, including order creation, verification, and refund processes.
- Introduced new migration scripts to add Acode Pro related columns to the user table and update purchase orders.
- Enhanced user entity to include Acode Pro status and purchase details.
- Implemented CSRF protection in the main server file for state-changing requests.
- Removed unused `authenticationProvider.mjs` and `authenticateWithProvider.mjs` files to clean up the codebase.
- Updated earnings calculation to account for Razorpay orders.
- Adjusted order update logic to skip Razorpay verification.
- Cleaned up API routes by removing commented-out code.
@deadlyjack
Copy link
Copy Markdown
Member

@greptile_apps review once more with confidence score

Comment thread server/apis/razorpay.js
Comment on lines +666 to +668
const elapsed = Date.now() - new Date(`${purchasedAt}Z`).getTime();
if (elapsed > REFUND_WINDOW_MS) {
res.status(400).send({ error: 'Refund window has expired. Refunds are only available within 2 hours of purchase.' });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Refund window bypass — double Z makes elapsed NaN

pro_purchased_at is stored via new Date().toISOString(), which already ends in Z (e.g. "2023-01-01T12:00:00.000Z"). Appending another Z produces "...000ZZ", which Date parses as Invalid Date. That makes elapsed = NaN, and NaN > REFUND_WINDOW_MS evaluates to false, so the refund window check never blocks the refund — users can request a Pro refund at any time, days or months after purchase.

The same double-Z pattern on line 473 makes elapsed <= REFUND_WINDOW_MS always false, so the UI never shows the refund button even when the user is within the 2-hour window.

Suggested change
const elapsed = Date.now() - new Date(`${purchasedAt}Z`).getTime();
if (elapsed > REFUND_WINDOW_MS) {
res.status(400).send({ error: 'Refund window has expired. Refunds are only available within 2 hours of purchase.' });
const elapsed = Date.now() - new Date(purchasedAt).getTime();

Comment thread server/apis/razorpay.js
Comment on lines +769 to +782

// Issue full refund via Razorpay
await getRazorpay().payments.refund(order.token, {
speed: 'normal',
});

// Update order state to canceled and zero out amount
await Order.update(
[
[Order.STATE, Order.STATE_CANCELED],
[Order.AMOUNT, 0],
],
[Order.ID, order.id],
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Race condition: order state not locked before calling Razorpay

The Razorpay refund API is called (line 771) before the order state is updated to STATE_CANCELED (line 776). Two concurrent POST /refund-plugin requests for the same order will both pass the Number(order.state) !== STATE_PURCHASED check on line 747, both call getRazorpay().payments.refund(...), and both succeed — issuing a double refund.

The /refund-pro handler on line 674 correctly follows the "reset first, restore on failure" pattern. The same approach should be applied here.

@deadlyjack
Copy link
Copy Markdown
Member

@greptile_apps review once more

Comment on lines +14 to +19

const queries = [
// Add user_id column if it doesn't exist
`ALTER TABLE purchase_order ADD COLUMN user_id INTEGER REFERENCES user(id)`,
];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Migration missing provider column

The migration comment and title both say it adds "user_id and provider columns to purchase_order," but the queries array only includes the ALTER TABLE statement for user_id. The provider column is never created on existing databases. After this migration runs, any query that SELECTs or filters on provider — including the /download/:id endpoint (verifyPurchase), my-purchases, and the minColumns getter — will fail with "table purchase_order has no column named provider."

Add the missing statement to the queries array:

ALTER TABLE purchase_order ADD COLUMN provider TEXT DEFAULT 'google_play'

@deadlyjack deadlyjack merged commit 81657ef into main May 11, 2026
@deadlyjack deadlyjack deleted the razorpay branch May 16, 2026 21:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants