razorpay_dart 0.1.0 copy "razorpay_dart: ^0.1.0" to clipboard
razorpay_dart: ^0.1.0 copied to clipboard

A Dart port of the official razorpay-node SDK for the Razorpay API.

razorpay_dart #

pub package pub points

A Dart port of the official razorpay-node SDK, tracking release 2.9.8. Pure Dart, no Flutter dependency.

⚠️ This belongs on a server, not in an app #

This SDK authenticates with your account's key_secret, which grants full access to your Razorpay account: creating orders, issuing refunds, moving settlements.

Never ship it inside a Flutter app or a web bundle. Anything on a device can be decompiled, so a secret placed there is a published secret. Use it only from a server you control — dart_frog, shelf, serverpod, a Cloud Function, a CLI. Your app should talk to your backend, and your backend talks to Razorpay.

For collecting payments in a Flutter app, use Razorpay's own razorpay_flutter checkout plugin instead, and verify the resulting signature on your server with this package.

Install #

dependencies:
  razorpay_dart: ^0.1.0
dart pub add razorpay_dart

Quick start #

import 'dart:io';

import 'package:razorpay_dart/razorpay_dart.dart';

Future<void> main() async {
  final razorpay = Razorpay(
    keyId: Platform.environment['RAZORPAY_KEY_ID'],
    keySecret: Platform.environment['RAZORPAY_KEY_SECRET'],
  );

  // Amounts are in the smallest currency unit: 50000 paise = ₹500.
  final order = await razorpay.orders.create(
    params: const RazorpayOrderCreateRequestBody(
      amount: 50000,
      receipt: 'receipt#1',
      notes: {'purpose': 'demo'},
    ),
  );

  print('${order.id} — ${order.amount} ${order.currency}');
}

Every method resolves to the entity itself, matching razorpay-node. There is no response wrapper and no HTTP client type in any signature.

Authentication #

An API key pair:

final razorpay = Razorpay(keyId: '...', keySecret: '...');

Or, as a platform partner, an OAuth access token:

final razorpay = Razorpay(oauthToken: '...');

Partners acting on a submerchant's behalf can add the account header:

final razorpay = Razorpay(
  keyId: '...',
  keySecret: '...',
  headers: {'X-Razorpay-Account': 'acc_XXXXXXXX'},
);

Other headers are ignored, so a stray one cannot displace authentication.

Verifying signatures #

This is the part that matters for security. A razorpay_payment_id arriving at your endpoint proves nothing on its own — anyone can post one. Only the signature does.

// In your Checkout callback handler.
final valid = Razorpay.validatePaymentVerification(
  params: {
    'order_id': orderId,
    'payment_id': paymentId,
  },
  signature: razorpaySignature,
  secret: keySecret,
);

if (!valid) {
  // Treat the payment as not made. Do not fulfil the order.
}

For webhooks, pass the raw request body:

final valid = Razorpay.validateWebhookSignature(
  body: rawRequestBody, // exactly as received, byte for byte
  signature: request.headers['x-razorpay-signature']!,
  secret: webhookSecret,
);

Do not decode and re-encode the body first. Re-serialising JSON reorders keys and changes whitespace, which changes the HMAC and makes every legitimate webhook look forged.

RazorpaySignature exposes the same functions, plus RazorpaySignature.paymentPayload for inspecting exactly what is being signed. Comparison is constant-time, and every function is pinned by tests to vectors generated from razorpay-node itself.

Error handling #

Errors are typed, so you can tell a rejected request from one that never arrived:

try {
  await razorpay.orders.create(params: params);
} on RazorpayApiException catch (e) {
  // Razorpay replied with a non-2xx. Your request was the problem.
  print('${e.statusCode} ${e.code}: ${e.description}');
  print('field: ${e.field}');
} on RazorpayNetworkException catch (e) {
  // Nothing reached Razorpay: timeout, DNS, TLS. Safe to retry.
  print(e.message);
} on RazorpaySerializationException catch (e) {
  // The response did not fit the model. This is a bug in this package --
  // please report it, with e.responseBody.
  print(e);
}

All three extend RazorpayException, so catch that to handle any SDK failure.

Pagination #

List endpoints return RazorpayList<T>:

final page = await razorpay.orders.all(
  params: const RazorpayOrderQuery(count: 25, skip: 0),
);

for (final order in page.items) {
  print(order.id);
}

count and skip are omitted from the request when you do not set them, so Razorpay's own defaults (count: 10, skip: 0) apply.

Date filters accept whichever form you have:

await razorpay.orders.all(
  params: RazorpayOrderQuery(
    from: DateTime.now().subtract(const Duration(days: 7)),
    to: 1700000000,                 // epoch seconds
  ),
);
// or an ISO 8601 string: from: '2023-11-14T22:13:20Z'

Uploading files #

File endpoints take a RazorpayFile, so you never need to depend on the HTTP client:

await razorpay.documents.create(
  purpose: 'dispute_evidence',
  file: RazorpayFile.fromPath('evidence.pdf'),
);

await razorpay.accounts.uploadAccountDoc(
  accountId: 'acc_XXXXXXXX',
  documentType: 'business_proof_url',
  file: RazorpayFile.fromBytes(bytes, filename: 'proof.pdf'),
);

OAuth for platform partners #

final oauth = OAuthTokenClient();

// 1. Send the merchant here to grant access.
final url = oauth.generateAuthUrl(
  params: const InitiateAuthorisationRequest(
    client_id: 'XXXXXXXXXXXXXX',
    response_type: 'code',
    redirect_uri: 'https://example.com/callback',
    scope: ['read_write'],
    state: 'a-random-nonce-you-check-on-return',
  ),
);

// 2. Exchange the code your callback receives.
final token = await oauth.getAccessToken(
  params: const OAuthTokenRequest(
    client_id: 'XXXXXXXXXXXXXX',
    client_secret: '...',
    redirect_uri: 'https://example.com/callback',
    code: '...',
  ),
);

// 3. Act on the merchant's behalf.
final razorpay = Razorpay(oauthToken: token.access_token);

// Later, before it expires:
final refreshed = await oauth.refreshToken(
  params: OAuthTokenRequest(
    client_id: 'XXXXXXXXXXXXXX',
    client_secret: '...',
    refresh_token: token.refresh_token,
  ),
);

Malformed input throws ArgumentError rather than being sent, so a bad redirect_uri fails at the call site instead of as a redirect that never arrives.

Resources #

Reached from the Razorpay instance:

razorpay.accounts razorpay.addons razorpay.cards
razorpay.customers razorpay.disputes razorpay.documents
razorpay.fundAccount razorpay.iins razorpay.invoices
razorpay.items razorpay.orders razorpay.paymentLink
razorpay.payments razorpay.plans razorpay.products
razorpay.qrCode razorpay.refunds razorpay.settlements
razorpay.stakeholders razorpay.subscriptions razorpay.tokens
razorpay.transfers razorpay.virtualAccounts razorpay.webhooks

Method names match razorpay-node, so its documentation and the Razorpay API reference both apply. Arguments are named rather than positional.

Testing against this package #

Point a client at your own stub server:

final razorpay = Razorpay(
  keyId: 'test',
  keySecret: 'test',
  baseUrl: 'http://127.0.0.1:8080',
);

test/support/mock_api.dart in this repository is a recording stub server you can crib from.

How closely this matches razorpay-node #

Different layers are verified differently, so it's worth being precise:

Layer How it's verified
Endpoints — verb, API version, path Machine-checked. All 148 endpoints across 142 methods match razorpay-node@2.9.8 exactly. Enforced in CI.
Signature algorithms Verified by execution. Test vectors were produced by running razorpay-node's own razorpay-utils.js, and are committed as fixtures.
Request bodies and query strings Asserted against a recording stub server in the resource tests, but not diffed against razorpay-node.
Response models Hand-ported from razorpay-node's .d.ts. Least verified — see the caveat below.

tool/node_endpoints.json is a manifest of razorpay-node's endpoint surface, extracted by running the official package against a recording stub (tool/extract_node_endpoints.js). tool/check_parity.py compares this package against it and runs in CI, so drift fails the build:

python3 tool/check_parity.py --verbose

Two differences are deliberate and normalised away, both because razorpay-node bakes into a path what belongs in a query string, or leaves a stray trailing slash. The script's docstring explains each.

Caveat on response models #

The models were ported from razorpay-node's TypeScript declarations, and those declarations are wrong in places — RazorpayCard marks number and cvv required, though Razorpay never returns either. Cases like that were fixed where found, but the models have not been validated field-by-field against live API responses. If a response fails to decode you'll get a RazorpaySerializationException naming the field; please open an issue with it.

About the pub.flutter-io.cn score #

One line item is worth explaining rather than leaving a mystery: pub.flutter-io.cn's "pass static analysis" score does not use this package's analysis_options.yaml. It substitutes its own fixed rule set when computing that score, and that substitution ignores this repository's analyzer: exclude for generated code and its analyzer: errors: {non_constant_identifier_names: ignore} override.

The practical effect: every field this package exposes as snake_case — because that's the wire format Razorpay's JSON API uses (account_id, created_at, payment_link_id, and so on throughout every model) — gets flagged by pub.flutter-io.cn's scorer in both the hand-written model file and its generated .freezed.dart / .g.dart counterparts, once per field. That's the overwhelming majority of the static-analysis point loss, and it is a naming-convention observation, not a defect: dart analyze --fatal-infos against this repository's own (non-overridden) rule set reports zero issues, and that is what CI enforces.

Renaming every field to camelCase with a @JsonKey(name: '...') mapping back to the wire format would close this gap, at the cost of touching every model in the package for a purely cosmetic score improvement. Not planned for now; happy to revisit if it turns out to matter to users choosing between packages.

Contributing #

dart pub get
dart run build_runner build --delete-conflicting-outputs
dart analyze --fatal-infos
dart test
python3 tool/check_parity.py

Generated files (*.freezed.dart, *.g.dart) are committed, and CI fails if they differ from what the builders produce — so run build_runner and commit its output with any model change.

To bump the tracked razorpay-node release:

git clone --depth 1 https://github.com/razorpay/razorpay-node.git /tmp/rzp-node
node tool/extract_node_endpoints.js /tmp/rzp-node
python3 tool/check_parity.py     # shows what changed upstream

License #

MIT. See LICENSE.

0
likes
160
points
95
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Dart port of the official razorpay-node SDK for the Razorpay API.

Repository (GitHub)
View/report issues

Topics

#razorpay #payment #gateway #finance

License

MIT (license)

Dependencies

crypto, dio, freezed_annotation, json_annotation, pointycastle

More

Packages that depend on razorpay_dart