Skip to content
Work

Razorpay Auto-Invoicer,

Monthly invoices for overseas clients who pay through Razorpay

My first open-source project. It bills my overseas clients on the 1st of every month, emails each one an invoice with the right bank details, and tells me which invoice every transfer pays, so Razorpay can settle it.

The Razorpay International Auto-Invoicer logo: a document with an orange globe badge on a teal tile
The project's README on GitHub, with the logo, the title Razorpay International Auto-Invoicer and a list of what it does
The razorpay-intl-auto-invoicer repository on GitHub, with its file list, topics and the v0.1.0 release
The project on GitHub: the README and the repository.

The problem

I bill a few clients abroad every month. They pay by bank transfer into my Razorpay MoneySaver Export Account, which gives me local bank accounts in their currency and pays me out in rupees.

It works well, but every month came with the same routine:

  1. Create an invoice for each client in Razorpay.
  2. Make a PDF with the right bank details for that client's currency.
  3. Email it.
  4. When a transfer landed, dig out the matching PDF.
  5. Upload it in Razorpay, because Razorpay doesn't settle an export payment until it has the invoice.

None of it is hard. All of it is easy to forget, and a missed upload means money sitting unsettled.

Hands-on steps for each client, each month

The routine above, before and after the invoicer.

Hands-on steps for each client, each month
HowSteps
By hand5 steps
With the invoicer1 step
The one step left is the upload in Razorpay. Razorpay has no API for it, so a person has to do it.

What I built

A small tool that does the routine for me. It runs in Docker on my own server.

A month with the invoicer

  1. Invoices go out1st, 09:00

    One Razorpay invoice per client, in their currency, and an email with a PDF that has my bank details for that currency.

  2. The client paysAny day

    They send a normal local bank transfer, with the invoice number as the reference.

  3. The tool finds the invoiceEvery day, 10:00

    It checks for new transfers, works out which invoice each one pays, and emails me that PDF.

  4. I upload the PDFAfter the email

    In Razorpay, under Pending Actions. Razorpay then settles the money in rupees.

  5. The books are done3rd, 09:00

    Last month's settlements arrive as a CSV, with the invoice number next to each payment it matched.

An invoice that gets paid

The PDF carries everything a client's accounts team needs to pay without asking: the invoice number, the period, the amount, and a box with the bank details for their currency. The invoice number is also the payment reference, which matters later.

A sample invoice made by the tool: invoice INV-202610-client-a for USD 3,500 of software development in October 2026, with a box of US bank transfer details and the invoice number as the payment reference
A sample invoice with made-up details. The bank box changes with the client's currency.

Matching a transfer to its invoice

This is the core of it. A bank transfer doesn't arrive with an invoice attached. It arrives with an amount and, if you're lucky, a reference. The tool tries three rules, most certain first:

  1. The transfer was paid through the invoice's own Razorpay link.
  2. The invoice number appears in the transfer's reference.
  3. It's the only open invoice in that currency for exactly that amount.

Anything else comes to me to decide. A guess by amount is flagged, and the client gets no receipt until I confirm it. Try it with three open invoices:

Which invoice does this transfer pay?

Reference on the transfer
Amount received

Open invoices

  • INV-202610-client-aUSD 3,500
  • INV-202610-client-bUSD 500
  • INV-202610-client-cUSD 500

INV-202610-client-a. The invoice number is in the reference, so this one is certain.

Setup in four files

You run one command and it writes the config for you: .env for keys and email, clients.json for who to bill and how much, bank-details.json for the bank details per currency, and a docker-compose.yml. Then:

  • probe shows what Razorpay returns for your account, without changing anything.
  • invoice --dry-run previews every invoice and email. Nothing is created or sent.
  • docker compose up -d starts the schedule.
The README on GitHub, on a laptop and on a phone
The README on GitHub, on a laptop and on a phone, on a phone
The README walks through setup, every setting and what to do when a client pays.

What I learned about Razorpay's API

Most of the work wasn't the invoices. It was finding out what Razorpay's API can and can't do for export payments, and building around the gaps.

  • The upload has no API. Razorpay won't settle an export payment until you attach the invoice and the buyer's address in the dashboard, and there's no API for that. So the tool can't do it, but its daily check hands you the right PDF within a day.
  • Bank details have no API either. The account numbers for each currency live in the MoneySaver dashboard. You copy them into bank-details.json once.
  • Invoices stay "issued". The money arrives by bank transfer, not through the invoice link, so Razorpay never marks these invoices as paid. The tool keeps its own record of what's paid.
  • No tax on foreign-currency invoices. The API can't make GST invoices or add tax to them. Exports under a Letter of Undertaking (LUT) don't charge GST anyway, so that wording goes in the invoice footer.
  • Quantities must be whole numbers. 37.5 hours can't be a quantity, so fractional hours become one line with the full amount.
  • How export transfers show up in the API isn't documented. So client receipts and reminders are off by default, and the README says to turn them on only after a dry run has matched a real transfer correctly.
  • FIRS certificates are manual too. Razorpay emails them, but there's no API to fetch them.

Built so it can't email anyone twice

A billing tool that sends a client the same invoice twice, or a reminder for something they've paid, does more harm than good. So most of the design is about being safe to run again:

  • The invoice number is the duplicate guard. It's built from the month and the client (INV-202610-client-a) and stored with the Razorpay invoice. Before creating one, the tool looks it up. Run it ten times, get one invoice.
  • A small state file remembers which payments it has seen and which receipts and reminders went out. If that file is damaged, the tool stops instead of starting over, because starting over could resend everything.
  • One run at a time. A lock file stops a manual run and the scheduler from writing over each other.
  • The first check only takes notes. It records the payments that already exist, so turning the tool on never sends a pile of late receipts.
  • Nothing goes out stale. Turning receipts on never sends them for old payments, and a reminder more than a week past its day is dropped.
  • Missed the 1st? If the server was down, the tool catches up when it starts again, as long as that's within three days. Invoices that exist are skipped.
  • Failed emails retry on the next run and every day after, up to four tries, then it tells me.
  • Logs stay clean. They never print keys, names, addresses, bank details or amounts.

Open source from day one

It's MIT licensed, with a Docker image on GitHub's container registry, CI on every pull request, and contributing, security and code of conduct files. Node runs the TypeScript directly, so there's no build step, and there are only five runtime dependencies.

It does one job for one kind of account. If you bill clients abroad through a MoneySaver Export Account, it should save you the same monthly routine it saved me.

New to the MoneySaver Export Account? Sign up for Razorpay with my referral link.

Get it on GitHub, or if you'd like a tool like this built for your own business, see what I build or get in touch.

Have work that AI should be doing?

Tell me about the job and who does it today. I'll reply with how I'd approach it.