Add PayDunya payment provider integration
- Implemented PayDunya payment provider with necessary models, controllers, and views. - Added configuration files for Docker and Odoo setup. - Included .gitignore for Python and Odoo specific files.
This commit is contained in:
140
.gitignore
vendored
Normal file
140
.gitignore
vendored
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
Pipfile.lock
|
||||||
|
|
||||||
|
# PEP 582
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# IDE settings
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Odoo specific
|
||||||
|
data/odoo-db/
|
||||||
|
*.pyc
|
||||||
|
*~
|
||||||
|
.*.swp
|
||||||
|
addons/*/static/description/icon.png
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
.dockerignore
|
||||||
2
addons/payment_paydunya/__init__.py
Normal file
2
addons/payment_paydunya/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from . import models
|
||||||
|
from . import controllers
|
||||||
17
addons/payment_paydunya/__manifest__.py
Normal file
17
addons/payment_paydunya/__manifest__.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
'name': 'PayDunya Odoo',
|
||||||
|
'version': '1.0.0',
|
||||||
|
'summary': 'PayDunya payment provider',
|
||||||
|
'description': 'Payment acquirer integration for PayDunya',
|
||||||
|
'category': 'Accounting/Payment Providers',
|
||||||
|
'author': 'MMG',
|
||||||
|
'depends': ['payment'],
|
||||||
|
'data': [
|
||||||
|
'data/payment_provider_data.xml',
|
||||||
|
'data/payment_method_data.xml',
|
||||||
|
'views/payment_paydunya_views.xml',
|
||||||
|
'views/payment_paydunya_templates.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'application': False,
|
||||||
|
}
|
||||||
1
addons/payment_paydunya/controllers/__init__.py
Normal file
1
addons/payment_paydunya/controllers/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import main
|
||||||
80
addons/payment_paydunya/controllers/main.py
Normal file
80
addons/payment_paydunya/controllers/main.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
from odoo import http
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PaydunyaController(http.Controller):
|
||||||
|
@http.route('/payment/paydunya/return', type='http', auth='public', methods=['GET'], csrf=False)
|
||||||
|
def paydunya_return(self, **kwargs):
|
||||||
|
"""Handle return from PayDunya after payment attempt."""
|
||||||
|
token = kwargs.get('token') or http.request.params.get('token')
|
||||||
|
if not token:
|
||||||
|
return http.request.redirect('/')
|
||||||
|
|
||||||
|
provider = http.request.env['payment.provider'].sudo().search([('code', '=', 'paydunya')], limit=1)
|
||||||
|
if not provider:
|
||||||
|
_logger.warning('PayDunya return called but no provider found')
|
||||||
|
return http.request.redirect('/')
|
||||||
|
|
||||||
|
# Build check URL using the confirm endpoint
|
||||||
|
base = provider._get_paydunya_api_base()
|
||||||
|
confirm_url = base + '/checkout-invoice/confirm/{}'.format(token)
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'PAYDUNYA-MASTER-KEY': provider.paydunya_master_key or '',
|
||||||
|
'PAYDUNYA-PRIVATE-KEY': provider.paydunya_private_key or '',
|
||||||
|
'PAYDUNYA-TOKEN': provider.paydunya_token or ''
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# GET request to verify status
|
||||||
|
resp = requests.get(confirm_url, headers=headers, timeout=15)
|
||||||
|
data = resp.json()
|
||||||
|
_logger.info('PayDunya confirm response: %s', data)
|
||||||
|
except Exception as e:
|
||||||
|
_logger.exception('Error verifying PayDunya invoice: %s', e)
|
||||||
|
data = {'token': token, 'status': 'failed'}
|
||||||
|
|
||||||
|
# Trigger transaction handling with the confirmed data
|
||||||
|
try:
|
||||||
|
tx_model = http.request.env['payment.transaction'].sudo()
|
||||||
|
handled = tx_model._handle_notification_data(data)
|
||||||
|
if handled:
|
||||||
|
return http.request.redirect('/payment/process')
|
||||||
|
except Exception:
|
||||||
|
_logger.exception('Error handling PayDunya notification data')
|
||||||
|
|
||||||
|
return http.request.redirect('/')
|
||||||
|
|
||||||
|
@http.route('/payment/paydunya/cancel', type='http', auth='public', methods=['GET'], csrf=False)
|
||||||
|
def paydunya_cancel(self, **kwargs):
|
||||||
|
"""Handle cancellation from PayDunya."""
|
||||||
|
token = kwargs.get('token') or http.request.params.get('token')
|
||||||
|
_logger.info('PayDunya payment cancelled: token=%s', token)
|
||||||
|
return http.request.redirect('/shop/cart')
|
||||||
|
|
||||||
|
@http.route('/payment/paydunya/callback', type='http', auth='public', methods=['POST'], csrf=False)
|
||||||
|
def paydunya_callback(self, **kwargs):
|
||||||
|
"""Handle IPN callback from PayDunya.
|
||||||
|
|
||||||
|
PayDunya sends data as application/x-www-form-urlencoded with 'data' key containing JSON.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# The 'data' parameter contains the JSON-encoded transaction info
|
||||||
|
import json
|
||||||
|
data_str = http.request.params.get('data')
|
||||||
|
if data_str:
|
||||||
|
data = json.loads(data_str)
|
||||||
|
_logger.info('PayDunya IPN callback received: %s', data)
|
||||||
|
|
||||||
|
tx_model = http.request.env['payment.transaction'].sudo()
|
||||||
|
tx_model._handle_notification_data(data)
|
||||||
|
except Exception:
|
||||||
|
_logger.exception('Error handling PayDunya callback')
|
||||||
|
|
||||||
|
# Always return 200 OK to PayDunya
|
||||||
|
return 'OK'
|
||||||
|
|
||||||
32
addons/payment_paydunya/data/payment_method_data.xml
Normal file
32
addons/payment_paydunya/data/payment_method_data.xml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<!-- Payment methods for PayDunya provider -->
|
||||||
|
|
||||||
|
<!-- Carte bancaire / Bank Card -->
|
||||||
|
<record id="payment_method_paydunya_card" model="payment.method">
|
||||||
|
<field name="name">Carte bancaire</field>
|
||||||
|
<field name="code">card</field>
|
||||||
|
<field name="primary_payment_method_id" ref="payment.payment_method_card"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Wave -->
|
||||||
|
<record id="payment_method_paydunya_wave" model="payment.method">
|
||||||
|
<field name="name">Wave</field>
|
||||||
|
<field name="code">wave-senegal</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Orange Money / OM -->
|
||||||
|
<record id="payment_method_paydunya_om" model="payment.method">
|
||||||
|
<field name="name">Orange Money</field>
|
||||||
|
<field name="code">orange-money-senegal</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Link methods to PayDunya provider -->
|
||||||
|
<record id="payment_provider_paydunya" model="payment.provider">
|
||||||
|
<field name="payment_method_ids" eval="[(6, 0, [
|
||||||
|
ref('payment_method_paydunya_card'),
|
||||||
|
ref('payment_method_paydunya_wave'),
|
||||||
|
ref('payment_method_paydunya_om')
|
||||||
|
])]"/>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
8
addons/payment_paydunya/data/payment_provider_data.xml
Normal file
8
addons/payment_paydunya/data/payment_provider_data.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="payment_provider_paydunya" model="payment.provider">
|
||||||
|
<field name="name">PayDunya</field>
|
||||||
|
<field name="code">paydunya</field>
|
||||||
|
<field name="sequence">10</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
2
addons/payment_paydunya/models/__init__.py
Normal file
2
addons/payment_paydunya/models/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from . import payment_provider
|
||||||
|
from . import payment_transaction
|
||||||
32
addons/payment_paydunya/models/payment_provider.py
Normal file
32
addons/payment_paydunya/models/payment_provider.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import logging
|
||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentProvider(models.Model):
|
||||||
|
_inherit = 'payment.provider'
|
||||||
|
|
||||||
|
# Override code field to add paydunya option
|
||||||
|
code = fields.Selection(
|
||||||
|
selection_add=[('paydunya', 'PayDunya')],
|
||||||
|
ondelete={'paydunya': 'cascade'}
|
||||||
|
)
|
||||||
|
|
||||||
|
# PayDunya specific credentials
|
||||||
|
paydunya_master_key = fields.Char('PayDunya Master Key')
|
||||||
|
paydunya_public_key = fields.Char('PayDunya Public Key')
|
||||||
|
paydunya_private_key = fields.Char('PayDunya Private Key')
|
||||||
|
paydunya_token = fields.Char('PayDunya Token')
|
||||||
|
environment = fields.Selection([
|
||||||
|
('sandbox', 'Sandbox'),
|
||||||
|
('production', 'Production')
|
||||||
|
], string='Environment', default='sandbox')
|
||||||
|
|
||||||
|
def _get_paydunya_api_base(self):
|
||||||
|
"""Return the correct API base URL for PayDunya based on environment."""
|
||||||
|
self.ensure_one()
|
||||||
|
if self.environment == 'production':
|
||||||
|
return 'https://app.paydunya.com/api/v1'
|
||||||
|
return 'https://app.paydunya.com/sandbox-api/v1'
|
||||||
|
|
||||||
174
addons/payment_paydunya/models/payment_transaction.py
Normal file
174
addons/payment_paydunya/models/payment_transaction.py
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
import hashlib
|
||||||
|
from odoo import models
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentTransaction(models.Model):
|
||||||
|
_inherit = 'payment.transaction'
|
||||||
|
|
||||||
|
def _get_specific_rendering_values(self, **kwargs):
|
||||||
|
"""Create invoice on PayDunya and return rendering values for redirection."""
|
||||||
|
self.ensure_one()
|
||||||
|
provider = False
|
||||||
|
# try common fields used across Odoo versions
|
||||||
|
provider = getattr(self, 'provider_id', False) or getattr(self, 'acquirer_id', False)
|
||||||
|
if not provider:
|
||||||
|
provider = self.env['payment.provider'].search([('code', '=', 'paydunya')], limit=1)
|
||||||
|
if not provider:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
base = provider._get_paydunya_api_base()
|
||||||
|
create_url = base + '/checkout-invoice/create'
|
||||||
|
|
||||||
|
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||||
|
return_url = base_url + '/payment/paydunya/return'
|
||||||
|
cancel_url = base_url + '/payment/paydunya/cancel'
|
||||||
|
callback_url = base_url + '/payment/paydunya/callback'
|
||||||
|
|
||||||
|
# Build payload according to PayDunya documentation
|
||||||
|
# Required: invoice.total_amount, store.name
|
||||||
|
payload = {
|
||||||
|
'invoice': {
|
||||||
|
'total_amount': int(self.amount),
|
||||||
|
'description': self.reference or self.name or 'Payment',
|
||||||
|
},
|
||||||
|
'store': {
|
||||||
|
'name': self.env.company.name or 'Store',
|
||||||
|
},
|
||||||
|
'actions': {
|
||||||
|
'return_url': return_url,
|
||||||
|
'cancel_url': cancel_url,
|
||||||
|
'callback_url': callback_url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add customer info if available
|
||||||
|
if self.partner_id:
|
||||||
|
payload['invoice']['customer'] = {
|
||||||
|
'name': self.partner_id.name or '',
|
||||||
|
'email': self.partner_id.email or '',
|
||||||
|
'phone': self.partner_id.phone or '',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Authentication headers
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'PAYDUNYA-MASTER-KEY': provider.paydunya_master_key or '',
|
||||||
|
'PAYDUNYA-PRIVATE-KEY': provider.paydunya_private_key or '',
|
||||||
|
'PAYDUNYA-TOKEN': provider.paydunya_token or ''
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = requests.post(create_url, json=payload, headers=headers, timeout=15)
|
||||||
|
data = resp.json()
|
||||||
|
|
||||||
|
# Check response_code (success = '00')
|
||||||
|
response_code = data.get('response_code')
|
||||||
|
if response_code != '00':
|
||||||
|
_logger.warning('PayDunya API error: %s - %s', response_code, data.get('response_text'))
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Extract token from response
|
||||||
|
token = data.get('token')
|
||||||
|
# response_text is the checkout URL
|
||||||
|
redirect_url = data.get('response_text')
|
||||||
|
|
||||||
|
if token:
|
||||||
|
# store reference to match notifications
|
||||||
|
self.acquirer_reference = token
|
||||||
|
self._cr.commit()
|
||||||
|
_logger.info('PayDunya invoice created: token=%s, url=%s', token, redirect_url)
|
||||||
|
# Return the template name and rendering values expected by Odoo
|
||||||
|
return {
|
||||||
|
'rendering_template': 'payment_paydunya.paydunya_redirect_form',
|
||||||
|
'rendering_values': {
|
||||||
|
'paydunya_token': token,
|
||||||
|
'redirect_url': redirect_url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
_logger.warning('PayDunya: no token in response: %s', data)
|
||||||
|
except Exception as e:
|
||||||
|
_logger.exception('Error creating PayDunya invoice: %s', e)
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _get_tx_from_notification(self, data):
|
||||||
|
"""Find the transaction corresponding to a PayDunya notification payload."""
|
||||||
|
# Extract invoice data if wrapped
|
||||||
|
if isinstance(data, dict) and 'data' in data:
|
||||||
|
invoice_data = data['data']
|
||||||
|
else:
|
||||||
|
invoice_data = data
|
||||||
|
|
||||||
|
token = invoice_data.get('invoice', {}).get('token') or invoice_data.get('token')
|
||||||
|
if not token:
|
||||||
|
return None
|
||||||
|
tx = self.search([('acquirer_reference', '=', token)], limit=1)
|
||||||
|
return tx or None
|
||||||
|
|
||||||
|
def _handle_notification_data(self, data):
|
||||||
|
"""Handle PayDunya notification payload and update transaction state.
|
||||||
|
|
||||||
|
Validates the hash according to PayDunya documentation (SHA-512 of MASTER-KEY).
|
||||||
|
PayDunya sends data as application/x-www-form-urlencoded with key 'data' containing JSON.
|
||||||
|
"""
|
||||||
|
# Extract the actual data if wrapped
|
||||||
|
if isinstance(data, dict) and 'data' in data:
|
||||||
|
invoice_data = data['data']
|
||||||
|
else:
|
||||||
|
invoice_data = data
|
||||||
|
|
||||||
|
# Verify hash to ensure the callback is from PayDunya
|
||||||
|
provided_hash = invoice_data.get('hash')
|
||||||
|
if provided_hash:
|
||||||
|
provider = self.env['payment.provider'].search([('code', '=', 'paydunya')], limit=1)
|
||||||
|
if provider and provider.paydunya_master_key:
|
||||||
|
expected_hash = hashlib.sha512(
|
||||||
|
provider.paydunya_master_key.encode()
|
||||||
|
).hexdigest()
|
||||||
|
if provided_hash != expected_hash:
|
||||||
|
_logger.warning('PayDunya: Hash mismatch! Possible security issue. Expected %s, got %s',
|
||||||
|
expected_hash, provided_hash)
|
||||||
|
return False
|
||||||
|
|
||||||
|
tx = self._get_tx_from_notification(invoice_data)
|
||||||
|
if not tx:
|
||||||
|
_logger.warning('PayDunya: no transaction found for notification: %s', invoice_data)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Extract status (valid values: pending, completed, cancelled, failed)
|
||||||
|
status = invoice_data.get('status') or (
|
||||||
|
invoice_data.get('invoice', {}).get('status')
|
||||||
|
)
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
_logger.warning('PayDunya: notification without status: %s', invoice_data)
|
||||||
|
return False
|
||||||
|
|
||||||
|
status = str(status).lower()
|
||||||
|
if status == 'completed':
|
||||||
|
if hasattr(tx, '_set_transaction_done'):
|
||||||
|
tx._set_transaction_done()
|
||||||
|
else:
|
||||||
|
tx.state = 'done'
|
||||||
|
_logger.info('PayDunya: Transaction %s marked as done', tx.reference)
|
||||||
|
return True
|
||||||
|
if status in ('cancelled', 'failed'):
|
||||||
|
if hasattr(tx, '_set_transaction_cancel'):
|
||||||
|
tx._set_transaction_cancel()
|
||||||
|
else:
|
||||||
|
tx.state = 'cancel'
|
||||||
|
_logger.info('PayDunya: Transaction %s marked as cancelled/failed', tx.reference)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# fallback: mark as pending
|
||||||
|
if hasattr(tx, '_set_transaction_pending'):
|
||||||
|
tx._set_transaction_pending()
|
||||||
|
else:
|
||||||
|
tx.state = 'pending'
|
||||||
|
_logger.info('PayDunya: Transaction %s marked as pending', tx.reference)
|
||||||
|
return True
|
||||||
29
addons/payment_paydunya/views/payment_paydunya_templates.xml
Normal file
29
addons/payment_paydunya/views/payment_paydunya_templates.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<template id="paydunya_redirect_form">
|
||||||
|
<t t-call="website.layout">
|
||||||
|
<div class="container my-5">
|
||||||
|
<div class="oe_paydunya_redirect">
|
||||||
|
<h3>Redirection vers PayDunya...</h3>
|
||||||
|
|
||||||
|
<form id="paydunya_redirect_form" t-att-action="redirect_url" method="post">
|
||||||
|
<input type="hidden" name="token" t-att-value="paydunya_token"/>
|
||||||
|
<noscript>
|
||||||
|
<button type="submit" class="btn btn-primary">Payer</button>
|
||||||
|
</noscript>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<t t-if="redirect_url">
|
||||||
|
<script type="text/javascript">
|
||||||
|
(function () {
|
||||||
|
const f = document.getElementById('paydunya_redirect_form');
|
||||||
|
if (f) { f.submit(); }
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
||||||
21
addons/payment_paydunya/views/payment_paydunya_views.xml
Normal file
21
addons/payment_paydunya/views/payment_paydunya_views.xml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<!-- Form view for PayDunya payment provider -->
|
||||||
|
<record id="payment_provider_paydunya_form" model="ir.ui.view">
|
||||||
|
<field name="name">payment.provider.paydunya.form</field>
|
||||||
|
<field name="model">payment.provider</field>
|
||||||
|
<field name="inherit_id" ref="payment.payment_provider_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//page[@name='credentials']" position="inside">
|
||||||
|
<group id="paydunya_credentials" invisible='code != "paydunya"'>
|
||||||
|
<h2>PayDunya Credentials</h2>
|
||||||
|
<field name="environment"/>
|
||||||
|
<field name="paydunya_master_key" password="True"/>
|
||||||
|
<field name="paydunya_public_key"/>
|
||||||
|
<field name="paydunya_private_key" password="True"/>
|
||||||
|
<field name="paydunya_token" password="True"/>
|
||||||
|
</group>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
25
docker-compose.yml
Normal file
25
docker-compose.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
services:
|
||||||
|
web:
|
||||||
|
image: odoo:18.0
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
ports:
|
||||||
|
- "9000:8069"
|
||||||
|
volumes:
|
||||||
|
- ./addons:/mnt/extra-addons
|
||||||
|
- ./odoo.conf:/etc/odoo/odoo.conf
|
||||||
|
environment:
|
||||||
|
- HOST=db
|
||||||
|
- USER=odoo
|
||||||
|
- PASSWORD=odoo
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:15
|
||||||
|
environment:
|
||||||
|
- POSTGRES_DB=postgres
|
||||||
|
- POSTGRES_PASSWORD=odoo
|
||||||
|
- POSTGRES_USER=odoo
|
||||||
|
ports:
|
||||||
|
- "6432:5432"
|
||||||
|
volumes:
|
||||||
|
- ./data/odoo-db:/var/lib/postgresql/data
|
||||||
Reference in New Issue
Block a user