Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9da65a6bad | ||
|
|
e11cbab174 | ||
|
|
fadfb0f6a8 | ||
|
|
9dc936ef33 | ||
|
|
9705023233 | ||
|
|
dc6b7a5758 | ||
|
|
b935000fda | ||
|
|
dec467e55b | ||
|
|
a6b435d7e2 | ||
|
|
706860e35f | ||
|
|
25b06ab911 | ||
|
|
7994834151 | ||
|
|
6042284704 | ||
|
|
848b1f5e30 | ||
|
|
d3f8e2a60c | ||
|
|
43920ce700 | ||
|
|
7cb3ee2400 | ||
|
|
8cb6afd918 | ||
|
|
86cf5af6fa | ||
|
|
0742b68e48 | ||
|
|
0aa893767d | ||
|
|
6a22f82a73 | ||
|
|
17cb4f7814 | ||
|
|
c469a3b4c1 | ||
|
|
cfbee77a5b |
No files matched your search
@@ -0,0 +1,12 @@
|
|||||||
|
# BTCPay Server payment gateway for Odoo 16, 17 and 18
|
||||||
|
|
||||||
|
## Accept bitcoin payments on Odoo 16, 17 and 18
|
||||||
|
This odoo addon allows you to accept bitcoin and Lightning Network (and other cryptocurrency) payments in your Odoo e-commerce store.
|
||||||
|

|
||||||
|
|
||||||
|
## How to install
|
||||||
|
|
||||||
|
Please find the installation instructions here:
|
||||||
|
- [Odoo 18 documentation](https://github.com/btcpayserver/odoo/blob/18.0/payment_btcpayserver/README.md)
|
||||||
|
- [Odoo 17 documentation](https://github.com/btcpayserver/odoo/blob/17.0/payment_btcpayserver/README.md)
|
||||||
|
- [Odoo 16 documentation](https://github.com/btcpayserver/odoo/blob/16.0/payment_btcpayserver/README.md)
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
# BTCPay Server payment gateway for Odoo 16
|
# BTCPay Server payment gateway for Odoo 18
|
||||||
|
|
||||||
## This is the module to connect Odoo 16 and BTCPay Server
|
## This is the module to connect Odoo 18 and BTCPay Server
|
||||||
This module allows you to accept bitcoin (and other cryptocurrency) payments in your Odoo e-commerce store.
|
This module allows you to accept bitcoin (and other cryptocurrency) payments in your Odoo e-commerce store.
|
||||||

|

|
||||||
|
|
||||||
|
:::tip
|
||||||
|
If you use Odoo 16 you can find the documentation [here](https://github.com/btcpayserver/odoo/blob/16.0/payment_btcpayserver/README.md) and for Odoo 17 [here](https://github.com/btcpayserver/odoo/blob/17.0/payment_btcpayserver/README.md).
|
||||||
|
:::
|
||||||
|
|
||||||
## Install the module
|
## Install the module
|
||||||
* Clone our [repository](https://github.com/btcpayserver/odoo) or download the .zip from the [releases page](https://github.com/btcpayserver/odoo/releases)
|
* Clone our [repository](https://github.com/btcpayserver/odoo) or download the .zip from the [releases page](https://github.com/btcpayserver/odoo/releases)
|
||||||
* Make sure you are on branch `16.0` or downloaded a release tagged with version v16.x
|
* Make sure you are on branch `18.0` or downloaded a release tagged with version v18.x
|
||||||
* Place the `payment_btcpayserver` directory in your Odoo addons directory
|
* Place the `payment_btcpayserver` directory in your Odoo addons directory
|
||||||
* Install dependencies by running `pip install -r requirements.txt` (from inside the `payment_btcpayserver` directory)
|
* Install dependencies by running `pip install -r requirements.txt` (from inside the `payment_btcpayserver` directory)
|
||||||
* Restart Odoo
|
* Restart Odoo
|
||||||
@@ -33,16 +37,22 @@ In the BTCPay settings form, tab "Credentials":
|
|||||||
* Field Facade, keep default 'merchant'.
|
* Field Facade, keep default 'merchant'.
|
||||||
|
|
||||||
On the tab "Configuration":
|
On the tab "Configuration":
|
||||||
* Set field "Payment Journal" to "Bank", you can click the dropdown and click on the suggestion "Bank"
|
* Make sure field "Payment Journal" is set to "Bank", otherwise you can click the dropdown and click on the suggestion "Bank"
|
||||||
* Now you can **save** the settings
|
* Now you can **save** the settings
|
||||||
|
|
||||||

|
Check the payment method is enabled:
|
||||||
|
* Go to **Website** -> **Configuration** -> **Payment Methods**
|
||||||
|
* Make sure "Pay with Bitcoin / Lightning Network" is active
|
||||||
|
|
||||||
|
Congrats, all done. Do some testing to be sure all works.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## How does the payment page look?
|
## How does the payment page look?
|
||||||
|
|
||||||
During the checkout the customers will have the option to select the payment method "Pay with Bitcoin / Lightning Network". After selecting they will be redirected to the BTCPay checkout page as shown below.
|
During the checkout the customers will have the option to select the payment method "Pay with Bitcoin / Lightning Network". After selecting they will be redirected to the BTCPay checkout page as shown below.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Transaction BTCPay Details
|
## Transaction BTCPay Details
|
||||||
@@ -51,4 +61,8 @@ In transaction object, you will find more technical information about this metho
|
|||||||
* Invoice Id: the id of the invoice for which you want to fetch an event token
|
* Invoice Id: the id of the invoice for which you want to fetch an event token
|
||||||
* Transaction Status: That indicates state of transaction
|
* Transaction Status: That indicates state of transaction
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
### The order and transaction status does not get updated to "paid"
|
||||||
|
If the BTCPay connection generally works, like redirect to BTCPay checkout page (QR-code) then check your odoo logs and make sure PDF generation generally works. If there are errors mentioning wkhtmltopdf then you need to install `wkhtmltopdf` on your server.
|
||||||
@@ -6,9 +6,9 @@ from . import models
|
|||||||
from odoo.addons.payment import setup_provider, reset_payment_provider
|
from odoo.addons.payment import setup_provider, reset_payment_provider
|
||||||
|
|
||||||
|
|
||||||
def post_init_hook(cr, registry):
|
def post_init_hook(env):
|
||||||
setup_provider(cr, registry, 'btcpayserver')
|
setup_provider(env, 'btcpayserver')
|
||||||
|
|
||||||
|
|
||||||
def uninstall_hook(cr, registry):
|
def uninstall_hook(env):
|
||||||
reset_payment_provider(cr, registry, 'btcpayserver')
|
reset_payment_provider(env, 'btcpayserver')
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#******************************************************************************
|
#******************************************************************************
|
||||||
# PAYMENT BTCPAY SERVER FOR ODOO
|
# PAYMENT BTCPAY SERVER FOR ODOO
|
||||||
#
|
#
|
||||||
# Copyright (C) 2023 Susanna Fort <susannafm@gmail.com>, ndeet
|
# Copyright (C) 2024 Susanna Fort <susannafm@gmail.com>, ndeet
|
||||||
#
|
#
|
||||||
#******************************************************************************
|
#******************************************************************************
|
||||||
#
|
#
|
||||||
@@ -21,11 +21,11 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
'name': 'Payment Provider: BTCPay Server',
|
'name': 'Payment Provider: BTCPay Server',
|
||||||
'summary': 'This module integrates BTCPAY - pay with Bitcoin - with Odoo v16.0',
|
'summary': 'This module integrates BTCPAY - pay with Bitcoin - with Odoo v18.0',
|
||||||
'author': 'BTCPay Server team and contributors',
|
'author': 'BTCPay Server team and contributors',
|
||||||
'website': 'https://github.com/btcpayserver/odoo',
|
'website': 'https://github.com/btcpayserver/odoo',
|
||||||
'category': 'Accounting/Payment Providers',
|
'category': 'Accounting/Payment Providers',
|
||||||
'version': '16.0.2.0',
|
'version': '18.0.1.1',
|
||||||
'license': 'GPL-3',
|
'license': 'GPL-3',
|
||||||
'currency': 'USD',
|
'currency': 'USD',
|
||||||
'application': False,
|
'application': False,
|
||||||
@@ -38,9 +38,11 @@
|
|||||||
'views/payment_transaction_views.xml',
|
'views/payment_transaction_views.xml',
|
||||||
'data/payment_provider_data.xml',
|
'data/payment_provider_data.xml',
|
||||||
],
|
],
|
||||||
'images': ['static/description/BTCPay-Odoo-16-featured.png'],
|
'images': ['static/description/BTCPay-Odoo-17-featured.png'],
|
||||||
|
# The BTCPay client (legacy BitPay API) is vendored in models/libs and
|
||||||
|
# only requires 'ecdsa' (plus 'requests', shipped with Odoo).
|
||||||
'external_dependencies': {
|
'external_dependencies': {
|
||||||
'python': ['btcpay-python']
|
'python': ['ecdsa']
|
||||||
},
|
},
|
||||||
'post_init_hook': 'post_init_hook',
|
'post_init_hook': 'post_init_hook',
|
||||||
'uninstall_hook': 'uninstall_hook',
|
'uninstall_hook': 'uninstall_hook',
|
||||||
|
|||||||
@@ -19,23 +19,17 @@
|
|||||||
#
|
#
|
||||||
# ******************************************************************************
|
# ******************************************************************************
|
||||||
|
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import pprint
|
|
||||||
|
|
||||||
import requests
|
|
||||||
import werkzeug
|
|
||||||
from werkzeug import urls
|
from werkzeug import urls
|
||||||
from werkzeug.exceptions import Forbidden
|
|
||||||
|
|
||||||
from odoo import _, http, SUPERUSER_ID
|
from odoo import _, http
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
from odoo.tools import html_escape
|
|
||||||
|
|
||||||
import json
|
|
||||||
from ..models.libs.client import BTCPayClient
|
from ..models.libs.client import BTCPayClient
|
||||||
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -44,64 +38,261 @@ class BTCPayController(http.Controller):
|
|||||||
_notify_url = '/payment/btcpay/ipn'
|
_notify_url = '/payment/btcpay/ipn'
|
||||||
_return_url = '/payment/btcpay/return'
|
_return_url = '/payment/btcpay/return'
|
||||||
|
|
||||||
@http.route(_checkout_url, type='http', auth='public', csrf=False, website=True)
|
@staticmethod
|
||||||
|
def _btcpay_client(provider):
|
||||||
|
return BTCPayClient(
|
||||||
|
host=provider.btcpay_location,
|
||||||
|
pem=provider.btcpay_privateKey,
|
||||||
|
tokens={provider.btcpay_facade: provider.btcpay_token},
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _lock_transaction(tx_sudo):
|
||||||
|
"""Lock a transaction until the current database transaction ends.
|
||||||
|
|
||||||
|
Checkout and IPN requests can arrive concurrently. Serializing them on
|
||||||
|
the payment transaction prevents two checkout requests from creating
|
||||||
|
separate invoices and makes an IPN re-check the invoice ID after a
|
||||||
|
checkout replaces an expired invoice.
|
||||||
|
"""
|
||||||
|
tx_sudo.ensure_one()
|
||||||
|
tx_sudo.flush_recordset()
|
||||||
|
tx_sudo.env.cr.execute(
|
||||||
|
"SELECT id FROM payment_transaction WHERE id = %s FOR UPDATE",
|
||||||
|
[tx_sudo.id],
|
||||||
|
)
|
||||||
|
tx_sudo.invalidate_recordset([
|
||||||
|
'state', 'btcpay_invoiceId', 'provider_reference',
|
||||||
|
])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _ensure_invoice_id_is_unique(tx_sudo, invoice_id):
|
||||||
|
"""Reject an invoice ID already linked to another transaction."""
|
||||||
|
other_tx = tx_sudo.env['payment.transaction'].sudo().search([
|
||||||
|
('id', '!=', tx_sudo.id),
|
||||||
|
('provider_code', '=', 'btcpayserver'),
|
||||||
|
'|',
|
||||||
|
('btcpay_invoiceId', '=', invoice_id),
|
||||||
|
('provider_reference', '=', invoice_id),
|
||||||
|
], limit=1)
|
||||||
|
if other_tx:
|
||||||
|
raise ValidationError(_(
|
||||||
|
"BTCPay: The invoice is already linked to another transaction."
|
||||||
|
))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _validate_invoice_binding(
|
||||||
|
cls, tx_sudo, incoming_invoice_id, incoming_order_id, fetched_invoice):
|
||||||
|
"""Bind the untrusted IPN payload to one authenticated invoice and transaction."""
|
||||||
|
if not isinstance(fetched_invoice, dict):
|
||||||
|
raise ValidationError(_("BTCPay: Received an invalid invoice response."))
|
||||||
|
|
||||||
|
fetched_invoice_id = fetched_invoice.get('id')
|
||||||
|
fetched_order_id = fetched_invoice.get('orderId')
|
||||||
|
if not (
|
||||||
|
incoming_invoice_id
|
||||||
|
and incoming_invoice_id == fetched_invoice_id
|
||||||
|
and incoming_invoice_id == tx_sudo.btcpay_invoiceId
|
||||||
|
and incoming_invoice_id == tx_sudo.provider_reference
|
||||||
|
):
|
||||||
|
raise ValidationError(_(
|
||||||
|
"BTCPay: The notification invoice does not match the transaction."
|
||||||
|
))
|
||||||
|
if not (
|
||||||
|
incoming_order_id
|
||||||
|
and incoming_order_id == fetched_order_id
|
||||||
|
and incoming_order_id == tx_sudo.reference
|
||||||
|
):
|
||||||
|
raise ValidationError(_(
|
||||||
|
"BTCPay: The notification order does not match the transaction."
|
||||||
|
))
|
||||||
|
cls._ensure_invoice_id_is_unique(tx_sudo, incoming_invoice_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _find_transaction_by_invoice_id(cls, invoice_id):
|
||||||
|
"""Find exactly one BTCPay transaction from its stored invoice ID."""
|
||||||
|
if not isinstance(invoice_id, str) or not invoice_id:
|
||||||
|
raise ValidationError(_("BTCPay: Missing invoice ID in notification data."))
|
||||||
|
|
||||||
|
txs_sudo = request.env['payment.transaction'].sudo().search([
|
||||||
|
('provider_code', '=', 'btcpayserver'),
|
||||||
|
('btcpay_invoiceId', '=', invoice_id),
|
||||||
|
], limit=2)
|
||||||
|
if len(txs_sudo) != 1:
|
||||||
|
raise ValidationError(_(
|
||||||
|
"BTCPay: No unique transaction found matching the invoice ID."
|
||||||
|
))
|
||||||
|
return txs_sudo
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _validate_invoice_amount(tx_sudo, invoice):
|
||||||
|
"""Reject reuse of an invoice with a different amount or currency."""
|
||||||
|
try:
|
||||||
|
amount = float(invoice.get('price'))
|
||||||
|
except (AttributeError, TypeError, ValueError):
|
||||||
|
amount = None
|
||||||
|
if (
|
||||||
|
amount is None
|
||||||
|
or invoice.get('currency') != tx_sudo.currency_id.name
|
||||||
|
or tx_sudo.currency_id.compare_amounts(amount, tx_sudo.amount) != 0
|
||||||
|
):
|
||||||
|
raise ValidationError(_(
|
||||||
|
"BTCPay: The existing invoice amount or currency does not match the transaction."
|
||||||
|
))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _validate_existing_invoice(cls, tx_sudo, invoice_id, invoice):
|
||||||
|
"""Validate a stored invoice before redirecting the customer to it."""
|
||||||
|
cls._validate_invoice_binding(
|
||||||
|
tx_sudo, invoice_id, tx_sudo.reference, invoice,
|
||||||
|
)
|
||||||
|
if not isinstance(invoice.get('status'), str) or not invoice['status']:
|
||||||
|
raise ValidationError(_("BTCPay: The existing invoice response is incomplete."))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _validate_created_invoice(cls, tx_sudo, invoice):
|
||||||
|
"""Validate a newly created invoice before storing its identity."""
|
||||||
|
if not isinstance(invoice, dict):
|
||||||
|
raise ValidationError(_("BTCPay: Received an invalid invoice response."))
|
||||||
|
invoice_id = invoice.get('id')
|
||||||
|
if (
|
||||||
|
not isinstance(invoice_id, str)
|
||||||
|
or not invoice_id
|
||||||
|
or not isinstance(invoice.get('url'), str)
|
||||||
|
or not invoice['url']
|
||||||
|
or invoice.get('orderId') not in (None, tx_sudo.reference)
|
||||||
|
):
|
||||||
|
raise ValidationError(_("BTCPay: The created invoice response is incomplete."))
|
||||||
|
cls._ensure_invoice_id_is_unique(tx_sudo, invoice_id)
|
||||||
|
|
||||||
|
@http.route(_checkout_url, type='http', auth='public', methods=['POST'],
|
||||||
|
csrf=False, website=True)
|
||||||
def checkout(self, **data):
|
def checkout(self, **data):
|
||||||
|
""" Create the BTCPay invoice and redirect the buyer to it.
|
||||||
|
|
||||||
|
The redirect form only carries the transaction reference. The invoice
|
||||||
|
price, currency and buyer details are read from the transaction
|
||||||
|
server-side and are never taken from the (client-controlled) request
|
||||||
|
data, so a buyer cannot have an invoice created for a tampered amount.
|
||||||
|
"""
|
||||||
|
_logger.info("BTCPay: checkout request for transaction %s", data.get('reference'))
|
||||||
|
tx_sudo = request.env['payment.transaction'].sudo()._get_tx_from_notification_data(
|
||||||
|
'btcpayserver', data)
|
||||||
|
self._lock_transaction(tx_sudo)
|
||||||
|
if tx_sudo.state != 'draft':
|
||||||
|
# Already processed (e.g. double submission): fall back to the
|
||||||
|
# generic payment status page.
|
||||||
|
return request.redirect('/payment/status')
|
||||||
|
|
||||||
_logger.info("CHECKOUT: notification received from BTCPay with data:\n%s", pprint.pformat(data))
|
|
||||||
tx_sudo = request.env['payment.transaction'].sudo()._get_tx_from_notification_data('btcpayserver', data)
|
|
||||||
provider = tx_sudo.provider_id
|
provider = tx_sudo.provider_id
|
||||||
notification_url = str(data.get('notify_url')).replace("http://", "https://")
|
base_url = provider.get_base_url()
|
||||||
base_url = request.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
client = self._btcpay_client(provider)
|
||||||
redirect_url = urls.url_join(base_url, self._return_url)
|
|
||||||
client = BTCPayClient(host=provider.btcpay_location, pem=provider.btcpay_privateKey, tokens={provider.btcpay_facade: provider.btcpay_token})
|
|
||||||
invoice = client.create_invoice(
|
|
||||||
{"price": data.get('amount'),
|
|
||||||
"currency": data.get('currency_id'),
|
|
||||||
"orderId": data.get('reference'),
|
|
||||||
"token": provider.btcpay_token,
|
|
||||||
"redirectURL": redirect_url,
|
|
||||||
"notificationURL": notification_url,
|
|
||||||
"extendedNotifications": True,
|
|
||||||
"buyer": {"email": data.get('email') or 'noemailavailable@example.com',
|
|
||||||
"name": data.get('name'),
|
|
||||||
"address1": data.get('street'),
|
|
||||||
"locality": data.get('city'),
|
|
||||||
"postalCode": data.get('zip'),
|
|
||||||
"country": data.get('country'),
|
|
||||||
"notify": False}})
|
|
||||||
_logger.info('Invoice %s \n NOTIFY URL: %s', invoice, notification_url)
|
|
||||||
return werkzeug.utils.redirect(invoice['url'])
|
|
||||||
|
|
||||||
@http.route(_notify_url, type='json', auth='public', csrf=False)
|
# A browser retry must not create a second payable invoice. Reuse the
|
||||||
|
# stored invoice while it remains payable. If it expired, the new ID is
|
||||||
|
# stored before the lock is released, so late notifications for the old
|
||||||
|
# invoice can no longer select this transaction.
|
||||||
|
if tx_sudo.btcpay_invoiceId:
|
||||||
|
existing_invoice = client.get_invoice(tx_sudo.btcpay_invoiceId)
|
||||||
|
self._validate_existing_invoice(
|
||||||
|
tx_sudo, tx_sudo.btcpay_invoiceId, existing_invoice,
|
||||||
|
)
|
||||||
|
if existing_invoice['status'].lower() != 'expired':
|
||||||
|
self._validate_invoice_amount(tx_sudo, existing_invoice)
|
||||||
|
if (
|
||||||
|
not isinstance(existing_invoice.get('url'), str)
|
||||||
|
or not existing_invoice['url']
|
||||||
|
):
|
||||||
|
raise ValidationError(_(
|
||||||
|
"BTCPay: The existing invoice response is incomplete."
|
||||||
|
))
|
||||||
|
_logger.info(
|
||||||
|
"BTCPay: reusing invoice %s for transaction %s",
|
||||||
|
tx_sudo.btcpay_invoiceId, tx_sudo.reference,
|
||||||
|
)
|
||||||
|
return request.redirect(existing_invoice['url'], local=False)
|
||||||
|
|
||||||
|
invoice = client.create_invoice({
|
||||||
|
"price": tx_sudo.amount,
|
||||||
|
"currency": tx_sudo.currency_id.name,
|
||||||
|
"orderId": tx_sudo.reference,
|
||||||
|
"token": provider.btcpay_token,
|
||||||
|
"redirectURL": urls.url_join(base_url, self._return_url),
|
||||||
|
"notificationURL": urls.url_join(base_url, self._notify_url),
|
||||||
|
"extendedNotifications": True,
|
||||||
|
"buyer": {
|
||||||
|
"email": tx_sudo.partner_email or 'noemailavailable@example.com',
|
||||||
|
"name": tx_sudo.partner_name,
|
||||||
|
"address1": tx_sudo.partner_address,
|
||||||
|
"locality": tx_sudo.partner_city,
|
||||||
|
"postalCode": tx_sudo.partner_zip,
|
||||||
|
"country": tx_sudo.partner_country_id.code,
|
||||||
|
"notify": False,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
self._validate_created_invoice(tx_sudo, invoice)
|
||||||
|
tx_sudo.write({
|
||||||
|
'btcpay_invoiceId': invoice['id'],
|
||||||
|
'provider_reference': invoice['id'],
|
||||||
|
})
|
||||||
|
_logger.info("BTCPay: created invoice %s for transaction %s",
|
||||||
|
invoice['id'], tx_sudo.reference)
|
||||||
|
return request.redirect(invoice['url'], local=False)
|
||||||
|
|
||||||
|
@http.route(_notify_url, type='json', auth='public', methods=['POST'], csrf=False)
|
||||||
def btcpay_ipn(self, **post):
|
def btcpay_ipn(self, **post):
|
||||||
""" BTCPay IPN. """
|
""" BTCPay IPN. """
|
||||||
_logger.info('BTCPAY IPN RECEIVED... ')
|
_logger.info('BTCPay: IPN received')
|
||||||
data = json.loads(request.httprequest.data)
|
|
||||||
_logger.info("%s", pprint.pformat(data))
|
|
||||||
try:
|
try:
|
||||||
notification_data = {"reference": data['data']['orderId'],
|
data = json.loads(request.httprequest.data)
|
||||||
"invoiceID": data['data']['id']}
|
except (json.JSONDecodeError, TypeError, UnicodeDecodeError):
|
||||||
# Check the origin and integrity of the notification
|
_logger.warning("BTCPay: received malformed JSON notification data")
|
||||||
tx_sudo = request.env['payment.transaction'].sudo()._get_tx_from_notification_data('btcpayserver', notification_data)
|
return ''
|
||||||
provider = tx_sudo.provider_id
|
|
||||||
client = BTCPayClient(host=provider.btcpay_location, pem=provider.btcpay_privateKey,
|
|
||||||
tokens={provider.btcpay_facade: provider.btcpay_token})
|
|
||||||
|
|
||||||
fetched_invoice = client.get_invoice(notification_data['invoiceID'])
|
try:
|
||||||
_logger.info('fetched_invoice = %s',pprint.pformat(fetched_invoice))
|
ipn_data = data.get('data') if isinstance(data, dict) else None
|
||||||
|
if not isinstance(ipn_data, dict):
|
||||||
|
raise ValidationError(_("BTCPay: Invalid notification data."))
|
||||||
|
incoming_invoice_id = ipn_data.get('id')
|
||||||
|
incoming_order_id = ipn_data.get('orderId')
|
||||||
|
|
||||||
notification_data = {"reference": fetched_invoice['orderId'],
|
# Never use the untrusted order ID to select a transaction. The
|
||||||
"status": fetched_invoice['status'],
|
# stored invoice ID is the lookup key.
|
||||||
"invoiceID": fetched_invoice['id'],
|
tx_sudo = self._find_transaction_by_invoice_id(incoming_invoice_id)
|
||||||
"txid": fetched_invoice['url']}
|
client = self._btcpay_client(tx_sudo.provider_id)
|
||||||
|
|
||||||
|
# Fetching through the authenticated API establishes the invoice's
|
||||||
|
# current state. Lock afterwards and re-check the stored ID so a
|
||||||
|
# concurrent checkout cannot replace it between lookup and use.
|
||||||
|
fetched_invoice = client.get_invoice(incoming_invoice_id)
|
||||||
|
self._lock_transaction(tx_sudo)
|
||||||
|
self._validate_invoice_binding(
|
||||||
|
tx_sudo, incoming_invoice_id, incoming_order_id, fetched_invoice,
|
||||||
|
)
|
||||||
|
_logger.info(
|
||||||
|
"BTCPay: verified invoice %s with status %s for transaction %s",
|
||||||
|
fetched_invoice.get('id'), fetched_invoice.get('status'), tx_sudo.reference,
|
||||||
|
)
|
||||||
|
|
||||||
|
notification_data = {
|
||||||
|
"reference": tx_sudo.reference,
|
||||||
|
"status": fetched_invoice.get('status'),
|
||||||
|
"invoiceID": incoming_invoice_id,
|
||||||
|
"txid": fetched_invoice.get('url'),
|
||||||
|
# Settled amount and currency, verified against the transaction
|
||||||
|
# before it is set done.
|
||||||
|
"amount": fetched_invoice.get('price'),
|
||||||
|
"currency": fetched_invoice.get('currency'),
|
||||||
|
}
|
||||||
|
|
||||||
# Handle the notification data
|
# Handle the notification data
|
||||||
tx_sudo._handle_notification_data('btcpayserver', notification_data)
|
tx_sudo._handle_notification_data('btcpayserver', notification_data)
|
||||||
except ValidationError: # Acknowledge the notification to avoid getting spammed
|
except ValidationError as error: # Acknowledge rejected notifications without retries.
|
||||||
_logger.exception("Unable to handle the notification data; skipping to acknowledge")
|
_logger.warning("BTCPay: rejected notification: %s", error)
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@http.route(_return_url, type='http', auth="public", methods=['GET'], crsf=False, save_session=False)
|
@http.route(_return_url, type='http', auth="public", methods=['GET'],
|
||||||
|
csrf=False, save_session=False)
|
||||||
def btcpay_return_from_redirect(self, **data):
|
def btcpay_return_from_redirect(self, **data):
|
||||||
""" BTCPay return
|
""" BTCPay return
|
||||||
|
|
||||||
@@ -109,4 +300,4 @@ class BTCPayController(http.Controller):
|
|||||||
IPN anyway, so just show the user the order confirmation / payment status page.
|
IPN anyway, so just show the user the order confirmation / payment status page.
|
||||||
"""
|
"""
|
||||||
_logger.info("BTCPay: user returned to shop after payment")
|
_logger.info("BTCPay: user returned to shop after payment")
|
||||||
return request.redirect('/payment/status')
|
return request.redirect('/payment/status')
|
||||||
@@ -1,13 +1,26 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo noupdate="1">
|
<odoo noupdate="1">
|
||||||
|
|
||||||
|
<record id="payment_method_btcpayserver" model="payment.method">
|
||||||
|
<field name="name">Pay with Bitcoin / Lightning Network</field>
|
||||||
|
<field name="code">btcpayserver</field>
|
||||||
|
<field name="active">True</field>
|
||||||
|
<field name="image" type="base64" file="payment_btcpayserver/static/description/icon.png"/>
|
||||||
|
<field name="support_tokenization">False</field>
|
||||||
|
<field name="support_express_checkout">False</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
<record id="payment_provider_btcpayserver" model="payment.provider">
|
<record id="payment_provider_btcpayserver" model="payment.provider">
|
||||||
<field name="name">BTCPay payments</field>
|
<field name="name">BTCPay Server payments</field>
|
||||||
<field name="display_as">Pay with Bitcoin / Lightning Network</field>
|
|
||||||
<field name="code">btcpayserver</field>
|
<field name="code">btcpayserver</field>
|
||||||
<field name="image_128" type="base64" file="payment_btcpayserver/static/description/icon.png"/>
|
<field name="image_128" type="base64" file="payment_btcpayserver/static/description/icon.png"/>
|
||||||
<field name="module_id" ref="base.module_payment_btcpayserver"/>
|
|
||||||
<field name="redirect_form_view_id" ref="redirect_form"/>
|
<field name="redirect_form_view_id" ref="redirect_form"/>
|
||||||
|
<field name="module_id" ref="base.module_payment_btcpayserver"/>
|
||||||
|
<field name="payment_method_ids"
|
||||||
|
eval="[Command.set([
|
||||||
|
ref('payment_btcpayserver.payment_method_btcpayserver'),
|
||||||
|
])]"
|
||||||
|
/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from odoo import _, api, fields, models
|
from odoo import api, fields, models
|
||||||
from .libs.client import BTCPayClient
|
from .libs.client import BTCPayClient
|
||||||
from .libs import crypto
|
from .libs import crypto
|
||||||
|
|
||||||
@@ -20,15 +20,9 @@ class PaymentProvider(models.Model):
|
|||||||
btcpay_privateKey = fields.Text(string='Private Key', help='Private Key for BTCPay Client. Leave empty, will be autogenerated during pairing.')
|
btcpay_privateKey = fields.Text(string='Private Key', help='Private Key for BTCPay Client. Leave empty, will be autogenerated during pairing.')
|
||||||
btcpay_facade = fields.Char(string='Facade', help='Token facade type: merchant/pos/payroll. Keep merchant', default='merchant')
|
btcpay_facade = fields.Char(string='Facade', help='Token facade type: merchant/pos/payroll. Keep merchant', default='merchant')
|
||||||
|
|
||||||
def create(self, values_list):
|
|
||||||
if self.code == 'btcpayserver':
|
|
||||||
values_list['btcpay_privateKey'] = crypto.generate_privakey()
|
|
||||||
|
|
||||||
return super(PaymentProvider, self).create(values_list)
|
|
||||||
|
|
||||||
@api.onchange('btcpay_pairingCode')
|
@api.onchange('btcpay_pairingCode')
|
||||||
def _onchange_pairingCode(self):
|
def _onchange_pairingCode(self):
|
||||||
if not self.btcpay_token and self.code == 'btcpayserver' and not self.btcpay_pairingCode == '':
|
if not self.btcpay_token and self.code == 'btcpayserver' and self.btcpay_pairingCode:
|
||||||
#_logger.info("ONCHANGE PAIRING CODE***SELF: %s %s %s", self.btcpay_location, self.btcpay_privateKey, self.btcpay_pairingCode)
|
#_logger.info("ONCHANGE PAIRING CODE***SELF: %s %s %s", self.btcpay_location, self.btcpay_privateKey, self.btcpay_pairingCode)
|
||||||
self.btcpay_privateKey = crypto.generate_privkey()
|
self.btcpay_privateKey = crypto.generate_privkey()
|
||||||
client = BTCPayClient(host=self.btcpay_location, pem=self.btcpay_privateKey)
|
client = BTCPayClient(host=self.btcpay_location, pem=self.btcpay_privateKey)
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import logging
|
import logging
|
||||||
import pprint
|
import pprint
|
||||||
from werkzeug import urls
|
|
||||||
|
|
||||||
from odoo import _, api, fields, models
|
from odoo import _, fields, models
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
from odoo.addons.payment import utils as payment_utils
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -16,12 +13,14 @@ class PaymentTransaction(models.Model):
|
|||||||
btcpay_invoiceId = fields.Char("Invoice Id")
|
btcpay_invoiceId = fields.Char("Invoice Id")
|
||||||
btcpay_txid = fields.Char("Transaction Id")
|
btcpay_txid = fields.Char("Transaction Id")
|
||||||
btcpay_status = fields.Char("Transaction Status")
|
btcpay_status = fields.Char("Transaction Status")
|
||||||
api_url = '/btcpay/checkout'
|
|
||||||
checkout_url = '/btcpay/checkout'
|
|
||||||
notify_url = 'payment/btcpay/ipn'
|
|
||||||
|
|
||||||
def _get_specific_rendering_values(self, processing_values):
|
def _get_specific_rendering_values(self, processing_values):
|
||||||
""" Override of payment to return Specific rendering values.
|
""" Override of payment to return BTCPay-specific rendering values.
|
||||||
|
|
||||||
|
The redirect form only needs to carry the transaction reference: the
|
||||||
|
BTCPay invoice is built server-side (see the checkout controller) from
|
||||||
|
the transaction, so no amount, currency or buyer data is sent through
|
||||||
|
the browser.
|
||||||
|
|
||||||
Note: self.ensure_one() from `_get_processing_values`
|
Note: self.ensure_one() from `_get_processing_values`
|
||||||
|
|
||||||
@@ -29,32 +28,13 @@ class PaymentTransaction(models.Model):
|
|||||||
:return: The dict of provider-specific processing values
|
:return: The dict of provider-specific processing values
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
|
|
||||||
res = super()._get_specific_rendering_values(processing_values)
|
res = super()._get_specific_rendering_values(processing_values)
|
||||||
|
|
||||||
if self.provider_code != 'btcpayserver':
|
if self.provider_code != 'btcpayserver':
|
||||||
return res
|
return res
|
||||||
|
|
||||||
base_url = self.provider_id.get_base_url()
|
|
||||||
_logger.info('Hola! API URL: %s', processing_values)
|
|
||||||
partner_first_name, partner_last_name = payment_utils.split_partner_name(self.partner_name)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'address1': self.partner_address,
|
'api_url': '/btcpay/checkout',
|
||||||
'amount': self.amount,
|
'reference': self.reference,
|
||||||
'city': self.partner_city,
|
|
||||||
'country': self.partner_country_id.code,
|
|
||||||
'currency_code': self.currency_id.name,
|
|
||||||
'email': self.partner_email,
|
|
||||||
'first_name': partner_first_name,
|
|
||||||
'item_name': f"{self.company_id.name}: {self.reference}",
|
|
||||||
'item_number': self.reference,
|
|
||||||
'last_name': partner_last_name,
|
|
||||||
'lc': self.partner_lang,
|
|
||||||
'state': self.partner_state_id.name,
|
|
||||||
'zip_code': self.partner_zip,
|
|
||||||
'api_url': self.checkout_url,
|
|
||||||
'notify_url': base_url + self.notify_url,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_tx_from_notification_data(self, provider_code, notification_data):
|
def _get_tx_from_notification_data(self, provider_code, notification_data):
|
||||||
@@ -67,7 +47,6 @@ class PaymentTransaction(models.Model):
|
|||||||
:raise: ValidationError if the data match no transaction
|
:raise: ValidationError if the data match no transaction
|
||||||
"""
|
"""
|
||||||
tx = super()._get_tx_from_notification_data(provider_code, notification_data)
|
tx = super()._get_tx_from_notification_data(provider_code, notification_data)
|
||||||
_logger.info('GET TX FROM NOTIFICATION Notification_data %s', pprint.pformat(notification_data))
|
|
||||||
if provider_code != 'btcpayserver' or len(tx) == 1:
|
if provider_code != 'btcpayserver' or len(tx) == 1:
|
||||||
return tx
|
return tx
|
||||||
|
|
||||||
@@ -79,22 +58,13 @@ class PaymentTransaction(models.Model):
|
|||||||
)
|
)
|
||||||
return tx
|
return tx
|
||||||
|
|
||||||
def _handle_notification_data(self, provider_code, notification_data):
|
|
||||||
""" Match the transaction with the notification data, update its state and return it.
|
|
||||||
|
|
||||||
:param str provider_code: The code of the provider handling the transaction.
|
|
||||||
:param dict notification_data: The notification data sent by the provider.
|
|
||||||
:return: The transaction.
|
|
||||||
:rtype: recordset of `payment.transaction`
|
|
||||||
"""
|
|
||||||
tx = self._get_tx_from_notification_data(provider_code, notification_data)
|
|
||||||
tx._process_notification_data(notification_data)
|
|
||||||
tx._execute_callback()
|
|
||||||
return tx
|
|
||||||
|
|
||||||
def _process_notification_data(self, notification_data):
|
def _process_notification_data(self, notification_data):
|
||||||
""" Override of payment to process the transaction based on BTCPay data.
|
""" Override of payment to process the transaction based on BTCPay data.
|
||||||
|
|
||||||
|
Only the transaction state is updated here; confirming the related
|
||||||
|
sale order, creating the payment and sending the confirmation email are
|
||||||
|
handled by the generic post-processing of the `payment` framework.
|
||||||
|
|
||||||
Note: self.ensure_one()
|
Note: self.ensure_one()
|
||||||
|
|
||||||
:param dict notification_data: The notification data sent by the provider
|
:param dict notification_data: The notification data sent by the provider
|
||||||
@@ -105,31 +75,69 @@ class PaymentTransaction(models.Model):
|
|||||||
if self.provider_code != 'btcpayserver':
|
if self.provider_code != 'btcpayserver':
|
||||||
return
|
return
|
||||||
|
|
||||||
_logger.info("_process_notification_data %s", pprint.pformat(notification_data))
|
_logger.info(
|
||||||
txn_id = notification_data.get('reference')
|
"processing notification data for BTCPay transaction with reference %s:\n%s",
|
||||||
if not all(txn_id):
|
self.reference, pprint.pformat(notification_data))
|
||||||
|
if not notification_data.get('reference'):
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
"BTCPay: " + _("Missing value for txn_id (%(txn_id)s)).", txn_id=txn_id))
|
"BTCPay: " + _("Received notification data with missing reference."))
|
||||||
|
|
||||||
self.provider_reference = txn_id
|
if notification_data.get('invoiceID'):
|
||||||
self.btcpay_txid = notification_data.get('txid')
|
self.btcpay_invoiceId = notification_data['invoiceID']
|
||||||
self.btcpay_status = notification_data.get('status')
|
self.provider_reference = notification_data['invoiceID']
|
||||||
|
if notification_data.get('txid'):
|
||||||
|
self.btcpay_txid = notification_data['txid']
|
||||||
|
status = notification_data.get('status')
|
||||||
|
self.btcpay_status = status
|
||||||
|
|
||||||
if self.btcpay_status in ['paid','processing']:
|
if status in ('paid', 'processing'):
|
||||||
self._set_pending(state_message=notification_data.get('pending_reason'))
|
self._set_pending(state_message=notification_data.get('pending_reason'))
|
||||||
elif self.btcpay_status in ['confirmed', 'complete']:
|
elif status in ('confirmed', 'complete'):
|
||||||
self._set_done()
|
if self._btcpay_verify_amount(notification_data):
|
||||||
confirmed_orders = self._check_amount_and_confirm_order()
|
self._set_done()
|
||||||
confirmed_orders._send_order_confirmation_mail()
|
elif status == 'new':
|
||||||
elif self.btcpay_status in ['new']:
|
pass # Invoice created on BTCPay, waiting for the buyer to pay.
|
||||||
self.btcpay_invoiceId = notification_data.get('invoiceID')
|
elif status in ('expired', 'cancel', 'cancelled'):
|
||||||
elif self.btcpay_status in ['cancel','cancelled']:
|
self._set_canceled(
|
||||||
self._set_canceled()
|
state_message="BTCPay: " + _("Invoice status: %s.", status))
|
||||||
elif self.btcpay_status in ['invalid']:
|
elif status == 'invalid':
|
||||||
_logger.info(
|
_logger.warning(
|
||||||
"received data with invalid payment status (%s) for transaction with reference %s",
|
"received invalid payment status for transaction with reference %s", self.reference)
|
||||||
self.btcpay_status, self.reference
|
|
||||||
)
|
|
||||||
self._set_error(
|
self._set_error(
|
||||||
"BTCPay: " + _("Received data with invalid payment status: %s", self.btcpay_status)
|
"BTCPay: " + _("Received data with invalid payment status: %s.", status))
|
||||||
)
|
else:
|
||||||
|
_logger.warning(
|
||||||
|
"received unrecognized payment status (%s) for transaction with reference %s",
|
||||||
|
status, self.reference)
|
||||||
|
self._set_error(
|
||||||
|
"BTCPay: " + _("Received data with unknown payment status: %s.", status))
|
||||||
|
|
||||||
|
def _btcpay_verify_amount(self, notification_data):
|
||||||
|
""" Check that the settled BTCPay invoice matches the transaction.
|
||||||
|
|
||||||
|
The BTCPay invoice is created from the transaction, but its settled
|
||||||
|
amount and currency are read back from BTCPay on notification and
|
||||||
|
compared here so that a tampered or mismatched invoice can never
|
||||||
|
confirm the transaction (and thus the order). On mismatch the
|
||||||
|
transaction is set in error and ``False`` is returned.
|
||||||
|
|
||||||
|
:param dict notification_data: The notification data sent by the provider
|
||||||
|
:return: Whether the settled amount and currency match the transaction
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
currency = notification_data.get('currency')
|
||||||
|
try:
|
||||||
|
amount = float(notification_data.get('amount'))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
amount = None
|
||||||
|
if (amount is None
|
||||||
|
or currency != self.currency_id.name
|
||||||
|
or self.currency_id.compare_amounts(amount, self.amount) != 0):
|
||||||
|
self._set_error("BTCPay: " + _(
|
||||||
|
"The settled amount (%(amount)s %(currency)s) does not match the "
|
||||||
|
"expected amount (%(expected)s %(expected_currency)s).",
|
||||||
|
amount=notification_data.get('amount'), currency=currency,
|
||||||
|
expected=self.amount, expected_currency=self.currency_id.name))
|
||||||
|
return False
|
||||||
|
return True
|
||||||
@@ -1 +1 @@
|
|||||||
btcpay-python
|
ecdsa
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 155 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 154 KiB |
@@ -7,7 +7,7 @@
|
|||||||
<h3 class="oe_slogan">This module allows you to accept Bitcoin / Lightning Network (and other cryptocurrency) payments in your Odoo e-commerce store.</h3>
|
<h3 class="oe_slogan">This module allows you to accept Bitcoin / Lightning Network (and other cryptocurrency) payments in your Odoo e-commerce store.</h3>
|
||||||
<div class="oe_span12">
|
<div class="oe_span12">
|
||||||
<div class="oe_bg_img">
|
<div class="oe_bg_img">
|
||||||
<img src="BTCPay-Odoo-16-featured.png" class="oe_picture oe_screenshot">
|
<img src="BTCPay-Odoo-17-featured.png" class="oe_picture oe_screenshot">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,11 +65,11 @@
|
|||||||
<div class="oe_span12">
|
<div class="oe_span12">
|
||||||
<p class="oe_mt32">
|
<p class="oe_mt32">
|
||||||
<ul>
|
<ul>
|
||||||
<li>Odoo 16 running</li>
|
<li>Odoo 17 running</li>
|
||||||
<li>eCommerce module enabled</li>
|
<li>eCommerce module enabled</li>
|
||||||
<li>You have a BTCPay Server version 1.10.0 or later, either <a href="https://docs.btcpayserver.org/Deployment/">self-hosted</a> or <a href="https://docs.btcpayserver.org/Deployment/ThirdPartyHosting/">hosted by a third-party</a></li>
|
<li>You have a BTCPay Server version 1.10.0 or later, either <a href="https://docs.btcpayserver.org/Deployment/">self-hosted</a> or <a href="https://docs.btcpayserver.org/Deployment/ThirdPartyHosting/">hosted by a third-party</a></li>
|
||||||
<li><a href="https://docs.btcpayserver.org/RegisterAccount/" class="">You've a registered account on the instance</a></li>
|
<li><a href="https://docs.btcpayserver.org/RegisterAccount/" class="">You've a registered account on the instance</a></li>
|
||||||
<li><a href="https://docs.btcpayserver.org/CreateStore/" class="">You've a BTCPay store on the instance</a></li>
|
<li><a href="https://docs.btcpayserver.org/CreateStore/" class="">You've a BTCPay store on the instance</a></li>
|
||||||
<li><a href="https://docs.btcpayserver.org/WalletSetup/" class="">You've a wallet connected to your store</a></li>
|
<li><a href="https://docs.btcpayserver.org/WalletSetup/" class="">You've a wallet connected to your store</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
<p class="oe_mt32">
|
<p class="oe_mt32">
|
||||||
<ul>
|
<ul>
|
||||||
<li>Clone our [repository](https://github.com/btcpayserver/odoo) or download the .zip from the [releases page](https://github.com/btcpayserver/odoo/releases)
|
<li>Clone our [repository](https://github.com/btcpayserver/odoo) or download the .zip from the [releases page](https://github.com/btcpayserver/odoo/releases)
|
||||||
<li>Make sure you are on branch `16.0` or downloaded a release tagged with version v16.x
|
<li>Make sure you are on branch `17.0` or downloaded a release tagged with version v17.x
|
||||||
<li>Place the `payment_btcpayserver` directory in your Odoo addons directory
|
<li>Place the `payment_btcpayserver` directory in your Odoo addons directory
|
||||||
<li>Install dependencies by running `pip install -r requirements.txt` (from inside the `payment_btcpayserver` directory)
|
<li>Install dependencies by running `pip install -r requirements.txt` (from inside the `payment_btcpayserver` directory)
|
||||||
<li>Restart Odoo
|
<li>Restart Odoo
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
|
|
||||||
<section class="oe_container">
|
<section class="oe_container">
|
||||||
<div class="oe_row oe_spaced">
|
<div class="oe_row oe_spaced">
|
||||||
<h2 class="oe_slogan">How it looks like?</h2>
|
<h2 class="oe_slogan">How it looks like?</h2>
|
||||||
<div class="oe_span6">
|
<div class="oe_span6">
|
||||||
<div class="oe_bg_img">
|
<div class="oe_bg_img">
|
||||||
<img src="BTCPayLooksLike.png" class="oe_picture oe_screenshot">
|
<img src="BTCPayLooksLike.png" class="oe_picture oe_screenshot">
|
||||||
@@ -171,7 +171,7 @@
|
|||||||
|
|
||||||
<section class="oe_container">
|
<section class="oe_container">
|
||||||
<div class="oe_row oe_spaced">
|
<div class="oe_row oe_spaced">
|
||||||
<h2 class="oe_slogan">Transaction BTCPay Details</h2>
|
<h2 class="oe_slogan">Transaction BTCPay Details</h2>
|
||||||
<div class="oe_span6">
|
<div class="oe_span6">
|
||||||
<p class="oe_mt32">
|
<p class="oe_mt32">
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@@ -1,25 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
|
<!-- The redirect form is auto-submitted by the payment framework. It only
|
||||||
|
carries the transaction reference; the BTCPay invoice is built
|
||||||
|
server-side from the transaction (amount and currency cannot be
|
||||||
|
tampered with from the browser). -->
|
||||||
<template id="redirect_form">
|
<template id="redirect_form">
|
||||||
<form t-att-action="api_url" method="post">
|
<form t-att-action="api_url" method="post">
|
||||||
<input type="hidden" name="reference" t-att-value="item_number"/>
|
<input type="hidden" name="reference" t-att-value="reference"/>
|
||||||
<input type="hidden" name="name" t-att-value="first_name"/>
|
|
||||||
<input type="hidden" name="email" t-att-value="email"/>
|
|
||||||
<input type="hidden" name="street" t-att-value="address1"/>
|
|
||||||
<input type="hidden" name="zip" t-att-value="zip_code"/>
|
|
||||||
<input type="hidden" name="city" t-att-value="city"/>
|
|
||||||
<input type="hidden" name="currency_id" t-att-value="currency_code"/>
|
|
||||||
<input type="hidden" name="country" t-att-value="country"/>
|
|
||||||
<input type="hidden" name="custom" t-att-value="item_name"/>
|
|
||||||
<input type="hidden" name="amount" t-att-value="amount"/>
|
|
||||||
<input type="hidden" name="notify_url" t-att-value="notify_url"/>
|
|
||||||
<button type="submit" width="100px" t-att-class="submit_class">
|
|
||||||
<img t-if="not submit_txt" src="/static/description/icon.png"/>
|
|
||||||
<span t-if="submit_txt"> <t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
@@ -7,11 +7,11 @@
|
|||||||
<field name="inherit_id" ref="payment.payment_provider_form"/>
|
<field name="inherit_id" ref="payment.payment_provider_form"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<group name="provider_credentials" position='inside'>
|
<group name="provider_credentials" position='inside'>
|
||||||
<group attrs="{'invisible': [('code', '!=', 'btcpayserver')]}">
|
<group invisible="code != 'btcpayserver'">
|
||||||
<field name="btcpay_location"/>
|
<field name="btcpay_location"/>
|
||||||
<field name="btcpay_pairingCode"/>
|
<field name="btcpay_pairingCode"/>
|
||||||
</group>
|
</group>
|
||||||
<group attrs="{'invisible': [('code', '!=', 'btcpayserver')]}">
|
<group invisible="code != 'btcpayserver'">
|
||||||
<field name="btcpay_token"/>
|
<field name="btcpay_token"/>
|
||||||
<field name="btcpay_privateKey"/>
|
<field name="btcpay_privateKey"/>
|
||||||
<field name="btcpay_facade"/>
|
<field name="btcpay_facade"/>
|
||||||
|
|||||||