• Модуль интеграций
  • Сообщество
  • Блог
Документация
Плагины и интеграцииВсе расширения для Medusa от сообществаСтартерыЗапускайте проекты быстрее с готовыми решениями
ЭкспертыПодберите специалиста для разработки и развития вашего проекта на MedusaКейсыПосмотрите примеры Medusa в продакшене и успешные внедрения
Т-Касса от Т-Банка для Medusa: приём платежей для РФ

16 октября 2026 г. · Продукт

Gorgo снижает затраты на адаптацию Medusa к локальным рынкам.

Мы разрабатываем плагины интеграции, осуществляем поддержку и развиваем сообщество разработчиков на Medusa в Telegram.

  • Ресурсы Medusa
  • Плагины и интеграции
  • Модуль интеграций
  • Стартеры
  • Эксперты
  • Кейсы
  • Medusa Чат в Telegram
  • Medusa Новости в Telegram
  • Документация Gorgo
  • Связаться с нами
  • TelegramGitHub
Плагины
Z

Zoho

Zoho Payments payment provider for Medusa v2 — India-first, with embedded Checkout Widget, hosted checkout, signed webhooks, refunds, and a browser checkout helper.

npm install medusa-payment-zoho
Категория
Платежи
Создано
Jayashakthi_vishnu
Версия
1.0.2
Последнее обновление
5 дней назад
Ежемесячные загрузки
Загрузка данных
Звезды на Github
0
npmNPMGitHubGithub

medusa-payment-zoho

Zoho Payments provider for Medusa v2 — India-first, with embedded checkout, hosted checkout, signed webhooks, and refunds.

connects a Medusa v2 store to Zoho Payments in India (, the default) or the United States (). It creates Zoho payment sessions, opens the Zoho Checkout Widget or Hosted Checkout, verifies payments server-side, accepts signed webhooks, and supports full and partial refunds.

This is an independent community integration. It is not affiliated with, endorsed by, or maintained by Zoho Corporation or Medusa.

Features

  • Embedded Zoho Checkout Widget for card, UPI, and other methods enabled on your Zoho account.
  • Zoho Hosted Checkout for strict-CSP, no-JS, or redirect-based storefronts.
  • Signed webhooks over the exact raw body, with account and live/test-mode checks.
  • Correct money handling — ISO-4217 fraction digits, decimal major-unit strings, arithmetic, no binary floating point.
  • Full and partial refunds against captured payments, with a local replay guard.
  • Automatic capture, stated honestly — successful Zoho payments are already captured; verifies instead of pretending to capture.
  • Safe authorization polling for widget results that arrive before Zoho's session status is final.
  • Framework-agnostic browser helper with a typed, discriminated result and no Node.js or Medusa imports in the client bundle.
  • Typed errors for configuration, authentication, API, validation, state, and webhook-verification failures.
  • 100% test coverage enforced for lines, branches, functions, and statements.

Requirements

RequirementDetails
Medusa; developed and verified against 2.22.0
Node.js (this package); Node is the effective floor when running Medusa 2.22
Zoho PaymentsAn active India or US merchant account
Zoho OAuthAn ORG client with the scopes below, created in the same data centre as the account
Zoho widget (widget mode)A publishable API key from Zoho Settings → Developer Space
WebhooksA publicly reachable HTTPS URL for your Medusa backend
Database / RedisWhatever your Medusa deployment already uses; this package adds no storage or cache dependency

Required Zoho OAuth scopes:

Webhook-management scopes are not required: Zoho webhooks are registered manually in the dashboard.

Installation

Install the package in your Medusa application:

npm i medusa-payment-zoho

Release status: is published on npm. Install the latest release with the command above. The repository also contains the full test suite, source, and release verification so you can audit the exact tarball you install.

Using pnpm or yarn works too; the package ships ESM and CommonJS builds with TypeScript declarations.

Zoho setup

  1. Create or confirm your Zoho Payments account in the correct data centre: India () or US (). The account, OAuth client, and configured region must all match.

  2. Create an OAuth client in the Zoho API console:

    • client type: ORG;
    • redirect URI: for example (match the value used by the helper script);
    • scopes: the four scopes listed above.
  3. Generate a refresh token with the repository helper: . Configure the account ID, client ID, client secret, region, and redirect URI first, then run:

    The helper performs the one-time authorization-code exchange and prints a long-lived refresh token. It uses Node.js built-ins only and reads credentials from the environment. Treat the output as a secret: do not paste it into issues, logs, or commits.

  4. Collect the credentials from Zoho:

    • Account ID — Payments account identifier, sent on every API call.
    • API key — Developer Space browser key, required only for widget mode. This key is publishable by design; it is not the OAuth client secret or signing key.
    • Signing key — Developer Space signing key used for webhooks and checkout signatures. Server-only.
  5. Store every secret in environment variables on the Medusa server. See for the variable names.

Only a Zoho Account Owner or Admin can generate an org-level refresh token. A user can hold at most 20 refresh tokens; generating more removes the oldest.

Backend configuration

Register the provider under the Payment Module in . The default region is India and the default checkout mode is the embedded widget:

The provider identifier is and the configured id is , so Medusa stores the provider as . Keep that ID stable: it appears in payment sessions, the Admin, and the webhook URL. Use the subpath exactly; the package root is not a module-provider default.

For hosted checkout, set and provide both URLs:

All options, validation rules, and defaults are documented in Configuration.

Webhooks

Register this URL in Zoho Payments under Settings → Developer Space → Webhooks:

https://<your-medusa-backend>/hooks/payment/zoho_zoho

Enable at least:

The provider verifies over the exact raw body and parses the event from those same verified bytes. It requires , checks when configured, and validates the payment amount/currency. A missing fails closed.

is emitted per failed attempt. The provider reconciles it against the Zoho session when Zoho supplies a session ID, so a later successful attempt in the same session still completes the order. Medusa 2.22's stock webhook subscriber ignores , , and actions; a terminal failure remains available for reconciliation rather than silently becoming paid. Refund events are acknowledged but map to ; reconcile refunds with or your own job.

Medusa's payment webhook route returns HTTP 200 before the provider finishes handling the event. A rejected signature therefore does not trigger a Zoho retry: monitor and reconcile. See Webhooks for the full setup, retry behaviour, and a signed test.

Enable the provider in Medusa Admin

  1. Start the Medusa backend after adding the module.
  2. Open Settings → Regions and edit the region that uses the matching currency.
  3. Under Payment Providers, enable Zoho ().
  4. Save the region and refresh the storefront.

If Zoho does not appear, confirm the module resolves, the backend restarted without configuration errors, and the region is saved. See Troubleshooting.

Storefront quickstart: embedded widget

The checkout flow is:

  1. Create or refresh the Medusa payment session for Zoho.
  2. Re-read the cart and pass the session's public to .
  3. Let the customer pay in the Zoho widget.
  4. Call . Medusa asks the provider to verify the payment with Zoho; client-reported success is never trusted.

The widget result is a hint; is the authoritative step. If Zoho is still processing an asynchronous method, the order may be created with the payment pending until a signed webhook or status poll confirms capture.

The provider stores only publishable checkout data in the Medusa payment session: , , , , , , , , , and (widget mode) or (hosted mode). OAuth secrets and the signing key are never included.

Full guide, session-expiry handling, CSP, and server-side widget signature verification: Storefront widget.

Storefront quickstart: hosted checkout

Set and configure and . Then redirect the browser:

On the return URL, verify Zoho's hosted-checkout signature on your server, then call . A redirect is not proof of settlement: ACH can return , and the webhook is the backstop if the customer closes the browser. Success/failure route examples: Storefront hosted checkout.

Configuration options

OptionDefaultRequiredDescription
—YesZoho Payments account ID.
—YesOAuth ORG client ID.
—YesOAuth client secret. Server-only.
—YesLong-lived offline refresh token. Server-only.
No (India) or . Account and OAuth client must match.
No or (India only).
No or .
—Widget modePublishable Zoho widget API key. Safe to expose to the browser.
—Strongly recommendedDeveloper Space signing key for webhooks, widget, and hosted-return signatures.
—Hosted modeHosted checkout success URL. HTTPS in production.
—Hosted modeHosted checkout failure URL. HTTPS in production.
NoWhen set, reject webhooks whose differs.
NoCustomer-facing description, max 500 characters.
NoDisplayed in the embedded widget header when set.
NoPass-through allow-list, for example .
NoDisplay-symbol overrides keyed by uppercase ISO code, e.g. .
NoUp to 3 extra non-sensitive Zoho metadata entries.
NoPer-request HTTP timeout, 1–120000 ms.
NoRetries for safe calls and OAuth, 0–10.
NoExponential-backoff base, 0–60000 ms.
NoAuthorization status polling attempts, 0–10.
NoDelay between authorization polls, 0–10000 ms.
NoTimestamp tolerance; disables the check. A positive value must cover Zoho's retry window.
Region defaultNoAdvanced override of the Zoho Payments API base URL.
Region defaultNoAdvanced override of the OAuth accounts base URL.
Region defaultNoAdvanced override of the hosted-checkout base URL.
Global NoCustom fetch implementation for proxies or instrumentation.
Medusa loggerNoStructured logger with , , and .

Environment variable names used in the examples:

These variables are read by your , not automatically by the provider. See Configuration for the full reference.

What is supported

CapabilitySupportedNotes
Embedded Zoho Checkout WidgetYesDefault mode; requires .
Zoho Hosted CheckoutYesOpt-in; requires and .
Payment session create/updateYesReuses a session only when amount, currency, mode, and expiry match.
Authorization and captureYesZoho auto-captures; the provider verifies the captured payment.
Full and partial refundsYesAgainst a captured payment; asynchronous Zoho status.
Signed webhooksYes, , .
India and US data centresYesOne provider instance per Zoho account.
Zoho SandboxYesIndia-only; separate sandbox credentials and .
Multi-currencyYesSubject to your Zoho account's supported currencies.
Saved payment methodsNoAccount-holder and saved-method APIs are not implemented in v1.
Manual or partial captureNoZoho does not offer it.
Cancel a pending paymentNoNo Zoho API; the session expires (15 minutes by default).
Cancel a captured paymentNoRefund instead.
Refund webhook → MedusaNoAcknowledged as ; reconcile with .
Payment links, payoutsNoOutside this provider's scope.
Automatic webhook creationNoRegister webhooks manually in Zoho.
Automatic reconciliationNoUse the exported in your own job.
Multi-account routingNoOne provider instance maps to one Zoho account.
Built-in storefront UINoUse the documented helpers or hosted checkout.

A successful Zoho payment is already captured. There is no separate capture step, no partial capture, and no cancel API. re-reads the payment and confirms the capture; it never creates a second charge. Refund settlement can be asynchronous; the provider records Zoho's refund status and logs pending/initiated refunds for reconciliation.

Security

  • Secrets stay server-side. , , , and access tokens are never placed in payment-session , logs, errors, or browser bundles. The widget is publishable by design.
  • Webhooks fail closed. The signature is verified over the exact raw body before any field is trusted. There is no development mode that skips verification.
  • Account and mode are checked. A valid signature from another Zoho account or from test mode does not complete a live payment.
  • Payment ownership is verified. Storefront-supplied session data cannot inject a prior payment or Zoho session ID; authorization checks the Zoho payment's reference and metadata against the current Medusa payment session.
  • Amount and currency are reconciled. Zoho amounts are compared with the Medusa session before an order is treated as captured, and positive amounts that round below the currency's precision are rejected.
  • The storefront never decides success. It may display a hint, but Medusa asks the provider to retrieve the Zoho session/payment before creating a payment.
  • Hosted checkout URLs are capabilities. They contain Zoho's ; do not log them, send them to analytics, or expose them in referrer URLs.
  • No card data touches your server. Using Zoho's widget or hosted page keeps card entry on Zoho's infrastructure. This reduces, but does not eliminate, PCI DSS scope; confirm your obligations with your acquirer and a qualified assessor.

Report vulnerabilities privately via the process in SECURITY.md.

Testing

enforces 100% lines, branches, functions, and statements. The suite covers amount formatting, status mapping, all three signature schemes, OAuth caching and retries, HTTP retry policy, both checkout modes, webhook authorization, failed-attempt reconciliation, refunds, and browser-helper behaviour.

Documentation

  • Configuration
  • Storefront: embedded widget
  • Storefront: hosted checkout
  • Webhooks
  • Signature verification
  • Troubleshooting
  • Architecture
  • Compatibility and limitations
  • Contributing
  • Security policy

License

MIT © Jayashakthi Vishnu.

Zoho, Zoho Payments, and related marks are trademarks of Zoho Corporation. Medusa is a trademark of Medusa, Inc. This project is an independent community integration and is not affiliated with either company.

Еще в этой категории

Посмотреть все
Платежи
Braintree logo

Braintree

От Lambda Curry

Поддержка платежей и 3D Secure через Braintree

Загрузка данных
GitHubnpm
Платежи
Pay. logo

Pay.

От Webbers

Принимайте кредитные карты, цифровые платежи и купи сейчас, плати потом

Загрузка данных
GitHubnpm
Платежи
Mollie logo

Mollie

От Variable Vic

Легко принимайте мультивалютные платежи через Mollie

Загрузка данных
GitHubnpm
1ZohoPay.payments.CREATE
2ZohoPay.payments.READ
3ZohoPay.refunds.CREATE
4ZohoPay.refunds.READ
node scripts/get-refresh-token.mjs
1import { defineConfig, loadEnv } from "@medusajs/framework/utils"
2
3loadEnv(process.env.NODE_ENV || "development", process.cwd())
4
5module.exports = defineConfig({
6 projectConfig: {
7 databaseUrl: process.env.DATABASE_URL,
8 http: {
9 storeCors: process.env.STORE_CORS!,
10 adminCors: process.env.ADMIN_CORS!,
11 authCors: process.env.AUTH_CORS!,
12 jwtSecret: process.env.JWT_SECRET!,
13 cookieSecret: process.env.COOKIE_SECRET!,
14 },
15 },
16 modules: [
17 {
18 resolve: "@medusajs/medusa/payment",
19 options: {
20 providers: [
21 {
22 resolve: "medusa-payment-zoho/providers/zoho",
23 id: "zoho",
24 options: {
25 accountId: process.env.ZOHO_PAYMENTS_ACCOUNT_ID!,
26 clientId: process.env.ZOHO_PAYMENTS_CLIENT_ID!,
27 clientSecret: process.env.ZOHO_PAYMENTS_CLIENT_SECRET!,
28 refreshToken: process.env.ZOHO_PAYMENTS_REFRESH_TOKEN!,
29 signingKey: process.env.ZOHO_PAYMENTS_SIGNING_KEY!,
30
31 // India is the default; use "us" for the US data centre.
32 region: "in",
33
34 // "live" (default) or "sandbox". Sandbox is India-only.
35 environment: "live",
36
37 // "widget" (default) or "hosted".
38 checkoutMode: "widget",
39
40 // Publishable browser key, required for widget mode.
41 apiKey: process.env.ZOHO_PAYMENTS_API_KEY!,
42
43 // Reject webhooks whose live_mode does not match this value.
44 // Or map ZOHO_PAYMENTS_LIVE_MODE for an explicit test/live switch.
45 liveMode: process.env.NODE_ENV === "production",
46 },
47 },
48 ],
49 },
50 },
51 ],
52})
1checkoutMode: "hosted",
2successUrl: process.env.ZOHO_PAYMENTS_SUCCESS_URL!,
3failureUrl: process.env.ZOHO_PAYMENTS_FAILURE_URL!,
4// apiKey is not required in hosted mode
1"use client"
2
3import { useState } from "react"
4import Medusa from "@medusajs/js-sdk"
5import { payWithZohoCheckout } from "medusa-payment-zoho/client"
6
7const sdk = new Medusa({
8 baseUrl: process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL!,
9 publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY!,
10})
11
12export function ZohoCheckoutButton({ cartId }: { cartId: string }) {
13 const [message, setMessage] = useState<string>()
14
15 async function handlePay() {
16 setMessage("Preparing Zoho checkout…")
17
18 const { cart } = await sdk.store.cart.retrieve(cartId)
19 await sdk.store.payment.initiatePaymentSession(cart, {
20 provider_id: "pp_zoho_zoho",
21 })
22
23 const { cart: freshCart } = await sdk.store.cart.retrieve(cartId)
24 const session = freshCart.payment_collection?.payment_sessions?.find(
25 (candidate) => candidate.provider_id === "pp_zoho_zoho"
26 )
27
28 if (!session) {
29 setMessage("Zoho payment session is missing.")
30 return
31 }
32
33 const result = await payWithZohoCheckout({ session: session.data })
34
35 if (!result.ok) {
36 setMessage(
37 result.reason === "closed"
38 ? "Payment cancelled."
39 : (result.message ?? `Zoho payment failed (${result.reason}).`)
40 )
41 return
42 }
43
44 // The provider retrieves the payment from Zoho during completion.
45 const completion = await sdk.store.cart.complete(freshCart.id)
46
47 if (completion.type === "order") {
48 window.location.assign(`/order/confirmed/${completion.order.id}`)
49 return
50 }
51
52 setMessage(completion.error?.message ?? "Could not complete the order.")
53 }
54
55 return (
56 <button onClick={handlePay} disabled={message === "Preparing Zoho checkout…"}>
57 Pay with Zoho
58 </button>
59 )
60}
1"use client"
2
3import Medusa from "@medusajs/js-sdk"
4import { redirectToZohoHostedCheckout } from "medusa-payment-zoho/client"
5
6const sdk = new Medusa({
7 baseUrl: process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL!,
8 publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY!,
9})
10
11export async function startHostedCheckout(cartId: string) {
12 const { cart } = await sdk.store.cart.retrieve(cartId)
13 await sdk.store.payment.initiatePaymentSession(cart, {
14 provider_id: "pp_zoho_zoho",
15 })
16
17 const { cart: freshCart } = await sdk.store.cart.retrieve(cartId)
18 const session = freshCart.payment_collection?.payment_sessions?.find(
19 (candidate) => candidate.provider_id === "pp_zoho_zoho"
20 )
21
22 if (!session) {
23 throw new Error("Zoho payment session is missing")
24 }
25
26 redirectToZohoHostedCheckout(session.data)
27}
1ZOHO_PAYMENTS_ACCOUNT_ID=
2ZOHO_PAYMENTS_CLIENT_ID=
3ZOHO_PAYMENTS_CLIENT_SECRET=
4ZOHO_PAYMENTS_REFRESH_TOKEN=
5ZOHO_PAYMENTS_SIGNING_KEY=
6ZOHO_PAYMENTS_API_KEY=
7ZOHO_PAYMENTS_REGION=in
8ZOHO_PAYMENTS_CHECKOUT_MODE=widget
9ZOHO_PAYMENTS_SUCCESS_URL=
10ZOHO_PAYMENTS_FAILURE_URL=
11ZOHO_PAYMENTS_BUSINESS_NAME=
12ZOHO_PAYMENTS_LIVE_MODE=false
1pnpm install --frozen-lockfile
2pnpm lint
3pnpm typecheck
4pnpm test:coverage
5pnpm build
6pnpm check