Formerly known as the Monero Subscription Code Standard
This standard has been implemented in several programming languages to facilitate its use across different platforms and applications. Here are the available libraries:
- Python: monerorequest
pip install monerorequest
- JavaScript: monerorequest-js
npm install -S monerorequest-js
- Kotlin: monerorequest-kotlin (coming soon to Maven Central)
- Java: monerorequest-java (coming soon to Maven Central)
- Ruby: monerorequest-ruby
gem install monerorequest
- Rust: monerorequest-rust (in progress)
- Introduction
- How It Works
- Advantages
- Mission
- Tools For Creating
monero-request
codes - Latest Version
- Field Explanations
- Old Versions
The Monero Payment Request Standard aims to address the complexities and limitations associated with traditional and existing cryptographic payment methods. It facilitates a straightforward and decentralized way to handle one-time, recurring, and scheduled Monero payments.
- Generate A Code: Create a unique payment code (called a "Payment Request") encapsulating the payment information — billing frequency, amount, etc.
- Modification Requests: Merchants can request to update previously sent Payment Requests, accommodating changing business needs. The customer retains the ability to accept these changes, or remove the Payment Request.
- Input The Code: Insert the merchant-provided Payment Request into your Monero wallet, review the Payment Request details, and click to confirm.
- Retain Control: Upon confirmation, the payment proceeds according to the agreed-upon schedule. Buyers retain full control over their funds and can cancel Payment Requests at any time.
- Efficiency: The protocol eliminates redundant steps, enabling users to establish payment conditions with a singular code.
- No Intermediaries: The protocol operates without the need for a middle man, enabling a direct transactional relationship between the buyer and merchant.
- Multi-Currency Flexibility: The protocol supports pricing in various currencies, providing flexibility for merchants whose pricing may be based on fiat currencies.
- Privacy Preserving: Merchants can confirm the origin of payments without collecting any information from the buyer.
To simplify all kinds of payments on Monero, allowing buyers to retain full control of funds, automate payments, and keep transactions private.
- Monero Payment Request Code Creator Website
- Monero Payment Request Code Creator CLI Tool
- Monero Payment Request Code Creator Pip Package
- More
monero-request
integration tools coming soon...
To decode a Monero Payment Request, follow these steps:
- Remove the Monero Payment Request identifier
monero-request:
and version identifier, and extract the encoded string.- Python:
prefix, version, encoded_str = monero_payment_request.split(':')
- JavaScript:
const [prefix, version, encoded_str] = monero_payment_request.split(':');
- Python:
- Decode the string from Base64 to obtain the compressed data.
- Python:
compressed_data = base64.b64decode(encoded_str)
- JavaScript:
const compressed_data = atob(encoded_str)
- Python:
- Decompress the compressed data using gzip to get the JSON string.
- Python:
json_str = gzip.decompress(compressed_data)
- JavaScript:
const json_str = pako.inflate(compressed_data, { to: 'string' })
(using pako library)
- Python:
- Parse the JSON string to extract the field values.
- Python:
json.loads(json_str)
- JavaScript:
JSON.parse(json_str)
- Python:
import base64
import gzip
import json
def decode_monero_payment_request(monero_payment_request):
# Extract prefix, version, and Base64-encoded data
prefix, version, encoded_str = monero_payment_request.split(':')
# Decode the Base64-encoded string to bytes
encoded_str = base64.b64decode(encoded_str.encode('ascii'))
# Decompress the bytes using gzip decompression
decompressed_data = gzip.decompress(encoded_str)
# Convert the decompressed bytes into to a JSON string
json_str = decompressed_data.decode('utf-8')
# Parse the JSON string into a Python object
monero_payment_request_data = json.loads(json_str)
return monero_payment_request_data
monero_payment_request = 'monero-request:1:H4sIAAAAAAAC/y2P207DMAyGXwXleocex9q7bmxIoCGxDRi7idLEWyNyKDnQtYh3J0Nc2f79+7P9jYjUXjlUxsWkKEaINkSdAXPFOCVOG+yNQCXqum4CFyJbAROq5ZS0fCq1AqPHBj49WIfCrDcGFO2D/2V39ydYpyUWpIYrZNPf7HxtqeGt41oFAyO9xS0YXHMhuDpj2lMBqEyjEVJe1qGjT7glvQTlLCqD/F9gzgKxONH5PJpHLGY5o1ERkBaEAGNxR0IMf6GscukhN1+vfbvXp7P08FTY4tmZgW0hX3hYG/tRHXl8u9DvdTP0Vg+D3qwXs+FN7R/Z/XJWXVZVvVrldFhv0yZkD7WVWbOEQ7K7rnTEOMyIC5ejJErScZSNk9k+TsssL9P0iH5+AS8IcFpoAQAA'
decoded_data = decode_monero_payment_request(monero_payment_request)
print(decoded_data)
To encode a Monero Payment Request, follow these steps:
-
Convert the payment details to a JSON object. Minimize whitespace and sort the keys.
- Python:
json.dumps(data, separators=(',', ':'), sort_keys=True)
- JavaScript:
JSON.stringify(data, Object.keys(data).sort())
- Python:
-
Compress the JSON object using gzip compression. Set the modification time to a constant value to ensure consistency.
- Python:
gzip.compress(data, mtime=0)
- JavaScript:
pako.gzip(data, { level: 9, windowBits: 31 })
(using pako library)
- Python:
-
Encode the compressed data in Base64 format.
- Python:
base64.b64encode(data).decode('ascii')
- JavaScript:
btoa(String.fromCharCode.apply(null, new Uint8Array(data)))
- Python:
-
Add the Monero Payment Request identifier
monero-request:
and version number1:
to the encoded string.- Python/JavaScript:
'monero-request:1:' + encodedString
- Python/JavaScript:
import base64
import gzip
import json
def make_monero_payment_request(json_data, version=1):
# Convert the JSON data to a string
json_str = json.dumps(json_data, separators=(',', ':'), sort_keys=True)
# Compress the string using gzip compression
compressed_data = gzip.compress(json_str.encode('utf-8'), mtime=0)
# Encode the compressed data into a Base64-encoded string
encoded_str = base64.b64encode(compressed_data).decode('ascii')
# Add the Monero Subscription identifier & version number
monero_payment_request = 'monero-request:' + str(version) + ':' + encoded_str
return monero_payment_request
json_data = {
"custom_label": "My Subscription", # This can be any text
"sellers_wallet": "4At3X5rvVypTofgmueN9s9QtrzdRe5BueFrskAZi17BoYbhzysozzoMFB6zWnTKdGC6AxEAbEE5czFR3hbEEJbsm4hCeX2S",
"currency": "USD", # Currently supports "USD" or "XMR"
"amount": "19.99",
"payment_id": "9fc88080d1d5dc09", # Unique identifier so the merchant knows which customer the payment relates to
"start_date": "2023-04-26T13:45:33Z", # Start date in RFC3339 timestamp format
"days_per_billing_cycle": 30, # How often it should be billed
"number_of_payments": 0, # 1 for one-time, >1 for set-number, 0 for recurring until canceled
"change_indicator_url": "www.example.com/api/monero-request", # Optional. Small merchants should leave this blank.
}
monero_payment_request = make_monero_payment_request(json_data=json_data)
print(monero_payment_request)
The custom_label
is a string field allowing users to attach a descriptive label to the payment request. This label can be any text that helps identify or categorize the payment for the user.
- Data Type: String
- Details: While there's no strict character limit, labels longer than 80 characters are likely to be truncated in most interfaces.
- Examples:
- "Monthly Subscription"
- "Donation to XYZ"
- "Invoice #12345" (For one-time payments)
The sellers_wallet
field holds the Monero wallet address where payments will be sent. This address must not be a subaddress since integrated addresses (which combine a Monero address and a payment ID) don't support subaddresses.
- Data Type: String (Monero wallet address format)
- Limitations: Must be a valid main Monero wallet address.
- Examples:
- "4At3X5rvVypTofgmueN9s9QtrzdRe5BueFrskAZi17BoYbhzysozzoMFB6zWnTKdGC6AxEAbEE5czFR3hbEEJbsm4hCeX2S"
All payments are made in Monero. The currency
field is used to specify the currency in which the payment amount is denominated. This allows merchants to base their prices on a variety of currencies.
- Data Type: String
- Details: While payments are always in Monero, the amount can be specified in any recognized currency or cryptocurrency ticker (e.g., USD, AUD, GBP, BTC). The exact amount of Monero sent will be based on the current exchange rate of the specified currency to Monero.
- Examples:
- "USD"
- "GBP"
- "XMR"
The amount
field specifies the quantity of the specified currency to be transferred. The actual Monero amount sent will be based on this value and the current exchange rate.
- Data Type: String
- Examples:
- "19.99" (for 19.99 USD worth of Monero — assuming "Currency" was set to USD)
- "0.5" (for 0.5 XMR — assuming "Currency" was set to XMR)
The payment_id
is a unique identifier generated for the payment request. It is used when generating an integrated address for Monero payments. Merchants can identify which customer made a payment based on this ID, ensuring privacy for the customer.
- Data Type: String (Monero payment ID format)
- Details: Typically in hexadecimal format, it's recommended to generate a unique ID for each customer.
- Examples:
- "9fc88080d1d5dc09"
The start_date
field indicates when the first payment or subscription should commence.
- Data Type: String (truncated RFC3339 timestamp format)
- Python:
from datetime import datetime, timezone current_time = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
- JavaScript:
const current_time = new Date().toISOString().slice(0, -1) + 'Z';
- Examples:
- 2023-04-26T13:45:33.123Z
- 2023-10-26T04:55:37.443Z
The days_per_billing_cycle
field defines the frequency of payments for recurring payments.
- Data Type: Integer
- Examples:
- 30 (for monthly payments)
- 7 (for weekly payments)
The number_of_payments
field indicates how many times a payment will occur.
- Data Type: Integer
- Examples:
- 1 (for a one-time payment)
- 6 (for six scheduled payments)
- 0 (for payments that will recur until canceled)
The change_indicator_url
is a field designed for large merchants who wish to have the flexibility to request modifications to an existing payment request. It's important to note that the merchant cannot enforce these changes. When a change is requested, all related automatic payments are paused until the customer reviews and either confirms or rejects the changes (canceling the payment request).
-
Merchant Requests, Not Commands: The main utility of this feature is for merchants to request changes, such as updating a wallet address or changing the price. The merchant cannot force these changes on the customer.
-
Automatic Pause on Changes: The wallet will query the
change_indicator_url
before making any payment. If it detects a change, automatic payments are paused and the customer is notified of the requested change (customer can choose to accept the changes, or cancel the payment request). -
Customer Consent Required: Payments remain paused until the customer actively confirms or rejects the proposed changes. If confirmed, the payment request is updated and payments resume; if rejected, the payment request is canceled.
-
URL Formation: For the
change_indicator_url
, provide only the base URL of the endpoint that can be checked for a Payment Request changes. The system will automatically append the uniquepayment_id
associated with the payment request as a query parameter to this URL.- Base URL you provide:
www.mysite.com/api/monero-request
- Final URL the customer will query:
www.mysite.com/api/monero-request?payment_id=9fc88080d1d5dc09
- Base URL you provide:
-
Merchant Changes: To request a change, the merchant updates the information at the
change_indicator_url
. These changes remain in the status of "requested" until approved or declined by the customer. -
Customer Notification and Confirmation: Upon detecting a change, the wallet notifies the customer who then must make a decision to accept the changes to the payment request, or cancel the payment request. Payments stay paused until this decision is made.
-
Endpoint Setup: Merchants should create a REST endpoint capable of handling GET requests for the
change_indicator_url
. -
Initiating Changes: To request changes, the merchant updates the content at the REST endpoint according to the Monero Payment Request Protocol format.
-
Cancellation Request: If a merchant wishes to cancel the payment request entirely, they can specify this at the
change_indicator_url
(e.g.,"status": "cancelled"
). -
Supplemental Customer Notification: Though the
change_indicator_url
should trigger automatic notifications, merchants are encouraged to also notify customers through other channels as a best practice.
Merchants can send JSON data with the following fields to initiate different types of changes:
-
To Cancel Subscription
{ "action": "cancel", "note": "We are going out of business." }
-
To Update Any Payment Request Fields
{ "action": "update", "fields": { "amount": "25.99", "currency": "USD" }, "note": "Price has changed due to increased costs." }
Merchants can ignore the payment_id
query parameter to initiate blanket updates for all payment_ids associated with the change_indicator_url
.
This optional change_indicator_url
feature enhances the protocol's flexibility, enabling merchants to request changes while ensuring customers maintain full control over their payment options.
- Data Type: String
- Examples:
- "" (for small merchants who do not want to use this feature)
- "https://www.example.com/api/monero-request"
- "https://mywebsite.com/update-monero-payments"