├── versions ├── v0.md ├── v1.md └── v2.md ├── LICENSE └── README.md /versions/v0.md: -------------------------------------------------------------------------------- 1 | # Verson 0 2 | 3 | > :warning: At first launch, the Monero Payment Request Standard was called the The Monero Subscription Code Standard. It has been depreciated, but the original documentation can be found here, under the [Monero Subscription Code Standard](https://github.com/lukeprofits/Monero_Subscription_Code_Standard) 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Luke Profits 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /versions/v1.md: -------------------------------------------------------------------------------- 1 | # Monero Payment Request Standard 2 | > Formerly the [Monero Subscription Code Standard](https://github.com/lukeprofits/Monero_Subscription_Code_Standard) 3 | 4 | # Version 1: 5 | ### Decoding A Monero Payment Request 6 | To decode a Monero Payment Request, follow these steps: 7 | 8 | 1. Remove the Monero Payment Request identifier `monero-request:` and version identifier, and extract the encoded string. 9 | - Python: `prefix, version, encoded_str = monero_payment_request.split(':')` 10 | - JavaScript: `const [prefix, version, encoded_str] = monero_payment_request.split(':');` 11 | 2. Decode the string from [Base64](https://en.wikipedia.org/wiki/Base64) to obtain the compressed data. 12 | - Python: `compressed_data = base64.b64decode(encoded_str)` 13 | - JavaScript: `const compressed_data = atob(encoded_str)` 14 | 3. Decompress the compressed data using [gzip](https://docs.python.org/3/library/gzip.html) to get the JSON string. 15 | - Python: `json_str = gzip.decompress(compressed_data)` 16 | - JavaScript: `const json_str = pako.inflate(compressed_data, { to: 'string' })` *(using [pako library](https://github.com/nodeca/pako))* 17 | 4. Parse the JSON string to extract the field values. 18 | - Python: `json.loads(json_str)` 19 | - JavaScript: `JSON.parse(json_str)` 20 | 21 | 22 | 23 | ### Example Python Function To Decode A Monero Payment Request: 24 | ```python 25 | import base64 26 | import gzip 27 | import json 28 | 29 | def decode_monero_payment_request(monero_payment_request): 30 | # Extract prefix, version, and Base64-encoded data 31 | prefix, version, encoded_str = monero_payment_request.split(':') 32 | 33 | # Decode the Base64-encoded string to bytes 34 | encoded_str = base64.b64decode(encoded_str.encode('ascii')) 35 | 36 | # Decompress the bytes using gzip decompression 37 | decompressed_data = gzip.decompress(encoded_str) 38 | 39 | # Convert the decompressed bytes into to a JSON string 40 | json_str = decompressed_data.decode('utf-8') 41 | 42 | # Parse the JSON string into a Python object 43 | monero_payment_request_data = json.loads(json_str) 44 | 45 | return monero_payment_request_data 46 | 47 | 48 | monero_payment_request = 'monero-request:1:H4sIAAAAAAAC/y2P207DMAyGXwXleocex9q7bmxIoCGxDRi7idLEWyNyKDnQtYh3J0Nc2f79+7P9jYjUXjlUxsWkKEaINkSdAXPFOCVOG+yNQCXqum4CFyJbAROq5ZS0fCq1AqPHBj49WIfCrDcGFO2D/2V39ydYpyUWpIYrZNPf7HxtqeGt41oFAyO9xS0YXHMhuDpj2lMBqEyjEVJe1qGjT7glvQTlLCqD/F9gzgKxONH5PJpHLGY5o1ERkBaEAGNxR0IMf6GscukhN1+vfbvXp7P08FTY4tmZgW0hX3hYG/tRHXl8u9DvdTP0Vg+D3qwXs+FN7R/Z/XJWXVZVvVrldFhv0yZkD7WVWbOEQ7K7rnTEOMyIC5ejJErScZSNk9k+TsssL9P0iH5+AS8IcFpoAQAA' 49 | 50 | decoded_data = decode_monero_payment_request(monero_payment_request) 51 | 52 | print(decoded_data) 53 | ``` 54 | 55 | ### Encoding A Monero Payment Request 56 | To encode a Monero Payment Request, follow these steps: 57 | 58 | 1. Convert the payment details to a JSON object. Minimize whitespace and sort the keys. 59 | - Python: `json.dumps(data, separators=(',', ':'), sort_keys=True)` 60 | - JavaScript: `JSON.stringify(data, Object.keys(data).sort())` 61 | 62 | 2. Compress the JSON object using gzip compression. Set the modification time to a constant value to ensure consistency. 63 | - Python: `gzip.compress(data, mtime=0)` 64 | - JavaScript: `pako.gzip(data, {mtime: 0})` *(using pako library)* 65 | 66 | 3. Encode the compressed data in Base64 format. 67 | - Python: `base64.b64encode(data).decode('ascii')` 68 | - JavaScript: `btoa(String.fromCharCode.apply(null, new Uint8Array(data)))` 69 | 70 | 4. Add the Monero Payment Request identifier `monero-request:` and version number `1:` to the encoded string. 71 | - Python/JavaScript: `'monero-request:1:' + encodedString` 72 | 73 | 74 | ### Example Python Function To Create A Monero Payment Request: 75 | ```python 76 | import base64 77 | import gzip 78 | import json 79 | 80 | 81 | def make_monero_payment_request(json_data, version=1): 82 | # Convert the JSON data to a string 83 | json_str = json.dumps(json_data, separators=(',', ':'), sort_keys=True) 84 | 85 | # Compress the string using gzip compression 86 | compressed_data = gzip.compress(json_str.encode('utf-8'), mtime=0) 87 | 88 | # Encode the compressed data into a Base64-encoded string 89 | encoded_str = base64.b64encode(compressed_data).decode('ascii') 90 | 91 | # Add the Monero Subscription identifier & version number 92 | monero_payment_request = 'monero-request:' + str(version) + ':' + encoded_str 93 | 94 | return monero_payment_request 95 | 96 | 97 | json_data = { 98 | "custom_label": "My Subscription", # This can be any text 99 | "sellers_wallet": "4At3X5rvVypTofgmueN9s9QtrzdRe5BueFrskAZi17BoYbhzysozzoMFB6zWnTKdGC6AxEAbEE5czFR3hbEEJbsm4hCeX2S", 100 | "currency": "USD", # Currently supports "USD" or "XMR" 101 | "amount": "19.99", 102 | "payment_id": "9fc88080d1d5dc09", # Unique identifier so the merchant knows which customer the payment relates to 103 | "start_date": "2023-04-26T13:45:33Z", # Start date in RFC3339 timestamp format 104 | "days_per_billing_cycle": 30, # How often it should be billed 105 | "number_of_payments": 0, # 1 for one-time, >1 for set-number, 0 for recurring until canceled 106 | "change_indicator_url": "www.example.com/api/monero-request", # Optional. Small merchants should leave this blank. 107 | } 108 | 109 | monero_payment_request = make_monero_payment_request(json_data=json_data) 110 | 111 | print(monero_payment_request) 112 | ``` 113 | 114 | ## Field Explanations 115 | ### Custom Label 116 | 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. 117 | 118 | - **Data Type**: String 119 | - **Details**: While there's no strict character limit, labels longer than 80 characters are likely to be truncated in most interfaces. 120 | - **Examples**: 121 | - "Monthly Subscription" 122 | - "Donation to XYZ" 123 | - "Invoice #12345" *(For one-time payments)* 124 | 125 | ### Sellers Wallet 126 | 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. 127 | 128 | - **Data Type**: String *(Monero wallet address format)* 129 | - **Limitations**: Must be a valid main Monero wallet address. 130 | - **Examples**: 131 | - "4At3X5rvVypTofgmueN9s9QtrzdRe5BueFrskAZi17BoYbhzysozzoMFB6zWnTKdGC6AxEAbEE5czFR3hbEEJbsm4hCeX2S" 132 | 133 | ### Currency 134 | 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. 135 | 136 | - **Data Type**: String 137 | - **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. 138 | - **Examples**: 139 | - "USD" 140 | - "GBP" 141 | - "XMR" 142 | 143 | ### Amount 144 | 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. 145 | 146 | - **Data Type**: String 147 | - **Examples**: 148 | - "19.99" *(for 19.99 USD worth of Monero — assuming "Currency" was set to USD)* 149 | - "0.5" *(for 0.5 XMR — assuming "Currency" was set to XMR)* 150 | 151 | ### Payment ID 152 | 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. 153 | 154 | - **Data Type**: String *(Monero payment ID format)* 155 | - **Details**: Typically in hexadecimal format, it's recommended to generate a unique ID for each customer. 156 | - **Examples**: 157 | - "9fc88080d1d5dc09" 158 | 159 | ### Start Date 160 | The `start_date` field indicates when the first payment or subscription should commence. 161 | 162 | - **Data Type**: String *(RFC3339 timestamp format)* 163 | - **Python**: 164 | ```python 165 | from datetime import datetime 166 | current_time = datetime.now().isoformat(timespec='milliseconds') 167 | ``` 168 | - **JavaScript**: 169 | ```javascript 170 | const current_time = new Date().toISOString(); 171 | ``` 172 | - **Examples**: 173 | - "2023-04-26T13:45:33.123Z" 174 | 175 | ### Days Per Billing Cycle 176 | The `days_per_billing_cycle` field defines the frequency of payments for recurring payments. 177 | 178 | - **Data Type**: Integer 179 | - **Examples**: 180 | - 30 *(for monthly payments)* 181 | - 7 *(for weekly payments)* 182 | 183 | ### Number of Payments 184 | The `number_of_payments` field indicates how many times a payment will occur. 185 | 186 | - **Data Type**: Integer 187 | - **Examples**: 188 | - 1 *(for a one-time payment)* 189 | - 6 *(for six scheduled payments)* 190 | - 0 *(for payments that will recur until canceled)* 191 | 192 | ### Change Indicator URL 193 | 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). 194 | 195 | #### Key Features and Constraints 196 | 197 | - **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. 198 | 199 | - **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). 200 | 201 | - **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. 202 | 203 | #### How it Works 204 | 205 | 1. **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 unique `payment_id` associated with the payment request as a query parameter to this URL. 206 | - Base URL you provide: `www.mysite.com/api/monero-request` 207 | - Final URL the customer will query: `www.mysite.com/api/monero-request?payment_id=9fc88080d1d5dc09` 208 | 209 | 2. **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. 210 | 211 | 3. **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. 212 | 213 | #### Merchant Guidelines 214 | 215 | - **Endpoint Setup**: Merchants should create a REST endpoint capable of handling GET requests for the `change_indicator_url`. 216 | 217 | - **Initiating Changes**: To request changes, the merchant updates the content at the REST endpoint according to the Monero Payment Request Protocol format. 218 | 219 | - **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"`). 220 | 221 | - **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. 222 | 223 | #### JSON Structure for Merchant Changes 224 | 225 | Merchants can send JSON data with the following fields to initiate different types of changes: 226 | 227 | - **To Cancel Subscription** 228 | ```json 229 | { 230 | "action": "cancel", 231 | "note": "We are going out of business." 232 | } 233 | ``` 234 | 235 | - **To Update Any Payment Request Fields** 236 | ```json 237 | { 238 | "action": "update", 239 | "fields": { 240 | "amount": 25.99, 241 | "currency": "USD" 242 | }, 243 | "note": "Price has changed due to increased costs." 244 | } 245 | ``` 246 | 247 | #### Bulk Updates 248 | 249 | Merchants can ignore the `payment_id` query parameter to initiate blanket updates for all payment_ids associated with the `change_indicator_url`. 250 | 251 | 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. 252 | 253 | - **Data Type**: String 254 | - **Examples**: 255 | - "" *(for small merchants who do not want to use this feature)* 256 | - "www.example.com/api/monero-request" 257 | - "www.mywebsite.com/update-monero-payments" 258 | -------------------------------------------------------------------------------- /versions/v2.md: -------------------------------------------------------------------------------- 1 | # Monero Payment Request Standard 2 | > Formerly the [Monero Subscription Code Standard](https://github.com/lukeprofits/Monero_Subscription_Code_Standard) 3 | 4 | # Version 2: 5 | ### Decoding A Monero Payment Request 6 | To decode a Monero Payment Request, follow these steps: 7 | 8 | 1. Remove the Monero Payment Request identifier `monero-request:` and version identifier, and extract the encoded string. 9 | - Python: `prefix, version, encoded_str = monero_payment_request.split(':')` 10 | - JavaScript: `const [prefix, version, encoded_str] = monero_payment_request.split(':');` 11 | 2. Decode the string from [Base64](https://en.wikipedia.org/wiki/Base64) to obtain the compressed data. 12 | - Python: `compressed_data = base64.b64decode(encoded_str)` 13 | - JavaScript: `const compressed_data = atob(encoded_str)` 14 | 3. Decompress the compressed data using [gzip](https://docs.python.org/3/library/gzip.html) to get the JSON string. 15 | - Python: `json_str = gzip.decompress(compressed_data)` 16 | - JavaScript: `const json_str = pako.inflate(compressed_data, { to: 'string' })` *(using [pako library](https://github.com/nodeca/pako))* 17 | 4. Parse the JSON string to extract the field values. 18 | - Python: `json.loads(json_str)` 19 | - JavaScript: `JSON.parse(json_str)` 20 | 21 | 22 | 23 | ### Example Python Function To Decode A Monero Payment Request: 24 | ```python 25 | import base64 26 | import gzip 27 | import json 28 | 29 | def decode_monero_payment_request(monero_payment_request): 30 | # Extract prefix, version, and Base64-encoded data 31 | prefix, version, encoded_str = monero_payment_request.split(':') 32 | 33 | # Decode the Base64-encoded string to bytes 34 | encoded_str = base64.b64decode(encoded_str.encode('ascii')) 35 | 36 | # Decompress the bytes using gzip decompression 37 | decompressed_data = gzip.decompress(encoded_str) 38 | 39 | # Convert the decompressed bytes into to a JSON string 40 | json_str = decompressed_data.decode('utf-8') 41 | 42 | # Parse the JSON string into a Python object 43 | monero_payment_request_data = json.loads(json_str) 44 | 45 | return monero_payment_request_data 46 | 47 | 48 | monero_payment_request = 'monero-request:2:H4sIAAAAAAACAy1P207DMAz9lSmP0y69jrVv3diQQEOCDRh7idLEWyOapORC1yL+nRTxYNk+PufY/kZEKCctysNslmUTRCsiL4C5ZJwSqzR2ukY5att2BlcimhpmVIk5afhcKAlaTTV8OjAWea3TGiTtPP9lf/sHGKsErkkJg8muG+1daajmjeVKeoJ0ogSN1Rk3pBMgrUF5MEH/DebMq7IzXS6DZcBCljIaZF5maAXM1eCn49F4FPoYDzDUNWiDW+KzfwklhY2Pqf567ZqDOl+Eg8fMZE9W9+wZ0pWDrTYfxYmHNyv1XlZ9Z1Tfq912tejf5OGB3a0XxXVTlJtNSvvtc1z56r40IqnWcIz2w0pLtMWM2OGWKIjiaZBMo8UhjPMkzeP4hH5+AZhorYxjAQAA' 49 | 50 | decoded_data = decode_monero_payment_request(monero_payment_request) 51 | 52 | print(decoded_data) 53 | ``` 54 | 55 | ### Encoding A Monero Payment Request 56 | To encode a Monero Payment Request, follow these steps: 57 | 58 | 1. Convert the payment details to a JSON object. Minimize whitespace and sort the keys. 59 | - Python: `json.dumps(data, separators=(',', ':'), sort_keys=True)` 60 | - JavaScript: `JSON.stringify(data, Object.keys(data).sort())` 61 | 62 | 2. Compress the JSON object using gzip compression. Set the modification time to a constant value to ensure consistency. 63 | - Python: `gzip.compress(data, mtime=0)` 64 | - JavaScript: `pako.gzip(data, {mtime: 0})` *(using pako library)* 65 | 66 | 3. Encode the compressed data in Base64 format. 67 | - Python: `base64.b64encode(data).decode('ascii')` 68 | - JavaScript: `btoa(String.fromCharCode.apply(null, new Uint8Array(data)))` 69 | 70 | 4. Add the Monero Payment Request identifier `monero-request:` and version number `2:` to the encoded string. 71 | - Python/JavaScript: `'monero-request:2:' + encodedString` 72 | 73 | 74 | ### Example Python Function To Create A Monero Payment Request: 75 | ```python 76 | import base64 77 | import gzip 78 | import json 79 | 80 | 81 | def make_monero_payment_request(json_data, version=2): 82 | # Convert the JSON data to a string 83 | json_str = json.dumps(json_data, separators=(',', ':'), sort_keys=True) 84 | 85 | # Compress the string using gzip compression 86 | compressed_data = gzip.compress(json_str.encode('utf-8'), mtime=0) 87 | 88 | # Encode the compressed data into a Base64-encoded string 89 | encoded_str = base64.b64encode(compressed_data).decode('ascii') 90 | 91 | # Add the Monero Subscription identifier & version number 92 | monero_payment_request = 'monero-request:' + str(version) + ':' + encoded_str 93 | 94 | return monero_payment_request 95 | 96 | 97 | json_data = { 98 | "custom_label": "My Subscription", # This can be any text 99 | "sellers_wallet": "4At3X5rvVypTofgmueN9s9QtrzdRe5BueFrskAZi17BoYbhzysozzoMFB6zWnTKdGC6AxEAbEE5czFR3hbEEJbsm4hCeX2S", 100 | "currency": "USD", # Currently supports "USD" or "XMR" 101 | "amount": "19.99", 102 | "payment_id": "9fc88080d1d5dc09", # Unique identifier so the merchant knows which customer the payment relates to 103 | "start_date": "2023-04-26T13:45:33Z", # Start date in RFC3339 timestamp format 104 | "schedule": '* * 1 * *', # How often it should be billed 105 | "number_of_payments": 0, # 1 for one-time, >1 for set-number, 0 for recurring until canceled 106 | "change_indicator_url": "www.example.com/api/monero-request", # Optional. Small merchants should leave this blank. 107 | } 108 | 109 | monero_payment_request = make_monero_payment_request(json_data=json_data) 110 | 111 | print(monero_payment_request) 112 | ``` 113 | 114 | ## Field Explanations 115 | ### Custom Label 116 | 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. 117 | 118 | - **Data Type**: String 119 | - **Details**: While there's no strict character limit, labels longer than 80 characters are likely to be truncated in most interfaces. 120 | - **Examples**: 121 | - "Monthly Subscription" 122 | - "Donation to XYZ" 123 | - "Invoice #12345" *(For one-time payments)* 124 | 125 | ### Sellers Wallet 126 | 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. 127 | 128 | - **Data Type**: String *(Monero wallet address format)* 129 | - **Limitations**: Must be a valid main Monero wallet address. 130 | - **Examples**: 131 | - "4At3X5rvVypTofgmueN9s9QtrzdRe5BueFrskAZi17BoYbhzysozzoMFB6zWnTKdGC6AxEAbEE5czFR3hbEEJbsm4hCeX2S" 132 | 133 | ### Currency 134 | 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. 135 | 136 | - **Data Type**: String 137 | - **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. 138 | - **Examples**: 139 | - "USD" 140 | - "GBP" 141 | - "XMR" 142 | 143 | ### Amount 144 | 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. 145 | 146 | - **Data Type**: String 147 | - **Examples**: 148 | - "19.99" *(for 19.99 USD worth of Monero — assuming "Currency" was set to USD)* 149 | - "0.5" *(for 0.5 XMR — assuming "Currency" was set to XMR)* 150 | 151 | ### Payment ID 152 | 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. 153 | 154 | - **Data Type**: String *(Monero payment ID format)* 155 | - **Details**: Typically in hexadecimal format, it's recommended to generate a unique ID for each customer. 156 | - **Examples**: 157 | - "9fc88080d1d5dc09" 158 | 159 | ### Start Date 160 | The `start_date` field indicates when the first payment or subscription should commence. 161 | 162 | - **Data Type**: String *(RFC3339 timestamp format)* 163 | - **Python**: 164 | ```python 165 | from datetime import datetime 166 | current_time = datetime.now().isoformat(timespec='milliseconds') 167 | ``` 168 | - **JavaScript**: 169 | ```javascript 170 | const current_time = new Date().toISOString(); 171 | ``` 172 | - **Examples**: 173 | - "2023-04-26T13:45:33.123Z" 174 | 175 | ### Schedule 176 | The `schedule` field defines the frequency of payments for recurring payments using a cron-style syntax. 177 | Minute (\*, 0-59) Hour (\*, 0-23) Day Of Month (\*, 1-31) Month (\*, 1-12) Day of Week (\*, 0-7). 178 | https://man7.org/linux/man-pages/man5/crontab.5.html Being the main reference for what should be supported. 179 | With the added symbol of `L` for the Day of Month entry to represent the last day of the month. 180 | 181 | - **Data Type**: String 182 | - **Examples**: 183 | - * * * * * *For payments every minute* 184 | - * * * * 0 *For weekly payments* 185 | 186 | ### Number of Payments 187 | The `number_of_payments` field indicates how many times a payment will occur. 188 | 189 | - **Data Type**: Integer 190 | - **Examples**: 191 | - 1 *(for a one-time payment)* 192 | - 6 *(for six scheduled payments)* 193 | - 0 *(for payments that will recur until canceled)* 194 | 195 | ### Change Indicator URL 196 | 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). 197 | 198 | #### Key Features and Constraints 199 | 200 | - **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. 201 | 202 | - **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). 203 | 204 | - **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. 205 | 206 | #### How it Works 207 | 208 | 1. **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 unique `payment_id` associated with the payment request as a query parameter to this URL. 209 | - Base URL you provide: `www.mysite.com/api/monero-request` 210 | - Final URL the customer will query: `www.mysite.com/api/monero-request?payment_id=9fc88080d1d5dc09` 211 | 212 | 2. **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. 213 | 214 | 3. **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. 215 | 216 | #### Merchant Guidelines 217 | 218 | - **Endpoint Setup**: Merchants should create a REST endpoint capable of handling GET requests for the `change_indicator_url`. 219 | 220 | - **Initiating Changes**: To request changes, the merchant updates the content at the REST endpoint according to the Monero Payment Request Protocol format. 221 | 222 | - **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"`). 223 | 224 | - **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. 225 | 226 | #### JSON Structure for Merchant Changes 227 | 228 | Merchants can send JSON data with the following fields to initiate different types of changes: 229 | 230 | - **To Cancel Subscription** 231 | ```json 232 | { 233 | "action": "cancel", 234 | "note": "We are going out of business." 235 | } 236 | ``` 237 | 238 | - **To Update Any Payment Request Fields** 239 | ```json 240 | { 241 | "action": "update", 242 | "fields": { 243 | "amount": 25.99, 244 | "currency": "USD" 245 | }, 246 | "note": "Price has changed due to increased costs." 247 | } 248 | ``` 249 | 250 | #### Bulk Updates 251 | 252 | Merchants can ignore the `payment_id` query parameter to initiate blanket updates for all payment_ids associated with the `change_indicator_url`. 253 | 254 | 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. 255 | 256 | - **Data Type**: String 257 | - **Examples**: 258 | - "" *(for small merchants who do not want to use this feature)* 259 | - "www.example.com/api/monero-request" 260 | - "www.mywebsite.com/update-monero-payments" 261 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Monero Payment Request Standard 2 | > Formerly known as the [Monero Subscription Code Standard](https://github.com/lukeprofits/Monero_Subscription_Code_Standard) 3 | 4 | ## Implementations 5 | 6 | This standard has been implemented in several programming languages to facilitate its use across different platforms and applications. Here are the available libraries: 7 | 8 | - Python: [monerorequest](https://github.com/lukeprofits/monerorequest) `pip install monerorequest` 9 | - JavaScript: [monerorequest-js](https://github.com/moshmage/monerorequest-js) `npm install -S monerorequest-js` 10 | - Kotlin: [monerorequest-kotlin](https://github.com/lukeprofits/monerorequest-kotlin) (coming soon to Maven Central) 11 | - Java: [monerorequest-java](https://github.com/lukeprofits/monerorequest-java) (coming soon to Maven Central) 12 | - Ruby: [monerorequest-ruby](https://github.com/snex/monerorequest-ruby) `gem install monerorequest` 13 | - Rust: [monerorequest-rust](https://github.com/NotGovernor/MoneroRequest-Rust) (in progress) 14 | 15 | 16 | ## Table of Contents 17 | 1. [Introduction](#introduction) 18 | 2. [How It Works](#how-it-works) 19 | 1. [For Merchants](#for-merchants) 20 | 2. [For Consumers](#for-consumers) 21 | 3. [Advantages](#advantages) 22 | 4. [Mission](#mission) 23 | 5. [Tools For Creating `monero-request` codes](#tools-for-creating-monero-request-codes) 24 | 6. [Latest Version](#latest-version) 25 | 1. [Decoding A Monero Payment Request](#decoding-a-monero-payment-request) 26 | 2. [Encoding A Monero Payment Request](#encoding-a-monero-payment-request) 27 | 7. [Field Explanations](#field-explanations) 28 | 1. [Custom Label](#custom-label) 29 | 2. [Sellers Wallet](#sellers-wallet) 30 | 3. [Currency](#currency) 31 | 4. [Amount](#amount) 32 | 5. [Payment ID](#payment-id) 33 | 6. [Start Date](#start-date) 34 | 7. [Schedule](#schedule) 35 | 8. [Number of Payments](#number-of-payments) 36 | 9. [Change Indicator URL](#change-indicator-url) 37 | 8. [Old Versions](#old-versions) 38 | 39 | ## Introduction 40 | 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. 41 | 42 | ## How It Works 43 | ### For Merchants 44 | 1. **Generate A Code**: Create a unique payment code (called a "Payment Request") encapsulating the payment information — billing frequency, amount, etc. 45 | 2. **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. 46 | 47 | ### For Consumers 48 | 1. **Input The Code**: Insert the merchant-provided Payment Request into your Monero wallet, review the Payment Request details, and click to confirm. 49 | 2. **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. 50 | 51 | ## Advantages 52 | - **Efficiency**: The protocol eliminates redundant steps, enabling users to establish payment conditions with a singular code. 53 | - **No Intermediaries**: The protocol operates without the need for a middle man, enabling a direct transactional relationship between the buyer and merchant. 54 | - **Multi-Currency Flexibility**: The protocol supports pricing in various currencies, providing flexibility for merchants whose pricing may be based on fiat currencies. 55 | - **Privacy Preserving**: Merchants can confirm the origin of payments without collecting any information from the buyer. 56 | 57 | ## Mission 58 | To simplify all kinds of payments on Monero, allowing buyers to retain full control of funds, automate payments, and keep transactions private. 59 | 60 | ## Tools For Creating `monero-request` codes 61 | - [Monero Payment Request Code Creator Website](https://monerosub.tux.pizza/) 62 | - [Monero Payment Request Code Creator CLI Tool](https://github.com/lukeprofits/Monero_Payment_Request_Code_Creator) 63 | - [Monero Payment Request Code Creator Pip Package](https://github.com/lukeprofits/monero_payment_request) 64 | - More `monero-request` integration tools coming soon... 65 | 66 | ## Latest Version 67 | [Version 2](https://github.com/lukeprofits/Monero_Payment_Request_Standard/blob/main/versions/v2.md) 68 | 69 | ### Decoding A Monero Payment Request 70 | To decode a Monero Payment Request, follow these steps: 71 | 72 | 1. Remove the Monero Payment Request identifier `monero-request:` and version identifier, and extract the encoded string. 73 | - Python: `prefix, version, encoded_str = monero_payment_request.split(':')` 74 | - JavaScript: `const [prefix, version, encoded_str] = monero_payment_request.split(':');` 75 | 2. Decode the string from [Base64](https://en.wikipedia.org/wiki/Base64) to obtain the compressed data. 76 | - Python: `compressed_data = base64.b64decode(encoded_str)` 77 | - JavaScript: `const compressed_data = atob(encoded_str)` 78 | 3. Decompress the compressed data using [gzip](https://docs.python.org/3/library/gzip.html) to get the JSON string. 79 | - Python: `json_str = gzip.decompress(compressed_data)` 80 | - JavaScript: `const json_str = pako.inflate(compressed_data, { to: 'string' })` *(using [pako library](https://github.com/nodeca/pako))* 81 | 4. Parse the JSON string to extract the field values. 82 | - Python: `json.loads(json_str)` 83 | - JavaScript: `JSON.parse(json_str)` 84 | 85 | 86 | 87 | ### Example Python Function To Decode A Monero Payment Request: 88 | ```python 89 | import base64 90 | import gzip 91 | import json 92 | 93 | def decode_monero_payment_request(monero_payment_request): 94 | # Extract prefix, version, and Base64-encoded data 95 | prefix, version, encoded_str = monero_payment_request.split(':') 96 | 97 | # Decode the Base64-encoded string to bytes 98 | encoded_str = base64.b64decode(encoded_str.encode('ascii')) 99 | 100 | # Decompress the bytes using gzip decompression 101 | decompressed_data = gzip.decompress(encoded_str) 102 | 103 | # Convert the decompressed bytes into to a JSON string 104 | json_str = decompressed_data.decode('utf-8') 105 | 106 | # Parse the JSON string into a Python object 107 | monero_payment_request_data = json.loads(json_str) 108 | 109 | return monero_payment_request_data 110 | 111 | 112 | monero_payment_request = 'monero-request:2.0.0:H4sIAAAAAAACAy1P207DMAz9lSmP0y69jrVv3diQQEOCDRh7idLEWyOapORC1yL+nRTxYNk+PufY/kZEKCctysNslmUTRCsiL4C5ZJwSqzR2ukY5att2BlcimhpmVIk5afhcKAlaTTV8OjAWea3TGiTtPP9lf/sHGKsErkkJg8muG+1daajmjeVKeoJ0ogSN1Rk3pBMgrUF5MEH/DebMq7IzXS6DZcBCljIaZF5maAXM1eCn49F4FPoYDzDUNWiDW+KzfwklhY2Pqf567ZqDOl+Eg8fMZE9W9+wZ0pWDrTYfxYmHNyv1XlZ9Z1Tfq912tejf5OGB3a0XxXVTlJtNSvvtc1z56r40IqnWcIz2w0pLtMWM2OGWKIjiaZBMo8UhjPMkzeP4hH5+AZhorYxjAQAA' 113 | 114 | decoded_data = decode_monero_payment_request(monero_payment_request) 115 | 116 | print(decoded_data) 117 | ``` 118 | 119 | ### Encoding A Monero Payment Request 120 | To encode a Monero Payment Request, follow these steps: 121 | 122 | 1. Convert the payment details to a JSON object. Minimize whitespace and sort the keys. 123 | - Python: `json.dumps(data, separators=(',', ':'), sort_keys=True)` 124 | - JavaScript: `JSON.stringify(data, Object.keys(data).sort())` 125 | 126 | 2. Compress the JSON object using gzip compression. Set the modification time to a constant value to ensure consistency. 127 | - Python: `gzip.compress(data, mtime=0)` 128 | - JavaScript: `pako.gzip(data, { level: 9, windowBits: 31 })` *(using pako library)* 129 | 130 | 3. Encode the compressed data in Base64 format. 131 | - Python: `base64.b64encode(data).decode('ascii')` 132 | - JavaScript: `btoa(String.fromCharCode.apply(null, new Uint8Array(data)))` 133 | 134 | 4. Add the Monero Payment Request identifier `monero-request:` and version number `1:` to the encoded string. 135 | - Python/JavaScript: `'monero-request:1:' + encodedString` 136 | 137 | 138 | ### Example Python Function To Create A Monero Payment Request: 139 | ```python 140 | import base64 141 | import gzip 142 | import json 143 | 144 | 145 | def make_monero_payment_request(json_data, version=1): 146 | # Convert the JSON data to a string 147 | json_str = json.dumps(json_data, separators=(',', ':'), sort_keys=True) 148 | 149 | # Compress the string using gzip compression 150 | compressed_data = gzip.compress(json_str.encode('utf-8'), mtime=0) 151 | 152 | # Encode the compressed data into a Base64-encoded string 153 | encoded_str = base64.b64encode(compressed_data).decode('ascii') 154 | 155 | # Add the Monero Subscription identifier & version number 156 | monero_payment_request = 'monero-request:' + str(version) + ':' + encoded_str 157 | 158 | return monero_payment_request 159 | 160 | 161 | json_data = { 162 | "custom_label": "My Subscription", # This can be any text 163 | "sellers_wallet": "4At3X5rvVypTofgmueN9s9QtrzdRe5BueFrskAZi17BoYbhzysozzoMFB6zWnTKdGC6AxEAbEE5czFR3hbEEJbsm4hCeX2S", 164 | "currency": "USD", # Currently supports "USD" or "XMR" 165 | "amount": 19.99, 166 | "payment_id": "9fc88080d1d5dc09", # Unique identifier so the merchant knows which customer the payment relates to 167 | "start_date": "2023-04-26T13:45:33Z", # Start date in RFC3339 timestamp format 168 | "schedule": '* * 1 * *', # How often it should be billed 169 | "number_of_payments": 0, # 1 for one-time, >1 for set-number, 0 for recurring until canceled 170 | "change_indicator_url": "www.example.com/api/monero-request", # Optional. Small merchants should leave this blank. 171 | } 172 | 173 | monero_payment_request = make_monero_payment_request(json_data=json_data) 174 | 175 | print(monero_payment_request) 176 | ``` 177 | 178 | ## Field Explanations 179 | ### Custom Label 180 | 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. 181 | 182 | - **Data Type**: String 183 | - **Details**: While there's no strict character limit, labels longer than 80 characters are likely to be truncated in most interfaces. 184 | - **Examples**: 185 | - "Monthly Subscription" 186 | - "Donation to XYZ" 187 | - "Invoice #12345" *(For one-time payments)* 188 | 189 | ### Sellers Wallet 190 | 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. 191 | 192 | - **Data Type**: String *(Monero wallet address format)* 193 | - **Limitations**: Must be a valid main Monero wallet address. 194 | - **Examples**: 195 | - "4At3X5rvVypTofgmueN9s9QtrzdRe5BueFrskAZi17BoYbhzysozzoMFB6zWnTKdGC6AxEAbEE5czFR3hbEEJbsm4hCeX2S" 196 | 197 | ### Currency 198 | 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. 199 | 200 | - **Data Type**: String 201 | - **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. 202 | - **Examples**: 203 | - "USD" 204 | - "GBP" 205 | - "XMR" 206 | 207 | ### Amount 208 | 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. 209 | 210 | - **Data Type**: String 211 | - **Examples**: 212 | - "19.99" *(for 19.99 USD worth of Monero — assuming "Currency" was set to USD)* 213 | - "0.5" *(for 0.5 XMR — assuming "Currency" was set to XMR)* 214 | 215 | ### Payment ID 216 | 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. 217 | 218 | - **Data Type**: String *(Monero payment ID format)* 219 | - **Details**: Typically in hexadecimal format, it's recommended to generate a unique ID for each customer. 220 | - **Examples**: 221 | - "9fc88080d1d5dc09" 222 | 223 | ### Start Date 224 | The `start_date` field indicates when the first payment or subscription should commence. 225 | 226 | - **Data Type**: String *(truncated RFC3339 timestamp format)* 227 | - **Python**: 228 | ```python 229 | from datetime import datetime, timezone 230 | current_time = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z' 231 | ``` 232 | - **JavaScript**: 233 | ```javascript 234 | const current_time = new Date().toISOString().slice(0, -1) + 'Z'; 235 | ``` 236 | - **Examples**: 237 | - 2023-04-26T13:45:33.123Z 238 | - 2023-10-26T04:55:37.443Z 239 | 240 | ### Schedule 241 | The `schedule` field defines the frequency of payments for recurring payments using a cron-style syntax. 242 | 243 | Minute (`*`, 0-59), Hour (`*`, 0-23), Day Of Month (`*`, 1-31), Month (`*`, 1-12), Day of Week (`*`, 0-7). 244 | 245 | See [crontab(5)](https://man7.org/linux/man-pages/man5/crontab.5.html) for the main reference on supported syntax. 246 | The symbol `L` is also supported for the Day of Month entry to represent the last day of the month. 247 | 248 | - **Data Type**: String 249 | - **Examples**: 250 | ```plaintext 251 | * * * * * # For payments every minute 252 | * * * * 0 # For weekly payments 253 | ``` 254 | 255 | ### Number of Payments 256 | The `number_of_payments` field indicates how many times a payment will occur. 257 | 258 | - **Data Type**: Integer 259 | - **Examples**: 260 | - `1` (for a one-time payment) 261 | - `6` (for six scheduled payments) 262 | - `0` (for payments that will recur until canceled) 263 | 264 | ### Change Indicator URL 265 | 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). 266 | 267 | #### Key Features and Constraints 268 | 269 | - **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. 270 | 271 | - **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). 272 | 273 | - **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. 274 | 275 | #### How it Works 276 | 277 | 1. **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 unique `payment_id` associated with the payment request as a query parameter to this URL. 278 | - Base URL you provide: `www.mysite.com/api/monero-request` 279 | - Final URL the customer will query: `www.mysite.com/api/monero-request?payment_id=9fc88080d1d5dc09` 280 | 281 | 2. **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. 282 | 283 | 3. **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. 284 | 285 | #### Merchant Guidelines 286 | 287 | - **Endpoint Setup**: Merchants should create a REST endpoint capable of handling GET requests for the `change_indicator_url`. 288 | 289 | - **Initiating Changes**: To request changes, the merchant updates the content at the REST endpoint according to the Monero Payment Request Protocol format. 290 | 291 | - **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"`). 292 | 293 | - **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. 294 | 295 | #### JSON Structure for Merchant Changes 296 | 297 | Merchants can send JSON data with the following fields to initiate different types of changes: 298 | 299 | - **To Cancel Subscription** 300 | ```json 301 | { 302 | "action": "cancel", 303 | "note": "We are going out of business." 304 | } 305 | ``` 306 | 307 | - **To Update Any Payment Request Fields** 308 | ```json 309 | { 310 | "action": "update", 311 | "fields": { 312 | "amount": "25.99", 313 | "currency": "USD" 314 | }, 315 | "note": "Price has changed due to increased costs." 316 | } 317 | ``` 318 | 319 | #### Bulk Updates 320 | 321 | Merchants can ignore the `payment_id` query parameter to initiate blanket updates for all payment_ids associated with the `change_indicator_url`. 322 | 323 | 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. 324 | 325 | - **Data Type**: String 326 | - **Examples**: 327 | - "" *(for small merchants who do not want to use this feature)* 328 | - "https://www.example.com/api/monero-request" 329 | - "https://mywebsite.com/update-monero-payments" 330 | 331 | ## Old Versions 332 | [Version 0](https://github.com/lukeprofits/Monero_Payment_Request_Standard/blob/main/versions/v0.md) 333 | [Version 1](https://github.com/lukeprofits/Monero_Payment_Request_Standard/blob/main/versions/v1.md) 334 | --------------------------------------------------------------------------------