diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b4f55b2..778fb2a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v2 - name: Build project - run: ./bin/release.sh ${{ env.VERSION }} + run: ./release.sh ${{ env.VERSION }} - name: Create Release id: create_release diff --git a/.ruff.toml b/.ruff.toml index 56f36b6..a72a82c 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -5,10 +5,14 @@ fix = true extend-select = [ "B", "C90", - "E501", # line too long (default 88) + # "E501", # line too long (default 88) "I", # isort "UP", # pyupgrade ] +ignore = [ + "C901", # Function is too complex + "B904", # Use `raise ... from err` within except clause +] extend-safe-fixes = ["UP008"] exclude = ["setup/*"] diff --git a/payment_multisafepay_official/__init__.py b/payment_multisafepay/__init__.py similarity index 76% rename from payment_multisafepay_official/__init__.py rename to payment_multisafepay/__init__.py index 8a530d0..fc7fe16 100644 --- a/payment_multisafepay_official/__init__.py +++ b/payment_multisafepay/__init__.py @@ -5,14 +5,14 @@ from . import models from . import controllers -from . import wizard from . import utils import odoo.addons.payment as payment + def post_init_hook(env): - payment.setup_provider(env, 'multisafepay') + payment.setup_provider(env, "multisafepay") def uninstall_hook(env): - payment.reset_payment_provider(env, 'multisafepay') + payment.reset_payment_provider(env, "multisafepay") diff --git a/payment_multisafepay/__manifest__.py b/payment_multisafepay/__manifest__.py new file mode 100644 index 0000000..523ce02 --- /dev/null +++ b/payment_multisafepay/__manifest__.py @@ -0,0 +1,27 @@ +{ + "name": "MultiSafepay", + "description": """ + Accept, manage and stimulate online sales with MultiSafepay. + Increase conversion rates with MultiSafepay unique solutions, + create the perfect checkout experience and the best payment method mix. + """, + "summary": """E-commerce is part of our DNA""", + "author": "MultiSafepay", + "website": "https://github.com//multisafepay", + "license": "AGPL-3", + "category": "eCommerce", + "version": "19.0.2.0.0", + "application": True, + "post_init_hook": "post_init_hook", + "uninstall_hook": "uninstall_hook", + "depends": [ + "payment", + ], + "external_dependencies": {"python": ["multisafepay"]}, + "data": [ + "views/payment_templates.xml", + "views/payment_provider_views.xml", + "views/payment_method_views.xml", + "data/payment_provider_data.xml", + ], +} diff --git a/payment_multisafepay/const.py b/payment_multisafepay/const.py new file mode 100644 index 0000000..3f19b84 --- /dev/null +++ b/payment_multisafepay/const.py @@ -0,0 +1,79 @@ +# Copyright (c) MultiSafepay, Inc. All rights reserved. +# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. +# See the LICENSE.md file for more information. +# See the DISCLAIMER.md file for disclaimer details + +# Payment method code prefix +PAYMENT_METHOD_PREFIX = "multisafepay_" + +SUPPORTED_CURRENCIES = [ + "AED", + "AUD", + "BGN", + "BRL", + "CAD", + "CHF", + "CLP", + "CNY", + "COP", + "CZK", + "DKK", + "EUR", + "GBP", + "HKD", + "HRK", + "HUF", + "ILS", + "INR", + "ISK", + "JPY", + "KRW", + "MXN", + "MYR", + "NOK", + "NZD", + "PEN", + "PHP", + "PLN", + "RON", + "RUB", + "SEK", + "SGD", + "THB", + "TRY", + "TWD", + "USD", + "VEF", + "ZAR", +] + +STATUS_MAPPING = { + "draft": ("initialized",), + "pending": (), + "authorized": ("reserved",), + "done": ("completed", "partial_refunded", "refunded", "uncleared"), + "cancel": ("canceled", "cancelled", "void", "expired"), + "error": ("declined", "chargedback", "charged_back"), +} + +PAYMENT_METHOD_PENDING = [ + "banktrans", + "multibanco", +] + +BNPL_METHODS = [ + "afterpay", + "einvoice", + "in3", + "klarna", + "payafter", + "bnpl_instm", + "bnpl_inst", + "in3b2b", + "santander", + "zinia", + "zinia_in3", + "bnpl_ob", + "bnpl_mf", + "billink", +] diff --git a/payment_multisafepay_official/controllers/__init__.py b/payment_multisafepay/controllers/__init__.py similarity index 100% rename from payment_multisafepay_official/controllers/__init__.py rename to payment_multisafepay/controllers/__init__.py diff --git a/payment_multisafepay/controllers/main.py b/payment_multisafepay/controllers/main.py new file mode 100644 index 0000000..9788311 --- /dev/null +++ b/payment_multisafepay/controllers/main.py @@ -0,0 +1,1119 @@ +# Copyright (c) MultiSafepay, Inc. All rights reserved. +# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. +# See the LICENSE.md file for more information. +# See the DISCLAIMER.md file for disclaimer details + +""" +MultiSafepay Payment Controller +Handles redirect flow for MultiSafepay payments +""" + +import json +import logging +from decimal import Decimal +from typing import cast +from urllib.parse import quote_plus + +from multisafepay.api.paths.orders.request.components.checkout_options import ( + CheckoutOptions, +) +from multisafepay.api.paths.orders.request.components.payment_options import ( + PaymentOptions, +) +from multisafepay.api.paths.orders.request.components.plugin import Plugin +from multisafepay.api.paths.orders.request.components.second_chance import SecondChance +from multisafepay.api.paths.orders.request.order_request import OrderRequest +from multisafepay.api.paths.orders.response.order_response import Order +from multisafepay.api.shared.cart.cart_item import CartItem +from multisafepay.api.shared.cart.shopping_cart import ShoppingCart +from multisafepay.api.shared.checkout.tax_rate import TaxRate +from multisafepay.api.shared.checkout.tax_rule import TaxRule +from multisafepay.api.shared.customer import Customer +from multisafepay.api.shared.delivery import Delivery +from multisafepay.api.shared.description import Description +from multisafepay.util.address_parser import AddressParser +from multisafepay.util.webhook import Webhook +from multisafepay.value_object.amount import Amount +from multisafepay.value_object.currency import Currency +from multisafepay.value_object.weight import Weight +from werkzeug.exceptions import Forbidden + +from odoo import _, http +from odoo.exceptions import UserError, ValidationError +from odoo.http import request + +from ..const import PAYMENT_METHOD_PENDING, PAYMENT_METHOD_PREFIX + +_logger = logging.getLogger(__name__) + + +class MultiSafepayController(http.Controller): + """Controller to handle MultiSafepay payment flow""" + + _redirect_url = "/payment/multisafepay/redirect" + _webhook_url = "/payment/multisafepay/webhook" + _return_url = "/payment/multisafepay/return" + _cancel_url = "/payment/multisafepay/cancel" + + # =================================== + # ROUTES (ODOO) + # =================================== + + @http.route( + "/payment/multisafepay/redirect", + type="http", + auth="public", + methods=["POST"], + csrf=False, + ) + def multisafepay_redirect(self, **post): + """Handle redirect to MultiSafepay payment page""" + _logger.info("MultiSafepay redirect endpoint") + _logger.debug("MultiSafepay redirect endpoint request POST data: %s", str(post)) + + try: + reference = post.get("reference") + if not reference: + return self._redirect_to_payment_with_error( + "missing_reference", + _("Payment reference is missing. Please try again."), + ) + + payment_transaction = ( + request.env["payment.transaction"] + .sudo() + .search( + [ + ("reference", "=", reference), + ("provider_code", "=", "multisafepay"), + ], + limit=1, + ) + ) + + if not payment_transaction: + return self._redirect_to_payment_with_error( + "transaction_not_found", + _("Transaction not found. Please refresh and try again."), + ) + + try: + base_url = self._get_correct_base_url() + order = self._get_order(base_url, payment_transaction) + + if not order or not order.payment_url: + return self._redirect_to_payment_with_error( + "multiSafepay_api_error", + _( + "MultiSafepay API error: Transaction not found. Please refresh and try again." + ), + ) + + _logger.info("Payment url was generated successfully.") + _logger.debug( + "Payment url was generated successfully: %s", order.payment_url + ) + + return request.redirect(order.payment_url, local=False) + + except (ValidationError, UserError) as ve: + _logger.error("Validation/User error: %s", str(ve)) + return self._redirect_to_payment_with_error("validation_error", str(ve)) + + except Exception as api_error: + _logger.error("Unexpected error: %s", str(api_error)) + return self._redirect_to_payment_with_error( + "Unexpected error", + _("An unexpected error occurred. Please try again."), + ) + + except Exception as e: + _logger.error("Unexpected error: %s", str(e)) + return self._redirect_to_payment_with_error( + "unexpected_error", _("An unexpected error occurred. Please try again.") + ) + + @http.route( + "/payment/multisafepay/webhook", + type="http", + auth="public", + csrf=False, + methods=["POST", "GET"], + ) + def multisafepay_webhook(self, **kwargs): + """Handle MultiSafepay webhook notifications""" + + _logger.info("MultiSafepay webhook received via %s", request.httprequest.method) + _logger.debug("MultiSafepay webhook data: %s", str(kwargs)) + + try: + if request.httprequest.method == "POST": + return self._handle_post_webhook(**kwargs) + else: + return self._handle_get_webhook(**kwargs) + + except Exception as e: + _logger.error("Unexpected error in webhook processing: %s", str(e)) + return request.make_response("Internal Server Error", status=500) + + @http.route( + "/payment/multisafepay/return", + type="http", + auth="public", + csrf=False, + methods=["GET", "POST"], + ) + def multisafepay_return(self, **kwargs): + """Handle return from MultiSafepay payment page""" + _logger.info("MultiSafepay return received") + _logger.debug("Return data: %s", str(kwargs)) + + transactionid = kwargs.get("transactionid") + + payment_transaction = ( + request.env["payment.transaction"] + .sudo() + .search( + [ + ("reference", "=", transactionid), + ("provider_code", "=", "multisafepay"), + ], + limit=1, + ) + ) + + if ( + payment_transaction.payment_method_code.removeprefix(PAYMENT_METHOD_PREFIX) + in PAYMENT_METHOD_PENDING + ): + _logger.info( + "Payment method '%s' requires manual confirmation. Setting as pending.", + payment_transaction.payment_method_code, + ) + + # Get real state from MultiSafepay + provider = payment_transaction.provider_id + multisafepay_sdk = provider.get_multisafepay_sdk() + order_manager = multisafepay_sdk.get_order_manager() + order_response = order_manager.get(transactionid) + order = order_response.get_data() + + notification_data = { + "status": "pending", + "reference": transactionid, + } + + # Process notification to update transaction state + payment_transaction._process_notification_data(notification_data) + + # Redirect for pending payment - can be overridden by e-commerce module + return self._redirect_pending_payment( + payment_transaction, order, transactionid + ) + + return request.redirect("/payment/status") + + @http.route( + "/payment/multisafepay/cancel", + type="http", + auth="public", + csrf=False, + methods=["GET", "POST"], + ) + def multisafepay_cancel(self, **kwargs): + """Handle cancellation from MultiSafepay payment page""" + _logger.info("MultiSafepay payment cancelled") + _logger.debug("Cancel data: %s", str(kwargs)) + + transactionid = kwargs.get("transactionid") + + payment_transaction = ( + request.env["payment.transaction"] + .sudo() + .search( + [ + ("reference", "=", transactionid), + ("provider_code", "=", "multisafepay"), + ], + limit=1, + ) + ) + + notification_data = { + "status": "cancelled", + "reference": transactionid, + } + + payment_transaction._process_notification_data(notification_data) + + _logger.info("MultiSafepay payment cancelled: %s", transactionid) + + return request.redirect("/payment/status?cancelled=1") + + # =================================== + # MULTISAFEPAY - Webhook Handling + # =================================== + + def _handle_get_webhook(self, **kwargs): + """Handle GET webhook from MultiSafepay""" + + transactionid = kwargs.get("transactionid") + if not transactionid: + _logger.error("No transaction ID provided in webhook") + return request.make_response("Transaction ID required", status=400) + + payment_transaction = ( + request.env["payment.transaction"] + .sudo() + .search( + [ + ("reference", "=", transactionid), + ("provider_code", "=", "multisafepay"), + ], + limit=1, + ) + ) + + if not payment_transaction: + _logger.error( + "Transaction not found for webhook validation: %s", transactionid + ) + return request.make_response("Transaction not found", status=404) + + # Fetch order details from MultiSafepay API to verify status + provider = payment_transaction.provider_id + multisafepay_sdk = provider.get_multisafepay_sdk() + + order_manager = multisafepay_sdk.get_order_manager() + order_response = order_manager.get(transactionid) + order = order_response.get_data() + if not order: + _logger.error("No order data found for transaction ID: %s", transactionid) + return request.make_response("No order data found", status=404) + + if not order.status: + _logger.error( + "No status found in order data for transaction ID: %s", transactionid + ) + return request.make_response("No status found", status=400) + + # Prevent duplicate processing of same status + duplicated = self._duplicated_status(payment_transaction, order.status) + if duplicated: + _logger.debug( + "Duplicate webhook received for transaction %s with status %s, ignoring.", + transactionid, + order.status, + ) + return request.make_response("OK", status=200) + + # Prepare notification data for Odoo transaction processing + notification_data = { + "status": order.status, + "reference": order.transaction_id + if hasattr(order, "transaction_id") + else None, + } + + # Update transaction state based on webhook notification + payment_transaction._process_notification_data(notification_data) + + _logger.info( + "Webhook processing completed for transaction %s with status %s", + transactionid, + order.status, + ) + return request.make_response("OK", status=200) + + def _handle_post_webhook(self, **kwargs): + """Handle POST webhook from MultiSafepay""" + + request_body_raw = request.httprequest.get_data(as_text=True) + try: + _logger.debug("POST webhook data: %s", request_body_raw) + + # Parse JSON body into Order object + order = None + if request_body_raw: + try: + order_data = json.loads(request_body_raw) + # Convert dict to MultiSafepay Order object + order = Order.from_dict(order_data) + _logger.debug("Parsed POST webhook data: %s", str(order_data)) + + except json.JSONDecodeError: + _logger.error( + "Failed to parse POST webhook body as JSON, using form data" + ) + return request.make_response( + "Invalid JSON format in webhook body", status=403 + ) + + # Validate that order data contains transaction ID + if not order or not order.order_id: + _logger.error("No transaction ID provided in webhook") + return request.make_response("Transaction ID required", status=400) + + transactionid = order.order_id + payment_transaction = ( + request.env["payment.transaction"] + .sudo() + .search( + [ + ("reference", "=", transactionid), + ("provider_code", "=", "multisafepay"), + ], + limit=1, + ) + ) + + if not payment_transaction: + _logger.error( + "Transaction not found for webhook validation: %s", transactionid + ) + return request.make_response("Transaction not found", status=404) + + # Get authentication header and API key for webhook signature validation + auth_header = request.httprequest.headers.get("Auth", "") + + provider = payment_transaction.provider_id + api_key = provider.multisafepay_api_key + + try: + # Validate webhook signature using MultiSafepay SDK + # This ensures the webhook is genuinely from MultiSafepay + validated = Webhook.validate( + request=request_body_raw, + auth=auth_header, + api_key=api_key, + validation_time_in_seconds=600, + ) + + if not validated: + _logger.error( + "Webhook validation failed for transaction %s: Invalid signature", + transactionid, + ) + payment_transaction._set_error( + "Webhook validation failed: Invalid signature" + ) + return request.make_response( + "Webhook validation failed", status=403 + ) + + _logger.info( + "Webhook validation successful for transaction: %s", transactionid + ) + + if not order.status: + _logger.error( + "No status found in order data for transaction ID: %s", + transactionid, + ) + return request.make_response("No status found", status=400) + + # Check if we already processed this status to avoid duplicate processing + duplicated = self._duplicated_status(payment_transaction, order.status) + if duplicated: + _logger.info( + "Duplicate webhook received for transaction %s with status %s, ignoring.", + transactionid, + order.status, + ) + return request.make_response("OK", status=200) + + notification_data = { + "status": order.status, + "reference": order.transaction_id + if hasattr(order, "transaction_id") + else None, + } + + payment_transaction._process_notification_data(notification_data) + + _logger.info( + "Webhook processing completed for transaction %s with status %s", + transactionid, + order.status, + ) + return request.make_response("OK", status=200) + + except Exception as validation_error: + _logger.error( + "Webhook validation failed for transaction %s: %s", + transactionid, + str(validation_error), + ) + return request.make_response("Webhook validation failed", status=403) + + except Forbidden as e: + _logger.error("Forbidden access to MultiSafepay webhook: %s", str(e)) + return request.make_response("Forbidden", status=403) + + # =================================== + # MULTISAFEPAY - Helpers + # =================================== + + def _redirect_to_payment_with_error(self, error_code, message): + """Redirect to payment form with error message + + Returns to standard payment status page. + Can be overridden by e-commerce module to redirect to /shop/payment. + """ + return request.redirect( + f"/payment/status?error={error_code}&message={quote_plus(message)}" + ) + + def _redirect_pending_payment(self, payment_transaction, order, transactionid): + """Redirect after pending payment confirmation + + Returns to standard payment status page with payment details. + Can be overridden by e-commerce module to redirect to /shop/confirmation with order details. + + :param payment_transaction: The payment transaction record + :param order: The MultiSafepay order object + :param transactionid: The transaction ID + :return: HTTP redirect response + """ + params = { + "payment_method": payment_transaction.payment_method_code, + "payment_status": order.status if order else "pending", + "transaction_id": transactionid, + "multisafepay_order_id": order.order_id + if order and hasattr(order, "order_id") + else "", + "amount": payment_transaction.amount, + "currency": payment_transaction.currency_id.name, + } + + # Build URL with parameters + url_params = "&".join( + [f"{k}={quote_plus(str(v))}" for k, v in params.items() if v] + ) + return request.redirect(f"/payment/status?{url_params}") + + def _duplicated_status(self, payment_transaction, new_status, message=""): + """Check if the transaction already has the specified status to detect duplicates.""" + _logger.debug( + "Transaction %s status changed to %s: %s", + payment_transaction.id, + new_status, + message, + ) + return ( + payment_transaction.state + == payment_transaction._get_multisafepay_status_to_odoo_state(new_status) + ) + + def _extract_partner_data(self, partner): + """Extract and validate partner data for MultiSafepay order + + Extracts country code, email, phone, ref, and address information from a partner record. + Returns a dictionary with raw values that can be passed directly to SDK. + All values default to None if not available. + + :param partner: res.partner recordset + :return: Dictionary with partner data ready for SDK + :rtype: dict + """ + # Extract country code (only from country_id) + country_raw = None + if partner and partner.country_id and partner.country_id.code: + country_raw = partner.country_id.code + + # Extract raw values - SDK will validate them + email_raw = None + phone_raw = None + ref_raw = None + + if partner: + email_raw = getattr(partner, "email", None) + phone_raw = getattr(partner, "phone", None) + ref_raw = getattr(partner, "ref", None) + + # Odoo may return False for empty fields, convert to None + email_raw = email_raw if email_raw else None + phone_raw = phone_raw if phone_raw else None + ref_raw = ref_raw if ref_raw else None + + # Parse name + first_name = None + last_name = None + if partner: + partner_name = getattr(partner, "name", None) + if partner_name: + name_parts = partner_name.split(" ") + if name_parts: + first_name = name_parts[0] + if len(name_parts) > 1: + last_name = " ".join(name_parts[1:]) + + # Parse address + address1 = None + house_number = None + if partner: + street = getattr(partner, "street", None) + street2 = getattr(partner, "street2", None) + address_parser = AddressParser() + parsed_address = address_parser.parse(street, street2) + if parsed_address: + address1 = parsed_address[0] + if len(parsed_address) > 1: + house_number = parsed_address[1] + + # Extract location data + zip_code = None + city = None + + if partner: + zip_code = getattr(partner, "zip", None) + city = getattr(partner, "city", None) + + # Odoo may return False for empty fields, convert to None + zip_code = zip_code if zip_code else None + city = city if city else None + state = None + # Many2one fields return False when empty, so we need to check both existence and type + if partner and partner.state_id and hasattr(partner.state_id, "name"): + state = partner.state_id.name + + # Convert False values to None for SDK compatibility (Odoo quirk) + first_name = first_name if first_name else None + last_name = last_name if last_name else None + address1 = address1 if address1 else None + house_number = house_number if house_number else None + state = state if state else None + + return { + "country_raw": country_raw, + "email_raw": email_raw, + "phone_raw": phone_raw, + "ref_raw": ref_raw, + "first_name": first_name, + "last_name": last_name, + "address1": address1, + "house_number": house_number, + "zip_code": zip_code, + "city": city, + "state": state, + } + + # =================================== + # MULTISAFEPAY - Order Creation + # =================================== + + def _validate_transaction(self, payment_transaction): + """Validate transaction has required fields + + :param payment_transaction: The payment transaction record + :raises ValidationError: If validation fails + """ + if not getattr(payment_transaction, "reference", None): + raise ValidationError(_("Transaction reference is missing.")) + + amount = getattr(payment_transaction, "amount", 0) + if amount <= 0: + raise ValidationError(_("Transaction amount is missing or invalid.")) + + currency_id = getattr(payment_transaction, "currency_id", None) + if not currency_id or not getattr(currency_id, "name", None): + raise ValidationError(_("Transaction currency is missing or invalid.")) + + def _prepare_amount_and_currency(self, payment_transaction): + """Prepare amount and currency for MultiSafepay API + + Converts amount to smallest currency unit (cents) to avoid floating-point + precision issues. Example: 100.00 EUR → 10000 cents + + :param payment_transaction: The payment transaction record + :return: Tuple of (Currency object, amount in cents as int, currency_id record) + :rtype: tuple + """ + currency_id = payment_transaction.currency_id + amount = payment_transaction.amount + + decimal_places = getattr(currency_id, "decimal_places", 2) + + # Use Decimal to avoid float precision errors (29.99 * 100 = 2998.999...) + multiplier = 10**decimal_places + amount_decimal = Decimal(str(amount)) + multiplied_amount = amount_decimal * multiplier + normalized_amount = int(multiplied_amount) + + # Create SDK value objects - these validate format internally + amount_obj = Amount(amount=normalized_amount).amount + currency_obj = Currency(currency=currency_id.name) + + return (currency_obj, amount_obj, currency_id) + + def _get_partners_from_transaction(self, payment_transaction): + """Get invoice and shipping partners from transaction + + Handles multiple Odoo payment scenarios: + - E-commerce: Uses sale_order partners (may differ for billing/shipping) + - Invoice payment: Uses transaction partner for both + - Manual payment: Fallback to transaction partner + + :param payment_transaction: The payment transaction record + :return: Tuple of (partner_invoice, partner_shipping, sale_order or None, sale_orders) + :rtype: tuple + """ + sale_orders = getattr(payment_transaction, "sale_order_ids", None) + partner_invoice = None + partner_shipping = None + sale_order = None + + if sale_orders: + sale_order = sale_orders[0] # Assuming one sale order per transaction + partner_invoice = getattr(sale_order, "partner_invoice_id", None) + partner_shipping = getattr(sale_order, "partner_shipping_id", None) + else: + # Fallback: use transaction partner for both billing and shipping + partner_invoice = getattr(payment_transaction, "partner_id", None) + partner_shipping = partner_invoice + + if not partner_invoice: + raise ValidationError(_("No customer info partner associated.")) + + return (partner_invoice, partner_shipping, sale_order, sale_orders) + + def _get_order(self, url, payment_transaction) -> Order: + """Create the order request for MultiSafepay transactions. + + Orchestrates order creation by: + 1. Validating transaction data + 2. Preparing amount/currency (converts to cents) + 3. Extracting partners (from sale order or transaction) + 4. Building customer and delivery info + 5. Constructing shopping cart (from invoice or sale order lines) + 6. Configuring payment options and callbacks + 7. Creating order via MultiSafepay SDK + + Works across multiple Odoo modules: payment, account, sale, website_sale, loyalty + + :param str url: The base URL for callbacks + :param payment_transaction: The payment.transaction record + :return: The order created in MultiSafepay + :rtype: Order + """ + + # Validate transaction data + self._validate_transaction(payment_transaction) + + # Prepare amount and currency (convert to cents) + currency, amount, currency_id = self._prepare_amount_and_currency( + payment_transaction + ) + + # Get partners from transaction (invoice/shipping addresses) + partner_invoice, partner_shipping, sale_order, sale_orders = ( + self._get_partners_from_transaction(payment_transaction) + ) + + # Basic order metadata + order_id = payment_transaction.reference + description = Description(description="Order #" + order_id) + language = getattr(payment_transaction, "partner_lang", "en_US") or "en_US" + + # Extract partner data and HTTP metadata for fraud prevention + partner_invoice_data = self._extract_partner_data(partner_invoice) + + environ = ( + getattr(request.httprequest, "environ", {}) + if request and hasattr(request, "httprequest") + else {} + ) + partner_invoice_ip_address_raw = ( + environ.get("REMOTE_ADDR", None) if isinstance(environ, dict) else None + ) + partner_invoice_forwarded_ip_raw = ( + environ.get("HTTP_X_FORWARDED_FOR", None) + if isinstance(environ, dict) + else None + ) + partner_invoice_forwarded_ip_raw = ( + partner_invoice_forwarded_ip_raw or partner_invoice_ip_address_raw + ) + partner_invoice_referrer = ( + environ.get("HTTP_REFERER", None) if isinstance(environ, dict) else None + ) + + user_agent_string = None + if ( + request + and hasattr(request, "httprequest") + and hasattr(request.httprequest, "user_agent") + ): + user_agent = request.httprequest.user_agent + user_agent_string = ( + getattr(user_agent, "string", None) if user_agent else None + ) + + # Build Customer object + customer = cast( + Customer, + ( + Customer(**{}) + .add_locale(language) + .add_ip_address(partner_invoice_ip_address_raw) + .add_forwarded_ip(partner_invoice_forwarded_ip_raw) + .add_referrer(partner_invoice_referrer) + .add_user_agent(user_agent_string) + .add_reference(partner_invoice_data["ref_raw"]) + .add_first_name(partner_invoice_data["first_name"]) + .add_last_name(partner_invoice_data["last_name"]) + .add_phone(partner_invoice_data["phone_raw"]) + .add_email(partner_invoice_data["email_raw"]) + .add_address1(partner_invoice_data["address1"]) + .add_address2(None) + .add_house_number(partner_invoice_data["house_number"]) + .add_zip_code(partner_invoice_data["zip_code"]) + .add_city(partner_invoice_data["city"]) + .add_state(partner_invoice_data["state"]) + .add_country(partner_invoice_data["country_raw"]) + ), + ) + + # SALE MODULE INTEGRATION - Shipping Address + # If sale order exists and has different shipping address, use it + # Otherwise, use invoice address for delivery (standard fallback) + use_delivery_as_billing = False + partner_shipping = None + delivery = Delivery(**{}) + + if sale_order: + partner_shipping = sale_order.partner_shipping_id + + # Check if invoice and shipping addresses are the same partner + if partner_invoice.id == partner_shipping.id: + use_delivery_as_billing = True + + if partner_shipping: + # Extract partner data using helper function + partner_shipping_data = self._extract_partner_data(partner_shipping) + + if sale_orders and not use_delivery_as_billing: + delivery = ( + Delivery(**{}) + .add_first_name(partner_shipping_data["first_name"]) + .add_last_name(partner_shipping_data["last_name"]) + .add_phone(partner_shipping_data["phone_raw"]) + .add_email(partner_shipping_data["email_raw"]) + .add_address1(partner_shipping_data["address1"]) + .add_address2(None) + .add_house_number(partner_shipping_data["house_number"]) + .add_zip_code(partner_shipping_data["zip_code"]) + .add_city(partner_shipping_data["city"]) + .add_state(partner_shipping_data["state"]) + .add_country(partner_shipping_data["country_raw"]) + ) + + if not sale_orders or sale_orders and use_delivery_as_billing: + # If no sale order, use the invoice partner details for delivery + delivery = ( + Delivery(**{}) + .add_first_name(partner_invoice_data["first_name"]) + .add_last_name(partner_invoice_data["last_name"]) + .add_phone(partner_invoice_data["phone_raw"]) + .add_email(partner_invoice_data["email_raw"]) + .add_address1(partner_invoice_data["address1"]) + .add_address2(None) + .add_house_number(partner_invoice_data["house_number"]) + .add_zip_code(partner_invoice_data["zip_code"]) + .add_city(partner_invoice_data["city"]) + .add_state(partner_invoice_data["state"]) + .add_country(partner_invoice_data["country_raw"]) + ) + + # Create a Plugin object with the necessary details + plugin = ( + Plugin(**{}) + .add_plugin_version("1.0.0") + .add_shop("Odoo") + .add_shop_version("19.0") + .add_shop_root_url(url) + ) + + # Create payment options for the order + payment_options = ( + PaymentOptions(**{}) + .add_notification_method("POST") + .add_close_window(True) + .add_notification_url(f"{url}/payment/multisafepay/webhook") + .add_redirect_url(f"{url}/payment/multisafepay/return") + .add_cancel_url(f"{url}/payment/multisafepay/cancel") + ) + + # Prevent Second Chance, not supported at this moment + second_chance = SecondChance(send_email=False) + + # =================================================================== + # SHOPPING CART CONSTRUCTION + # =================================================================== + # Build cart from invoice lines (preferred) or sale order lines (fallback) + # Cart is mandatory for correct tax calculations in MultiSafepay API + + order_lines = [] + source_type = None + + # Try invoice lines first (most accurate - final prices and taxes) + invoice_ids = getattr(payment_transaction, "invoice_ids", None) + if invoice_ids and len(invoice_ids) > 0: + invoice = invoice_ids[0] + all_invoice_lines = getattr(invoice, "invoice_line_ids", []) + + # Filter: Exclude only known decorative/informational line types + # Conservative approach: better to include an extra line than miss a monetary one + # Known decorative types: line_section, line_subsection, line_note + order_lines = [ + line + for line in all_invoice_lines + if getattr(line, "display_type", None) + not in ("line_section", "line_subsection", "line_note") + ] + + if order_lines: + source_type = "invoice" + _logger.debug( + "Using invoice lines for cart: %d lines (filtered from %d total)", + len(order_lines), + len(all_invoice_lines), + ) + + # Fallback to sale order if no invoice + if not order_lines and sale_order: + all_sale_lines = getattr(sale_order, "order_line", []) + + # Same filtering: exclude only known decorative types + order_lines = [ + line + for line in all_sale_lines + if getattr(line, "display_type", None) + not in ("line_section", "line_subsection", "line_note") + ] + + if order_lines: + source_type = "sale_order" + _logger.debug( + "Using sale order lines for cart: %d lines (filtered from %d total)", + len(order_lines), + len(all_sale_lines), + ) + + # Build cart items + cart_items = [] + + for line in order_lines: + product = getattr(line, "product_id", None) + + # Detect source to use correct field names (invoice vs sale order) + is_invoice_line = source_type == "invoice" + + # Extract tax rate for MultiSafepay tax calculations + tax_table_selector = None + if hasattr(line, "tax_ids") and line.tax_ids: + tax_table_selector = line.tax_ids[0].amount + + # Build merchant_item_id: SKU → product.id → line.id + if product and product.default_code: + merchant_item_id = product.default_code + elif product: + merchant_item_id = str(product.id) + else: + merchant_item_id = f"line-{line.id}" + + # Loyalty integration: prefix with reward/program type (sale orders only) + # reward_id only exists on sale.order.line, not on invoice lines + if source_type == "sale_order": + reward_id = getattr(line, "reward_id", None) + if reward_id: + reward_type = getattr(reward_id, "reward_type", None) + if reward_type: + merchant_item_id = f"{reward_type}-{merchant_item_id}" + program_type = getattr(reward_id, "program_type", None) + if program_type: + merchant_item_id = f"{program_type}-{merchant_item_id}" + + # Append product variant attributes to item ID + if hasattr(line, "product_no_variant_attribute_value_ids"): + for variant in line.product_no_variant_attribute_value_ids: + merchant_item_id += f"-{variant.name}" + + # Get line fields - compatible with both sale and invoice lines + line_name = getattr(line, "name", "") or "" + line_description = "" + if product: + line_description = getattr(product, "description_sale", "") or "" + + line_price_unit = getattr(line, "price_unit", 0.0) + + # Field mapping: sale.order.line uses 'product_uom_qty', invoice uses 'quantity' + if is_invoice_line: + line_quantity = getattr(line, "quantity", 0) + else: + line_quantity = getattr(line, "product_uom_qty", 0) + + # Weight: from product + line_weight = 0.0 + if product: + line_weight = getattr(product, "weight", 0.0) or 0.0 + + cart_item = ( + CartItem(**{}) + .add_name(line_name) + .add_description(line_description) + .add_unit_price(line_price_unit) + .add_quantity(line_quantity) + .add_merchant_item_id(merchant_item_id) + .add_weight(Weight(value=line_weight, unit="kg")) + ) + + if tax_table_selector is not None: + cart_item.add_tax_rate_percentage(tax_table_selector) + else: + cart_item.add_tax_rate_percentage(0) + + cart_items.append(cart_item) + _logger.debug("Order line details: %s", str(cart_item.to_dict())) + + shopping_cart = ShoppingCart(items=cart_items) + # Generate checkout options from cart (includes tax tables) + checkout_options = CheckoutOptions.generate_from_shopping_cart(shopping_cart) + if checkout_options: + # Enable cart validation on MultiSafepay side + checkout_options.add_validate_cart(True) + + # Create a 0% tax rule as fallback for items without tax + tax_rule = TaxRule( + name="0", rules=[TaxRate(rate=0, country="")], standalone=None + ) + + # Check if any cart item already has a 0% tax rate + has_zero_tax = any(item.name == "0" for item in cart_items) + if not has_zero_tax: + # Add 0% tax rule to ensure all items can be processed + if checkout_options: + if checkout_options.tax_tables: + checkout_options.tax_tables.add_tax_rule(tax_rule) + + # Get the payment method code from transaction and map it to MultiSafepay format + odoo_method_code = payment_transaction.payment_method_code + + # Convert to MultiSafepay format using the provider's mapping function + provider = getattr(payment_transaction, "provider_id", None) + if provider: + multisafepay_gateway_code = provider._map_odoo_to_multisafepay_code( + odoo_method_code + ) + else: + multisafepay_gateway_code = odoo_method_code.upper() + + _logger.debug( + "Mapping Odoo method '%s' to MultiSafepay gateway '%s'", + odoo_method_code, + multisafepay_gateway_code, + ) + + order_request = ( + OrderRequest(**{}) + .add_type("redirect") + .add_gateway(multisafepay_gateway_code) + .add_order_id(order_id) + .add_currency(currency.currency) + .add_amount(amount) + .add_payment_options(payment_options) + .add_customer(customer) + .add_delivery(delivery) + .add_description(description.description if description.description else "") + .add_shopping_cart(shopping_cart) + .add_plugin(plugin) + .add_second_chance(second_chance) + ) + + if checkout_options: + order_request.add_checkout_options(checkout_options) + + # Get payment provider and initialize MultiSafepay SDK client + provider = getattr(payment_transaction, "provider_id", None) + if not provider: + _logger.error("Payment provider not found on transaction.") + raise ValidationError(_("Payment provider not found.")) + + multisafepay_sdk = provider.get_multisafepay_sdk() + + if not multisafepay_sdk: + _logger.error("MultiSafepay SDK not initialized.") + raise ValidationError(_("MultiSafepay SDK is not initialized.")) + + _logger.debug("Order request: %s", order_request.to_dict()) + + # Create order via MultiSafepay API + order_manager = multisafepay_sdk.get_order_manager() + create_response = order_manager.create(order_request) + + _logger.debug("Order created response: %s", str(create_response)) + + # Extract order data from API response + order: Order = create_response.get_data() + + # Verify order was created successfully + _order_id = getattr(order, "order_id", None) + + if not _order_id: + # Order creation failed - provide user-friendly error message + raise ValidationError( + _( + 'There was a problem processing your payment. Possible reasons could be: "insufficient funds", or "verification failed".' + ) + ) + + _logger.info("Order created successfully with ID: %s", _order_id) + return order + + def _get_correct_base_url(self): + """Get the correct base URL, prioritizing configured domain over localhost""" + + # Priority 1: System parameter (configured domain) + base_url = ( + request.env["ir.config_parameter"].sudo().get_param("web.base.url", "") + ) + if base_url and not ("localhost" in base_url or "127.0.0.1" in base_url): + _logger.debug("Using configured base URL: %s", base_url) + return base_url + + # Priority 2: X-Forwarded-Host header (from proxy) + x_forwarded_host = request.httprequest.headers.get("X-Forwarded-Host") + x_forwarded_proto = request.httprequest.headers.get( + "X-Forwarded-Proto", "https" + ) + + if x_forwarded_host: + base_url = f"{x_forwarded_proto}://{x_forwarded_host}" + _logger.debug("Using X-Forwarded-Host: %s", base_url) + return base_url + + # Priority 3: Host header + host = request.httprequest.headers.get("Host") + if host and not ("localhost" in host or "127.0.0.1" in host): + is_secure = request.httprequest.is_secure or x_forwarded_proto == "https" + scheme = "https" if is_secure else "http" + base_url = f"{scheme}://{host}" + _logger.debug("Using Host header: %s", base_url) + return base_url + + # Priority 4: Website domain (if configured) + if hasattr(request, "website") and request.website: + website_domain = request.website.domain + if website_domain and not ( + "localhost" in website_domain or "127.0.0.1" in website_domain + ): + base_url = f"https://{website_domain}" + _logger.debug("Using website domain: %s", base_url) + return base_url + + # Fallback: Use original method but log warning + base_url = request.httprequest.url_root.rstrip("/") + _logger.warning( + "Falling back to request URL root (may be localhost): %s", base_url + ) + return base_url diff --git a/payment_multisafepay_official/data/payment_provider_data.xml b/payment_multisafepay/data/payment_provider_data.xml similarity index 50% rename from payment_multisafepay_official/data/payment_provider_data.xml rename to payment_multisafepay/data/payment_provider_data.xml index 1bdb14d..49f100b 100644 --- a/payment_multisafepay_official/data/payment_provider_data.xml +++ b/payment_multisafepay/data/payment_provider_data.xml @@ -1,19 +1,22 @@ - + MultiSafepay multisafepay - + - - + + partial - - - - - + + + + diff --git a/payment_multisafepay/i18n/de_DE.po b/payment_multisafepay/i18n/de_DE.po new file mode 100644 index 0000000..49f5b1b --- /dev/null +++ b/payment_multisafepay/i18n/de_DE.po @@ -0,0 +1,333 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * payment_multisafepay +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-17 09:50+0000\n" +"PO-Revision-Date: 2025-11-17 09:50+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: payment_multisafepay +#: model_terms:ir.ui.view,arch_db:payment_multisafepay.payment_provider_form_multisafepay +msgid " Sync Payment Methods" +msgstr " Zahlungsmethoden synchronisieren" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__multisafepay_api_key +msgid "API Key" +msgstr "API-Schlüssel" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "An unexpected error occurred. Please try again." +msgstr "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es erneut." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__code +msgid "Code" +msgstr "Code" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Could not connect to MultiSafepay API for transaction %s.\n" +"\n" +"Error: %s" +msgstr "" +"Verbindung zur MultiSafepay API für Transaktion %s konnte nicht hergestellt werden.\n" +"\n" +"Fehler: %s" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Could not retrieve order information from MultiSafepay for transaction %s.\n" +"\n" +"Reference: %s\n" +"Provider: %s\n" +"\n" +"Please check your internet connection and try again later." +msgstr "" +"Bestellinformationen konnten nicht von MultiSafepay für Transaktion %s abgerufen werden.\n" +"\n" +"Referenz: %s\n" +"Anbieter: %s\n" +"\n" +"Bitte überprüfen Sie Ihre Internetverbindung und versuchen Sie es später erneut." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__display_name +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__display_name +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_transaction__display_name +msgid "Display Name" +msgstr "Anzeigename" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__id +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__id +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_transaction__id +msgid "ID" +msgstr "ID" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__main_currency_id +msgid "Main Currency" +msgstr "Hauptwährung" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__maximum_amount +msgid "Maximum Amount (EUR)" +msgstr "Maximalbetrag (EUR)" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__minimum_amount +msgid "Minimum Amount (EUR)" +msgstr "Mindestbetrag (EUR)" + +#. module: payment_multisafepay +#: model:payment.provider,name:payment_multisafepay.payment_provider_multisafepay +msgid "MultiSafepay" +msgstr "MultiSafepay" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "" +"MultiSafepay API error: Transaction not found. Please refresh and try again." +msgstr "" +"MultiSafepay API-Fehler: Transaktion nicht gefunden. Bitte aktualisieren und erneut versuchen." + +#. module: payment_multisafepay +#: model_terms:ir.ui.view,arch_db:payment_multisafepay.payment_provider_form_multisafepay +msgid "MultiSafepay Credentials" +msgstr "MultiSafepay-Anmeldedaten" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "MultiSafepay SDK is not initialized." +msgstr "MultiSafepay SDK ist nicht initialisiert." + +#. module: payment_multisafepay +#: model:ir.model.fields.selection,name:payment_multisafepay.selection__payment_provider__code__multisafepay +msgid "Multisafepay" +msgstr "Multisafepay" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "No customer info partner associated." +msgstr "Kein Kundeninformationspartner zugeordnet." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"No response received from MultiSafepay when attempting to refund transaction %s.\n" +"\n" +"Please check your internet connection and try again later." +msgstr "" +"Keine Antwort von MultiSafepay beim Versuch, Transaktion %s zu erstatten.\n" +"\n" +"Bitte überprüfen Sie Ihre Internetverbindung und versuchen Sie es später erneut." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__only_multisafepay +msgid "Only Multisafepay" +msgstr "Nur Multisafepay" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_method +msgid "Payment Method" +msgstr "Zahlungsmethode" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_provider +msgid "Payment Provider" +msgstr "Zahlungsanbieter" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_transaction +msgid "Payment Transaction" +msgstr "Zahlungstransaktion" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Payment provider not found." +msgstr "Zahlungsanbieter nicht gefunden." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Payment reference is missing. Please try again." +msgstr "Zahlungsreferenz fehlt. Bitte versuchen Sie es erneut." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Refund amount exceeds available amount.\n" +"\n" +"Transaction: %s\n" +"Requested Refund: %.2f %s\n" +"Maximum Available: %.2f %s\n" +"Already Refunded: %.2f %s\n" +"\n" +"Please adjust the refund amount." +msgstr "" +"Erstattungsbetrag überschreitet verfügbaren Betrag.\n" +"\n" +"Transaktion: %s\n" +"Angeforderte Erstattung: %.2f %s\n" +"Maximal verfügbar: %.2f %s\n" +"Bereits erstattet: %.2f %s\n" +"\n" +"Bitte passen Sie den Erstattungsbetrag an." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "Refund failed at MultiSafepay: %s\n" +msgstr "Erstattung bei MultiSafepay fehlgeschlagen: %s\n" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Refunds can only be processed from a refund transaction linked to an " +"original payment transaction." +msgstr "" +"Erstattungen können nur über eine Erstattungstransaktion verarbeitet werden, die mit einer " +"ursprünglichen Zahlungstransaktion verknüpft ist." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__only_multisafepay +msgid "" +"Technical field to identify if this payment method has MultiSafepay as one " +"of its providers." +msgstr "" +"Technisches Feld zur Identifizierung, ob diese Zahlungsmethode MultiSafepay als einen " +"ihrer Anbieter hat." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_provider__multisafepay_api_key +msgid "" +"The API key for the Multisafepay account. This is used to authenticate " +"requests to the Multisafepay API." +msgstr "" +"Der API-Schlüssel für das Multisafepay-Konto. Dieser wird verwendet, um " +"Anfragen an die Multisafepay API zu authentifizieren." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__main_currency_id +msgid "The main currency of the first provider linked to this payment method." +msgstr "Die Hauptwährung des ersten Anbieters, der mit dieser Zahlungsmethode verknüpft ist." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__maximum_amount +msgid "" +"The maximum payment amount in EUR that this payment provider is available " +"for. Leave 0 to make it available for any payment amount." +msgstr "" +"Der maximale Zahlungsbetrag in EUR, für den dieser Zahlungsanbieter verfügbar ist. " +"Lassen Sie 0, um ihn für jeden Zahlungsbetrag verfügbar zu machen." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__minimum_amount +msgid "" +"The minimum payment amount in EUR that this payment provider is available " +"for. Leave 0 to make it available for any payment amount." +msgstr "" +"Der minimale Zahlungsbetrag in EUR, für den dieser Zahlungsanbieter verfügbar ist. " +"Lassen Sie 0, um ihn für jeden Zahlungsbetrag verfügbar zu machen." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_provider__code +msgid "The technical code of this payment provider." +msgstr "Der technische Code dieses Zahlungsanbieters." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "" +"There was a problem processing your payment. Possible reasons could be: " +"\"insufficient funds\", or \"verification failed\"." +msgstr "" +"Bei der Verarbeitung Ihrer Zahlung ist ein Problem aufgetreten. Mögliche Gründe könnten sein: " +"\"unzureichende Mittel\", oder \"Überprüfung fehlgeschlagen\"." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"This order has already been fully refunded.\n" +"\n" +"Transaction: %s\n" +"Order Status: %s\n" +"Original Amount: %.2f %s\n" +"\n" +"No additional refunds can be processed." +msgstr "" +"Diese Bestellung wurde bereits vollständig erstattet.\n" +"\n" +"Transaktion: %s\n" +"Bestellstatus: %s\n" +"Ursprünglicher Betrag: %.2f %s\n" +"\n" +"Keine zusätzlichen Erstattungen können verarbeitet werden." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction amount is missing or invalid." +msgstr "Transaktionsbetrag fehlt oder ist ungültig." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction currency is missing or invalid." +msgstr "Transaktionswährung fehlt oder ist ungültig." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction not found. Please refresh and try again." +msgstr "Transaktion nicht gefunden. Bitte aktualisieren und erneut versuchen." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction reference is missing." +msgstr "Transaktionsreferenz fehlt." + +#. module: payment_multisafepay +#: model_terms:payment.provider,auth_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been authorized." +msgstr "Ihre Zahlung wurde autorisiert." + +#. module: payment_multisafepay +#: model_terms:payment.provider,cancel_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been cancelled." +msgstr "Ihre Zahlung wurde storniert." + +#. module: payment_multisafepay +#: model_terms:payment.provider,pending_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been processed but is waiting for approval." +msgstr "Ihre Zahlung wurde verarbeitet, wartet aber auf Genehmigung." + +#. module: payment_multisafepay +#: model_terms:payment.provider,done_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been processed." +msgstr "Ihre Zahlung wurde verarbeitet." diff --git a/payment_multisafepay/i18n/es_ES.po b/payment_multisafepay/i18n/es_ES.po new file mode 100644 index 0000000..fe680b3 --- /dev/null +++ b/payment_multisafepay/i18n/es_ES.po @@ -0,0 +1,333 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * payment_multisafepay +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-17 09:38+0000\n" +"PO-Revision-Date: 2025-11-17 09:38+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: payment_multisafepay +#: model_terms:ir.ui.view,arch_db:payment_multisafepay.payment_provider_form_multisafepay +msgid " Sync Payment Methods" +msgstr " Sincronizar Métodos de Pago" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__multisafepay_api_key +msgid "API Key" +msgstr "Clave API" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "An unexpected error occurred. Please try again." +msgstr "Ocurrió un error inesperado. Por favor, inténtelo de nuevo." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__code +msgid "Code" +msgstr "Código" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Could not connect to MultiSafepay API for transaction %s.\n" +"\n" +"Error: %s" +msgstr "" +"No se pudo conectar a la API de MultiSafepay para la transacción %s.\n" +"\n" +"Error: %s" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Could not retrieve order information from MultiSafepay for transaction %s.\n" +"\n" +"Reference: %s\n" +"Provider: %s\n" +"\n" +"Please check your internet connection and try again later." +msgstr "" +"No se pudo recuperar la información del pedido de MultiSafepay para la transacción %s.\n" +"\n" +"Referencia: %s\n" +"Proveedor: %s\n" +"\n" +"Por favor, verifique su conexión a Internet e inténtelo de nuevo más tarde." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__display_name +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__display_name +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_transaction__display_name +msgid "Display Name" +msgstr "Nombre para mostrar" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__id +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__id +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_transaction__id +msgid "ID" +msgstr "ID" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__main_currency_id +msgid "Main Currency" +msgstr "Moneda Principal" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__maximum_amount +msgid "Maximum Amount (EUR)" +msgstr "Monto Máximo (EUR)" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__minimum_amount +msgid "Minimum Amount (EUR)" +msgstr "Monto Mínimo (EUR)" + +#. module: payment_multisafepay +#: model:payment.provider,name:payment_multisafepay.payment_provider_multisafepay +msgid "MultiSafepay" +msgstr "MultiSafepay" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "" +"MultiSafepay API error: Transaction not found. Please refresh and try again." +msgstr "" +"Error de API de MultiSafepay: Transacción no encontrada. Por favor, actualice e inténtelo de nuevo." + +#. module: payment_multisafepay +#: model_terms:ir.ui.view,arch_db:payment_multisafepay.payment_provider_form_multisafepay +msgid "MultiSafepay Credentials" +msgstr "Credenciales de MultiSafepay" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "MultiSafepay SDK is not initialized." +msgstr "El SDK de MultiSafepay no está inicializado." + +#. module: payment_multisafepay +#: model:ir.model.fields.selection,name:payment_multisafepay.selection__payment_provider__code__multisafepay +msgid "Multisafepay" +msgstr "Multisafepay" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "No customer info partner associated." +msgstr "No hay información de cliente asociada." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"No response received from MultiSafepay when attempting to refund transaction %s.\n" +"\n" +"Please check your internet connection and try again later." +msgstr "" +"No se recibió respuesta de MultiSafepay al intentar reembolsar la transacción %s.\n" +"\n" +"Por favor, verifique su conexión a Internet e inténtelo de nuevo más tarde." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__only_multisafepay +msgid "Only Multisafepay" +msgstr "Solo Multisafepay" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_method +msgid "Payment Method" +msgstr "Método de pago" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_provider +msgid "Payment Provider" +msgstr "Proveedor de pago" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_transaction +msgid "Payment Transaction" +msgstr "Transacción de pago" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Payment provider not found." +msgstr "Proveedor de pago no encontrado." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Payment reference is missing. Please try again." +msgstr "Falta la referencia de pago. Por favor, inténtelo de nuevo." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Refund amount exceeds available amount.\n" +"\n" +"Transaction: %s\n" +"Requested Refund: %.2f %s\n" +"Maximum Available: %.2f %s\n" +"Already Refunded: %.2f %s\n" +"\n" +"Please adjust the refund amount." +msgstr "" +"El monto del reembolso excede el monto disponible.\n" +"\n" +"Transacción: %s\n" +"Reembolso Solicitado: %.2f %s\n" +"Máximo Disponible: %.2f %s\n" +"Ya Reembolsado: %.2f %s\n" +"\n" +"Por favor, ajuste el monto del reembolso." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "Refund failed at MultiSafepay: %s\n" +msgstr "El reembolso falló en MultiSafepay: %s\n" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Refunds can only be processed from a refund transaction linked to an " +"original payment transaction." +msgstr "" +"Los reembolsos solo se pueden procesar desde una transacción de reembolso vinculada a una " +"transacción de pago original." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__only_multisafepay +msgid "" +"Technical field to identify if this payment method has MultiSafepay as one " +"of its providers." +msgstr "" +"Campo técnico para identificar si este método de pago tiene MultiSafepay como uno " +"de sus proveedores." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_provider__multisafepay_api_key +msgid "" +"The API key for the Multisafepay account. This is used to authenticate " +"requests to the Multisafepay API." +msgstr "" +"La clave API para la cuenta de Multisafepay. Se utiliza para autenticar " +"solicitudes a la API de Multisafepay." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__main_currency_id +msgid "The main currency of the first provider linked to this payment method." +msgstr "La moneda principal del primer proveedor vinculado a este método de pago." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__maximum_amount +msgid "" +"The maximum payment amount in EUR that this payment provider is available " +"for. Leave 0 to make it available for any payment amount." +msgstr "" +"El monto máximo de pago en EUR para el que está disponible este proveedor de pago. " +"Deje 0 para que esté disponible para cualquier monto de pago." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__minimum_amount +msgid "" +"The minimum payment amount in EUR that this payment provider is available " +"for. Leave 0 to make it available for any payment amount." +msgstr "" +"El monto mínimo de pago en EUR para el que está disponible este proveedor de pago. " +"Deje 0 para que esté disponible para cualquier monto de pago." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_provider__code +msgid "The technical code of this payment provider." +msgstr "El código técnico de este proveedor de pagos." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "" +"There was a problem processing your payment. Possible reasons could be: " +"\"insufficient funds\", or \"verification failed\"." +msgstr "" +"Hubo un problema al procesar su pago. Las razones posibles podrían ser: " +"\"fondos insuficientes\" o \"verificación fallida\"." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"This order has already been fully refunded.\n" +"\n" +"Transaction: %s\n" +"Order Status: %s\n" +"Original Amount: %.2f %s\n" +"\n" +"No additional refunds can be processed." +msgstr "" +"Este pedido ya ha sido reembolsado completamente.\n" +"\n" +"Transacción: %s\n" +"Estado del Pedido: %s\n" +"Monto Original: %.2f %s\n" +"\n" +"No se pueden procesar reembolsos adicionales." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction amount is missing or invalid." +msgstr "El monto de la transacción falta o no es válido." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction currency is missing or invalid." +msgstr "La moneda de la transacción falta o no es válida." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction not found. Please refresh and try again." +msgstr "Transacción no encontrada. Por favor, actualice e inténtelo de nuevo." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction reference is missing." +msgstr "Falta la referencia de la transacción." + +#. module: payment_multisafepay +#: model_terms:payment.provider,auth_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been authorized." +msgstr "Su pago ha sido autorizado." + +#. module: payment_multisafepay +#: model_terms:payment.provider,cancel_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been cancelled." +msgstr "Su pago ha sido cancelado." + +#. module: payment_multisafepay +#: model_terms:payment.provider,pending_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been processed but is waiting for approval." +msgstr "Su pago ha sido procesado pero está esperando aprobación." + +#. module: payment_multisafepay +#: model_terms:payment.provider,done_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been processed." +msgstr "Su pago ha sido procesado." diff --git a/payment_multisafepay/i18n/fr_FR.po b/payment_multisafepay/i18n/fr_FR.po new file mode 100644 index 0000000..b2f7851 --- /dev/null +++ b/payment_multisafepay/i18n/fr_FR.po @@ -0,0 +1,333 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * payment_multisafepay +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-17 09:49+0000\n" +"PO-Revision-Date: 2025-11-17 09:49+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: payment_multisafepay +#: model_terms:ir.ui.view,arch_db:payment_multisafepay.payment_provider_form_multisafepay +msgid " Sync Payment Methods" +msgstr " Synchroniser les méthodes de paiement" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__multisafepay_api_key +msgid "API Key" +msgstr "Clé API" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "An unexpected error occurred. Please try again." +msgstr "Une erreur inattendue s'est produite. Veuillez réessayer." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__code +msgid "Code" +msgstr "Code" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Could not connect to MultiSafepay API for transaction %s.\n" +"\n" +"Error: %s" +msgstr "" +"Impossible de se connecter à l'API MultiSafepay pour la transaction %s.\n" +"\n" +"Erreur : %s" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Could not retrieve order information from MultiSafepay for transaction %s.\n" +"\n" +"Reference: %s\n" +"Provider: %s\n" +"\n" +"Please check your internet connection and try again later." +msgstr "" +"Impossible de récupérer les informations de commande depuis MultiSafepay pour la transaction %s.\n" +"\n" +"Référence : %s\n" +"Fournisseur : %s\n" +"\n" +"Veuillez vérifier votre connexion Internet et réessayer ultérieurement." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__display_name +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__display_name +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_transaction__display_name +msgid "Display Name" +msgstr "Nom d'affichage" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__id +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__id +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_transaction__id +msgid "ID" +msgstr "ID" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__main_currency_id +msgid "Main Currency" +msgstr "Devise principale" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__maximum_amount +msgid "Maximum Amount (EUR)" +msgstr "Montant maximum (EUR)" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__minimum_amount +msgid "Minimum Amount (EUR)" +msgstr "Montant minimum (EUR)" + +#. module: payment_multisafepay +#: model:payment.provider,name:payment_multisafepay.payment_provider_multisafepay +msgid "MultiSafepay" +msgstr "MultiSafepay" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "" +"MultiSafepay API error: Transaction not found. Please refresh and try again." +msgstr "" +"Erreur de l'API MultiSafepay : Transaction introuvable. Veuillez actualiser et réessayer." + +#. module: payment_multisafepay +#: model_terms:ir.ui.view,arch_db:payment_multisafepay.payment_provider_form_multisafepay +msgid "MultiSafepay Credentials" +msgstr "Identifiants MultiSafepay" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "MultiSafepay SDK is not initialized." +msgstr "Le SDK MultiSafepay n'est pas initialisé." + +#. module: payment_multisafepay +#: model:ir.model.fields.selection,name:payment_multisafepay.selection__payment_provider__code__multisafepay +msgid "Multisafepay" +msgstr "Multisafepay" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "No customer info partner associated." +msgstr "Aucun partenaire d'informations client associé." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"No response received from MultiSafepay when attempting to refund transaction %s.\n" +"\n" +"Please check your internet connection and try again later." +msgstr "" +"Aucune réponse reçue de MultiSafepay lors de la tentative de remboursement de la transaction %s.\n" +"\n" +"Veuillez vérifier votre connexion Internet et réessayer ultérieurement." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__only_multisafepay +msgid "Only Multisafepay" +msgstr "Uniquement Multisafepay" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_method +msgid "Payment Method" +msgstr "Mode de paiement" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_provider +msgid "Payment Provider" +msgstr "Fournisseur de paiement" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_transaction +msgid "Payment Transaction" +msgstr "Transaction de paiement" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Payment provider not found." +msgstr "Fournisseur de paiement introuvable." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Payment reference is missing. Please try again." +msgstr "La référence de paiement est manquante. Veuillez réessayer." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Refund amount exceeds available amount.\n" +"\n" +"Transaction: %s\n" +"Requested Refund: %.2f %s\n" +"Maximum Available: %.2f %s\n" +"Already Refunded: %.2f %s\n" +"\n" +"Please adjust the refund amount." +msgstr "" +"Le montant du remboursement dépasse le montant disponible.\n" +"\n" +"Transaction : %s\n" +"Remboursement demandé : %.2f %s\n" +"Maximum disponible : %.2f %s\n" +"Déjà remboursé : %.2f %s\n" +"\n" +"Veuillez ajuster le montant du remboursement." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "Refund failed at MultiSafepay: %s\n" +msgstr "Le remboursement a échoué chez MultiSafepay : %s\n" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Refunds can only be processed from a refund transaction linked to an " +"original payment transaction." +msgstr "" +"Les remboursements ne peuvent être traités qu'à partir d'une transaction de remboursement liée à une " +"transaction de paiement d'origine." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__only_multisafepay +msgid "" +"Technical field to identify if this payment method has MultiSafepay as one " +"of its providers." +msgstr "" +"Champ technique pour identifier si ce mode de paiement a MultiSafepay comme l'un " +"de ses fournisseurs." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_provider__multisafepay_api_key +msgid "" +"The API key for the Multisafepay account. This is used to authenticate " +"requests to the Multisafepay API." +msgstr "" +"La clé API pour le compte Multisafepay. Elle est utilisée pour authentifier " +"les requêtes vers l'API Multisafepay." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__main_currency_id +msgid "The main currency of the first provider linked to this payment method." +msgstr "La devise principale du premier fournisseur lié à ce mode de paiement." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__maximum_amount +msgid "" +"The maximum payment amount in EUR that this payment provider is available " +"for. Leave 0 to make it available for any payment amount." +msgstr "" +"Le montant de paiement maximum en EUR pour lequel ce fournisseur de paiement est disponible. " +"Laisser 0 pour le rendre disponible pour tout montant de paiement." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__minimum_amount +msgid "" +"The minimum payment amount in EUR that this payment provider is available " +"for. Leave 0 to make it available for any payment amount." +msgstr "" +"Le montant de paiement minimum en EUR pour lequel ce fournisseur de paiement est disponible. " +"Laisser 0 pour le rendre disponible pour tout montant de paiement." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_provider__code +msgid "The technical code of this payment provider." +msgstr "Le code technique de ce fournisseur de paiement." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "" +"There was a problem processing your payment. Possible reasons could be: " +"\"insufficient funds\", or \"verification failed\"." +msgstr "" +"Un problème est survenu lors du traitement de votre paiement. Les raisons possibles pourraient être : " +"\"fonds insuffisants\", ou \"échec de la vérification\"." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"This order has already been fully refunded.\n" +"\n" +"Transaction: %s\n" +"Order Status: %s\n" +"Original Amount: %.2f %s\n" +"\n" +"No additional refunds can be processed." +msgstr "" +"Cette commande a déjà été entièrement remboursée.\n" +"\n" +"Transaction : %s\n" +"Statut de la commande : %s\n" +"Montant d'origine : %.2f %s\n" +"\n" +"Aucun remboursement supplémentaire ne peut être traité." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction amount is missing or invalid." +msgstr "Le montant de la transaction est manquant ou invalide." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction currency is missing or invalid." +msgstr "La devise de la transaction est manquante ou invalide." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction not found. Please refresh and try again." +msgstr "Transaction introuvable. Veuillez actualiser et réessayer." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction reference is missing." +msgstr "La référence de la transaction est manquante." + +#. module: payment_multisafepay +#: model_terms:payment.provider,auth_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been authorized." +msgstr "Votre paiement a été autorisé." + +#. module: payment_multisafepay +#: model_terms:payment.provider,cancel_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been cancelled." +msgstr "Votre paiement a été annulé." + +#. module: payment_multisafepay +#: model_terms:payment.provider,pending_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been processed but is waiting for approval." +msgstr "Votre paiement a été traité mais est en attente d'approbation." + +#. module: payment_multisafepay +#: model_terms:payment.provider,done_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been processed." +msgstr "Votre paiement a été traité." diff --git a/payment_multisafepay/i18n/it_IT.po b/payment_multisafepay/i18n/it_IT.po new file mode 100644 index 0000000..2f895d2 --- /dev/null +++ b/payment_multisafepay/i18n/it_IT.po @@ -0,0 +1,333 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * payment_multisafepay +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-17 09:50+0000\n" +"PO-Revision-Date: 2025-11-17 09:50+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: payment_multisafepay +#: model_terms:ir.ui.view,arch_db:payment_multisafepay.payment_provider_form_multisafepay +msgid " Sync Payment Methods" +msgstr " Sincronizza metodi di pagamento" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__multisafepay_api_key +msgid "API Key" +msgstr "Chiave API" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "An unexpected error occurred. Please try again." +msgstr "Si è verificato un errore imprevisto. Riprova." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__code +msgid "Code" +msgstr "Codice" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Could not connect to MultiSafepay API for transaction %s.\n" +"\n" +"Error: %s" +msgstr "" +"Impossibile connettersi all'API MultiSafepay per la transazione %s.\n" +"\n" +"Errore: %s" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Could not retrieve order information from MultiSafepay for transaction %s.\n" +"\n" +"Reference: %s\n" +"Provider: %s\n" +"\n" +"Please check your internet connection and try again later." +msgstr "" +"Impossibile recuperare le informazioni sull'ordine da MultiSafepay per la transazione %s.\n" +"\n" +"Riferimento: %s\n" +"Fornitore: %s\n" +"\n" +"Controlla la tua connessione Internet e riprova più tardi." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__display_name +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__display_name +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_transaction__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__id +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__id +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_transaction__id +msgid "ID" +msgstr "ID" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__main_currency_id +msgid "Main Currency" +msgstr "Valuta principale" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__maximum_amount +msgid "Maximum Amount (EUR)" +msgstr "Importo massimo (EUR)" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__minimum_amount +msgid "Minimum Amount (EUR)" +msgstr "Importo minimo (EUR)" + +#. module: payment_multisafepay +#: model:payment.provider,name:payment_multisafepay.payment_provider_multisafepay +msgid "MultiSafepay" +msgstr "MultiSafepay" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "" +"MultiSafepay API error: Transaction not found. Please refresh and try again." +msgstr "" +"Errore API MultiSafepay: Transazione non trovata. Ricarica e riprova." + +#. module: payment_multisafepay +#: model_terms:ir.ui.view,arch_db:payment_multisafepay.payment_provider_form_multisafepay +msgid "MultiSafepay Credentials" +msgstr "Credenziali MultiSafepay" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "MultiSafepay SDK is not initialized." +msgstr "SDK MultiSafepay non inizializzato." + +#. module: payment_multisafepay +#: model:ir.model.fields.selection,name:payment_multisafepay.selection__payment_provider__code__multisafepay +msgid "Multisafepay" +msgstr "Multisafepay" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "No customer info partner associated." +msgstr "Nessun partner informazioni cliente associato." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"No response received from MultiSafepay when attempting to refund transaction %s.\n" +"\n" +"Please check your internet connection and try again later." +msgstr "" +"Nessuna risposta ricevuta da MultiSafepay durante il tentativo di rimborsare la transazione %s.\n" +"\n" +"Controlla la tua connessione Internet e riprova più tardi." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__only_multisafepay +msgid "Only Multisafepay" +msgstr "Solo Multisafepay" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_method +msgid "Payment Method" +msgstr "Metodo di pagamento" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_provider +msgid "Payment Provider" +msgstr "Fornitore di pagamento" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_transaction +msgid "Payment Transaction" +msgstr "Transazione di pagamento" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Payment provider not found." +msgstr "Fornitore di pagamento non trovato." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Payment reference is missing. Please try again." +msgstr "Riferimento di pagamento mancante. Riprova." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Refund amount exceeds available amount.\n" +"\n" +"Transaction: %s\n" +"Requested Refund: %.2f %s\n" +"Maximum Available: %.2f %s\n" +"Already Refunded: %.2f %s\n" +"\n" +"Please adjust the refund amount." +msgstr "" +"L'importo del rimborso supera l'importo disponibile.\n" +"\n" +"Transazione: %s\n" +"Rimborso richiesto: %.2f %s\n" +"Massimo disponibile: %.2f %s\n" +"Già rimborsato: %.2f %s\n" +"\n" +"Modifica l'importo del rimborso." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "Refund failed at MultiSafepay: %s\n" +msgstr "Rimborso fallito su MultiSafepay: %s\n" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Refunds can only be processed from a refund transaction linked to an " +"original payment transaction." +msgstr "" +"I rimborsi possono essere elaborati solo da una transazione di rimborso collegata a una " +"transazione di pagamento originale." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__only_multisafepay +msgid "" +"Technical field to identify if this payment method has MultiSafepay as one " +"of its providers." +msgstr "" +"Campo tecnico per identificare se questo metodo di pagamento ha MultiSafepay come uno " +"dei suoi fornitori." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_provider__multisafepay_api_key +msgid "" +"The API key for the Multisafepay account. This is used to authenticate " +"requests to the Multisafepay API." +msgstr "" +"La chiave API per l'account Multisafepay. Viene utilizzata per autenticare " +"le richieste all'API Multisafepay." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__main_currency_id +msgid "The main currency of the first provider linked to this payment method." +msgstr "La valuta principale del primo fornitore collegato a questo metodo di pagamento." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__maximum_amount +msgid "" +"The maximum payment amount in EUR that this payment provider is available " +"for. Leave 0 to make it available for any payment amount." +msgstr "" +"L'importo massimo di pagamento in EUR per cui questo fornitore di pagamento è disponibile. " +"Lasciare 0 per renderlo disponibile per qualsiasi importo di pagamento." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__minimum_amount +msgid "" +"The minimum payment amount in EUR that this payment provider is available " +"for. Leave 0 to make it available for any payment amount." +msgstr "" +"L'importo minimo di pagamento in EUR per cui questo fornitore di pagamento è disponibile. " +"Lasciare 0 per renderlo disponibile per qualsiasi importo di pagamento." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_provider__code +msgid "The technical code of this payment provider." +msgstr "Codice tecnico del fornitore di pagamento." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "" +"There was a problem processing your payment. Possible reasons could be: " +"\"insufficient funds\", or \"verification failed\"." +msgstr "" +"Si è verificato un problema durante l'elaborazione del pagamento. Le possibili ragioni potrebbero essere: " +"\"fondi insufficienti\", o \"verifica fallita\"." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"This order has already been fully refunded.\n" +"\n" +"Transaction: %s\n" +"Order Status: %s\n" +"Original Amount: %.2f %s\n" +"\n" +"No additional refunds can be processed." +msgstr "" +"Questo ordine è già stato completamente rimborsato.\n" +"\n" +"Transazione: %s\n" +"Stato ordine: %s\n" +"Importo originale: %.2f %s\n" +"\n" +"Nessun rimborso aggiuntivo può essere elaborato." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction amount is missing or invalid." +msgstr "L'importo della transazione è mancante o non valido." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction currency is missing or invalid." +msgstr "La valuta della transazione è mancante o non valida." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction not found. Please refresh and try again." +msgstr "Transazione non trovata. Ricarica e riprova." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction reference is missing." +msgstr "Riferimento transazione mancante." + +#. module: payment_multisafepay +#: model_terms:payment.provider,auth_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been authorized." +msgstr "Il tuo pagamento è stato autorizzato." + +#. module: payment_multisafepay +#: model_terms:payment.provider,cancel_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been cancelled." +msgstr "Il tuo pagamento è stato annullato." + +#. module: payment_multisafepay +#: model_terms:payment.provider,pending_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been processed but is waiting for approval." +msgstr "Il tuo pagamento è stato elaborato ma è in attesa di approvazione." + +#. module: payment_multisafepay +#: model_terms:payment.provider,done_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been processed." +msgstr "Il tuo pagamento è stato elaborato." diff --git a/payment_multisafepay/i18n/nl_BE.po b/payment_multisafepay/i18n/nl_BE.po new file mode 100644 index 0000000..a7fd0a1 --- /dev/null +++ b/payment_multisafepay/i18n/nl_BE.po @@ -0,0 +1,333 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * payment_multisafepay +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-17 09:44+0000\n" +"PO-Revision-Date: 2025-11-17 09:44+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: payment_multisafepay +#: model_terms:ir.ui.view,arch_db:payment_multisafepay.payment_provider_form_multisafepay +msgid " Sync Payment Methods" +msgstr " Synchroniseer betaalmethoden" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__multisafepay_api_key +msgid "API Key" +msgstr "API-sleutel" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "An unexpected error occurred. Please try again." +msgstr "Er is een onverwachte fout opgetreden. Gelieve opnieuw te proberen." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__code +msgid "Code" +msgstr "Code" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Could not connect to MultiSafepay API for transaction %s.\n" +"\n" +"Error: %s" +msgstr "" +"Kon geen verbinding maken met MultiSafepay API voor transactie %s.\n" +"\n" +"Fout: %s" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Could not retrieve order information from MultiSafepay for transaction %s.\n" +"\n" +"Reference: %s\n" +"Provider: %s\n" +"\n" +"Please check your internet connection and try again later." +msgstr "" +"Kon geen bestelgegevens ophalen van MultiSafepay voor transactie %s.\n" +"\n" +"Referentie: %s\n" +"Aanbieder: %s\n" +"\n" +"Gelieve uw internetverbinding te controleren en later opnieuw te proberen." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__display_name +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__display_name +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_transaction__display_name +msgid "Display Name" +msgstr "Weergavenaam" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__id +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__id +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_transaction__id +msgid "ID" +msgstr "ID" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__main_currency_id +msgid "Main Currency" +msgstr "Hoofdmunt" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__maximum_amount +msgid "Maximum Amount (EUR)" +msgstr "Maximumbedrag (EUR)" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__minimum_amount +msgid "Minimum Amount (EUR)" +msgstr "Minimumbedrag (EUR)" + +#. module: payment_multisafepay +#: model:payment.provider,name:payment_multisafepay.payment_provider_multisafepay +msgid "MultiSafepay" +msgstr "MultiSafepay" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "" +"MultiSafepay API error: Transaction not found. Please refresh and try again." +msgstr "" +"MultiSafepay API-fout: Transactie niet gevonden. Gelieve te vernieuwen en opnieuw te proberen." + +#. module: payment_multisafepay +#: model_terms:ir.ui.view,arch_db:payment_multisafepay.payment_provider_form_multisafepay +msgid "MultiSafepay Credentials" +msgstr "MultiSafepay-referenties" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "MultiSafepay SDK is not initialized." +msgstr "MultiSafepay SDK is niet geïnitialiseerd." + +#. module: payment_multisafepay +#: model:ir.model.fields.selection,name:payment_multisafepay.selection__payment_provider__code__multisafepay +msgid "Multisafepay" +msgstr "Multisafepay" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "No customer info partner associated." +msgstr "Geen klantinformatie partner gekoppeld." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"No response received from MultiSafepay when attempting to refund transaction %s.\n" +"\n" +"Please check your internet connection and try again later." +msgstr "" +"Geen reactie ontvangen van MultiSafepay bij het terugbetalen van transactie %s.\n" +"\n" +"Gelieve uw internetverbinding te controleren en later opnieuw te proberen." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__only_multisafepay +msgid "Only Multisafepay" +msgstr "Alleen Multisafepay" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_method +msgid "Payment Method" +msgstr "Betaalmethode" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_provider +msgid "Payment Provider" +msgstr "Betaalprovider" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_transaction +msgid "Payment Transaction" +msgstr "Betalingstransactie" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Payment provider not found." +msgstr "Betaalprovider niet gevonden." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Payment reference is missing. Please try again." +msgstr "Betalingsreferentie ontbreekt. Gelieve opnieuw te proberen." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Refund amount exceeds available amount.\n" +"\n" +"Transaction: %s\n" +"Requested Refund: %.2f %s\n" +"Maximum Available: %.2f %s\n" +"Already Refunded: %.2f %s\n" +"\n" +"Please adjust the refund amount." +msgstr "" +"Terugbetalingsbedrag overschrijdt beschikbaar bedrag.\n" +"\n" +"Transactie: %s\n" +"Gevraagde terugbetaling: %.2f %s\n" +"Maximaal beschikbaar: %.2f %s\n" +"Reeds terugbetaald: %.2f %s\n" +"\n" +"Gelieve het terugbetalingsbedrag aan te passen." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "Refund failed at MultiSafepay: %s\n" +msgstr "Terugbetaling mislukt bij MultiSafepay: %s\n" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Refunds can only be processed from a refund transaction linked to an " +"original payment transaction." +msgstr "" +"Terugbetalingen kunnen enkel worden verwerkt vanuit een terugbetalingstransactie gekoppeld aan een " +"oorspronkelijke betalingstransactie." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__only_multisafepay +msgid "" +"Technical field to identify if this payment method has MultiSafepay as one " +"of its providers." +msgstr "" +"Technisch veld om te identificeren of deze betaalmethode MultiSafepay heeft als een " +"van zijn providers." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_provider__multisafepay_api_key +msgid "" +"The API key for the Multisafepay account. This is used to authenticate " +"requests to the Multisafepay API." +msgstr "" +"De API-sleutel voor het Multisafepay-account. Deze wordt gebruikt om " +"verzoeken naar de Multisafepay API te authenticeren." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__main_currency_id +msgid "The main currency of the first provider linked to this payment method." +msgstr "De hoofdmunt van de eerste provider gekoppeld aan deze betaalmethode." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__maximum_amount +msgid "" +"The maximum payment amount in EUR that this payment provider is available " +"for. Leave 0 to make it available for any payment amount." +msgstr "" +"Het maximale betalingsbedrag in EUR waarvoor deze betaalprovider beschikbaar is. " +"Laat 0 om deze beschikbaar te maken voor elk betalingsbedrag." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__minimum_amount +msgid "" +"The minimum payment amount in EUR that this payment provider is available " +"for. Leave 0 to make it available for any payment amount." +msgstr "" +"Het minimale betalingsbedrag in EUR waarvoor deze betaalprovider beschikbaar is. " +"Laat 0 om deze beschikbaar te maken voor elk betalingsbedrag." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_provider__code +msgid "The technical code of this payment provider." +msgstr "De technische code van deze betaalprovider." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "" +"There was a problem processing your payment. Possible reasons could be: " +"\"insufficient funds\", or \"verification failed\"." +msgstr "" +"Er was een probleem bij het verwerken van uw betaling. Mogelijke redenen kunnen zijn: " +"\"onvoldoende saldo\", of \"verificatie mislukt\"." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"This order has already been fully refunded.\n" +"\n" +"Transaction: %s\n" +"Order Status: %s\n" +"Original Amount: %.2f %s\n" +"\n" +"No additional refunds can be processed." +msgstr "" +"Deze bestelling is reeds volledig terugbetaald.\n" +"\n" +"Transactie: %s\n" +"Bestelstatus: %s\n" +"Oorspronkelijk bedrag: %.2f %s\n" +"\n" +"Er kunnen geen extra terugbetalingen worden verwerkt." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction amount is missing or invalid." +msgstr "Transactiebedrag ontbreekt of is ongeldig." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction currency is missing or invalid." +msgstr "Transactiemunt ontbreekt of is ongeldig." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction not found. Please refresh and try again." +msgstr "Transactie niet gevonden. Gelieve te vernieuwen en opnieuw te proberen." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction reference is missing." +msgstr "Transactiereferentie ontbreekt." + +#. module: payment_multisafepay +#: model_terms:payment.provider,auth_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been authorized." +msgstr "Uw betaling is goedgekeurd." + +#. module: payment_multisafepay +#: model_terms:payment.provider,cancel_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been cancelled." +msgstr "Uw betaling is geannuleerd." + +#. module: payment_multisafepay +#: model_terms:payment.provider,pending_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been processed but is waiting for approval." +msgstr "Uw betaling is verwerkt maar wacht op goedkeuring." + +#. module: payment_multisafepay +#: model_terms:payment.provider,done_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been processed." +msgstr "Uw betaling is verwerkt." diff --git a/payment_multisafepay/i18n/nl_NL.po b/payment_multisafepay/i18n/nl_NL.po new file mode 100644 index 0000000..2e59217 --- /dev/null +++ b/payment_multisafepay/i18n/nl_NL.po @@ -0,0 +1,333 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * payment_multisafepay +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-17 09:44+0000\n" +"PO-Revision-Date: 2025-11-17 09:44+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: payment_multisafepay +#: model_terms:ir.ui.view,arch_db:payment_multisafepay.payment_provider_form_multisafepay +msgid " Sync Payment Methods" +msgstr " Synchroniseer Betaalmethoden" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__multisafepay_api_key +msgid "API Key" +msgstr "API-sleutel" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "An unexpected error occurred. Please try again." +msgstr "Er is een onverwachte fout opgetreden. Probeer het opnieuw." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__code +msgid "Code" +msgstr "Code" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Could not connect to MultiSafepay API for transaction %s.\n" +"\n" +"Error: %s" +msgstr "" +"Kon geen verbinding maken met MultiSafepay API voor transactie %s.\n" +"\n" +"Fout: %s" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Could not retrieve order information from MultiSafepay for transaction %s.\n" +"\n" +"Reference: %s\n" +"Provider: %s\n" +"\n" +"Please check your internet connection and try again later." +msgstr "" +"Kon orderinformatie niet ophalen van MultiSafepay voor transactie %s.\n" +"\n" +"Referentie: %s\n" +"Provider: %s\n" +"\n" +"Controleer uw internetverbinding en probeer het later opnieuw." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__display_name +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__display_name +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_transaction__display_name +msgid "Display Name" +msgstr "Weergavenaam" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__id +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__id +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_transaction__id +msgid "ID" +msgstr "ID" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__main_currency_id +msgid "Main Currency" +msgstr "Hoofdvaluta" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__maximum_amount +msgid "Maximum Amount (EUR)" +msgstr "Maximumbedrag (EUR)" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__minimum_amount +msgid "Minimum Amount (EUR)" +msgstr "Minimumbedrag (EUR)" + +#. module: payment_multisafepay +#: model:payment.provider,name:payment_multisafepay.payment_provider_multisafepay +msgid "MultiSafepay" +msgstr "MultiSafepay" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "" +"MultiSafepay API error: Transaction not found. Please refresh and try again." +msgstr "" +"MultiSafepay API-fout: Transactie niet gevonden. Vernieuw de pagina en probeer het opnieuw." + +#. module: payment_multisafepay +#: model_terms:ir.ui.view,arch_db:payment_multisafepay.payment_provider_form_multisafepay +msgid "MultiSafepay Credentials" +msgstr "MultiSafepay Inloggegevens" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "MultiSafepay SDK is not initialized." +msgstr "MultiSafepay SDK is niet geïnitialiseerd." + +#. module: payment_multisafepay +#: model:ir.model.fields.selection,name:payment_multisafepay.selection__payment_provider__code__multisafepay +msgid "Multisafepay" +msgstr "Multisafepay" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "No customer info partner associated." +msgstr "Geen klantinformatie gekoppeld." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"No response received from MultiSafepay when attempting to refund transaction %s.\n" +"\n" +"Please check your internet connection and try again later." +msgstr "" +"Geen reactie ontvangen van MultiSafepay bij poging tot terugbetaling van transactie %s.\n" +"\n" +"Controleer uw internetverbinding en probeer het later opnieuw." + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__only_multisafepay +msgid "Only Multisafepay" +msgstr "Alleen Multisafepay" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_method +msgid "Payment Method" +msgstr "Betaalmethode" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_provider +msgid "Payment Provider" +msgstr "Betaalprovider" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_transaction +msgid "Payment Transaction" +msgstr "Betalingstransactie" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Payment provider not found." +msgstr "Betaalprovider niet gevonden." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Payment reference is missing. Please try again." +msgstr "Betalingsreferentie ontbreekt. Probeer het opnieuw." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Refund amount exceeds available amount.\n" +"\n" +"Transaction: %s\n" +"Requested Refund: %.2f %s\n" +"Maximum Available: %.2f %s\n" +"Already Refunded: %.2f %s\n" +"\n" +"Please adjust the refund amount." +msgstr "" +"Terugbetalingsbedrag overschrijdt beschikbaar bedrag.\n" +"\n" +"Transactie: %s\n" +"Gevraagde Terugbetaling: %.2f %s\n" +"Maximaal Beschikbaar: %.2f %s\n" +"Reeds Terugbetaald: %.2f %s\n" +"\n" +"Pas het terugbetalingsbedrag aan." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "Refund failed at MultiSafepay: %s\n" +msgstr "Terugbetaling mislukt bij MultiSafepay: %s\n" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Refunds can only be processed from a refund transaction linked to an " +"original payment transaction." +msgstr "" +"Terugbetalingen kunnen alleen worden verwerkt vanuit een terugbetalingstransactie gekoppeld aan een " +"originele betalingstransactie." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__only_multisafepay +msgid "" +"Technical field to identify if this payment method has MultiSafepay as one " +"of its providers." +msgstr "" +"Technisch veld om te identificeren of deze betaalmethode MultiSafepay als een " +"van zijn providers heeft." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_provider__multisafepay_api_key +msgid "" +"The API key for the Multisafepay account. This is used to authenticate " +"requests to the Multisafepay API." +msgstr "" +"De API-sleutel voor het Multisafepay-account. Dit wordt gebruikt om " +"verzoeken aan de Multisafepay API te authenticeren." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__main_currency_id +msgid "The main currency of the first provider linked to this payment method." +msgstr "De hoofdvaluta van de eerste provider gekoppeld aan deze betaalmethode." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__maximum_amount +msgid "" +"The maximum payment amount in EUR that this payment provider is available " +"for. Leave 0 to make it available for any payment amount." +msgstr "" +"Het maximale betalingsbedrag in EUR waarvoor deze betaalprovider beschikbaar is. " +"Laat 0 staan om het beschikbaar te maken voor elk betalingsbedrag." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__minimum_amount +msgid "" +"The minimum payment amount in EUR that this payment provider is available " +"for. Leave 0 to make it available for any payment amount." +msgstr "" +"Het minimale betalingsbedrag in EUR waarvoor deze betaalprovider beschikbaar is. " +"Laat 0 staan om het beschikbaar te maken voor elk betalingsbedrag." + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_provider__code +msgid "The technical code of this payment provider." +msgstr "De technische code van deze betaalprovider." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "" +"There was a problem processing your payment. Possible reasons could be: " +"\"insufficient funds\", or \"verification failed\"." +msgstr "" +"Er was een probleem bij het verwerken van uw betaling. Mogelijke redenen kunnen zijn: " +"\"onvoldoende saldo\" of \"verificatie mislukt\"." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"This order has already been fully refunded.\n" +"\n" +"Transaction: %s\n" +"Order Status: %s\n" +"Original Amount: %.2f %s\n" +"\n" +"No additional refunds can be processed." +msgstr "" +"Deze bestelling is al volledig terugbetaald.\n" +"\n" +"Transactie: %s\n" +"Bestelstatus: %s\n" +"Oorspronkelijk Bedrag: %.2f %s\n" +"\n" +"Er kunnen geen extra terugbetalingen worden verwerkt." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction amount is missing or invalid." +msgstr "Transactiebedrag ontbreekt of is ongeldig." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction currency is missing or invalid." +msgstr "Transactievaluta ontbreekt of is ongeldig." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction not found. Please refresh and try again." +msgstr "Transactie niet gevonden. Vernieuw de pagina en probeer het opnieuw." + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction reference is missing." +msgstr "Transactiereferentie ontbreekt." + +#. module: payment_multisafepay +#: model_terms:payment.provider,auth_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been authorized." +msgstr "Uw betaling is geautoriseerd." + +#. module: payment_multisafepay +#: model_terms:payment.provider,cancel_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been cancelled." +msgstr "Uw betaling is geannuleerd." + +#. module: payment_multisafepay +#: model_terms:payment.provider,pending_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been processed but is waiting for approval." +msgstr "Uw betaling is verwerkt maar wacht op goedkeuring." + +#. module: payment_multisafepay +#: model_terms:payment.provider,done_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been processed." +msgstr "Uw betaling is verwerkt." diff --git a/payment_multisafepay/i18n/payment_multisafepay.pot b/payment_multisafepay/i18n/payment_multisafepay.pot new file mode 100644 index 0000000..850b500 --- /dev/null +++ b/payment_multisafepay/i18n/payment_multisafepay.pot @@ -0,0 +1,293 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * payment_multisafepay +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-17 09:33+0000\n" +"PO-Revision-Date: 2025-11-17 09:33+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: payment_multisafepay +#: model_terms:ir.ui.view,arch_db:payment_multisafepay.payment_provider_form_multisafepay +msgid " Sync Payment Methods" +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__multisafepay_api_key +msgid "API Key" +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "An unexpected error occurred. Please try again." +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__code +msgid "Code" +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Could not connect to MultiSafepay API for transaction %s.\n" +"\n" +"Error: %s" +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Could not retrieve order information from MultiSafepay for transaction %s.\n" +"\n" +"Reference: %s\n" +"Provider: %s\n" +"\n" +"Please check your internet connection and try again later." +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__display_name +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__display_name +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_transaction__display_name +msgid "Display Name" +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__id +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_provider__id +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_transaction__id +msgid "ID" +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__main_currency_id +msgid "Main Currency" +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__maximum_amount +msgid "Maximum Amount (EUR)" +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__minimum_amount +msgid "Minimum Amount (EUR)" +msgstr "" + +#. module: payment_multisafepay +#: model:payment.provider,name:payment_multisafepay.payment_provider_multisafepay +msgid "MultiSafepay" +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "" +"MultiSafepay API error: Transaction not found. Please refresh and try again." +msgstr "" + +#. module: payment_multisafepay +#: model_terms:ir.ui.view,arch_db:payment_multisafepay.payment_provider_form_multisafepay +msgid "MultiSafepay Credentials" +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "MultiSafepay SDK is not initialized." +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model.fields.selection,name:payment_multisafepay.selection__payment_provider__code__multisafepay +msgid "Multisafepay" +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "No customer info partner associated." +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"No response received from MultiSafepay when attempting to refund transaction %s.\n" +"\n" +"Please check your internet connection and try again later." +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model.fields,field_description:payment_multisafepay.field_payment_method__only_multisafepay +msgid "Only Multisafepay" +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_method +msgid "Payment Method" +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_provider +msgid "Payment Provider" +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model,name:payment_multisafepay.model_payment_transaction +msgid "Payment Transaction" +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Payment provider not found." +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Payment reference is missing. Please try again." +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Refund amount exceeds available amount.\n" +"\n" +"Transaction: %s\n" +"Requested Refund: %.2f %s\n" +"Maximum Available: %.2f %s\n" +"Already Refunded: %.2f %s\n" +"\n" +"Please adjust the refund amount." +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "Refund failed at MultiSafepay: %s\n" +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"Refunds can only be processed from a refund transaction linked to an " +"original payment transaction." +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__only_multisafepay +msgid "" +"Technical field to identify if this payment method has MultiSafepay as one " +"of its providers." +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_provider__multisafepay_api_key +msgid "" +"The API key for the Multisafepay account. This is used to authenticate " +"requests to the Multisafepay API." +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__main_currency_id +msgid "The main currency of the first provider linked to this payment method." +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__maximum_amount +msgid "" +"The maximum payment amount in EUR that this payment provider is available " +"for. Leave 0 to make it available for any payment amount." +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_method__minimum_amount +msgid "" +"The minimum payment amount in EUR that this payment provider is available " +"for. Leave 0 to make it available for any payment amount." +msgstr "" + +#. module: payment_multisafepay +#: model:ir.model.fields,help:payment_multisafepay.field_payment_provider__code +msgid "The technical code of this payment provider." +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "" +"There was a problem processing your payment. Possible reasons could be: " +"\"insufficient funds\", or \"verification failed\"." +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/models/payment_transaction.py:0 +msgid "" +"This order has already been fully refunded.\n" +"\n" +"Transaction: %s\n" +"Order Status: %s\n" +"Original Amount: %.2f %s\n" +"\n" +"No additional refunds can be processed." +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction amount is missing or invalid." +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction currency is missing or invalid." +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction not found. Please refresh and try again." +msgstr "" + +#. module: payment_multisafepay +#. odoo-python +#: code:addons/payment_multisafepay/controllers/main.py:0 +msgid "Transaction reference is missing." +msgstr "" + +#. module: payment_multisafepay +#: model_terms:payment.provider,auth_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been authorized." +msgstr "" + +#. module: payment_multisafepay +#: model_terms:payment.provider,cancel_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been cancelled." +msgstr "" + +#. module: payment_multisafepay +#: model_terms:payment.provider,pending_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been processed but is waiting for approval." +msgstr "" + +#. module: payment_multisafepay +#: model_terms:payment.provider,done_msg:payment_multisafepay.payment_provider_multisafepay +msgid "Your payment has been processed." +msgstr "" diff --git a/payment_multisafepay_official/models/__init__.py b/payment_multisafepay/models/__init__.py similarity index 86% rename from payment_multisafepay_official/models/__init__.py rename to payment_multisafepay/models/__init__.py index ba477e1..92708b5 100644 --- a/payment_multisafepay_official/models/__init__.py +++ b/payment_multisafepay/models/__init__.py @@ -6,5 +6,4 @@ from . import payment_provider from . import payment_method from . import payment_transaction -from . import stock_picking -from . import account_move + diff --git a/payment_multisafepay/models/payment_method.py b/payment_multisafepay/models/payment_method.py new file mode 100644 index 0000000..67b71e0 --- /dev/null +++ b/payment_multisafepay/models/payment_method.py @@ -0,0 +1,337 @@ +# Copyright (c) MultiSafepay, Inc. All rights reserved. +# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. +# See the LICENSE.md file for more information. +# See the DISCLAIMER.md file for disclaimer details + +import logging + +from odoo import api, fields, models + +try: + from odoo.http import request +except ImportError: + # Graceful handling when http module is not available (e.g., during module installation) + request = None + +_logger = logging.getLogger(__name__) + + +class PaymentMethod(models.Model): + """MultiSafepay Payment Method. + + Extends Odoo's payment.method model to add MultiSafepay-specific functionality + including amount filtering, currency handling, and compatibility checks. + """ + + _inherit = "payment.method" + + # =================================== + # FIELDS + # =================================== + + main_currency_id = fields.Many2one( + "res.currency", + string="Main Currency", + compute="_compute_main_currency_id", + store=False, + help="The main currency of the first provider linked to this payment method.", + ) + + minimum_amount = fields.Float( + string="Minimum Amount (EUR)", + help="The minimum payment amount in EUR that this payment provider is available for. " + "Leave 0 to make it available for any payment amount.", + default=0.0, + digits=(16, 2), + ) + + maximum_amount = fields.Float( + string="Maximum Amount (EUR)", + help="The maximum payment amount in EUR that this payment provider is available for. " + "Leave 0 to make it available for any payment amount.", + default=0.0, + digits=(16, 2), + ) + + only_multisafepay = fields.Boolean( + compute="_compute_is_multisafepay", + store=False, + help="Technical field to identify if this payment method has MultiSafepay as one of its providers.", + ) + + + # =================================== + # COMPUTE METHODS (ODOO) + # =================================== + + @api.depends("provider_ids.code") + def _compute_is_multisafepay(self): + """Compute if this payment method has MultiSafepay as one of its providers. + + :return: None + """ + for method in self: + providers = method.provider_ids + # Check if ANY provider is MultiSafepay (not just if it's the only one) + method.only_multisafepay = any( + provider.code == "multisafepay" for provider in providers + ) + + @api.depends("provider_ids.main_currency_id") + def _compute_main_currency_id(self): + """Compute the main currency from the first provider. + + :return: None + """ + for method in self: + provider = method.provider_ids[:1] + method.main_currency_id = provider.main_currency_id if provider else False + + # =================================== + # BUSINESS METHODS (ODOO) + # =================================== + + def _get_compatible_payment_methods( + self, + provider_ids, + partner_id, + currency_id=None, + force_tokenization=False, + is_express_checkout=False, + report=None, + **kwargs, + ): + """Override core method to add amount filtering for MultiSafepay payment methods. + + This method extends Odoo's standard payment method filtering to support: + + - Amount-based filtering (minimum/maximum amount from MultiSafepay API) + - Availability reporting for debugging + + .. note:: + Pricelist filtering moved to payment_multisafepay_enhaced module (custom feature) + + :param provider_ids: List of provider IDs to filter payment methods + :param partner_id: ID of the partner for whom the payment methods are being fetched + :param currency_id: ID of the currency for which the payment methods are being fetched + :param force_tokenization: Boolean indicating if tokenization is forced + :param is_express_checkout: Boolean indicating if the request is for express checkout + :param report: Dictionary to store availability report information + :param kwargs: Additional keyword arguments (amount, sale_order_id, etc.) + :return: Filtered payment methods based on the provided criteria + :rtype: recordset of `payment.method` + """ + + payment_methods = super()._get_compatible_payment_methods( + provider_ids, + partner_id, + currency_id, + force_tokenization, + is_express_checkout, + report=report, + **kwargs, + ) + + amount = kwargs.get("amount") + + sale_order_id = kwargs.get("sale_order_id") + if sale_order_id and "sale.order" in self.env: + try: + order = self.env["sale.order"].browse(sale_order_id) + if order.exists(): + amount = order.amount_total + _logger.debug( + "Using amount %s from sale order %s", amount, order.name + ) + except (ValueError, TypeError, AttributeError) as e: + _logger.error("Error getting amount from sale order: %s", str(e)) + + if amount is None: + _logger.debug("Amount not found in kwargs, checking request parameters") + try: + if ( + request + and hasattr(request, "httprequest") + and hasattr(request.httprequest, "args") + ): + amount_str = request.httprequest.args.get("amount") + if amount_str: + try: + amount = float(amount_str) + _logger.debug("Using amount %s from URL parameters", amount) + except (ValueError, TypeError): + _logger.error( + "Could not convert URL amount parameter to float: %s", + amount_str, + ) + except ImportError: + _logger.error("Could not import request object to get URL parameters") + except (AttributeError, KeyError) as e: + _logger.error("Error getting amount from request: %s", str(e)) + + # Apply amount filtering (minimum_amount / maximum_amount from MultiSafepay API) + if amount is not None: + methods_before_amount = payment_methods.filtered( + lambda m: m.only_multisafepay + ) + payment_methods = payment_methods._filter_by_amount(amount) + methods_after_amount = payment_methods.filtered( + lambda m: m.only_multisafepay + ) + + # Report filtered methods + filtered_out = methods_before_amount - methods_after_amount + self._report_filtered_methods( + report, filtered_out, "amount", {"amount": amount} + ) + + + return payment_methods + + # =================================== + # MULTISAFEPAY - Filtering + # =================================== + + def _filter_by_amount(self, amount): + """Filter payment methods based on amount restrictions. + + Only applies amount filtering to MultiSafepay payment methods. + Other payment providers are not affected by amount restrictions. + + :param amount: The payment amount to filter by + :type amount: float + :return: Filtered payment methods that are allowed for the given amount + :rtype: recordset + """ + if amount is None: + return self + + # Separate MultiSafepay methods from other payment methods + multisafepay_methods = self.filtered(lambda method: method.only_multisafepay) + other_methods = self.filtered(lambda method: not method.only_multisafepay) + + # Apply amount filtering only to MultiSafepay methods + allowed_multisafepay_methods = multisafepay_methods.filtered( + lambda method: ( + not method.minimum_amount or amount >= method.minimum_amount + ) + and (not method.maximum_amount or amount <= method.maximum_amount) + ) + + # Log filtering information + filtered_out_methods = multisafepay_methods - allowed_multisafepay_methods + if filtered_out_methods: + filtered_method_names = [m.name for m in filtered_out_methods] + _logger.info( + "Amount filtering: %d MultiSafepay methods filtered out for amount %.2f: %s", + len(filtered_out_methods), + amount, + ", ".join(filtered_method_names), + ) + + # Return all non-MultiSafepay methods plus filtered MultiSafepay methods + return other_methods + allowed_multisafepay_methods + + + # =================================== + # MULTISAFEPAY - Availability Report + # =================================== + + def _add_method_to_availability_report( + self, report, method, reason, available=False + ): + """Add or update a payment method entry in the availability report. + + :param report: The availability report dictionary + :type report: dict or None + :param method: The payment method to add/update + :type method: recordset + :param reason: The reason why the method is/isn't available + :type reason: str + :param available: Whether the method is available + :type available: bool + :return: None + """ + if report is None: + return + + if "payment_methods" not in report: + report["payment_methods"] = {} + + if method in report["payment_methods"]: + # Update existing entry - combine reasons + existing_reason = report["payment_methods"][method].get("reason", "") + if existing_reason: + report["payment_methods"][method]["reason"] = ( + f"{existing_reason}; {reason}" + ) + else: + report["payment_methods"][method]["reason"] = reason + report["payment_methods"][method]["available"] = available + else: + # Create new entry + report["payment_methods"][method] = { + "available": available, + "reason": reason, + "supported_providers": [ + (provider, True) for provider in method.provider_ids + ], + } + + def _report_filtered_methods(self, report, filtered_methods, filter_type, context): + """Add filtered methods to availability report with appropriate reasons. + + :param report: Availability report dictionary (can be None) + :type report: dict or None + :param filtered_methods: Recordset of methods that were filtered out + :type filtered_methods: recordset + :param filter_type: Type of filter ('amount' or 'pricelist') + :type filter_type: str + :param context: Dictionary with context data (e.g., {'amount': 100.0} or {'pricelist': recordset}) + :type context: dict + :return: None + """ + if report is None or not filtered_methods: + return + + for method in filtered_methods: + if filter_type == "amount": + reason_parts = self._generate_amount_filter_reasons( + method, context.get("amount") + ) + elif filter_type == "pricelist": + reason_parts = self._generate_pricelist_filter_reasons( + method, context.get("pricelist") + ) + else: + reason_parts = [f"Filtered by {filter_type}"] + + if reason_parts: + self._add_method_to_availability_report( + report, method, "; ".join(reason_parts), available=False + ) + + def _generate_amount_filter_reasons(self, method, amount): + """Generate reason messages for amount filtering. + + Checks minimum_amount and maximum_amount restrictions from MultiSafepay API. + + :param method: The payment method + :type method: recordset + :param amount: The payment amount + :type amount: float + :return: List of reason strings + :rtype: list + """ + reason_parts = [] + if method.minimum_amount and amount < method.minimum_amount: + reason_parts.append( + f"Amount {amount} below minimum {method.minimum_amount}" + ) + if method.maximum_amount and amount > method.maximum_amount: + reason_parts.append( + f"Amount {amount} above maximum {method.maximum_amount}" + ) + return reason_parts + + # Note: _generate_pricelist_filter_reasons() method moved to payment_multisafepay_enhaced module (custom feature) diff --git a/payment_multisafepay_official/models/payment_provider.py b/payment_multisafepay/models/payment_provider.py similarity index 58% rename from payment_multisafepay_official/models/payment_provider.py rename to payment_multisafepay/models/payment_provider.py index 9c42793..b0d3a92 100644 --- a/payment_multisafepay_official/models/payment_provider.py +++ b/payment_multisafepay/models/payment_provider.py @@ -3,8 +3,8 @@ # See the LICENSE.md file for more information. # See the DISCLAIMER.md file for disclaimer details -from decimal import Decimal import logging +from decimal import Decimal from multisafepay.sdk import Sdk @@ -15,25 +15,42 @@ _logger = logging.getLogger(__name__) + class PaymentProvider(models.Model): + """MultiSafepay Payment Provider. + + Extends Odoo's payment.provider model to add MultiSafepay-specific functionality + including API integration, payment method synchronization, and provider configuration. + """ + + _inherit = "payment.provider" - _inherit = 'payment.provider' + # =================================== + # FIELDS + # =================================== code = fields.Selection( - selection_add=[('multisafepay', "Multisafepay")], ondelete={'multisafepay': 'set default'}) + selection_add=[("multisafepay", "Multisafepay")], + ondelete={"multisafepay": "set default"}, + ) multisafepay_api_key = fields.Char( - string='API Key', + string="API Key", help="The API key for the Multisafepay account. This is used to authenticate requests to the Multisafepay API.", + copy=False, ) + # =================================== + # CRUD METHODS (ODOO) + # =================================== + def write(self, vals): """Override to handle MultiSafepay provider configuration changes. Detects changes to provider state and API key, then triggers appropriate sync or cleanup operations to maintain payment method consistency. - :param dict vals: Dictionary of field values to write + :param vals: Dictionary of field values to write :return: Result from parent write() method :rtype: bool """ @@ -43,7 +60,7 @@ def write(self, vals): result = super().write(vals) - multisafepay_records = self.filtered(lambda r: r.code == 'multisafepay') + multisafepay_records = self.filtered(lambda r: r.code == "multisafepay") for record in multisafepay_records: old_state = old_states.get(record.id) @@ -51,104 +68,201 @@ def write(self, vals): new_state = record.state new_api_key = record.multisafepay_api_key - state_changed = 'state' in vals and old_state != new_state - api_key_changed = 'multisafepay_api_key' in vals and old_api_key != new_api_key + state_changed = "state" in vals and old_state != new_state + api_key_changed = ( + "multisafepay_api_key" in vals and old_api_key != new_api_key + ) try: if state_changed or api_key_changed: record._on_multisafepay_config_changed( - old_state, new_state, state_changed, new_api_key, api_key_changed + old_state, + new_state, + state_changed, + new_api_key, + api_key_changed, ) except Exception as e: - _logger.error(f"Error in MultiSafepay configuration change handling: {e}") + _logger.error( + f"Error in MultiSafepay configuration change handling: {e}" + ) return result - def _on_multisafepay_config_changed(self, old_state, new_state, state_changed, new_api_key, api_key_changed): + # =================================== + # COMPUTE METHODS (ODOO) + # =================================== + + def _compute_feature_support_fields(self): + """Override to define MultiSafepay-specific feature support. + + Configures which payment features are supported by MultiSafepay: + + - Express checkout: Enabled + - Manual capture: Full amount only + - Refunds: Partial refunds supported + - Tokenization: Enabled for recurring payments + + :return: None + """ + + super()._compute_feature_support_fields() + self.filtered(lambda p: p.code == "multisafepay").update( + { + "support_express_checkout": True, + "support_manual_capture": "full_only", + "support_refund": "partial", + "support_tokenization": True, + } + ) + + # =================================== + # BUSINESS METHODS (ODOO) + # =================================== + + def _get_supported_currencies(self): + """Override of `payment` to return supported currencies for MultiSafepay. + + Filters the parent method's currency list to only include currencies + supported by the MultiSafepay payment provider (defined in const.SUPPORTED_CURRENCIES). + + :return: res.currency records containing only MultiSafepay-supported currencies + :rtype: recordset + """ + + supported_currencies = super()._get_supported_currencies() + if self.code == "multisafepay": + supported_currencies = supported_currencies.filtered( + lambda c: c.name in const.SUPPORTED_CURRENCIES + ) + return supported_currencies + + # =================================== + # ACTION METHODS (ODOO) + # =================================== + + def pull_merchant_payment_methods(self): + """Manual sync trigger for merchant payment methods. + + This is a button action that triggers the full sync process from the API. + It calls _fetch_merchant_payment_methods() to perform the actual sync. + + :return: None + """ + + self._fetch_merchant_payment_methods() + _logger.debug("MultiSafepay payment methods sync completed") + + # =================================== + # MULTISAFEPAY - Configuration + # =================================== + + def _on_multisafepay_config_changed( + self, old_state, new_state, state_changed, new_api_key, api_key_changed + ): """Handle MultiSafepay configuration changes (state and/or API key). This method processes state and API key changes to: + - Clean up payment methods when provider is disabled - Sync payment methods when provider is enabled/test with valid API key - Avoid duplicate operations when both state and API key change simultaneously - :param str old_state: Previous provider state - :param str new_state: New provider state ('enabled', 'test', 'disabled') - :param bool state_changed: Whether the state field changed - :param str new_api_key: New API key value - :param bool api_key_changed: Whether the API key field changed + :param old_state: Previous provider state + :param new_state: New provider state ('enabled', 'test', 'disabled') + :param state_changed: Whether the state field changed + :param new_api_key: New API key value + :param api_key_changed: Whether the API key field changed :return: None - :rtype: None """ self.ensure_one() - if new_state == 'disabled': + if new_state == "disabled": self._on_disable_deactivate_all_methods() return - mode_label = "PRODUCTION" if new_state == 'enabled' else "TEST" + mode_label = "PRODUCTION" if new_state == "enabled" else "TEST" if state_changed and api_key_changed: - _logger.debug(f"MultiSafepay provider {self.name}: state changed {old_state} → {new_state} " - f"and API key changed in {mode_label} environment") + _logger.debug( + f"MultiSafepay provider {self.name}: state changed {old_state} → {new_state} " + f"and API key changed in {mode_label} environment" + ) elif state_changed: - _logger.debug(f"MultiSafepay provider {self.name}: state changed {old_state} → {new_state}") + _logger.debug( + f"MultiSafepay provider {self.name}: state changed {old_state} → {new_state}" + ) elif api_key_changed: _logger.debug(f"MultiSafepay API key changed for {mode_label} environment") - if new_state in ['enabled', 'test']: + if new_state in ["enabled", "test"]: try: if new_api_key: self._fetch_merchant_payment_methods() else: - _logger.debug(f"No API key found. Please configure your API key to fetch payment methods in {mode_label} environment.") + _logger.debug( + f"No API key found. Please configure your API key to fetch payment methods in {mode_label} environment." + ) except Exception as e: _logger.error(f"Error syncing: {e}") - def _get_supported_currencies(self): - """Override of `payment` to return supported currencies for MultiSafepay. + def get_multisafepay_sdk(self): + """Get MultiSafepay SDK instance for API operations. - Filters the parent method's currency list to only include currencies - supported by the MultiSafepay payment provider (defined in const.SUPPORTED_CURRENCIES). + Creates an SDK client configured with the provider's API key and environment + (production or test mode based on provider state). - :return: res.currency records containing only MultiSafepay-supported currencies - :rtype: recordset + :return: Configured MultiSafepay SDK instance for making API requests + :rtype: Sdk """ - supported_currencies = super()._get_supported_currencies() - if self.code == 'multisafepay': - supported_currencies = supported_currencies.filtered( - lambda c: c.name in const.SUPPORTED_CURRENCIES - ) - return supported_currencies + return Sdk( + api_key=self.multisafepay_api_key, is_production=(self.state == "enabled") + ) + + # =================================== + # MULTISAFEPAY - Synchronization + # =================================== - def _on_sync_deactivate_unavailable_payment_method_codes(self, synced_payment_method_codes): + def _on_sync_deactivate_unavailable_payment_method_codes( + self, synced_payment_method_codes + ): """Deactivate payment methods no longer available in API. When a payment method is no longer returned by the MultiSafepay API, it is deactivated to preserve transaction history. - :param set synced_payment_method_codes: Set of payment method codes currently returned by API + :param synced_payment_method_codes: Set of payment method codes currently returned by API + :type synced_payment_method_codes: set :return: None - :rtype: None """ self.ensure_one() - all_provider_methods = self.env['payment.method'].with_context(active_test=False).search([ - ('provider_ids', 'in', [self.id]), - ('code', '=like', f'{const.PAYMENT_METHOD_PREFIX}%') - ]) + all_provider_methods = ( + self.env["payment.method"] + .with_context(active_test=False) + .search( + [ + ("provider_ids", "in", [self.id]), + ("code", "=like", f"{const.PAYMENT_METHOD_PREFIX}%"), + ] + ) + ) - unavailable_payment_method_codes = all_provider_methods.filtered(lambda m: m.code not in synced_payment_method_codes) + unavailable_payment_method_codes = all_provider_methods.filtered( + lambda m: m.code not in synced_payment_method_codes + ) if not unavailable_payment_method_codes: return - unavailable_payment_method_codes.write({'active': False}) + unavailable_payment_method_codes.write({"active": False}) - _logger.debug(f"Deactivated {len(unavailable_payment_method_codes)} unavailable payment methods:") + _logger.debug( + f"Deactivated {len(unavailable_payment_method_codes)} unavailable payment methods:" + ) for method in unavailable_payment_method_codes: _logger.debug(f" - {method.name} (code: {method.code})") @@ -159,23 +273,26 @@ def _on_disable_deactivate_all_methods(self): are deactivated to prevent their use while preserving transaction history. :return: None - :rtype: None """ self.ensure_one() - _logger.debug("MultiSafepay provider disabled - deactivating all associated payment methods") + _logger.debug( + "MultiSafepay provider disabled - deactivating all associated payment methods" + ) - multisafepay_methods = self.env['payment.method'].with_context(active_test=False).search([ - ('provider_ids', 'in', [self.id]) - ]) + multisafepay_methods = ( + self.env["payment.method"] + .with_context(active_test=False) + .search([("provider_ids", "in", [self.id])]) + ) if not multisafepay_methods: _logger.debug("No payment methods found for this MultiSafepay provider") return try: - multisafepay_methods.write({'active': False}) + multisafepay_methods.write({"active": False}) deactivated_count = len(multisafepay_methods) _logger.debug(f"Deactivated {deactivated_count} payment methods:") @@ -191,23 +308,24 @@ def _fetch_merchant_payment_methods(self): """Sync merchant payment methods from MultiSafepay API. This method: + - Fetches available payment methods (gateways and brands) from the API - Creates new methods or updates existing ones preserving user configurations - Associates methods with this provider - Removes unavailable methods that are no longer in the API - .. note:: User configurations like 'active' status are preserved on updates. + .. note:: + User configurations like 'active' status are preserved on updates. :return: None (Logs sync results: count of new and updated methods) - :rtype: None """ self.ensure_one() - _logger.debug('Syncing merchant payment methods') + _logger.debug("Syncing merchant payment methods") if not self.multisafepay_api_key: - _logger.warning('No API key found. Please configure your API key first.') + _logger.warning("No API key found. Please configure your API key first.") return try: @@ -216,7 +334,7 @@ def _fetch_merchant_payment_methods(self): custom_response = gateway_manager.get_payment_methods() gateways = custom_response.get_data() if not gateways or len(gateways) == 0: - _logger.warning('No payment methods found') + _logger.warning("No payment methods found") return # Track codes from API to identify unavailable methods later @@ -224,61 +342,67 @@ def _fetch_merchant_payment_methods(self): count_new = 0 count_updated = 0 - payment_method = self.env['payment.method'] + payment_method = self.env["payment.method"] for gateway in gateways: multisafepay_code = gateway.id.lower() unique_code = self._map_multisafepay_to_odoo_code(multisafepay_code) - _logger.debug("Mapping MultiSafepay '%s' to Odoo code '%s'", multisafepay_code, unique_code) + _logger.debug( + "Mapping MultiSafepay '%s' to Odoo code '%s'", + multisafepay_code, + unique_code, + ) synced_payment_method_codes.add(unique_code) # Prepare values that can be safely updated country_ids = self._get_country_ids(gateway.allowed_countries) currency_ids = self._get_currency_ids(gateway.allowed_currencies) - existing = payment_method.with_context(active_test=False).search([ - ('code', '=', unique_code) - ], limit=1) + existing = payment_method.with_context(active_test=False).search( + [("code", "=", unique_code)], limit=1 + ) # Values that are ALWAYS updated from API api_vals = { - 'name': gateway.name or gateway.id, - 'code': unique_code, - 'support_refund': 'partial', + "name": gateway.name or gateway.id, + "code": unique_code, + "support_refund": "partial", } # New methods are disabled by default creation_only_vals = { - 'active': False, + "active": False, } # Optional values from API (update if present) if gateway.allowed_amount: if gateway.allowed_amount.min: min_decimal = Decimal(str(gateway.allowed_amount.min)) - api_vals['minimum_amount'] = float(min_decimal / Decimal('100')) + api_vals["minimum_amount"] = float(min_decimal / Decimal("100")) if gateway.allowed_amount.max: max_decimal = Decimal(str(gateway.allowed_amount.max)) - api_vals['maximum_amount'] = float(max_decimal / Decimal('100')) + api_vals["maximum_amount"] = float(max_decimal / Decimal("100")) if country_ids: - api_vals['supported_country_ids'] = [(6, 0, country_ids)] + api_vals["supported_country_ids"] = [(6, 0, country_ids)] if currency_ids: - api_vals['supported_currency_ids'] = [(6, 0, currency_ids)] + api_vals["supported_currency_ids"] = [(6, 0, currency_ids)] if gateway.icon_urls and gateway.icon_urls.large: - api_vals['image'] = _get_image_base64(url=gateway.icon_urls.large) + api_vals["image"] = _get_image_base64(url=gateway.icon_urls.large) if not existing: # Use all values: api_vals and creation_only_vals - main_method = payment_method.create({**api_vals, **creation_only_vals}) - main_method.write({'provider_ids': [(4, self.id, 0)]}) + main_method = payment_method.create( + {**api_vals, **creation_only_vals} + ) + main_method.write({"provider_ids": [(4, self.id, 0)]}) count_new += 1 else: existing.write(api_vals) if self.id not in existing.provider_ids.ids: - existing.write({'provider_ids': [(4, self.id, 0)]}) + existing.write({"provider_ids": [(4, self.id, 0)]}) main_method = existing count_updated += 1 @@ -287,65 +411,91 @@ def _fetch_merchant_payment_methods(self): continue multisafepay_brand_code = brand.id.lower() - brand_code = self._map_multisafepay_to_odoo_code(multisafepay_brand_code) + brand_code = self._map_multisafepay_to_odoo_code( + multisafepay_brand_code + ) synced_payment_method_codes.add(brand_code) - existing_brand = payment_method.with_context(active_test=False).search([ - ('code', '=', brand_code) - ], limit=1) + existing_brand = payment_method.with_context( + active_test=False + ).search([("code", "=", brand_code)], limit=1) # Values that are ALWAYS updated from API brand_api_vals = { - 'name': brand.name or brand.id, - 'code': brand_code, - 'primary_payment_method_id': main_method.id, - 'support_refund': 'partial', + "name": brand.name or brand.id, + "code": brand_code, + "primary_payment_method_id": main_method.id, + "support_refund": "partial", } # Brands are inactive by default when created brand_creation_vals = { - 'active': False, + "active": False, } if brand.icon_urls and brand.icon_urls.large: - brand_api_vals['image'] = _get_image_base64(brand.icon_urls.large) + brand_api_vals["image"] = _get_image_base64( + brand.icon_urls.large + ) - brand_currency_ids = self._get_currency_ids(gateway.allowed_currencies) + brand_currency_ids = self._get_currency_ids( + gateway.allowed_currencies + ) if brand_currency_ids: - brand_api_vals['supported_currency_ids'] = [(6, 0, brand_currency_ids)] + brand_api_vals["supported_currency_ids"] = [ + (6, 0, brand_currency_ids) + ] brand_country_ids = self._get_country_ids(brand.allowed_countries) if brand_country_ids: - brand_api_vals['supported_country_ids'] = [(6, 0, brand_country_ids)] + brand_api_vals["supported_country_ids"] = [ + (6, 0, brand_country_ids) + ] if not existing_brand: - brand_method = payment_method.create({**brand_api_vals, **brand_creation_vals}) - brand_method.write({'provider_ids': [(4, self.id, 0)]}) + brand_method = payment_method.create( + {**brand_api_vals, **brand_creation_vals} + ) + brand_method.write({"provider_ids": [(4, self.id, 0)]}) count_new += 1 - _logger.debug(f"Created new brand: {brand_method.name} (code: {brand_method.code})") + _logger.debug( + f"Created new brand: {brand_method.name} (code: {brand_method.code})" + ) else: existing_brand.write(brand_api_vals) if self.id not in existing_brand.provider_ids.ids: - existing_brand.write({'provider_ids': [(4, self.id, 0)]}) - _logger.info(f"Associated brand with provider: {existing_brand.name}") + existing_brand.write({"provider_ids": [(4, self.id, 0)]}) + _logger.info( + f"Associated brand with provider: {existing_brand.name}" + ) else: - _logger.debug(f"Updated brand (already associated): {existing_brand.name}") + _logger.debug( + f"Updated brand (already associated): {existing_brand.name}" + ) count_updated += 1 - self._on_sync_deactivate_unavailable_payment_method_codes(synced_payment_method_codes) + self._on_sync_deactivate_unavailable_payment_method_codes( + synced_payment_method_codes + ) - _logger.debug(f'Successfully synchronized {count_new} new methods, {count_updated} methods updated') + _logger.debug( + f"Successfully synchronized {count_new} new methods, {count_updated} methods updated" + ) except Exception as e: _logger.error("Error loading payment methods: %s", e) + # =================================== + # MULTISAFEPAY - Helpers + # =================================== + def _get_country_ids(self, country_codes): """Convert ISO country codes to Odoo country record IDs. - :param list country_codes: List of ISO 3166-1 alpha-2 country codes (e.g., ['US', 'GB']) + :param country_codes: List of ISO 3166-1 alpha-2 country codes (e.g., ['US', 'GB']) :return: List of res.country record IDs matching the provided codes :rtype: list """ @@ -353,7 +503,7 @@ def _get_country_ids(self, country_codes): if not country_codes: return [] - all_countries = self.env['res.country'].search([]) + all_countries = self.env["res.country"].search([]) country_map = {c.code: c.id for c in all_countries} return [country_map[code] for code in country_codes if code in country_map] @@ -363,7 +513,7 @@ def _get_currency_ids(self, currency_codes): Searches for currencies regardless of their active status to support temporarily inactive currencies that may be reactivated. - :param list currency_codes: List of ISO 4217 currency codes (e.g., ['EUR', 'USD']) + :param currency_codes: List of ISO 4217 currency codes (e.g., ['EUR', 'USD']) :return: List of res.currency record IDs matching the provided codes :rtype: list """ @@ -371,69 +521,22 @@ def _get_currency_ids(self, currency_codes): if not currency_codes: return [] - all_currencies = self.env['res.currency'].with_context(active_test=False).search([]) + all_currencies = ( + self.env["res.currency"].with_context(active_test=False).search([]) + ) currency_map = {c.name: c.id for c in all_currencies} return [currency_map[code] for code in currency_codes if code in currency_map] - def pull_merchant_payment_methods(self): - """Manual sync trigger for merchant payment methods. - - This is a button action that triggers the full sync process from the API. - It calls _fetch_merchant_payment_methods() to perform the actual sync. - - :return: None - :rtype: None - """ - - self._fetch_merchant_payment_methods() - _logger.debug("MultiSafepay payment methods sync completed") - - def _compute_feature_support_fields(self): - """Override to define MultiSafepay-specific feature support. - - Configures which payment features are supported by MultiSafepay: - - Express checkout: Enabled - - Manual capture: Full amount only - - Refunds: Partial refunds supported - - Tokenization: Enabled for recurring payments - - :return: None - :rtype: None - """ - - super()._compute_feature_support_fields() - self.filtered(lambda p: p.code == 'multisafepay').update({ - 'support_express_checkout': True, - 'support_manual_capture': 'full_only', - 'support_refund': 'partial', - 'support_tokenization': True, - }) - - def get_multisafepay_sdk(self): - """Get MultiSafepay SDK instance for API operations. - - Creates an SDK client configured with the provider's API key and environment - (production or test mode based on provider state). - - :return: Configured MultiSafepay SDK instance for making API requests - :rtype: Sdk - """ - - return Sdk( - api_key=self.multisafepay_api_key, - is_production=(self.state == 'enabled') - ) - def _map_multisafepay_to_odoo_code(self, multisafepay_code): """Map MultiSafepay payment method code to Odoo code with multisafepay_ prefix. - :param str multisafepay_code: Payment method code from MultiSafepay (e.g., 'MISTERCASH', 'mistercash') + :param multisafepay_code: Payment method code from MultiSafepay (e.g., 'MISTERCASH', 'mistercash') :return: Odoo code with multisafepay_ prefix (e.g., 'multisafepay_mistercash') :rtype: str """ if not multisafepay_code: - return '' + return "" msp_code_lower = multisafepay_code.lower() return f"{const.PAYMENT_METHOD_PREFIX}{msp_code_lower}" @@ -441,16 +544,16 @@ def _map_multisafepay_to_odoo_code(self, multisafepay_code): def _map_odoo_to_multisafepay_code(self, odoo_code): """Map Odoo payment method code to MultiSafepay code, handling multisafepay_ prefix. - :param str odoo_code: Odoo internal payment method code (e.g., 'multisafepay_mistercash') + :param odoo_code: Odoo internal payment method code (e.g., 'multisafepay_mistercash') :return: MultiSafepay code in uppercase (e.g., 'MISTERCASH') :rtype: str """ if not odoo_code: - return '' + return "" clean_code = odoo_code if odoo_code.startswith(const.PAYMENT_METHOD_PREFIX): - clean_code = odoo_code[len(const.PAYMENT_METHOD_PREFIX):] + clean_code = odoo_code[len(const.PAYMENT_METHOD_PREFIX) :] return clean_code.upper() diff --git a/payment_multisafepay/models/payment_transaction.py b/payment_multisafepay/models/payment_transaction.py new file mode 100644 index 0000000..c95cdb7 --- /dev/null +++ b/payment_multisafepay/models/payment_transaction.py @@ -0,0 +1,472 @@ +# Copyright (c) MultiSafepay, Inc. All rights reserved. +# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. +# See the LICENSE.md file for more information. +# See the DISCLAIMER.md file for disclaimer details + +import logging +import pprint +import uuid +from decimal import Decimal + +from multisafepay.api.paths.orders.order_id.refund.request.refund_request import ( + RefundOrderRequest, +) +from multisafepay.api.shared.cart.cart_item import CartItem +from multisafepay.api.shared.description import Description +from multisafepay.value_object.currency import Currency + +from odoo import _, models +from odoo.exceptions import UserError + +from .. import const + +_logger = logging.getLogger(__name__) + + +class PaymentTransaction(models.Model): + """MultiSafepay Payment Transaction. + + Extends Odoo's payment.transaction model to add MultiSafepay-specific functionality + including transaction processing, status mapping, and refund handling. + """ + + _inherit = "payment.transaction" + + # =================================== + # FIELDS + # =================================== + + # Note: multisafepay_refund_reason field moved to payment_multisafepay_enhaced (custom feature) + + # =================================== + # BUSINESS METHODS (ODOO) + # =================================== + + def _get_specific_processing_values(self, processing_values): + """Override to return specific processing values for MultiSafepay. + + :param processing_values: The generic processing values + :return: MultiSafepay-specific processing values + :rtype: dict + """ + self.ensure_one() + + res = super()._get_specific_processing_values(processing_values) + + if self.provider_code != "multisafepay": + return res + + _logger.debug("Input processing values: %s", str(processing_values)) + + res.update( + { + "reference": self.reference, + } + ) + + _logger.debug("Final processing values: %s", str(res)) + + return res + + def _get_specific_rendering_values(self, processing_values): + """Override to return specific rendering values for MultiSafepay. + + :param processing_values: The generic processing values + :return: MultiSafepay-specific rendering values + :rtype: dict + """ + self.ensure_one() + + res = super()._get_specific_rendering_values(processing_values) + + if self.provider_code != "multisafepay": + return res + + return processing_values + + def _get_tx_from_notification_data(self, provider_code, notification_data): + """Override of payment to find the transaction based on MultiSafepay data. + + :param provider_code: The code of the provider that handled the transaction + :param notification_data: The notification data sent by the provider + :return: The transaction if found + :rtype: recordset of `payment.transaction` + :raises ValidationError: If inconsistent data were received + :raises ValidationError: If the data match no transaction + """ + tx = super()._get_tx_from_notification_data(provider_code, notification_data) + if provider_code != "multisafepay" or len(tx) == 1: + return tx + + reference = notification_data.get("reference") + if reference: + tx = self.search( + [("reference", "=", reference), ("provider_code", "=", "multisafepay")] + ) + + return tx + + def _process_notification_data(self, notification_data): + """Override of `payment` to process the transaction based on MultiSafepay data. + + :param notification_data: The notification data sent by MultiSafepay + :return: None + """ + self.ensure_one() + + # Fix: Odoo 19 compatibility - parent class doesn't have _process_notification_data + # Handle notification processing directly without calling super() + if self.provider_code != "multisafepay": + return + + transaction_id = notification_data.get("reference") + if transaction_id and not self.provider_reference: + self.write({"provider_reference": transaction_id}) + _logger.debug("Updated provider_reference to: %s", transaction_id) + + status = notification_data.get("status") + # If it's already a native Odoo state, return it directly + odoo_native_states = [ + "draft", + "pending", + "authorized", + "done", + "cancel", + "error", + ] + if status in odoo_native_states: + _logger.debug("Status '%s' is native Odoo state - using as-is", status) + odoo_state = status + else: + odoo_state = self._get_multisafepay_status_to_odoo_state(status) + + if odoo_state == "draft": + _logger.debug( + "MSP status '%s' → keeping transaction in draft (ref=%s)", + status, + self.reference, + ) + + elif odoo_state == "done": + _logger.debug( + "MSP status '%s' → marking transaction done (ref=%s)", + status, + self.reference, + ) + # Fix: Odoo 19 compatibility - _set_done() no longer accepts message parameter + self._set_done() + + elif odoo_state == "cancel": + _logger.debug( + "MSP status '%s' → canceling transaction (ref=%s)", + status, + self.reference, + ) + # Fix: Odoo 19 compatibility - _set_canceled() no longer accepts message parameter + self._set_canceled() + + elif odoo_state == "error": + _logger.debug( + "MSP status '%s' → setting transaction to error (ref=%s)", + status, + self.reference, + ) + # Fix: Odoo 19 compatibility - _set_error() no longer accepts message parameter + self._set_error() + + elif odoo_state == "pending": + _logger.debug( + "MSP status '%s' → setting transaction to pending (ref=%s)", + status, + self.reference, + ) + # Fix: Odoo 19 compatibility - _set_pending() no longer accepts message parameter + self._set_pending() + + elif odoo_state == "partial_refunded": + _logger.debug( + "MSP status '%s' → payment partially refunded (ref=%s)", + status, + self.reference, + ) + # Fix: Odoo 19 compatibility - _set_done() no longer accepts message parameter + self._set_done() + + _logger.info("Transaction %s updated to state '%s'", self.reference, odoo_state) + + # Note: _create_child_transaction override moved to payment_multisafepay_enhaced + # (handles multisafepay_refund_reason custom field) + + def _send_refund_request(self, amount_to_refund=0.0): + """Override of payment to send a refund request to MultiSafepay. + + :param amount_to_refund: The amount to refund (default: 0.0) + :return: None + :raises UserError: If refund fails or validation errors occur + """ + self.ensure_one() + + if self.provider_code != "multisafepay": + return super()._send_refund_request() + + # In Odoo 19, self is already the refund transaction + # Get the original transaction via source_transaction_id + if not self.source_transaction_id: + raise UserError( + _( + "Refunds can only be processed from a refund transaction linked to an original payment transaction." + ) + ) + + original_tx = self.source_transaction_id + original_reference = original_tx.reference + amount_to_refund = abs(self.amount) + + provider = self.provider_id + multisafepay_sdk = provider.get_multisafepay_sdk() + order_manager = multisafepay_sdk.get_order_manager() + + try: + # Get current order status from MultiSafepay using original reference + order_response = order_manager.get(original_reference) + order_data = order_response.get_data() + except Exception as api_error: + _logger.error( + "API error retrieving order %s: %s", original_reference, str(api_error) + ) + raise UserError( + _( + "Could not connect to MultiSafepay API for transaction %s.\n\nError: %s" + ) + % (self.reference, str(api_error)) + ) + + if not order_data: + _logger.error("Could not retrieve order data for %s", self.reference) + raise UserError( + _( + "Could not retrieve order information from MultiSafepay for transaction %s.\n\nReference: %s\nProvider: %s\n\nPlease check your internet connection and try again later." + ) + % (self.reference, original_reference, self.provider_id.name) + ) + + original_amount = abs(original_tx.amount) + decimal_places = self.currency_id.decimal_places or 2 + currency_divisor = 10**decimal_places + + # Check order status + order_status = getattr(order_data, "status", "") + + # Early check: if already refunded, don't proceed + if order_status == "refunded": + _logger.info("Order %s is already refunded", original_reference) + raise UserError( + _( + "This order has already been fully refunded.\n\nTransaction: %s\nOrder Status: %s\nOriginal Amount: %.2f %s\n\nNo additional refunds can be processed." + ) + % ( + original_reference, + order_status, + abs(amount_to_refund), + self.currency_id.name, + ) + ) + + remaining_amount = 0 + if hasattr(order_data, "amount_refunded") and order_data.amount_refunded: + _logger.warning( + "Order %s has already been partially refunded", original_reference + ) + + # Use Decimal for precise monetary calculations to avoid floating-point errors + refunded_decimal = Decimal(str(order_data.amount_refunded)) + divisor_decimal = Decimal(str(currency_divisor)) + refunded_amount = float(refunded_decimal / divisor_decimal) + remaining_amount = original_amount - refunded_amount + + if remaining_amount and amount_to_refund > remaining_amount: + _logger.error( + "Refund amount %s exceeds remaining amount %s for order %s", + amount_to_refund, + remaining_amount, + original_reference, + ) + raise UserError( + _( + "Refund amount exceeds available amount.\n\nTransaction: %s\nRequested Refund: %.2f %s\nMaximum Available: %.2f %s\nAlready Refunded: %.2f %s\n\nPlease adjust the refund amount." + ) + % ( + original_reference, + abs(amount_to_refund), + self.currency_id.name, + remaining_amount, + self.currency_id.name, + refunded_amount, + self.currency_id.name, + ) + ) + + try: + # Use Decimal for precise monetary calculations to avoid floating-point errors + amount_decimal = Decimal(str(abs(amount_to_refund))) + divisor_decimal = Decimal(str(currency_divisor)) + amount_in_cents = int(amount_decimal * divisor_decimal) + + refund_response = None + is_bnpl = ( + original_tx.payment_method_code.removeprefix( + const.PAYMENT_METHOD_PREFIX + ) + in const.BNPL_METHODS + ) + + try: + if is_bnpl: + refund_request = order_manager.create_refund_request(order_data) + cart_item = ( + CartItem(**{}) + .add_merchant_item_id(str(uuid.uuid4())) + .add_name(f"Refund for Odoo order {original_reference}") + .add_quantity(1) + .add_unit_price(-amount_to_refund) + .add_tax_table_selector("0") + ) + refund_request.checkout_data.add_item(cart_item) + refund_payload = RefundOrderRequest(**{}).add_checkout_data( + refund_request.checkout_data + ) + + else: + refund_payload = ( + RefundOrderRequest(**{}) + .add_amount(amount_in_cents) + .add_currency(Currency(currency=self.currency_id.name).currency) + .add_description( + Description(**{}).add_description( + f"Refund for Odoo order {original_reference}" + ) + ) + ) + + _logger.debug( + "Refund payload for order %s: %s", + original_reference, + str(refund_payload.dict()), + ) + + refund_response = order_manager.refund( + original_reference, refund_payload + ) + except Exception: + refund_response = False + + if not refund_response: + _logger.error( + "No response received from MultiSafepay for refund of order %s", + original_reference, + ) + raise UserError( + _( + "No response received from MultiSafepay when attempting to refund transaction %s.\n\nPlease check your internet connection and try again later." + ) + % original_reference + ) + + refund_data = refund_response.get_data() + _logger.info("Refund response data: %s", pprint.pformat(refund_data)) + + _logger.debug("Refund response data: %s", str(refund_data)) + + success_refund = False + if is_bnpl: + success_refund = refund_response.body.get("success", False) + else: + success_refund = refund_data and hasattr(refund_data, "transaction_id") + + if not success_refund: + error_msg = None + if refund_data: + for attr in ( + "error_info", + "message", + "description", + "detail", + "error", + ): + if hasattr(refund_data, attr): + error_msg = getattr(refund_data, attr) + break + + if ( + not error_msg + and hasattr(refund_response, "body") + and refund_response.body + ): + for attr in ( + "error_info", + "message", + "description", + "detail", + "error", + ): + error_value = refund_response.body.get(attr) + if error_value: + error_msg = error_value + break + + if error_msg: + _logger.error("Refund failed for %s: %s", self.reference, error_msg) + raise UserError( + _("Refund failed at MultiSafepay: %s\n") % error_msg + ) + + _logger.error( + "Something went wrong with refund for order %s", self.reference + ) + raise UserError( + _("Refund failed at MultiSafepay: %s\n") % self.reference + ) + + # Update refund transaction with provider reference + if refund_data and hasattr(refund_data, "transaction_id"): + self.provider_reference = refund_data.transaction_id + + # Mark refund as successful + notification_data = { + "status": "completed", + "amount": amount_to_refund, + "currency": self.currency_id.name, + } + if refund_data and hasattr(refund_data, "transaction_id"): + notification_data["reference"] = refund_data.transaction_id + + self._process_notification_data(notification_data) + + except Exception as e: + _logger.error( + "Something went wrong with refund for order %s: %s", + self.reference, + str(e), + ) + raise UserError(str(e)) + + # =================================== + # MULTISAFEPAY - Helpers + # =================================== + + def _get_multisafepay_status_to_odoo_state(self, multisafepay_status: str) -> str: + """Map a MultiSafepay status to its corresponding Odoo transaction state. + + This function performs a reverse lookup on the STATUS_MAPPING constant to find + the Odoo state (key) that corresponds to a given MultiSafepay status (value + within the tuple). + + :param multisafepay_status: The status received from MultiSafepay (e.g., 'completed', 'uncleared') + :return: The corresponding Odoo state (e.g., 'done', 'pending'). Defaults to 'error' + :rtype: str + """ + for odoo_state, msp_statuses in const.STATUS_MAPPING.items(): + if multisafepay_status in msp_statuses: + return odoo_state + + return "error" diff --git a/payment_multisafepay/pyproject.toml b/payment_multisafepay/pyproject.toml new file mode 100644 index 0000000..4231d0c --- /dev/null +++ b/payment_multisafepay/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/payment_multisafepay_official/static/description/icon.png b/payment_multisafepay/static/description/icon.png similarity index 100% rename from payment_multisafepay_official/static/description/icon.png rename to payment_multisafepay/static/description/icon.png diff --git a/payment_multisafepay_official/static/description/icon.svg b/payment_multisafepay/static/description/icon.svg similarity index 100% rename from payment_multisafepay_official/static/description/icon.svg rename to payment_multisafepay/static/description/icon.svg diff --git a/payment_multisafepay_official/tests/__init__.py b/payment_multisafepay/tests/__init__.py similarity index 90% rename from payment_multisafepay_official/tests/__init__.py rename to payment_multisafepay/tests/__init__.py index e3191c6..8e54cb9 100644 --- a/payment_multisafepay_official/tests/__init__.py +++ b/payment_multisafepay/tests/__init__.py @@ -4,4 +4,5 @@ # See the DISCLAIMER.md file for disclaimer details from . import test_common +from . import test_payment_method from . import test_payment_transaction diff --git a/payment_multisafepay_official/tests/test_common.py b/payment_multisafepay/tests/test_common.py similarity index 67% rename from payment_multisafepay_official/tests/test_common.py rename to payment_multisafepay/tests/test_common.py index f62ce77..f94b198 100644 --- a/payment_multisafepay_official/tests/test_common.py +++ b/payment_multisafepay/tests/test_common.py @@ -5,10 +5,14 @@ from odoo.tests.common import TransactionCase -class TestMultiSafepayProvider(TransactionCase): +class TestMultiSafepayProvider(TransactionCase): def test_multisafepay_provider_exists(self): - provider = self.env['payment.provider'].search([('code', '=', 'multisafepay')], limit=1) - self.assertTrue(provider, "MultiSafepay provider should exist after module installation.") + provider = self.env["payment.provider"].search( + [("code", "=", "multisafepay")], limit=1 + ) + self.assertTrue( + provider, "MultiSafepay provider should exist after module installation." + ) self.assertEqual(provider.name, "MultiSafepay") self.assertEqual(provider.state, "disabled") diff --git a/payment_multisafepay/tests/test_payment_transaction.py b/payment_multisafepay/tests/test_payment_transaction.py new file mode 100644 index 0000000..5dd046f --- /dev/null +++ b/payment_multisafepay/tests/test_payment_transaction.py @@ -0,0 +1,53 @@ +# Copyright (c) MultiSafepay, Inc. All rights reserved. +# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. +# See the LICENSE.md file for more information. +# See the DISCLAIMER.md file for disclaimer details + + +from odoo.tests.common import TransactionCase + + +class TestPaymentTransaction(TransactionCase): + def setUp(self): + super().setUp() + self.provider = self.env["payment.provider"].create( + { + "name": "MultiSafepay Test", + "code": "multisafepay", + "state": "test", + } + ) + + self.partner = self.env["res.partner"].create( + { + "name": "Test Customer", + "email": "test@example.com", + } + ) + + self.currency = self.env["res.currency"].search([("name", "=", "EUR")], limit=1) + if not self.currency: + self.currency = self.env["res.currency"].create( + { + "name": "EUR", + "symbol": "€", + "rate": 1.0, + } + ) + + def test_compute_reference(self): + """Test _compute_reference method for MultiSafepay transactions""" + + reference = self.env["payment.transaction"]._compute_reference("multisafepay") + + + self.assertTrue(reference.startswith("MSP-")) + parts = reference.split("-") + self.assertEqual(len(parts), 3) # MSP-timestamp-uuid + + timestamp = parts[1] + self.assertEqual(len(timestamp), 14) + self.assertTrue(timestamp.isdigit()) + + uuid_part = parts[2] + self.assertEqual(len(uuid_part), 8) diff --git a/payment_multisafepay_official/utils.py b/payment_multisafepay/utils.py similarity index 94% rename from payment_multisafepay_official/utils.py rename to payment_multisafepay/utils.py index 0746281..cb52a6b 100644 --- a/payment_multisafepay_official/utils.py +++ b/payment_multisafepay/utils.py @@ -10,9 +10,10 @@ _logger = logging.getLogger(__name__) + def _get_image_base64(url): try: - _logger.debug('URL %s', url) + _logger.debug("URL %s", url) response = requests.get(url) if response.status_code == 200: return base64.b64encode(response.content) diff --git a/payment_multisafepay_official/views/payment_method_views.xml b/payment_multisafepay/views/payment_method_views.xml similarity index 64% rename from payment_multisafepay_official/views/payment_method_views.xml rename to payment_multisafepay/views/payment_method_views.xml index c73de06..f3d21c3 100644 --- a/payment_multisafepay_official/views/payment_method_views.xml +++ b/payment_multisafepay/views/payment_method_views.xml @@ -1,45 +1,41 @@ + payment.method.form.inherit.multisafepay payment.method + - - - + + + - + diff --git a/payment_multisafepay_official/views/payment_provider_views.xml b/payment_multisafepay/views/payment_provider_views.xml similarity index 85% rename from payment_multisafepay_official/views/payment_provider_views.xml rename to payment_multisafepay/views/payment_provider_views.xml index a6839b0..c0ed10d 100644 --- a/payment_multisafepay_official/views/payment_provider_views.xml +++ b/payment_multisafepay/views/payment_provider_views.xml @@ -15,14 +15,15 @@ - - + - Sync Payment Methods + Sync Payment Methods @@ -34,7 +35,6 @@ code == 'multisafepay' - diff --git a/payment_multisafepay/views/payment_templates.xml b/payment_multisafepay/views/payment_templates.xml new file mode 100644 index 0000000..5acbd5c --- /dev/null +++ b/payment_multisafepay/views/payment_templates.xml @@ -0,0 +1,207 @@ + + + + + + + + + + + + Payment Error + + + + + + + + + MultiSafepay Payment Details + + + + + + + + Payment Method + + + + + + + + Status + + + + + + + + + + Amount + + + + + + + + + + + + + + Transaction ID + + + + MultiSafepay ID + + + + + + + + + + Account Holder + + + + External Transaction ID + + + + + + Payment Description + + + + Account ID + + + + + + + + + Action required to complete your payment. + + + + + + + + + + + + + + + + + + + + diff --git a/payment_multisafepay_enhaced/__init__.py b/payment_multisafepay_enhaced/__init__.py new file mode 100644 index 0000000..82e6c83 --- /dev/null +++ b/payment_multisafepay_enhaced/__init__.py @@ -0,0 +1,8 @@ +# Copyright (c) MultiSafepay, Inc. All rights reserved. +# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. +# See the LICENSE.md file for more information. +# See the DISCLAIMER.md file for disclaimer details + +# ENHANCED MODULE: Extends payment_multisafepay (CORE) with custom business logic +from . import models +from . import wizard diff --git a/payment_multisafepay_enhaced/__manifest__.py b/payment_multisafepay_enhaced/__manifest__.py new file mode 100644 index 0000000..11e126c --- /dev/null +++ b/payment_multisafepay_enhaced/__manifest__.py @@ -0,0 +1,29 @@ +{ + "name": "MultiSafepay Enhanced", + "description": """ + Enhanced MultiSafepay payment provider with business logic. + Provides invoice updates, stock/picking management, loyalty integration, + pricelist filters, reason codes, and advanced validation. + """, + "summary": """Advanced MultiSafepay business logic integration""", + "author": "MultiSafepay", + "website": "https://github.com//multisafepay", + "license": "AGPL-3", + "category": "Accounting/Payment Providers", + "version": "19.0.1.0.0", + "application": True, + "depends": [ + "payment_multisafepay", + "account", + "stock", + "sale", + "sale_stock", + "sale_management", + ], + "external_dependencies": {"python": ["multisafepay"]}, + "data": [ + "views/payment_method_views.xml", + "views/payment_transaction_views.xml", + "wizard/payment_refund_wizard_views.xml", + ], +} diff --git a/payment_multisafepay_enhaced/i18n/de_DE.po b/payment_multisafepay_enhaced/i18n/de_DE.po new file mode 100644 index 0000000..f305ccc --- /dev/null +++ b/payment_multisafepay_enhaced/i18n/de_DE.po @@ -0,0 +1,144 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * payment_multisafepay_enhaced +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-17 10:08+0000\n" +"PO-Revision-Date: 2025-11-17 10:08+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__pricelist_ids +msgid "Allowed Pricelists" +msgstr "Zulässige Preislisten" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__display_name +msgid "Display Name" +msgstr "Anzeigename" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__id +msgid "ID" +msgstr "ID" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_account_bank_statement_line__multisafepay_notified +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_account_move__multisafepay_notified +msgid "Indicates whether MultiSafepay has been notified about this invoice" +msgstr "Gibt an, ob MultiSafepay über diese Rechnung benachrichtigt wurde" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_stock_picking__multisafepay_notified +msgid "Indicates whether MultiSafepay has been notified about this shipment" +msgstr "Gibt an, ob MultiSafepay über diese Sendung benachrichtigt wurde" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_account_move +msgid "Journal Entry" +msgstr "Journalbuchung" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_method_form_enhanced +msgid "Leave empty to allow all pricelists" +msgstr "Leer lassen, um alle Preislisten zuzulassen" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_bank_statement_line__multisafepay_notified +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__multisafepay_notified +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__multisafepay_notified +msgid "MultiSafepay Notified" +msgstr "MultiSafepay benachrichtigt" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_transaction_form_multisafepay +msgid "MultiSafepay Refund Information" +msgstr "MultiSafepay Rückerstattungsinformationen" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_refund_wizard__multisafepay_refund_reason +msgid "Optional reason for the refund (specific to MultiSafepay)" +msgstr "Optionaler Grund für die Rückerstattung (spezifisch für MultiSafepay)" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_refund_wizard_form_multisafepay +msgid "Optional: e.g., Customer requested, Damaged product..." +msgstr "Optional: z. B. Kundenanfrage, beschädigtes Produkt..." + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_method +msgid "Payment Method" +msgstr "Zahlungsmethode" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_refund_wizard +msgid "Payment Refund Wizard" +msgstr "Assistent für Zahlungserstattungen" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_transaction +msgid "Payment Transaction" +msgstr "Zahlungstransaktion" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__provider_code +msgid "Provider Code" +msgstr "Anbietercode" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_transaction__multisafepay_refund_reason +msgid "Reason for the refund (MultiSafepay specific)" +msgstr "Grund für die Rückerstattung (spezifisch für MultiSafepay)" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__multisafepay_refund_reason +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__multisafepay_refund_reason +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_refund_wizard_form_multisafepay +msgid "Refund Reason" +msgstr "Rückerstattungsgrund" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_method_form_enhanced +msgid "" +"Restrict this payment method to specific pricelists. If empty, the payment " +"method will be available for all pricelists." +msgstr "" +"Diese Zahlungsmethode auf bestimmte Preislisten beschränken. Wenn leer, ist die Zahlungsmethode " +"für alle Preislisten verfügbar." + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_method__pricelist_ids +msgid "" +"Restrict this payment method to specific pricelists. If empty, the payment " +"method will be available for all pricelists. When set, this payment method " +"will only be visible for orders using one of the selected pricelists." +msgstr "" +"Diese Zahlungsmethode auf bestimmte Preislisten beschränken. Wenn leer, ist die Zahlungsmethode " +"für alle Preislisten verfügbar. Wenn festgelegt, ist diese Zahlungsmethode " +"nur für Bestellungen sichtbar, die eine der ausgewählten Preislisten verwenden." + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_stock_picking +msgid "Transfer" +msgstr "Transfer" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_transaction_form_multisafepay +msgid "e.g., Customer requested, Damaged product, etc." +msgstr "z. B. Kundenanfrage, beschädigtes Produkt usw." diff --git a/payment_multisafepay_enhaced/i18n/es_ES.po b/payment_multisafepay_enhaced/i18n/es_ES.po new file mode 100644 index 0000000..46fb020 --- /dev/null +++ b/payment_multisafepay_enhaced/i18n/es_ES.po @@ -0,0 +1,144 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * payment_multisafepay_enhaced +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-17 10:05+0000\n" +"PO-Revision-Date: 2025-11-17 10:05+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__pricelist_ids +msgid "Allowed Pricelists" +msgstr "Listas de precios permitidas" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__display_name +msgid "Display Name" +msgstr "Nombre para mostrar" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__id +msgid "ID" +msgstr "ID" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_account_bank_statement_line__multisafepay_notified +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_account_move__multisafepay_notified +msgid "Indicates whether MultiSafepay has been notified about this invoice" +msgstr "Indica si MultiSafepay ha sido notificado sobre esta factura" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_stock_picking__multisafepay_notified +msgid "Indicates whether MultiSafepay has been notified about this shipment" +msgstr "Indica si MultiSafepay ha sido notificado sobre este envío" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_account_move +msgid "Journal Entry" +msgstr "Asiento contable" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_method_form_enhanced +msgid "Leave empty to allow all pricelists" +msgstr "Dejar vacío para permitir todas las listas de precios" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_bank_statement_line__multisafepay_notified +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__multisafepay_notified +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__multisafepay_notified +msgid "MultiSafepay Notified" +msgstr "MultiSafepay notificado" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_transaction_form_multisafepay +msgid "MultiSafepay Refund Information" +msgstr "Información de reembolso MultiSafepay" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_refund_wizard__multisafepay_refund_reason +msgid "Optional reason for the refund (specific to MultiSafepay)" +msgstr "Motivo opcional para el reembolso (específico de MultiSafepay)" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_refund_wizard_form_multisafepay +msgid "Optional: e.g., Customer requested, Damaged product..." +msgstr "Opcional: p. ej., Solicitado por el cliente, Producto dañado..." + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_method +msgid "Payment Method" +msgstr "Método de pago" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_refund_wizard +msgid "Payment Refund Wizard" +msgstr "Asistente de reembolso de pago" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_transaction +msgid "Payment Transaction" +msgstr "Transacción de pago" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__provider_code +msgid "Provider Code" +msgstr "Código del proveedor" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_transaction__multisafepay_refund_reason +msgid "Reason for the refund (MultiSafepay specific)" +msgstr "Motivo del reembolso (específico de MultiSafepay)" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__multisafepay_refund_reason +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__multisafepay_refund_reason +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_refund_wizard_form_multisafepay +msgid "Refund Reason" +msgstr "Motivo del reembolso" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_method_form_enhanced +msgid "" +"Restrict this payment method to specific pricelists. If empty, the payment " +"method will be available for all pricelists." +msgstr "" +"Restringir este método de pago a listas de precios específicas. Si está vacío, el método de pago " +"estará disponible para todas las listas de precios." + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_method__pricelist_ids +msgid "" +"Restrict this payment method to specific pricelists. If empty, the payment " +"method will be available for all pricelists. When set, this payment method " +"will only be visible for orders using one of the selected pricelists." +msgstr "" +"Restringir este método de pago a listas de precios específicas. Si está vacío, el método de pago " +"estará disponible para todas las listas de precios. Cuando se establece, este método de pago " +"solo será visible para pedidos que utilicen una de las listas de precios seleccionadas." + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_stock_picking +msgid "Transfer" +msgstr "Traslado" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_transaction_form_multisafepay +msgid "e.g., Customer requested, Damaged product, etc." +msgstr "p. ej., Solicitado por el cliente, Producto dañado, etc." diff --git a/payment_multisafepay_enhaced/i18n/fr_FR.po b/payment_multisafepay_enhaced/i18n/fr_FR.po new file mode 100644 index 0000000..db50947 --- /dev/null +++ b/payment_multisafepay_enhaced/i18n/fr_FR.po @@ -0,0 +1,144 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * payment_multisafepay_enhaced +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-17 10:08+0000\n" +"PO-Revision-Date: 2025-11-17 10:08+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__pricelist_ids +msgid "Allowed Pricelists" +msgstr "Listes de prix autorisées" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__display_name +msgid "Display Name" +msgstr "Nom d'affichage" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__id +msgid "ID" +msgstr "ID" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_account_bank_statement_line__multisafepay_notified +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_account_move__multisafepay_notified +msgid "Indicates whether MultiSafepay has been notified about this invoice" +msgstr "Indique si MultiSafepay a été notifié de cette facture" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_stock_picking__multisafepay_notified +msgid "Indicates whether MultiSafepay has been notified about this shipment" +msgstr "Indique si MultiSafepay a été notifié de cette expédition" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_account_move +msgid "Journal Entry" +msgstr "Écriture comptable" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_method_form_enhanced +msgid "Leave empty to allow all pricelists" +msgstr "Laisser vide pour autoriser toutes les listes de prix" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_bank_statement_line__multisafepay_notified +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__multisafepay_notified +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__multisafepay_notified +msgid "MultiSafepay Notified" +msgstr "MultiSafepay notifié" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_transaction_form_multisafepay +msgid "MultiSafepay Refund Information" +msgstr "Informations de remboursement MultiSafepay" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_refund_wizard__multisafepay_refund_reason +msgid "Optional reason for the refund (specific to MultiSafepay)" +msgstr "Raison optionnelle pour le remboursement (spécifique à MultiSafepay)" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_refund_wizard_form_multisafepay +msgid "Optional: e.g., Customer requested, Damaged product..." +msgstr "Optionnel : p. ex., Demande client, Produit endommagé..." + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_method +msgid "Payment Method" +msgstr "Mode de paiement" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_refund_wizard +msgid "Payment Refund Wizard" +msgstr "Assistant du remboursement de paiement" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_transaction +msgid "Payment Transaction" +msgstr "Transaction de paiement" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__provider_code +msgid "Provider Code" +msgstr "Code fournisseur" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_transaction__multisafepay_refund_reason +msgid "Reason for the refund (MultiSafepay specific)" +msgstr "Raison du remboursement (spécifique à MultiSafepay)" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__multisafepay_refund_reason +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__multisafepay_refund_reason +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_refund_wizard_form_multisafepay +msgid "Refund Reason" +msgstr "Raison du remboursement" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_method_form_enhanced +msgid "" +"Restrict this payment method to specific pricelists. If empty, the payment " +"method will be available for all pricelists." +msgstr "" +"Restreindre ce mode de paiement à des listes de prix spécifiques. Si vide, le mode de paiement " +"sera disponible pour toutes les listes de prix." + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_method__pricelist_ids +msgid "" +"Restrict this payment method to specific pricelists. If empty, the payment " +"method will be available for all pricelists. When set, this payment method " +"will only be visible for orders using one of the selected pricelists." +msgstr "" +"Restreindre ce mode de paiement à des listes de prix spécifiques. Si vide, le mode de paiement " +"sera disponible pour toutes les listes de prix. Lorsqu'il est défini, ce mode de paiement " +"ne sera visible que pour les commandes utilisant l'une des listes de prix sélectionnées." + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_stock_picking +msgid "Transfer" +msgstr "Transfert" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_transaction_form_multisafepay +msgid "e.g., Customer requested, Damaged product, etc." +msgstr "p. ex., Demande client, Produit endommagé, etc." diff --git a/payment_multisafepay_enhaced/i18n/it_IT.po b/payment_multisafepay_enhaced/i18n/it_IT.po new file mode 100644 index 0000000..4ab6424 --- /dev/null +++ b/payment_multisafepay_enhaced/i18n/it_IT.po @@ -0,0 +1,144 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * payment_multisafepay_enhaced +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-17 10:08+0000\n" +"PO-Revision-Date: 2025-11-17 10:08+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__pricelist_ids +msgid "Allowed Pricelists" +msgstr "Listini prezzi consentiti" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__id +msgid "ID" +msgstr "ID" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_account_bank_statement_line__multisafepay_notified +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_account_move__multisafepay_notified +msgid "Indicates whether MultiSafepay has been notified about this invoice" +msgstr "Indica se MultiSafepay è stato notificato di questa fattura" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_stock_picking__multisafepay_notified +msgid "Indicates whether MultiSafepay has been notified about this shipment" +msgstr "Indica se MultiSafepay è stato notificato di questa spedizione" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_account_move +msgid "Journal Entry" +msgstr "Registrazione contabile" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_method_form_enhanced +msgid "Leave empty to allow all pricelists" +msgstr "Lasciare vuoto per consentire tutti i listini prezzi" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_bank_statement_line__multisafepay_notified +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__multisafepay_notified +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__multisafepay_notified +msgid "MultiSafepay Notified" +msgstr "MultiSafepay notificato" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_transaction_form_multisafepay +msgid "MultiSafepay Refund Information" +msgstr "Informazioni di rimborso MultiSafepay" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_refund_wizard__multisafepay_refund_reason +msgid "Optional reason for the refund (specific to MultiSafepay)" +msgstr "Motivo facoltativo per il rimborso (specifico per MultiSafepay)" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_refund_wizard_form_multisafepay +msgid "Optional: e.g., Customer requested, Damaged product..." +msgstr "Facoltativo: es., Richiesta cliente, Prodotto danneggiato..." + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_method +msgid "Payment Method" +msgstr "Metodo di pagamento" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_refund_wizard +msgid "Payment Refund Wizard" +msgstr "Procedura guidata rimborso pagamento" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_transaction +msgid "Payment Transaction" +msgstr "Transazione di pagamento" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__provider_code +msgid "Provider Code" +msgstr "Codice fornitore" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_transaction__multisafepay_refund_reason +msgid "Reason for the refund (MultiSafepay specific)" +msgstr "Motivo del rimborso (specifico per MultiSafepay)" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__multisafepay_refund_reason +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__multisafepay_refund_reason +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_refund_wizard_form_multisafepay +msgid "Refund Reason" +msgstr "Motivo del rimborso" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_method_form_enhanced +msgid "" +"Restrict this payment method to specific pricelists. If empty, the payment " +"method will be available for all pricelists." +msgstr "" +"Limitare questo metodo di pagamento a listini prezzi specifici. Se vuoto, il metodo di pagamento " +"sarà disponibile per tutti i listini prezzi." + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_method__pricelist_ids +msgid "" +"Restrict this payment method to specific pricelists. If empty, the payment " +"method will be available for all pricelists. When set, this payment method " +"will only be visible for orders using one of the selected pricelists." +msgstr "" +"Limitare questo metodo di pagamento a listini prezzi specifici. Se vuoto, il metodo di pagamento " +"sarà disponibile per tutti i listini prezzi. Quando impostato, questo metodo di pagamento " +"sarà visibile solo per gli ordini che utilizzano uno dei listini prezzi selezionati." + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_stock_picking +msgid "Transfer" +msgstr "Trasferimento" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_transaction_form_multisafepay +msgid "e.g., Customer requested, Damaged product, etc." +msgstr "es., Richiesta cliente, Prodotto danneggiato, ecc." diff --git a/payment_multisafepay_enhaced/i18n/nl_BE.po b/payment_multisafepay_enhaced/i18n/nl_BE.po new file mode 100644 index 0000000..0e84bc3 --- /dev/null +++ b/payment_multisafepay_enhaced/i18n/nl_BE.po @@ -0,0 +1,144 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * payment_multisafepay_enhaced +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-17 10:05+0000\n" +"PO-Revision-Date: 2025-11-17 10:05+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__pricelist_ids +msgid "Allowed Pricelists" +msgstr "Toegestane prijslijsten" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__display_name +msgid "Display Name" +msgstr "Weergavenaam" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__id +msgid "ID" +msgstr "ID" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_account_bank_statement_line__multisafepay_notified +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_account_move__multisafepay_notified +msgid "Indicates whether MultiSafepay has been notified about this invoice" +msgstr "Geeft aan of MultiSafepay op de hoogte is gesteld van deze factuur" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_stock_picking__multisafepay_notified +msgid "Indicates whether MultiSafepay has been notified about this shipment" +msgstr "Geeft aan of MultiSafepay op de hoogte is gesteld van deze zending" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_account_move +msgid "Journal Entry" +msgstr "Boeking" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_method_form_enhanced +msgid "Leave empty to allow all pricelists" +msgstr "Leeg laten om alle prijslijsten toe te staan" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_bank_statement_line__multisafepay_notified +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__multisafepay_notified +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__multisafepay_notified +msgid "MultiSafepay Notified" +msgstr "MultiSafepay geïnformeerd" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_transaction_form_multisafepay +msgid "MultiSafepay Refund Information" +msgstr "MultiSafepay terugbetalingsinformatie" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_refund_wizard__multisafepay_refund_reason +msgid "Optional reason for the refund (specific to MultiSafepay)" +msgstr "Optionele reden voor de terugbetaling (specifiek voor MultiSafepay)" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_refund_wizard_form_multisafepay +msgid "Optional: e.g., Customer requested, Damaged product..." +msgstr "Optioneel: bijv. Klant verzocht, Beschadigd product..." + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_method +msgid "Payment Method" +msgstr "Betaalmethode" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_refund_wizard +msgid "Payment Refund Wizard" +msgstr "Wizard voor teruggave van betalingen" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_transaction +msgid "Payment Transaction" +msgstr "Betalingstransactie" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__provider_code +msgid "Provider Code" +msgstr "Providercode" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_transaction__multisafepay_refund_reason +msgid "Reason for the refund (MultiSafepay specific)" +msgstr "Reden voor de terugbetaling (specifiek voor MultiSafepay)" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__multisafepay_refund_reason +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__multisafepay_refund_reason +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_refund_wizard_form_multisafepay +msgid "Refund Reason" +msgstr "Reden voor terugbetaling" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_method_form_enhanced +msgid "" +"Restrict this payment method to specific pricelists. If empty, the payment " +"method will be available for all pricelists." +msgstr "" +"Beperk deze betaalmethode tot specifieke prijslijsten. Indien leeg, is de betaalmethode " +"beschikbaar voor alle prijslijsten." + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_method__pricelist_ids +msgid "" +"Restrict this payment method to specific pricelists. If empty, the payment " +"method will be available for all pricelists. When set, this payment method " +"will only be visible for orders using one of the selected pricelists." +msgstr "" +"Beperk deze betaalmethode tot specifieke prijslijsten. Indien leeg, is de betaalmethode " +"beschikbaar voor alle prijslijsten. Wanneer ingesteld, is deze betaalmethode " +"alleen zichtbaar voor bestellingen die een van de geselecteerde prijslijsten gebruiken." + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_stock_picking +msgid "Transfer" +msgstr "Verplaatsing" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_transaction_form_multisafepay +msgid "e.g., Customer requested, Damaged product, etc." +msgstr "bijv. Klant verzocht, Beschadigd product, enz." diff --git a/payment_multisafepay_enhaced/i18n/nl_NL.po b/payment_multisafepay_enhaced/i18n/nl_NL.po new file mode 100644 index 0000000..0e84bc3 --- /dev/null +++ b/payment_multisafepay_enhaced/i18n/nl_NL.po @@ -0,0 +1,144 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * payment_multisafepay_enhaced +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-17 10:05+0000\n" +"PO-Revision-Date: 2025-11-17 10:05+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__pricelist_ids +msgid "Allowed Pricelists" +msgstr "Toegestane prijslijsten" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__display_name +msgid "Display Name" +msgstr "Weergavenaam" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__id +msgid "ID" +msgstr "ID" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_account_bank_statement_line__multisafepay_notified +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_account_move__multisafepay_notified +msgid "Indicates whether MultiSafepay has been notified about this invoice" +msgstr "Geeft aan of MultiSafepay op de hoogte is gesteld van deze factuur" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_stock_picking__multisafepay_notified +msgid "Indicates whether MultiSafepay has been notified about this shipment" +msgstr "Geeft aan of MultiSafepay op de hoogte is gesteld van deze zending" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_account_move +msgid "Journal Entry" +msgstr "Boeking" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_method_form_enhanced +msgid "Leave empty to allow all pricelists" +msgstr "Leeg laten om alle prijslijsten toe te staan" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_bank_statement_line__multisafepay_notified +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__multisafepay_notified +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__multisafepay_notified +msgid "MultiSafepay Notified" +msgstr "MultiSafepay geïnformeerd" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_transaction_form_multisafepay +msgid "MultiSafepay Refund Information" +msgstr "MultiSafepay terugbetalingsinformatie" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_refund_wizard__multisafepay_refund_reason +msgid "Optional reason for the refund (specific to MultiSafepay)" +msgstr "Optionele reden voor de terugbetaling (specifiek voor MultiSafepay)" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_refund_wizard_form_multisafepay +msgid "Optional: e.g., Customer requested, Damaged product..." +msgstr "Optioneel: bijv. Klant verzocht, Beschadigd product..." + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_method +msgid "Payment Method" +msgstr "Betaalmethode" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_refund_wizard +msgid "Payment Refund Wizard" +msgstr "Wizard voor teruggave van betalingen" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_transaction +msgid "Payment Transaction" +msgstr "Betalingstransactie" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__provider_code +msgid "Provider Code" +msgstr "Providercode" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_transaction__multisafepay_refund_reason +msgid "Reason for the refund (MultiSafepay specific)" +msgstr "Reden voor de terugbetaling (specifiek voor MultiSafepay)" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__multisafepay_refund_reason +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__multisafepay_refund_reason +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_refund_wizard_form_multisafepay +msgid "Refund Reason" +msgstr "Reden voor terugbetaling" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_method_form_enhanced +msgid "" +"Restrict this payment method to specific pricelists. If empty, the payment " +"method will be available for all pricelists." +msgstr "" +"Beperk deze betaalmethode tot specifieke prijslijsten. Indien leeg, is de betaalmethode " +"beschikbaar voor alle prijslijsten." + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_method__pricelist_ids +msgid "" +"Restrict this payment method to specific pricelists. If empty, the payment " +"method will be available for all pricelists. When set, this payment method " +"will only be visible for orders using one of the selected pricelists." +msgstr "" +"Beperk deze betaalmethode tot specifieke prijslijsten. Indien leeg, is de betaalmethode " +"beschikbaar voor alle prijslijsten. Wanneer ingesteld, is deze betaalmethode " +"alleen zichtbaar voor bestellingen die een van de geselecteerde prijslijsten gebruiken." + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_stock_picking +msgid "Transfer" +msgstr "Verplaatsing" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_transaction_form_multisafepay +msgid "e.g., Customer requested, Damaged product, etc." +msgstr "bijv. Klant verzocht, Beschadigd product, enz." diff --git a/payment_multisafepay_enhaced/i18n/payment_multisafepay_enhaced.pot b/payment_multisafepay_enhaced/i18n/payment_multisafepay_enhaced.pot new file mode 100644 index 0000000..cefd466 --- /dev/null +++ b/payment_multisafepay_enhaced/i18n/payment_multisafepay_enhaced.pot @@ -0,0 +1,139 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * payment_multisafepay_enhaced +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-17 09:29+0000\n" +"PO-Revision-Date: 2025-11-17 09:29+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__pricelist_ids +msgid "Allowed Pricelists" +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__display_name +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__display_name +msgid "Display Name" +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_method__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__id +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__id +msgid "ID" +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_account_bank_statement_line__multisafepay_notified +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_account_move__multisafepay_notified +msgid "Indicates whether MultiSafepay has been notified about this invoice" +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_stock_picking__multisafepay_notified +msgid "Indicates whether MultiSafepay has been notified about this shipment" +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_account_move +msgid "Journal Entry" +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_method_form_enhanced +msgid "Leave empty to allow all pricelists" +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_bank_statement_line__multisafepay_notified +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_account_move__multisafepay_notified +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_stock_picking__multisafepay_notified +msgid "MultiSafepay Notified" +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_transaction_form_multisafepay +msgid "MultiSafepay Refund Information" +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_refund_wizard__multisafepay_refund_reason +msgid "Optional reason for the refund (specific to MultiSafepay)" +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_refund_wizard_form_multisafepay +msgid "Optional: e.g., Customer requested, Damaged product..." +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_method +msgid "Payment Method" +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_refund_wizard +msgid "Payment Refund Wizard" +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_payment_transaction +msgid "Payment Transaction" +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__provider_code +msgid "Provider Code" +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_transaction__multisafepay_refund_reason +msgid "Reason for the refund (MultiSafepay specific)" +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_refund_wizard__multisafepay_refund_reason +#: model:ir.model.fields,field_description:payment_multisafepay_enhaced.field_payment_transaction__multisafepay_refund_reason +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_refund_wizard_form_multisafepay +msgid "Refund Reason" +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_method_form_enhanced +msgid "" +"Restrict this payment method to specific pricelists. If empty, the payment " +"method will be available for all pricelists." +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model:ir.model.fields,help:payment_multisafepay_enhaced.field_payment_method__pricelist_ids +msgid "" +"Restrict this payment method to specific pricelists. If empty, the payment " +"method will be available for all pricelists. When set, this payment method " +"will only be visible for orders using one of the selected pricelists." +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model:ir.model,name:payment_multisafepay_enhaced.model_stock_picking +msgid "Transfer" +msgstr "" + +#. module: payment_multisafepay_enhaced +#: model_terms:ir.ui.view,arch_db:payment_multisafepay_enhaced.payment_transaction_form_multisafepay +msgid "e.g., Customer requested, Damaged product, etc." +msgstr "" diff --git a/payment_multisafepay_enhaced/models/__init__.py b/payment_multisafepay_enhaced/models/__init__.py new file mode 100644 index 0000000..7d1da28 --- /dev/null +++ b/payment_multisafepay_enhaced/models/__init__.py @@ -0,0 +1,12 @@ +# Copyright (c) MultiSafepay, Inc. All rights reserved. +# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. +# See the LICENSE.md file for more information. +# See the DISCLAIMER.md file for disclaimer details + +# ENHANCED MODULE: Extends CORE with custom business logic +from . import payment_method # Adds: pricelist_ids field + pricelist filtering +from . import payment_transaction # Adds: multisafepay_refund_reason field +from . import account_move # Adds: invoice update notifications to MultiSafepay +from . import stock_picking # Adds: shipping/tracking notifications to MultiSafepay + +# Note: payment_provider is inherited from CORE (no custom logic needed) diff --git a/payment_multisafepay_enhaced/models/account_move.py b/payment_multisafepay_enhaced/models/account_move.py new file mode 100644 index 0000000..5db3546 --- /dev/null +++ b/payment_multisafepay_enhaced/models/account_move.py @@ -0,0 +1,116 @@ +# Copyright (c) MultiSafepay, Inc. All rights reserved. +# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. +# See the LICENSE.md file for more information. +# See the DISCLAIMER.md file for disclaimer details + +import logging + +from multisafepay.api.paths.orders.order_id.update.request.update_request import ( + UpdateOrderRequest, +) + +from odoo import fields, models + +_logger = logging.getLogger(__name__) + + +class AccountMove(models.Model): + """Extend account.move to notify MultiSafepay of invoice status. + + This enhanced model adds automatic notification to MultiSafepay when an invoice + is posted/validated, allowing MultiSafepay to associate the invoice information + with the payment transaction for better record keeping. + """ + + _inherit = "account.move" + + # =================================== + # FIELDS + # =================================== + + multisafepay_notified = fields.Boolean( + string="MultiSafepay Notified", + default=False, + copy=False, + help="Indicates whether MultiSafepay has been notified about this invoice", + ) + + # =================================== + # BUSINESS METHODS + # =================================== + + def action_post(self): + """Override to detect when invoices are validated/posted and notify MultiSafepay. + + This method automatically notifies MultiSafepay when an invoice is posted, + including the invoice ID and access URL for customer reference. + + :return: Result from parent method + :rtype: bool + """ + result = super().action_post() + + # Check if the invoice is an outgoing invoice + for invoice in self.filtered(lambda m: m.move_type == "out_invoice"): + # If already notified, skip + if invoice.multisafepay_notified: + continue + + multisafepay_tx = False + + # Search for MultiSafepay transactions related to this invoice + multisafepay_tx = self.env["payment.transaction"].search( + [ + ("invoice_ids", "in", invoice.ids), + ("provider_code", "=", "multisafepay"), + ], + limit=1, + ) + + provider = multisafepay_tx.provider_id + + # If no transaction found, log and continue + if not provider: + _logger.warning( + "No provider found for transaction %s", multisafepay_tx.id + ) + continue + + multisafepay_sdk = provider.get_multisafepay_sdk() + + # If no SDK found, log and continue + if not multisafepay_sdk: + _logger.warning( + "No MultiSafepay SDK found for provider %s", provider.name + ) + continue + + order_manager = multisafepay_sdk.get_order_manager() + + try: + update_order_request = UpdateOrderRequest().add_invoice_id(invoice.name) + + _logger.debug( + "MultiSafepay update order request: %s", + str(update_order_request.to_dict()), + ) + + if invoice.access_url: + update_order_request.add_invoice_url(invoice.access_url) + + response = order_manager.update( + multisafepay_tx.reference, update_order_request + ) + + _logger.debug( + "MultiSafepay update order request response: %s", str(response) + ) + + if response.status_code == 200: + # Mark the invoice as notified + invoice.multisafepay_notified = True + + except Exception as e: + _logger.error("Error retrieving MultiSafepay order: %s", e) + + return result diff --git a/payment_multisafepay_enhaced/models/payment_method.py b/payment_multisafepay_enhaced/models/payment_method.py new file mode 100644 index 0000000..de6e0c5 --- /dev/null +++ b/payment_multisafepay_enhaced/models/payment_method.py @@ -0,0 +1,309 @@ +# Copyright (c) MultiSafepay, Inc. All rights reserved. +# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. +# See the LICENSE.md file for more information. +# See the DISCLAIMER.md file for disclaimer details + +""" +ENHANCED MODULE: Adds pricelist filtering functionality to MultiSafepay payment methods. +This module EXTENDS payment_multisafepay (CORE) by adding custom pricelist restrictions. +""" + +import logging + +from odoo import api, fields, models + +try: + from odoo.http import request +except ImportError: + request = None + +_logger = logging.getLogger(__name__) + + +class PaymentMethod(models.Model): + """Extend payment.method to add pricelist filtering (ENHANCED feature). + + This enhanced model extends the CORE payment_multisafepay functionality by adding + pricelist-based filtering for payment methods, allowing merchants to restrict + payment methods to specific pricelists. + """ + + _inherit = "payment.method" + + # =================================== + # ENHANCED CUSTOM FIELDS + # =================================== + + pricelist_ids = fields.Many2many( + "product.pricelist", + "payment_method_pricelist_rel", + "method_id", + "pricelist_id", + string="Allowed Pricelists", + help="Restrict this payment method to specific pricelists. " + "If empty, the payment method will be available for all pricelists. " + "When set, this payment method will only be visible for orders using one of the selected pricelists.", + ) + + # =================================== + # ENHANCED: OVERRIDE CORE METHOD + # =================================== + + def _get_compatible_payment_methods( + self, + provider_ids, + partner_id, + currency_id=None, + force_tokenization=False, + is_express_checkout=False, + report=None, + **kwargs, + ): + """Extend CORE method to add pricelist filtering for MultiSafepay methods. + + :param provider_ids: List of provider IDs to filter payment methods + :param partner_id: ID of the partner for whom payment methods are fetched + :param currency_id: ID of the currency for filtering + :param force_tokenization: Whether tokenization is forced + :param is_express_checkout: Whether this is for express checkout + :param report: Dictionary to store availability report information + :param kwargs: Additional keyword arguments + :return: Filtered payment methods based on criteria + :rtype: recordset + """ + + # Call CORE method first (handles amount filtering) + payment_methods = super()._get_compatible_payment_methods( + provider_ids, + partner_id, + currency_id, + force_tokenization, + is_express_checkout, + report=report, + **kwargs, + ) + + # ENHANCED: Apply pricelist filtering - try to get pricelist from multiple sources + pricelist = self._get_pricelist_from_context(kwargs) + + if pricelist and pricelist.exists(): + multisafepay_methods_before_pricelist = payment_methods.filtered( + lambda method: method.only_multisafepay + ) + payment_methods = payment_methods._filter_by_pricelist(pricelist) + multisafepay_methods_after_pricelist = payment_methods.filtered( + lambda method: method.only_multisafepay + ) + + # Add pricelist filtering information to report + if report is not None: + filtered_out_by_pricelist = ( + multisafepay_methods_before_pricelist + - multisafepay_methods_after_pricelist + ) + for method in filtered_out_by_pricelist: + pricelist_reason = f"Not allowed for pricelist '{pricelist.name}'" + self._add_method_to_availability_report( + report, method, pricelist_reason, available=False + ) + + _logger.debug( + "Applied pricelist filtering for pricelist: %s", pricelist.name + ) + else: + _logger.debug("No pricelist found in context, skipping pricelist filtering") + + return payment_methods + + # =================================== + # ENHANCED: PRICELIST FILTERING METHODS + # =================================== + + def _get_pricelist_from_context(self, kwargs): + """Get pricelist from multiple possible sources in the context. + + Tries to retrieve the pricelist in this priority order: + 1. Explicit pricelist_id in kwargs + 2. Sale order from sale_order_id in kwargs + 3. Current cart from request.cart (e-commerce) + 4. Website's default pricelist + + :param kwargs: Additional keyword arguments that may contain pricelist information + :type kwargs: dict + :return: Pricelist recordset or empty recordset + :rtype: recordset + """ + # 1. Check if pricelist_id was explicitly passed + pricelist_id = kwargs.get("pricelist_id") + if pricelist_id: + pricelist = self.env["product.pricelist"].browse(pricelist_id) + if pricelist.exists(): + _logger.debug( + "Pricelist found from explicit pricelist_id: %s", pricelist.name + ) + return pricelist + + # 2. Try to get pricelist from sale order + sale_order_id = kwargs.get("sale_order_id") + if sale_order_id and "sale.order" in self.env: + try: + order = self.env["sale.order"].browse(sale_order_id) + if order.exists() and order.pricelist_id: + _logger.debug( + "Pricelist found from sale order %s: %s", + order.name, + order.pricelist_id.name, + ) + return order.pricelist_id + except (ValueError, TypeError, AttributeError) as e: + _logger.debug("Error getting pricelist from sale order: %s", str(e)) + + # 3. Try to get pricelist from current cart (e-commerce context) + try: + if ( + request + and hasattr(request, "cart") + and request.cart + and request.cart.exists() + ): + if request.cart.pricelist_id: + _logger.debug( + "Pricelist found from cart: %s", request.cart.pricelist_id.name + ) + return request.cart.pricelist_id + except (ImportError, AttributeError) as e: + _logger.debug("Error getting pricelist from cart: %s", str(e)) + + # 4. Try to get pricelist from website (if in website context) + try: + if request and hasattr(request, "website") and request.website: + website_pricelist = request.website.get_current_pricelist() + if website_pricelist and website_pricelist.exists(): + _logger.debug( + "Pricelist found from website: %s", website_pricelist.name + ) + return website_pricelist + except (ImportError, AttributeError) as e: + _logger.debug("Error getting pricelist from website: %s", str(e)) + + # No pricelist found + return self.env["product.pricelist"] + + def _filter_by_pricelist(self, pricelist): + """Filter payment methods based on pricelist restrictions (ENHANCED feature). + + Only applies pricelist filtering to MultiSafepay payment methods. + Other payment providers are not affected by pricelist restrictions. + + :param pricelist: The pricelist to filter by + :type pricelist: recordset + :return: Filtered payment methods that are allowed for the given pricelist + :rtype: recordset + """ + if not pricelist: + return self + + # Separate MultiSafepay methods from other payment methods + multisafepay_methods = self.filtered(lambda method: method.only_multisafepay) + other_methods = self.filtered(lambda method: not method.only_multisafepay) + + # Apply pricelist filtering only to MultiSafepay methods + # Other methods are always allowed regardless of pricelist + allowed_multisafepay_methods = multisafepay_methods.filtered( + lambda method: not method.pricelist_ids or pricelist in method.pricelist_ids + ) + + # Log filtering information for debugging and reporting + filtered_out_methods = multisafepay_methods - allowed_multisafepay_methods + if filtered_out_methods: + filtered_method_names = [m.name for m in filtered_out_methods] + _logger.info( + "Pricelist filtering: %d MultiSafepay methods filtered out due to pricelist '%s': %s", + len(filtered_out_methods), + pricelist.name, + ", ".join(filtered_method_names), + ) + + if allowed_multisafepay_methods: + allowed_method_names = [m.name for m in allowed_multisafepay_methods] + _logger.info( + "Pricelist filtering: %d MultiSafepay methods allowed for pricelist '%s': %s", + len(allowed_multisafepay_methods), + pricelist.name, + ", ".join(allowed_method_names), + ) + + # Return all non-MultiSafepay methods plus filtered MultiSafepay methods + return other_methods + allowed_multisafepay_methods + + @api.model + def _get_compatible_payment_methods_with_pricelist( + self, + provider_ids, + partner_id, + currency_id=None, + force_tokenization=False, + is_express_checkout=False, + pricelist_id=None, + **kwargs, + ): + """Get payment methods compatible with given criteria, including pricelist filtering. + + This method extends the standard method filtering to include pricelist-based restrictions. + + :param provider_ids: Provider IDs for filtering + :type provider_ids: list + :param partner_id: Partner ID for filtering + :type partner_id: int + :param currency_id: Currency ID for filtering + :type currency_id: int or None + :param force_tokenization: Force tokenization flag + :type force_tokenization: bool + :param is_express_checkout: Express checkout flag + :type is_express_checkout: bool + :param pricelist_id: Pricelist ID for filtering + :type pricelist_id: int or None + :param kwargs: Additional filtering criteria + :return: Compatible payment methods + :rtype: recordset + """ + # Get standard compatible methods + methods = self._get_compatible_payment_methods( + provider_ids, + partner_id, + currency_id, + force_tokenization, + is_express_checkout, + **kwargs, + ) + + # Apply pricelist filtering if provided + if pricelist_id: + pricelist = self.env["product.pricelist"].browse(pricelist_id) + methods = methods._filter_by_pricelist(pricelist) + + return methods + + def _generate_pricelist_filter_reasons(self, method, pricelist): + """Generate human-readable reasons for pricelist filtering (ENHANCED feature). + + :param method: Payment method record + :type method: recordset + :param pricelist: Pricelist record + :type pricelist: recordset + :return: List of reason strings + :rtype: list + """ + reason_parts = [] + + if not pricelist: + return reason_parts + + if method.pricelist_ids and pricelist not in method.pricelist_ids: + allowed_names = ", ".join(method.pricelist_ids.mapped("name")) + reason_parts.append( + f"Not allowed for pricelist '{pricelist.name}' " + f"(allowed: {allowed_names})" + ) + + return reason_parts diff --git a/payment_multisafepay_enhaced/models/payment_transaction.py b/payment_multisafepay_enhaced/models/payment_transaction.py new file mode 100644 index 0000000..fb4381b --- /dev/null +++ b/payment_multisafepay_enhaced/models/payment_transaction.py @@ -0,0 +1,76 @@ +# Copyright (c) MultiSafepay, Inc. All rights reserved. +# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. +# See the LICENSE.md file for more information. +# See the DISCLAIMER.md file for disclaimer details + +""" +ENHANCED MODULE: Adds refund reason functionality to MultiSafepay payment transactions. +This module EXTENDS payment_multisafepay (CORE) by adding custom refund reason tracking. +""" + +import logging + +from odoo import fields, models + +_logger = logging.getLogger(__name__) + + +class PaymentTransaction(models.Model): + """Extend payment.transaction to add refund reason tracking (ENHANCED feature). + + This enhanced model extends the CORE payment_multisafepay functionality by adding + refund reason tracking for MultiSafepay transactions, allowing better audit trails + and customer communication during refund operations. + """ + + _inherit = "payment.transaction" + + # =================================== + # ENHANCED CUSTOM FIELDS + # =================================== + + multisafepay_refund_reason = fields.Char( + string="Refund Reason", + help="Reason for the refund (MultiSafepay specific)", + copy=False, + ) + + # =================================== + # ENHANCED: OVERRIDE CORE METHOD + # =================================== + + def _create_child_transaction( + self, amount, is_refund=False, **custom_create_values + ): + """Override to add MultiSafepay refund reason when creating refund transaction. + + The reason can come from context (set by the wizard). + + :param amount: The transaction amount + :param is_refund: Whether this is a refund transaction + :param custom_create_values: Additional values for transaction creation + :return: Created child transaction + :rtype: recordset + """ + # Get the refund reason from the context (set by the wizard) + if is_refund and self.provider_code == "multisafepay": + refund_reason = self.env.context.get("multisafepay_refund_reason") + _logger.debug( + "Creating refund transaction. Provider: %s, Reason from context: %s", + self.provider_code, + refund_reason, + ) + if refund_reason: + custom_create_values["multisafepay_refund_reason"] = refund_reason + _logger.debug("Added reason to custom_create_values: %s", refund_reason) + + # Call the parent method (CORE) with the updated custom_create_values + result = super()._create_child_transaction( + amount, is_refund=is_refund, **custom_create_values + ) + _logger.debug( + "Child transaction created. ID: %s, Reason field: %s", + result.id, + result.multisafepay_refund_reason, + ) + return result diff --git a/payment_multisafepay_enhaced/models/stock_picking.py b/payment_multisafepay_enhaced/models/stock_picking.py new file mode 100644 index 0000000..c839c02 --- /dev/null +++ b/payment_multisafepay_enhaced/models/stock_picking.py @@ -0,0 +1,197 @@ +# Copyright (c) MultiSafepay, Inc. All rights reserved. +# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. +# See the LICENSE.md file for more information. +# See the DISCLAIMER.md file for disclaimer details + +import logging + +from multisafepay.api.paths.orders.order_id.update.request.update_request import ( + UpdateOrderRequest, +) + +from odoo import fields, models + +_logger = logging.getLogger(__name__) + + +class StockPicking(models.Model): + """Extend stock.picking to notify MultiSafepay of shipment status. + + This enhanced model adds automatic notification to MultiSafepay when a picking + (shipment) is completed, including tracking information for improved customer + experience and order tracking. + """ + + _inherit = "stock.picking" + + # =================================== + # FIELDS + # =================================== + + multisafepay_notified = fields.Boolean( + string="MultiSafepay Notified", + copy=False, + default=False, + help="Indicates whether MultiSafepay has been notified about this shipment", + ) + + # =================================== + # BUSINESS METHODS + # =================================== + + def _action_done(self): + """Override to detect when pickings are marked as done and notify MultiSafepay. + + This method automatically notifies MultiSafepay when a shipment is completed, + including tracking information if available. + + :return: Result from parent method + :rtype: bool + """ + # Call original method first + result = super()._action_done() + + multisafepay_tx = False + + # Search for MultiSafepay transactions related to this picking + for picking in self: + # Check if this picking have is related to a multisafepay transaction + if picking.multisafepay_notified: + _logger.info( + "MultiSafepay already notified for picking %s", picking.name + ) + continue + + # Get sale order(s) from picking - Odoo 19 compatibility + # In Odoo 19, stock.picking has a direct computed field 'sale_id' + sale_order = picking.sale_id + + if not sale_order: + _logger.info("No sale order found for picking %s", picking.name) + continue + + # Find MultiSafepay transaction through the sale order + multisafepay_tx = self.env["payment.transaction"].search( + [ + ("sale_order_ids", "in", [sale_order.id]), + ("provider_code", "=", "multisafepay"), + ], + limit=1, + ) + + # If no transaction found, log and continue + if not multisafepay_tx: + _logger.info( + "No MultiSafepay transaction found for order %s", sale_order.name + ) + continue + + # Check if this is the last picking (all others are done or cancelled) + # In Odoo 19, use sale_id field directly instead of searching by origin + pending_pickings = self.env["stock.picking"].search( + [ + ("sale_id", "=", sale_order.id), + ("state", "not in", ["done", "cancel"]), + ("id", "!=", picking.id), # Exclude current picking + ] + ) + + # Only notify MultiSafepay if this is the last shipment + if pending_pickings: + _logger.info( + "Order %s has pending pickings: %s. Not notifying MultiSafepay yet.", + sale_order.name, + pending_pickings.mapped("name"), + ) + continue + + # If we have a transaction, check if we need to notify MultiSafepay + if not picking.multisafepay_notified: + _logger.info("Notifying MultiSafepay for picking %s", picking.name) + + try: + provider = multisafepay_tx.provider_id + multisafepay_sdk = provider.get_multisafepay_sdk() + + if not multisafepay_sdk: + _logger.warning( + "No MultiSafepay SDK found for provider %s", provider.name + ) + continue + + order_manager = multisafepay_sdk.get_order_manager() + + update_order_request = UpdateOrderRequest().add_status("shipped") + + # Get tracking info safely - these fields only exist with 'delivery' module installed + carrier_tracking_ref = getattr( + picking, "carrier_tracking_ref", None + ) + carrier_tracking_url = getattr( + picking, "carrier_tracking_url", None + ) + carrier_id = getattr(picking, "carrier_id", None) + + # Only add tracking info if we have at least the tracking reference + if carrier_tracking_ref and picking.date_done: + update_order_request.add_tracktrace_code(carrier_tracking_ref) + + # Add optional tracking info if available + if carrier_tracking_url: + update_order_request.add_tracktrace_url( + carrier_tracking_url + ) + + if carrier_id and hasattr(carrier_id, "name"): + update_order_request.add_carrier(carrier_id.name) + + update_order_request.add_ship_date( + picking.date_done.strftime("%Y-%m-%dT%H:%M:%S") + ) + _logger.debug( + "Including tracking info: %s", carrier_tracking_ref + ) + else: + # No tracking info available - notify shipment status only + _logger.info( + "No tracking info available for picking %s (delivery module may not be installed or tracking not set)", + picking.name, + ) + + _logger.debug( + "MultiSafepay update order request: %s", + str(update_order_request.to_dict()), + ) + + response = order_manager.update( + multisafepay_tx.reference, update_order_request + ) + + _logger.debug( + "MultiSafepay update order request response: %s", str(response) + ) + + if response.status_code == 200: + _logger.info( + "MultiSafepay notified successfully for picking %s", + picking.name, + ) + # Mark the picking as notified + picking.multisafepay_notified = True + + else: + _logger.error( + "Failed to notify MultiSafepay for picking %s", picking.name + ) + + except Exception as e: + _logger.error( + "Error notifying MultiSafepay for picking %s: %s", + picking.name, + str(e), + ) + continue + + picking.multisafepay_notified = True + + return result diff --git a/payment_multisafepay_enhaced/pyproject.toml b/payment_multisafepay_enhaced/pyproject.toml new file mode 100644 index 0000000..4231d0c --- /dev/null +++ b/payment_multisafepay_enhaced/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/payment_multisafepay_enhaced/static/description/icon.png b/payment_multisafepay_enhaced/static/description/icon.png new file mode 100644 index 0000000..b9b70d1 Binary files /dev/null and b/payment_multisafepay_enhaced/static/description/icon.png differ diff --git a/payment_multisafepay_enhaced/static/description/icon.svg b/payment_multisafepay_enhaced/static/description/icon.svg new file mode 100644 index 0000000..fb3ec5a --- /dev/null +++ b/payment_multisafepay_enhaced/static/description/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/payment_multisafepay_enhaced/views/payment_method_views.xml b/payment_multisafepay_enhaced/views/payment_method_views.xml new file mode 100644 index 0000000..0a70001 --- /dev/null +++ b/payment_multisafepay_enhaced/views/payment_method_views.xml @@ -0,0 +1,36 @@ + + + + + + payment.method.form.inherit.multisafepay.enhanced + payment.method + + + + + + + + + + + + + + diff --git a/payment_multisafepay_official/views/payment_transaction_views.xml b/payment_multisafepay_enhaced/views/payment_transaction_views.xml similarity index 58% rename from payment_multisafepay_official/views/payment_transaction_views.xml rename to payment_multisafepay_enhaced/views/payment_transaction_views.xml index 81382b8..ad9e966 100644 --- a/payment_multisafepay_official/views/payment_transaction_views.xml +++ b/payment_multisafepay_enhaced/views/payment_transaction_views.xml @@ -1,17 +1,21 @@ - + payment.transaction.form.multisafepay payment.transaction - + - - + + diff --git a/payment_multisafepay_official/wizard/__init__.py b/payment_multisafepay_enhaced/wizard/__init__.py similarity index 100% rename from payment_multisafepay_official/wizard/__init__.py rename to payment_multisafepay_enhaced/wizard/__init__.py diff --git a/payment_multisafepay_official/wizard/payment_refund_wizard.py b/payment_multisafepay_enhaced/wizard/payment_refund_wizard.py similarity index 70% rename from payment_multisafepay_official/wizard/payment_refund_wizard.py rename to payment_multisafepay_enhaced/wizard/payment_refund_wizard.py index 06fc0ee..d083c1b 100644 --- a/payment_multisafepay_official/wizard/payment_refund_wizard.py +++ b/payment_multisafepay_enhaced/wizard/payment_refund_wizard.py @@ -4,6 +4,7 @@ # See the DISCLAIMER.md file for disclaimer details import logging + from odoo import api, fields, models _logger = logging.getLogger(__name__) @@ -11,7 +12,8 @@ class PaymentRefundWizard(models.TransientModel): """Extends the standard Odoo payment refund wizard to add MultiSafepay-specific refund reason field.""" - _inherit = 'payment.refund.wizard' + + _inherit = "payment.refund.wizard" multisafepay_refund_reason = fields.Char( string="Refund Reason", @@ -19,31 +21,38 @@ class PaymentRefundWizard(models.TransientModel): ) provider_code = fields.Char( string="Provider Code", - compute='_compute_provider_code', + compute="_compute_provider_code", store=False, ) - @api.depends('transaction_id', 'transaction_id.provider_code') + @api.depends("transaction_id", "transaction_id.provider_code") def _compute_provider_code(self): """Compute the provider code from the transaction.""" for wizard in self: - wizard.provider_code = wizard.transaction_id.provider_code if wizard.transaction_id else False + wizard.provider_code = ( + wizard.transaction_id.provider_code if wizard.transaction_id else False + ) def action_refund(self): """Override to pass the refund reason to the MultiSafepay transaction via context.""" self.ensure_one() - - _logger.debug("Refund wizard - Provider code: %s, Reason: %s", - self.provider_code, self.multisafepay_refund_reason) - + + _logger.debug( + "Refund wizard - Provider code: %s, Reason: %s", + self.provider_code, + self.multisafepay_refund_reason, + ) + # If this is a MultiSafepay transaction and a reason is provided, # pass it through the context so _create_child_transaction can use it - if self.provider_code == 'multisafepay' and self.multisafepay_refund_reason: - _logger.debug("Passing reason through context: %s", self.multisafepay_refund_reason) + if self.provider_code == "multisafepay" and self.multisafepay_refund_reason: + _logger.debug( + "Passing reason through context: %s", self.multisafepay_refund_reason + ) return self.transaction_id.with_context( multisafepay_refund_reason=self.multisafepay_refund_reason ).action_refund(amount_to_refund=self.amount_to_refund) - + # For non-MultiSafepay transactions or when no reason provided, use standard flow _logger.debug("Using standard flow (no reason or not MultiSafepay)") return super().action_refund() diff --git a/payment_multisafepay_official/wizard/payment_refund_wizard_views.xml b/payment_multisafepay_enhaced/wizard/payment_refund_wizard_views.xml similarity index 57% rename from payment_multisafepay_official/wizard/payment_refund_wizard_views.xml rename to payment_multisafepay_enhaced/wizard/payment_refund_wizard_views.xml index fb7c8ee..e49741e 100644 --- a/payment_multisafepay_official/wizard/payment_refund_wizard_views.xml +++ b/payment_multisafepay_enhaced/wizard/payment_refund_wizard_views.xml @@ -1,23 +1,27 @@ - + payment.refund.wizard.form.multisafepay payment.refund.wizard - + - + - + - diff --git a/payment_multisafepay_official/__manifest__.py b/payment_multisafepay_official/__manifest__.py deleted file mode 100644 index a9f5709..0000000 --- a/payment_multisafepay_official/__manifest__.py +++ /dev/null @@ -1,34 +0,0 @@ -{ - 'name': 'MultiSafepay', - 'description': ''' - Accept, manage and stimulate online sales with MultiSafepay. - Increase conversion rates with MultiSafepay unique solutions, - create the perfect checkout experience and the best payment method mix. - ''', - 'summary': '''E-commerce is part of our DNA''', - 'author': 'MultiSafepay', - 'website': 'https://www.multisafepay.com', - 'license': 'AGPL-3', - 'category': 'eCommerce', - 'version': '19.0.1.2.0', - 'application': True, - 'post_init_hook': 'post_init_hook', - 'uninstall_hook': 'uninstall_hook', - 'depends': [ - 'account', - 'payment', - 'stock', - 'sale', - 'sale_stock', - 'website_sale', - ], - 'external_dependencies': {'python': ['multisafepay']}, - 'data': [ - 'views/payment_templates.xml', - 'views/payment_provider_views.xml', - 'views/payment_method_views.xml', - 'views/payment_transaction_views.xml', - 'wizard/payment_refund_wizard_views.xml', - 'data/payment_provider_data.xml', - ], -} diff --git a/payment_multisafepay_official/const.py b/payment_multisafepay_official/const.py deleted file mode 100644 index 0a5589e..0000000 --- a/payment_multisafepay_official/const.py +++ /dev/null @@ -1,79 +0,0 @@ -# Copyright (c) MultiSafepay, Inc. All rights reserved. -# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. -# See the LICENSE.md file for more information. -# See the DISCLAIMER.md file for disclaimer details - -# Payment method code prefix -PAYMENT_METHOD_PREFIX = 'multisafepay_' - -SUPPORTED_CURRENCIES = [ - 'AED', - 'AUD', - 'BGN', - 'BRL', - 'CAD', - 'CHF', - 'CLP', - 'CNY', - 'COP', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HRK', - 'HUF', - 'ILS', - 'INR', - 'ISK', - 'JPY', - 'KRW', - 'MXN', - 'MYR', - 'NOK', - 'NZD', - 'PEN', - 'PHP', - 'PLN', - 'RON', - 'RUB', - 'SEK', - 'SGD', - 'THB', - 'TRY', - 'TWD', - 'USD', - 'VEF', - 'ZAR' -] - -STATUS_MAPPING = { - 'draft': ('initialized',), - 'pending': (), - 'authorized': ('reserved',), - 'done': ('completed', 'partial_refunded', 'refunded', 'uncleared'), - 'cancel': ('canceled', 'cancelled', 'void', 'expired'), - 'error': ('declined', 'chargedback', 'charged_back'), -} - -PAYMENT_METHOD_PENDING = [ - 'banktrans', - 'multibanco', -] - -BNPL_METHODS = [ - 'afterpay', - 'einvoice', - 'in3', - 'klarna', - 'payafter', - 'bnpl_instm', - 'bnpl_inst', - 'in3b2b', - 'santander', - 'zinia', - 'zinia_in3', - 'bnpl_ob', - 'bnpl_mf', - 'billink', -] diff --git a/payment_multisafepay_official/controllers/main.py b/payment_multisafepay_official/controllers/main.py deleted file mode 100644 index a85626b..0000000 --- a/payment_multisafepay_official/controllers/main.py +++ /dev/null @@ -1,692 +0,0 @@ -# Copyright (c) MultiSafepay, Inc. All rights reserved. -# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. -# See the LICENSE.md file for more information. -# See the DISCLAIMER.md file for disclaimer details - -""" -MultiSafepay Payment Controller -Handles redirect flow for MultiSafepay payments -""" -from decimal import Decimal -import logging -import pprint -import json - -from werkzeug.exceptions import Forbidden -from typing import cast -from urllib.parse import quote_plus - -from odoo import http, _ -from odoo.http import request -from odoo.exceptions import ValidationError, UserError -from odoo.addons.payment.controllers.post_processing import PaymentPostProcessing - -from multisafepay.value_object.weight import Weight -from multisafepay.api.shared.cart.cart_item import CartItem -from multisafepay.api.paths.orders.request.components.checkout_options import CheckoutOptions -from multisafepay.api.paths.orders.request.components.payment_options import PaymentOptions -from multisafepay.api.paths.orders.request.components.plugin import Plugin -from multisafepay.api.paths.orders.request.components.second_chance import SecondChance -from multisafepay.api.shared.checkout.tax_rule import TaxRule -from multisafepay.api.shared.checkout.tax_rate import TaxRate -from multisafepay.api.paths.orders.request.order_request import OrderRequest -from multisafepay.api.shared.cart.shopping_cart import ShoppingCart -from multisafepay.api.shared.customer import Customer -from multisafepay.api.shared.delivery import Delivery -from multisafepay.sdk import Sdk -from multisafepay.value_object.amount import Amount -from multisafepay.value_object.country import Country -from multisafepay.value_object.currency import Currency -from multisafepay.api.shared.description import Description -from multisafepay.value_object.email_address import EmailAddress -from multisafepay.value_object.ip_address import IpAddress -from multisafepay.value_object.phone_number import PhoneNumber -from multisafepay.api.paths.orders.response.order_response import Order -from multisafepay.util.address_parser import AddressParser -from multisafepay.util.webhook import Webhook - -from ..const import PAYMENT_METHOD_PENDING, PAYMENT_METHOD_PREFIX - - -_logger = logging.getLogger(__name__) - - -class MultiSafepayController(http.Controller): - """Controller to handle MultiSafepay payment flow""" - - _redirect_url = '/payment/multisafepay/redirect' - _webhook_url = '/payment/multisafepay/webhook' - _return_url = '/payment/multisafepay/return' - _cancel_url = '/payment/multisafepay/cancel' - - @http.route('/payment/multisafepay/redirect', type='http', auth='public', methods=['POST'], csrf=False) - def multisafepay_redirect(self, **post): - """Handle redirect to MultiSafepay payment page""" - _logger.info("MultiSafepay redirect endpoint") - _logger.debug("MultiSafepay redirect endpoint request POST data: %s", str(post)) - - try: - reference = post.get('reference') - if not reference: - return self._redirect_to_payment_with_error( - "missing_reference", - _("Payment reference is missing. Please try again.") - ) - - payment_transaction = request.env['payment.transaction'].sudo().search([ - ('reference', '=', reference), - ('provider_code', '=', 'multisafepay') - ], limit=1) - - if not payment_transaction: - return self._redirect_to_payment_with_error( - "transaction_not_found", - _("Transaction not found. Please refresh and try again.") - ) - - try: - base_url = self._get_correct_base_url() - order = self._get_order(base_url, payment_transaction) - - if not order or not order.payment_url: - return self._redirect_to_payment_with_error( - "multiSafepay_api_error", - _("MultiSafepay API error: Transaction not found. Please refresh and try again.") - ) - - _logger.info("Payment url was generated successfully.") - _logger.debug("Payment url was generated successfully: %s", order.payment_url) - - return request.redirect(order.payment_url, local=False) - - except (ValidationError, UserError) as ve: - _logger.error("Validation/User error: %s", str(ve)) - return self._redirect_to_payment_with_error( - "validation_error", - str(ve) - ) - - except Exception as api_error: - _logger.error("Unexpected error: %s", str(api_error)) - return self._redirect_to_payment_with_error( - "Unexpected error", - _("An unexpected error occurred. Please try again.") - ) - - except Exception as e: - _logger.error("Unexpected error: %s", str(e)) - return self._redirect_to_payment_with_error( - "unexpected_error", - _("An unexpected error occurred. Please try again.") - ) - - def _redirect_to_payment_with_error(self, error_code, message): - """Redirect to payment form with error message""" - return request.redirect(f"/shop/payment?error={error_code}&message={quote_plus(message)}") - - - @http.route('/payment/multisafepay/webhook', type='http', auth='public', csrf=False, methods=['POST', 'GET']) - def multisafepay_webhook(self, **kwargs): - """Handle MultiSafepay webhook notifications""" - - _logger.info("MultiSafepay webhook received via %s", request.httprequest.method) - _logger.debug("MultiSafepay webhook data: %s", str(kwargs)) - - try: - if request.httprequest.method == 'POST': - return self._handle_post_webhook(**kwargs) - else: - return self._handle_get_webhook(**kwargs) - - except Exception as e: - _logger.error("Unexpected error in webhook processing: %s", str(e)) - return request.make_response('Internal Server Error', status=500) - - - - @http.route('/payment/multisafepay/return', type='http', auth='public', csrf=False, methods=['GET', 'POST']) - def multisafepay_return(self, **kwargs): - """Handle return from MultiSafepay payment page""" - _logger.info("MultiSafepay return received") - _logger.debug("Return data: %s", str(kwargs)) - - transactionid = kwargs.get('transactionid') - - payment_transaction = request.env['payment.transaction'].sudo().search([ - ('reference', '=', transactionid), - ('provider_code', '=', 'multisafepay') - ], limit=1) - - if payment_transaction.payment_method_code.removeprefix(PAYMENT_METHOD_PREFIX) in PAYMENT_METHOD_PENDING: - _logger.info("Payment method '%s' requires manual confirmation. Setting as pending.", payment_transaction.payment_method_code) - - # Get real state from MultiSafepay - provider = payment_transaction.provider_id - multisafepay_sdk = provider.get_multisafepay_sdk() - order_manager = multisafepay_sdk.get_order_manager() - order_response = order_manager.get(transactionid) - order = order_response.get_data() - - - notification_data = { - 'status':'pending', - 'reference': transactionid, - } - - # Process notification to update transaction state - payment_transaction._process_notification_data(notification_data) - - # Pass additional information in the URL - params = { - 'payment_method': payment_transaction.payment_method_code, - 'payment_status': order.status if order else 'pending', - 'transaction_id': transactionid, - 'multisafepay_order_id': order.order_id if order and hasattr(order, 'order_id') else '', - 'amount': payment_transaction.amount, - 'currency': payment_transaction.currency_id.name - } - - # Build URL with parameters - url_params = '&'.join([f"{k}={quote_plus(str(v))}" for k, v in params.items() if v]) - return request.redirect(f'/shop/confirmation?{url_params}') - - return request.redirect('/payment/status') - - - @http.route('/payment/multisafepay/cancel', type='http', auth='public', csrf=False, methods=['GET', 'POST']) - def multisafepay_cancel(self, **kwargs): - """Handle cancellation from MultiSafepay payment page""" - _logger.info("MultiSafepay payment cancelled") - _logger.debug("Cancel data: %s", str(kwargs)) - - transactionid = kwargs.get('transactionid') - - payment_transaction = request.env['payment.transaction'].sudo().search([ - ('reference', '=', transactionid), - ('provider_code', '=', 'multisafepay') - ], limit=1) - - notification_data = { - 'status': 'cancelled', - 'reference': transactionid, - } - - payment_transaction._process_notification_data(notification_data) - - _logger.info("MultiSafepay payment cancelled: %s", transactionid) - - return request.redirect('/payment/status?cancelled=1') - - def _handle_get_webhook(self, **kwargs): - """Handle GET webhook from MultiSafepay""" - - transactionid = kwargs.get('transactionid') - if not transactionid: - _logger.error("No transaction ID provided in webhook") - return request.make_response('Transaction ID required', status=400) - - payment_transaction = request.env['payment.transaction'].sudo().search([ - ('reference', '=', transactionid), - ('provider_code', '=', 'multisafepay') - ], limit=1) - - if not payment_transaction: - _logger.error("Transaction not found for webhook validation: %s", transactionid) - return request.make_response('Transaction not found', status=404) - - provider = payment_transaction.provider_id - multisafepay_sdk = provider.get_multisafepay_sdk() - - order_manager = multisafepay_sdk.get_order_manager() - order_response = order_manager.get(transactionid) - order = order_response.get_data() - if not order: - _logger.error("No order data found for transaction ID: %s", transactionid) - return request.make_response('No order data found', status=404) - - if not order.status: - _logger.error("No status found in order data for transaction ID: %s", transactionid) - return request.make_response('No status found', status=400) - - duplicated = self._duplicated_status(payment_transaction, order.status) - if duplicated: - _logger.debug("Duplicate webhook received for transaction %s with status %s, ignoring.", transactionid, order.status) - return request.make_response('OK', status=200) - - notification_data = { - 'status': order.status, - 'reference': order.transaction_id if hasattr(order, 'transaction_id') else None, - } - - payment_transaction._process_notification_data(notification_data) - - _logger.info("Webhook processing completed for transaction %s with status %s", transactionid, order.status) - return request.make_response('OK', status=200) - - def _handle_post_webhook(self, **kwargs): - """Handle POST webhook from MultiSafepay""" - - request_body_raw = request.httprequest.get_data(as_text=True) - try: - _logger.debug("POST webhook data: %s", request_body_raw) - - order = None - if request_body_raw: - - try: - order_data = json.loads(request_body_raw) - order = Order.from_dict(order_data) - _logger.debug("Parsed POST webhook data: %s", str(order_data)) - - except json.JSONDecodeError: - _logger.error("Failed to parse POST webhook body as JSON, using form data") - return request.make_response('Invalid JSON format in webhook body', status=403) - - - if not order or not order.order_id: - _logger.error("No transaction ID provided in webhook") - return request.make_response('Transaction ID required', status=400) - - transactionid = order.order_id - payment_transaction = request.env['payment.transaction'].sudo().search([ - ('reference', '=', transactionid), - ('provider_code', '=', 'multisafepay') - ], limit=1) - - if not payment_transaction: - _logger.error("Transaction not found for webhook validation: %s", transactionid) - return request.make_response('Transaction not found', status=404) - - auth_header = request.httprequest.headers.get('Auth', '') - - provider = payment_transaction.provider_id - api_key = provider.multisafepay_api_key - - try: - validated = Webhook.validate(request=request_body_raw, auth=auth_header, api_key=api_key, validation_time_in_seconds=600) - - if not validated: - _logger.error("Webhook validation failed for transaction %s: Invalid signature", transactionid) - return request.make_response('Webhook validation failed', status=403) - - _logger.info("Webhook validation successful for transaction: %s", transactionid) - - if not order.status: - _logger.error("No status found in order data for transaction ID: %s", transactionid) - return request.make_response('No status found', status=400) - - duplicated = self._duplicated_status(payment_transaction, order.status) - if duplicated: - _logger.info("Duplicate webhook received for transaction %s with status %s, ignoring.", transactionid, order.status) - return request.make_response('OK', status=200) - - notification_data = { - 'status': order.status, - 'reference': order.transaction_id if hasattr(order, 'transaction_id') else None, - } - - payment_transaction._process_notification_data(notification_data) - - _logger.info("Webhook processing completed for transaction %s with status %s", transactionid, order.status) - return request.make_response('OK', status=200) - - - except Exception as validation_error: - _logger.error("Webhook validation failed for transaction %s: %s", transactionid, str(validation_error)) - return request.make_response('Webhook validation failed', status=403) - - - except Forbidden as e: - _logger.error("Forbidden access to MultiSafepay webhook: %s", str(e)) - return request.make_response('Forbidden', status=403) - - - def _duplicated_status(self, payment_transaction, new_status, message=''): - """Check if the transaction already has the specified status to detect duplicates.""" - _logger.debug("Transaction %s status changed to %s: %s", payment_transaction.id, new_status, message) - return payment_transaction.state == payment_transaction._get_multisafepay_status_to_odoo_state(new_status) - - - - def _get_order(self, url, payment_transaction) -> Order: - """ Override of payment to create the order request for multisafepay transactions. - - :param dict processing_values: The processing values to be used for the transaction - :return: The order request created for the multisafepay transaction - :rtype: dict - """ - - # Get base URL from Odoo configuration - if not getattr(payment_transaction, 'reference', None): - raise ValidationError(_("Transaction reference is missing.")) - - amount = getattr(payment_transaction, 'amount', 0) - if amount <= 0: - raise ValidationError(_("Transaction amount is missing or invalid.")) - - currency_id = getattr(payment_transaction, 'currency_id', None) - if not currency_id or not getattr(currency_id, 'name', None): - raise ValidationError(_("Transaction currency is missing or invalid.")) - - sale_orders = getattr(payment_transaction, 'sale_order_ids', None) - partner_invoice = None - sale_order = None - - # If sale_order_ids is provided, use the first one to get the invoice partner - if sale_orders: - sale_order = sale_orders[0] # Assuming one sale order per transaction for simplicity - partner_invoice = getattr(sale_order, 'partner_invoice_id', None) - else: - partner_invoice = getattr(payment_transaction, 'partner_id', None) - - - # Create a ShoppingCart with the necessary details - order_id = payment_transaction.reference - - decimal_places = getattr(currency_id, 'decimal_places', 2) # Default to 2 if not found - - # Use Decimal for precise monetary calculations to avoid floating-point errors - multiplier = 10 ** decimal_places - amount_decimal = Decimal(str(amount)) - multiplied_amount = amount_decimal * multiplier - normalized_amount = int(multiplied_amount) - - amount = Amount(amount=normalized_amount).amount - currency = Currency(currency=currency_id.name) - - description = Description(description='Order #' + order_id) - language = getattr(payment_transaction, 'partner_lang', 'en_US') or 'en_US' - - if not partner_invoice: - raise ValidationError(_("No customer info partner associated.")) - - partner_invoice_country = Country(code=getattr(partner_invoice, 'country_code', '')).code - partner_invoice_email = EmailAddress(email_address=getattr(partner_invoice, 'email', '')).email_address - partner_invoice_phone_number = PhoneNumber(phone_number=getattr(partner_invoice, 'phone', '')).phone_number - partner_invoice_name = getattr(partner_invoice, 'name', '').split(' ') or [] - partner_invoice_first_name = partner_invoice_name[0] if partner_invoice_name else '' - partner_invoice_last_name = " ".join(partner_invoice_name[1:]) if len(partner_invoice_name) > 1 else '' - - # Get the IP address from the request environment - remote_addr = request.httprequest.environ.get('REMOTE_ADDR', '') - partner_invoice_ip_address = IpAddress(ip_address=remote_addr).ip_address if remote_addr else '' - - # If the HTTP_X_FORWARDED_FOR header is present, use it as the forwarded IP address - http_x_forwarded_for = request.httprequest.environ.get('HTTP_X_FORWARDED_FOR', '') or partner_invoice_ip_address - partner_invoice_forwarded_ip = IpAddress(ip_address=http_x_forwarded_for).ip_address - - partner_invoice_street = getattr(partner_invoice, 'street', '') or '' - partner_invoice_street2 = getattr(partner_invoice, 'street2', '') or '' - - address_parser = AddressParser() - partner_invoice_address = address_parser.parse(partner_invoice_street, partner_invoice_street2) - - partner_invoice_referrer = request.httprequest.environ.get('HTTP_REFERER', '') or '' - partner_invoice_ref = getattr(partner_invoice, 'ref', '') or '' - partner_invoice_address1 = partner_invoice_address[0] if partner_invoice_address else '' - partner_invoice_house_number = partner_invoice_address[1] if partner_invoice_address and len(partner_invoice_address) > 1 else '' - partner_invoice_zip = getattr(partner_invoice, 'zip', '') or '' - partner_invoice_city = getattr(partner_invoice, 'city', '') or '' - partner_invoice_state = partner_invoice.state_id.name if partner_invoice.state_id else '' - - # Create a Customer object with the necessary details - customer = cast(Customer, (Customer(**{}) - .add_locale(language) - .add_ip_address(partner_invoice_ip_address) - .add_forwarded_ip(partner_invoice_forwarded_ip) - .add_referrer(partner_invoice_referrer) - .add_user_agent(request.httprequest.user_agent.string) - .add_reference(partner_invoice_ref) - .add_first_name(partner_invoice_first_name) - .add_last_name(partner_invoice_last_name) - .add_phone(partner_invoice_phone_number) - .add_email(partner_invoice_email) - .add_address1(partner_invoice_address1) - .add_address2('') - .add_house_number(partner_invoice_house_number) - .add_zip_code(partner_invoice_zip) - .add_city(partner_invoice_city) - .add_state(partner_invoice_state) - .add_country(partner_invoice_country))) - - company = None - use_delivery_as_billing = False - partner_shipping = None - delivery = Delivery(**{}) - # Create a Delivery object with the same details as the customer - if sale_order: - partner_shipping = sale_order.partner_shipping_id - - if partner_invoice.id == partner_shipping.id: - use_delivery_as_billing = True - - if partner_shipping: - partner_shipping_name = getattr(partner_shipping, 'name', '').split(' ') if getattr(partner_shipping, 'name', None) else [] - partner_shipping_first_name = partner_shipping_name[0] if partner_shipping_name else '' - partner_shipping_last_name = " ".join(partner_shipping_name[1:]) if len(partner_shipping_name) > 1 else '' - partner_shipping_phone = PhoneNumber(phone_number=getattr(partner_shipping, 'phone', '')).phone_number - partner_shipping_email = EmailAddress(email_address=getattr(partner_shipping, 'email', '')).email_address - partner_shipping_state = getattr(partner_shipping.state_id, 'name', '') or '' - partner_shipping_country = Country(code=getattr(partner_shipping, 'country_code', '')).code - partner_shipping_street = getattr(partner_shipping, 'street', '') or '' - partner_shipping_street2 = getattr(partner_shipping, 'street2', '') or '' - partner_shipping_address = address_parser.parse(partner_shipping_street, partner_shipping_street2) - partner_shipping_address1 = partner_shipping_address[0] if partner_shipping_address else '' - partner_shipping_house_number = partner_shipping_address[1] if partner_shipping_address and len(partner_shipping_address1) > 1 else '' - partner_shipping_zip_code = getattr(partner_shipping, 'zip', '') or '' - partner_shipping_city = getattr(partner_shipping, 'city', '') or '' - - if sale_orders and not use_delivery_as_billing: - delivery = (Delivery(**{}) - .add_first_name(partner_shipping_first_name) - .add_last_name(partner_shipping_last_name) - .add_phone(partner_shipping_phone) - .add_email(partner_shipping_email) - .add_address1(partner_shipping_address1) - .add_address2('') - .add_house_number(partner_shipping_house_number) - .add_zip_code(partner_shipping_zip_code) - .add_city(partner_shipping_city) - .add_state(partner_shipping_state) - .add_country(partner_shipping_country)) - - if not sale_orders or sale_orders and use_delivery_as_billing: - # If no sale order, use the invoice partner details for delivery - delivery = (Delivery(**{}).add_first_name(partner_invoice_first_name) - .add_last_name(partner_invoice_last_name) - .add_phone(partner_invoice_phone_number) - .add_email(partner_invoice_email) - .add_address1(partner_invoice_address1) - .add_address2('') - .add_house_number(partner_invoice_house_number) - .add_zip_code(partner_invoice_zip) - .add_city(partner_invoice_city) - .add_state(partner_invoice_state) - .add_country(partner_invoice_country)) - - # Create a Plugin object with the necessary details - plugin = (Plugin(**{}) - .add_plugin_version('1.0.0') - .add_shop('Odoo') - .add_shop_version('19.0') - .add_shop_root_url(url)) - - # Create payment options for the order - payment_options = (PaymentOptions(**{}) - .add_notification_method('POST') - .add_close_window(True) - .add_notification_url(f'{url}/payment/multisafepay/webhook') - .add_redirect_url(f'{url}/payment/multisafepay/return') - .add_cancel_url(f'{url}/payment/multisafepay/cancel') - ) - - # Prevent Second Chance, not supported at this moment - second_chance = SecondChance(send_email=False) - - # Create a list of CartItem objects for the shopping cart - order_lines = getattr(sale_order, 'order_line', []) - cart_items = [] - - for line in order_lines: - product = getattr(line, 'product_id', None) - - # Fix: Handle tax_id field change from Odoo 18 to 19 (tax_id -> tax_ids) - tax_table_selector = None - if hasattr(line, 'tax_ids') and line.tax_ids: - # Odoo 19+: tax_ids is a many2many field, get the first tax rate - tax_table_selector = line.tax_ids[0].amount - - - merchant_item_id = line.product_id.code if line.product_id.code else str(line.product_id.id) - - # Check for reward/loyalty lines safely - reward_id = getattr(line, 'reward_id', None) - if reward_id: - reward_type = getattr(reward_id, 'reward_type', None) - if reward_type: - merchant_item_id = f"{reward_type}-{merchant_item_id}" - program_type = getattr(reward_id, 'program_type', None) - if program_type: - merchant_item_id = f"{program_type}-{merchant_item_id}" - - for variant in line.product_no_variant_attribute_value_ids: - merchant_item_id += f"-{variant.name}" - - cart_item = (CartItem(**{}) - .add_name(getattr(line, 'name', '') or '') - .add_description(getattr(product, 'description_sale', '') or '') - .add_unit_price(getattr(line, 'price_unit', 0.0)) - .add_quantity(getattr(line, 'product_qty', 0)) - .add_merchant_item_id(merchant_item_id) - .add_weight(Weight(value=getattr(product, 'weight', 0.0) or 0.0, unit='kg')) - ) - - if tax_table_selector is not None: - cart_item.add_tax_rate_percentage(tax_table_selector) - else: - cart_item.add_tax_rate_percentage(0) - - cart_items.append(cart_item) - _logger.debug("Order line details: %s", str(cart_item.to_dict())) - - shopping_cart = ShoppingCart(items=cart_items) - checkout_options = CheckoutOptions.generate_from_shopping_cart(shopping_cart) - if checkout_options: - checkout_options.add_validate_cart(True) - - tax_rule = TaxRule( - name="0", - rules=[TaxRate(rate=0, country="")], - standalone=None - ) - - # Iterate over the tax rates to see if there is a 0% tax rate, if not add it - has_zero_tax = any(item.name == '0' for item in cart_items) - if not has_zero_tax: - # Add a 0% tax rule to the checkout options - if checkout_options: - if checkout_options.tax_tables: - checkout_options.tax_tables.add_tax_rule(tax_rule) - - - # Get the payment method code from transaction and map it to MultiSafepay format - odoo_method_code = payment_transaction.payment_method_code - - # Convert to MultiSafepay format using the provider's mapping function - provider = getattr(payment_transaction, 'provider_id', None) - if provider: - multisafepay_gateway_code = provider._map_odoo_to_multisafepay_code(odoo_method_code) - else: - multisafepay_gateway_code = odoo_method_code.upper() - - _logger.debug("Mapping Odoo method '%s' to MultiSafepay gateway '%s'", odoo_method_code, multisafepay_gateway_code) - - order_request = (OrderRequest(**{}) - .add_type('redirect') - .add_gateway(multisafepay_gateway_code) - .add_order_id(order_id) - .add_currency(currency.currency) - .add_amount(amount) - .add_payment_options(payment_options) - .add_customer(customer) - .add_delivery(delivery) - .add_description(description.description if description.description else '') - .add_shopping_cart(shopping_cart) - .add_plugin(plugin) - .add_second_chance(second_chance) - ) - - if checkout_options: - order_request.add_checkout_options(checkout_options) - - provider = getattr(payment_transaction, 'provider_id', None) - if not provider: - _logger.error("Payment provider not found on transaction.") - raise ValidationError(_("Payment provider not found.")) - - multisafepay_sdk = provider.get_multisafepay_sdk() - - if not multisafepay_sdk: - _logger.error("MultiSafepay SDK not initialized.") - raise ValidationError(_("MultiSafepay SDK is not initialized.")) - - _logger.debug("Order request: %s", order_request.to_dict()) - - order_manager = multisafepay_sdk.get_order_manager() - create_response = order_manager.create(order_request) - - _logger.debug("Order created response: %s", str(create_response)) - - order: Order = create_response.get_data() - - # Prefer order_id, fallback to transaction_id - _order_id = getattr(order, 'order_id', None) - - if not _order_id: - # Provide a precise message; caller will redirect with it - raise ValidationError(_('There was a problem processing your payment. Possible reasons could be: "insufficient funds", or "verification failed".')) - - _logger.info("Order created successfully with ID: %s", _order_id) - return order - - - def _get_correct_base_url(self): - """Get the correct base URL, prioritizing configured domain over localhost""" - - # Priority 1: System parameter (configured domain) - base_url = request.env['ir.config_parameter'].sudo().get_param('web.base.url', '') - if base_url and not ('localhost' in base_url or '127.0.0.1' in base_url): - _logger.debug("Using configured base URL: %s", base_url) - return base_url - - # Priority 2: X-Forwarded-Host header (from proxy) - x_forwarded_host = request.httprequest.headers.get('X-Forwarded-Host') - x_forwarded_proto = request.httprequest.headers.get('X-Forwarded-Proto', 'https') - - if x_forwarded_host: - base_url = f"{x_forwarded_proto}://{x_forwarded_host}" - _logger.debug("Using X-Forwarded-Host: %s", base_url) - return base_url - - # Priority 3: Host header - host = request.httprequest.headers.get('Host') - if host and not ('localhost' in host or '127.0.0.1' in host): - is_secure = request.httprequest.is_secure or x_forwarded_proto == 'https' - scheme = 'https' if is_secure else 'http' - base_url = f"{scheme}://{host}" - _logger.debug("Using Host header: %s", base_url) - return base_url - - # Priority 4: Website domain (if configured) - if hasattr(request, 'website') and request.website: - website_domain = request.website.domain - if website_domain and not ('localhost' in website_domain or '127.0.0.1' in website_domain): - base_url = f"https://{website_domain}" - _logger.debug("Using website domain: %s", base_url) - return base_url - - # Fallback: Use original method but log warning - base_url = request.httprequest.url_root.rstrip('/') - _logger.warning("Falling back to request URL root (may be localhost): %s", base_url) - return base_url - diff --git a/payment_multisafepay_official/i18n/de_DE.po b/payment_multisafepay_official/i18n/de_DE.po deleted file mode 100644 index 4e9c22b..0000000 --- a/payment_multisafepay_official/i18n/de_DE.po +++ /dev/null @@ -1,386 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * payment_multisafepay_official -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 18.0+e\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-09-08 12:14+0000\n" -"PO-Revision-Date: 2025-09-08 12:14+0000\n" -"Last-Translator: \n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: \n" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "" -" \n" -" Action required to complete your payment." -msgstr "" -" \n" -" Aktion erforderlich, um Ihre Zahlung abzuschließen." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Account Holder" -msgstr "Kontoinhaber" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Account ID" -msgstr "Konto-ID" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Amount" -msgstr "Betrag" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "External Transaction ID" -msgstr "Externe Transaktions-ID" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "MultiSafepay ID" -msgstr "MultiSafepay-ID" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Payment Description" -msgstr "Zahlungsbeschreibung" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Payment Method" -msgstr "Zahlungsmethode" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Status" -msgstr "Status" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Transaction ID" -msgstr "Transaktions-ID" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_form_error_message -msgid "" -" Payment " -"Error" -msgstr "" -" Zahlungsfehler" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_provider__multisafepay_api_key -msgid "API Key" -msgstr "API-Schlüssel" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "An unexpected error occurred. Please try again." -msgstr "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es erneut." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_form_error_message -msgid "Close" -msgstr "Schließen" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_provider__code -msgid "Code" -msgstr "Code" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"Could not retrieve order information from MultiSafepay for transaction %s.\n" -"\n" -"Reference: %s\n" -"Provider: %s\n" -"\n" -"Please check your internet connection and try again later." -msgstr "" -"Bestellinformationen konnten nicht von MultiSafepay für Transaktion %s abgerufen werden.\n" -"\n" -"Referenz: %s\n" -"Anbieter: %s\n" -"\n" -"Bitte überprüfen Sie Ihre Internetverbindung und versuchen Sie es später erneut." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_account_bank_statement_line__multisafepay_notified -#: model:ir.model.fields,help:payment_multisafepay_official.field_account_move__multisafepay_notified -msgid "Indicates whether MultiSafepay has been notified about this invoice" -msgstr "Gibt an, ob MultiSafepay über diese Rechnung benachrichtigt wurde" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_stock_picking__multisafepay_notified -msgid "Indicates whether MultiSafepay has been notified about this shipment" -msgstr "Gibt an, ob MultiSafepay über diese Sendung benachrichtigt wurde" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_account_move -msgid "Journal Entry" -msgstr "Journalbuchung" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "Load available payment methods from your MultiSafepay account." -msgstr "Verfügbare Zahlungsmethoden aus Ihrem MultiSafepay-Konto laden." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__main_currency_id -msgid "Main Currency" -msgstr "Hauptwährung" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__maximum_amount -msgid "Maximum Amount" -msgstr "Maximalbetrag" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__minimum_amount -msgid "Minimum Amount" -msgstr "Mindestbetrag" - -#. module: payment_multisafepay_official -#: model:payment.provider,name:payment_multisafepay_official.payment_provider_multisafepay -msgid "MultiSafepay" -msgstr "MultiSafepay" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "" -"MultiSafepay API error: Transaction not found. Please refresh and try again." -msgstr "" -"MultiSafepay API-Fehler: Transaktion nicht gefunden. Bitte aktualisieren und erneut versuchen." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "MultiSafepay Credentials" -msgstr "MultiSafepay-Anmeldedaten" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_account_bank_statement_line__multisafepay_notified -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_account_move__multisafepay_notified -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_stock_picking__multisafepay_notified -msgid "MultiSafepay Notified" -msgstr "MultiSafepay benachrichtigt" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "MultiSafepay SDK is not initialized." -msgstr "MultiSafepay SDK ist nicht initialisiert." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "MultiSafepay did not return a valid order identifier." -msgstr "MultiSafepay hat keine gültige Bestellnummer zurückgegeben." - -#. module: payment_multisafepay_official -#: model:ir.model.fields.selection,name:payment_multisafepay_official.selection__payment_provider__code__multisafepay -msgid "Multisafepay" -msgstr "Multisafepay" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "No customer info partner associated." -msgstr "Kein Kundeninfo-Partner zugeordnet." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"No response received from MultiSafepay when attempting to refund transaction %s.\n" -"\n" -"Please check your internet connection and try again later." -msgstr "" -"Keine Antwort von MultiSafepay beim Versuch, Transaktion %s zu erstatten.\n" -"\n" -"Bitte überprüfen Sie Ihre Internetverbindung und versuchen Sie es später erneut." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__only_multisafepay -msgid "Only Multisafepay" -msgstr "Nur Multisafepay" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_method -msgid "Payment Method" -msgstr "Zahlungsmethode" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_provider -msgid "Payment Provider" -msgstr "Zahlungsanbieter" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_transaction -msgid "Payment Transaction" -msgstr "Zahlungstransaktion" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Payment provider not found." -msgstr "Zahlungsanbieter nicht gefunden." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Payment reference is missing. Please try again." -msgstr "Zahlungsreferenz fehlt. Bitte versuchen Sie es erneut." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "Pull Payment Methods" -msgstr "Zahlungsmethoden abrufen" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"Refund amount exceeds available amount.\n" -"\n" -"Transaction: %s\n" -"Requested Refund: %.2f %s\n" -"Maximum Available: %.2f %s\n" -"Already Refunded: %.2f %s\n" -"\n" -"Please adjust the refund amount." -msgstr "" -"Der Erstattungsbetrag überschreitet den verfügbaren Betrag.\n" -"\n" -"Transaktion: %s\n" -"Angeforderte Erstattung: %.2f %s\n" -"Maximal verfügbar: %.2f %s\n" -"Bereits erstattet: %.2f %s\n" -"\n" -"Bitte passen Sie den Erstattungsbetrag an." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "Refund failed at MultiSafepay: %s\n" -msgstr "Erstattung bei MultiSafepay fehlgeschlagen: %s\n" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_provider__multisafepay_api_key -msgid "" -"The API key for the Multisafepay account. This is used to authenticate " -"requests to the Multisafepay API." -msgstr "" -"Der API-Schlüssel für das Multisafepay-Konto. Dieser wird verwendet, um " -"Anfragen an die Multisafepay-API zu authentifizieren." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__main_currency_id -msgid "The main currency of the first provider linked to this payment method." -msgstr "Die Hauptwährung des ersten Anbieters, der mit dieser Zahlungsmethode verknüpft ist." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__maximum_amount -msgid "" -"The maximum payment amount that this payment provider is available for. " -"Leave blank to make it available for any payment amount." -msgstr "" -"Der maximale Zahlungsbetrag, für den dieser Zahlungsanbieter verfügbar ist. " -"Lassen Sie das Feld leer, um es für jeden Zahlungsbetrag verfügbar zu machen." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__minimum_amount -msgid "" -"The minimum payment amount that this payment provider is available for. " -"Leave blank to make it available for any payment amount." -msgstr "" -"Der minimale Zahlungsbetrag, für den dieser Zahlungsanbieter verfügbar ist. " -"Lassen Sie das Feld leer, um es für jeden Zahlungsbetrag verfügbar zu machen." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_provider__code -msgid "The technical code of this payment provider." -msgstr "Der technische Code dieses Zahlungsanbieters." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"This order has already been fully refunded.\n" -"\n" -"Transaction: %s\n" -"Order Status: %s\n" -"Original Amount: %.2f %s\n" -"\n" -"No additional refunds can be processed." -msgstr "" -"Diese Bestellung wurde bereits vollständig erstattet.\n" -"\n" -"Transaktion: %s\n" -"Bestellstatus: %s\n" -"Ursprünglicher Betrag: %.2f %s\n" -"\n" -"Es können keine weiteren Erstattungen verarbeitet werden." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction amount is missing or invalid." -msgstr "Transaktionsbetrag fehlt oder ist ungültig." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction currency is missing or invalid." -msgstr "Transaktionswährung fehlt oder ist ungültig." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction not found. Please refresh and try again." -msgstr "Transaktion nicht gefunden. Bitte aktualisieren und erneut versuchen." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction reference is missing." -msgstr "Transaktionsreferenz fehlt." - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_stock_picking -msgid "Transfer" -msgstr "Umbuchung" - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,auth_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been authorized." -msgstr "Ihre Zahlung wurde autorisiert." - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,cancel_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been cancelled." -msgstr "Ihre Zahlung wurde storniert." - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,pending_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "" -"Your payment has been successfully processed but is waiting for approval." -msgstr "" -"Ihre Zahlung wurde erfolgreich verarbeitet, wartet aber auf Genehmigung." - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,done_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been successfully processed." -msgstr "Ihre Zahlung wurde erfolgreich verarbeitet." diff --git a/payment_multisafepay_official/i18n/es_ES.po b/payment_multisafepay_official/i18n/es_ES.po deleted file mode 100644 index da3b718..0000000 --- a/payment_multisafepay_official/i18n/es_ES.po +++ /dev/null @@ -1,387 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * payment_multisafepay_official -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 18.0+e\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-09-08 12:17+0000\n" -"PO-Revision-Date: 2025-09-08 12:17+0000\n" -"Last-Translator: \n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: \n" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "" -" \n" -" Action required to complete your payment." -msgstr "" -" \n" -" Acción requerida para completar su pago." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Account Holder" -msgstr "Titular de la cuenta" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Account ID" -msgstr "ID de cuenta" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Amount" -msgstr "Importe" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "External Transaction ID" -msgstr "ID de transacción externa" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "MultiSafepay ID" -msgstr "ID de MultiSafepay" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Payment Description" -msgstr "Descripción del pago" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Payment Method" -msgstr "Método de pago" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Status" -msgstr "Estado" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Transaction ID" -msgstr "ID de transacción" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_form_error_message -msgid "" -" Payment " -"Error" -msgstr "" -" Error de " -"pago" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_provider__multisafepay_api_key -msgid "API Key" -msgstr "Clave API" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "An unexpected error occurred. Please try again." -msgstr "Ocurrió un error inesperado. Por favor, inténtelo de nuevo." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_form_error_message -msgid "Close" -msgstr "Cerrar" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_provider__code -msgid "Code" -msgstr "Código" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"Could not retrieve order information from MultiSafepay for transaction %s.\n" -"\n" -"Reference: %s\n" -"Provider: %s\n" -"\n" -"Please check your internet connection and try again later." -msgstr "" -"No se pudo recuperar la información del pedido de MultiSafepay para la transacción %s.\n" -"\n" -"Referencia: %s\n" -"Proveedor: %s\n" -"\n" -"Por favor, verifique su conexión a internet e inténtelo de nuevo más tarde." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_account_bank_statement_line__multisafepay_notified -#: model:ir.model.fields,help:payment_multisafepay_official.field_account_move__multisafepay_notified -msgid "Indicates whether MultiSafepay has been notified about this invoice" -msgstr "Indica si MultiSafepay ha sido notificado sobre esta factura" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_stock_picking__multisafepay_notified -msgid "Indicates whether MultiSafepay has been notified about this shipment" -msgstr "Indica si MultiSafepay ha sido notificado sobre este envío" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_account_move -msgid "Journal Entry" -msgstr "Asiento contable" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "Load available payment methods from your MultiSafepay account." -msgstr "Cargar métodos de pago disponibles desde su cuenta de MultiSafepay." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__main_currency_id -msgid "Main Currency" -msgstr "Moneda principal" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__maximum_amount -msgid "Maximum Amount" -msgstr "Importe máximo" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__minimum_amount -msgid "Minimum Amount" -msgstr "Importe mínimo" - -#. module: payment_multisafepay_official -#: model:payment.provider,name:payment_multisafepay_official.payment_provider_multisafepay -msgid "MultiSafepay" -msgstr "MultiSafepay" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "" -"MultiSafepay API error: Transaction not found. Please refresh and try again." -msgstr "" -"Error de API de MultiSafepay: Transacción no encontrada. Por favor, actualice e inténtelo de nuevo." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "MultiSafepay Credentials" -msgstr "Credenciales de MultiSafepay" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_account_bank_statement_line__multisafepay_notified -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_account_move__multisafepay_notified -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_stock_picking__multisafepay_notified -msgid "MultiSafepay Notified" -msgstr "MultiSafepay notificado" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "MultiSafepay SDK is not initialized." -msgstr "El SDK de MultiSafepay no está inicializado." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "MultiSafepay did not return a valid order identifier." -msgstr "MultiSafepay no devolvió un identificador de pedido válido." - -#. module: payment_multisafepay_official -#: model:ir.model.fields.selection,name:payment_multisafepay_official.selection__payment_provider__code__multisafepay -msgid "Multisafepay" -msgstr "Multisafepay" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "No customer info partner associated." -msgstr "No hay información de cliente asociada." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"No response received from MultiSafepay when attempting to refund transaction %s.\n" -"\n" -"Please check your internet connection and try again later." -msgstr "" -"No se recibió respuesta de MultiSafepay al intentar reembolsar la transacción %s.\n" -"\n" -"Por favor, verifique su conexión a internet e inténtelo de nuevo más tarde." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__only_multisafepay -msgid "Only Multisafepay" -msgstr "Solo Multisafepay" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_method -msgid "Payment Method" -msgstr "Método de pago" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_provider -msgid "Payment Provider" -msgstr "Proveedor de pago" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_transaction -msgid "Payment Transaction" -msgstr "Transacción de pago" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Payment provider not found." -msgstr "Proveedor de pago no encontrado." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Payment reference is missing. Please try again." -msgstr "Falta la referencia de pago. Por favor, inténtelo de nuevo." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "Pull Payment Methods" -msgstr "Obtener métodos de pago" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"Refund amount exceeds available amount.\n" -"\n" -"Transaction: %s\n" -"Requested Refund: %.2f %s\n" -"Maximum Available: %.2f %s\n" -"Already Refunded: %.2f %s\n" -"\n" -"Please adjust the refund amount." -msgstr "" -"El importe del reembolso excede el importe disponible.\n" -"\n" -"Transacción: %s\n" -"Reembolso solicitado: %.2f %s\n" -"Máximo disponible: %.2f %s\n" -"Ya reembolsado: %.2f %s\n" -"\n" -"Por favor, ajuste el importe del reembolso." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "Refund failed at MultiSafepay: %s\n" -msgstr "Reembolso fallido en MultiSafepay: %s\n" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_provider__multisafepay_api_key -msgid "" -"The API key for the Multisafepay account. This is used to authenticate " -"requests to the Multisafepay API." -msgstr "" -"La clave API para la cuenta de Multisafepay. Se utiliza para autenticar " -"solicitudes a la API de Multisafepay." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__main_currency_id -msgid "The main currency of the first provider linked to this payment method." -msgstr "La moneda principal del primer proveedor vinculado a este método de pago." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__maximum_amount -msgid "" -"The maximum payment amount that this payment provider is available for. " -"Leave blank to make it available for any payment amount." -msgstr "" -"El importe máximo de pago para el que este proveedor de pagos está disponible. " -"Deje en blanco para que esté disponible para cualquier importe de pago." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__minimum_amount -msgid "" -"The minimum payment amount that this payment provider is available for. " -"Leave blank to make it available for any payment amount." -msgstr "" -"El importe mínimo de pago para el que este proveedor de pagos está disponible. " -"Deje en blanco para que esté disponible para cualquier importe de pago." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_provider__code -msgid "The technical code of this payment provider." -msgstr "El código técnico de este proveedor de pagos." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"This order has already been fully refunded.\n" -"\n" -"Transaction: %s\n" -"Order Status: %s\n" -"Original Amount: %.2f %s\n" -"\n" -"No additional refunds can be processed." -msgstr "" -"Este pedido ya ha sido completamente reembolsado.\n" -"\n" -"Transacción: %s\n" -"Estado del pedido: %s\n" -"Importe original: %.2f %s\n" -"\n" -"No se pueden procesar reembolsos adicionales." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction amount is missing or invalid." -msgstr "El importe de la transacción falta o es inválido." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction currency is missing or invalid." -msgstr "La moneda de la transacción falta o es inválida." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction not found. Please refresh and try again." -msgstr "Transacción no encontrada. Por favor, actualice e inténtelo de nuevo." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction reference is missing." -msgstr "Falta la referencia de la transacción." - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_stock_picking -msgid "Transfer" -msgstr "Traslado" - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,auth_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been authorized." -msgstr "Su pago ha sido autorizado." - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,cancel_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been cancelled." -msgstr "Su pago ha sido cancelado." - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,pending_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "" -"Your payment has been successfully processed but is waiting for approval." -msgstr "" -"Su pago ha sido procesado exitosamente pero está esperando aprobación." - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,done_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been successfully processed." -msgstr "Su pago ha sido procesado exitosamente." diff --git a/payment_multisafepay_official/i18n/fr_FR.po b/payment_multisafepay_official/i18n/fr_FR.po deleted file mode 100644 index 0bc447c..0000000 --- a/payment_multisafepay_official/i18n/fr_FR.po +++ /dev/null @@ -1,388 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * payment_multisafepay_official -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 18.0+e\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-09-08 12:17+0000\n" -"PO-Revision-Date: 2025-09-08 15:00+0000\n" -"Last-Translator: GitHub Copilot\n" -"Language-Team: French\n" -"Language: fr_FR\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "" -" \n" -" Action required to complete your payment." -msgstr "" -" \n" -" Action requise pour finaliser votre paiement." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Account Holder" -msgstr "Titulaire du compte" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Account ID" -msgstr "ID du compte" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Amount" -msgstr "Montant" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "External Transaction ID" -msgstr "ID de transaction externe" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "MultiSafepay ID" -msgstr "ID MultiSafepay" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Payment Description" -msgstr "Description du paiement" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Payment Method" -msgstr "Mode de paiement" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Status" -msgstr "Statut" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Transaction ID" -msgstr "ID de transaction" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_form_error_message -msgid "" -" Payment " -"Error" -msgstr "" -" Erreur de " -"paiement" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_provider__multisafepay_api_key -msgid "API Key" -msgstr "Clé API" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "An unexpected error occurred. Please try again." -msgstr "Une erreur inattendue s'est produite. Veuillez réessayer." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_form_error_message -msgid "Close" -msgstr "Fermer" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_provider__code -msgid "Code" -msgstr "Code" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"Could not retrieve order information from MultiSafepay for transaction %s.\n" -"\n" -"Reference: %s\n" -"Provider: %s\n" -"\n" -"Please check your internet connection and try again later." -msgstr "" -"Impossible de récupérer les informations de commande depuis MultiSafepay pour la transaction %s.\n" -"\n" -"Référence: %s\n" -"Fournisseur: %s\n" -"\n" -"Veuillez vérifier votre connexion internet et réessayer plus tard." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_account_bank_statement_line__multisafepay_notified -#: model:ir.model.fields,help:payment_multisafepay_official.field_account_move__multisafepay_notified -msgid "Indicates whether MultiSafepay has been notified about this invoice" -msgstr "Indique si MultiSafepay a été notifié concernant cette facture" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_stock_picking__multisafepay_notified -msgid "Indicates whether MultiSafepay has been notified about this shipment" -msgstr "Indique si MultiSafepay a été notifié concernant cet envoi" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_account_move -msgid "Journal Entry" -msgstr "Pièce comptable" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "Load available payment methods from your MultiSafepay account." -msgstr "Charger les méthodes de paiement disponibles depuis votre compte MultiSafepay." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__main_currency_id -msgid "Main Currency" -msgstr "Devise principale" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__maximum_amount -msgid "Maximum Amount" -msgstr "Montant maximum" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__minimum_amount -msgid "Minimum Amount" -msgstr "Montant minimum" - -#. module: payment_multisafepay_official -#: model:payment.provider,name:payment_multisafepay_official.payment_provider_multisafepay -msgid "MultiSafepay" -msgstr "MultiSafepay" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "" -"MultiSafepay API error: Transaction not found. Please refresh and try again." -msgstr "" -"Erreur API MultiSafepay: Transaction introuvable. Veuillez actualiser et réessayer." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "MultiSafepay Credentials" -msgstr "Identifiants MultiSafepay" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_account_bank_statement_line__multisafepay_notified -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_account_move__multisafepay_notified -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_stock_picking__multisafepay_notified -msgid "MultiSafepay Notified" -msgstr "MultiSafepay notifié" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "MultiSafepay SDK is not initialized." -msgstr "Le SDK MultiSafepay n'est pas initialisé." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "MultiSafepay did not return a valid order identifier." -msgstr "MultiSafepay n'a pas retourné un identifiant de commande valide." - -#. module: payment_multisafepay_official -#: model:ir.model.fields.selection,name:payment_multisafepay_official.selection__payment_provider__code__multisafepay -msgid "Multisafepay" -msgstr "Multisafepay" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "No customer info partner associated." -msgstr "Aucun partenaire d'informations client associé." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"No response received from MultiSafepay when attempting to refund transaction %s.\n" -"\n" -"Please check your internet connection and try again later." -msgstr "" -"Aucune réponse reçue de MultiSafepay lors de la tentative de remboursement de la transaction %s.\n" -"\n" -"Veuillez vérifier votre connexion internet et réessayer plus tard." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__only_multisafepay -msgid "Only Multisafepay" -msgstr "MultiSafepay uniquement" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_method -msgid "Payment Method" -msgstr "Mode de paiement" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_provider -msgid "Payment Provider" -msgstr "Fournisseur de paiement" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_transaction -msgid "Payment Transaction" -msgstr "Transaction de paiement" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Payment provider not found." -msgstr "Fournisseur de paiement introuvable." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Payment reference is missing. Please try again." -msgstr "La référence de paiement est manquante. Veuillez réessayer." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "Pull Payment Methods" -msgstr "Récupérer les méthodes de paiement" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"Refund amount exceeds available amount.\n" -"\n" -"Transaction: %s\n" -"Requested Refund: %.2f %s\n" -"Maximum Available: %.2f %s\n" -"Already Refunded: %.2f %s\n" -"\n" -"Please adjust the refund amount." -msgstr "" -"Le montant du remboursement dépasse le montant disponible.\n" -"\n" -"Transaction: %s\n" -"Remboursement demandé: %.2f %s\n" -"Maximum disponible: %.2f %s\n" -"Déjà remboursé: %.2f %s\n" -"\n" -"Veuillez ajuster le montant du remboursement." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "Refund failed at MultiSafepay: %s\n" -msgstr "Échec du remboursement chez MultiSafepay: %s\n" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_provider__multisafepay_api_key -msgid "" -"The API key for the Multisafepay account. This is used to authenticate " -"requests to the Multisafepay API." -msgstr "" -"La clé API pour le compte Multisafepay. Elle est utilisée pour authentifier " -"les requêtes vers l'API Multisafepay." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__main_currency_id -msgid "The main currency of the first provider linked to this payment method." -msgstr "La devise principale du premier fournisseur lié à ce mode de paiement." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__maximum_amount -msgid "" -"The maximum payment amount that this payment provider is available for. " -"Leave blank to make it available for any payment amount." -msgstr "" -"Le montant maximum de paiement pour lequel ce fournisseur de paiement est disponible. " -"Laissez vide pour le rendre disponible pour tout montant de paiement." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__minimum_amount -msgid "" -"The minimum payment amount that this payment provider is available for. " -"Leave blank to make it available for any payment amount." -msgstr "" -"Le montant minimum de paiement pour lequel ce fournisseur de paiement est disponible. " -"Laissez vide pour le rendre disponible pour tout montant de paiement." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_provider__code -msgid "The technical code of this payment provider." -msgstr "Le code technique de ce fournisseur de paiement." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"This order has already been fully refunded.\n" -"\n" -"Transaction: %s\n" -"Order Status: %s\n" -"Original Amount: %.2f %s\n" -"\n" -"No additional refunds can be processed." -msgstr "" -"Cette commande a déjà été entièrement remboursée.\n" -"\n" -"Transaction: %s\n" -"Statut de la commande: %s\n" -"Montant original: %.2f %s\n" -"\n" -"Aucun remboursement supplémentaire ne peut être traité." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction amount is missing or invalid." -msgstr "Le montant de la transaction est manquant ou invalide." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction currency is missing or invalid." -msgstr "La devise de la transaction est manquante ou invalide." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction not found. Please refresh and try again." -msgstr "Transaction introuvable. Veuillez actualiser et réessayer." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction reference is missing." -msgstr "La référence de transaction est manquante." - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_stock_picking -msgid "Transfer" -msgstr "Transfert" - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,auth_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been authorized." -msgstr "Votre paiement a été autorisé." - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,cancel_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been cancelled." -msgstr "Votre paiement a été annulé." - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,pending_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "" -"Your payment has been successfully processed but is waiting for approval." -msgstr "" -"Votre paiement a été traité avec succès mais est en attente d'approbation." - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,done_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been successfully processed." -msgstr "Votre paiement a été traité avec succès." diff --git a/payment_multisafepay_official/i18n/it_IT.po b/payment_multisafepay_official/i18n/it_IT.po deleted file mode 100644 index cf40a98..0000000 --- a/payment_multisafepay_official/i18n/it_IT.po +++ /dev/null @@ -1,388 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * payment_multisafepay_official -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 18.0+e\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-09-08 12:18+0000\n" -"PO-Revision-Date: 2025-09-08 14:50+0000\n" -"Last-Translator: GitHub Copilot\n" -"Language-Team: Italian\n" -"Language: it_IT\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "" -" \n" -" Action required to complete your payment." -msgstr "" -" \n" -" Azione richiesta per completare il pagamento." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Account Holder" -msgstr "Titolare del Conto" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Account ID" -msgstr "ID Conto" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Amount" -msgstr "Importo" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "External Transaction ID" -msgstr "ID Transazione Esterna" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "MultiSafepay ID" -msgstr "ID MultiSafepay" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Payment Description" -msgstr "Descrizione Pagamento" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Payment Method" -msgstr "Metodo di Pagamento" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Status" -msgstr "Stato" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Transaction ID" -msgstr "ID Transazione" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_form_error_message -msgid "" -" Payment " -"Error" -msgstr "" -" Errore " -"Pagamento" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_provider__multisafepay_api_key -msgid "API Key" -msgstr "Chiave API" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "An unexpected error occurred. Please try again." -msgstr "Si è verificato un errore imprevisto. Riprova." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_form_error_message -msgid "Close" -msgstr "Chiudi" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_provider__code -msgid "Code" -msgstr "Codice" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"Could not retrieve order information from MultiSafepay for transaction %s.\n" -"\n" -"Reference: %s\n" -"Provider: %s\n" -"\n" -"Please check your internet connection and try again later." -msgstr "" -"Impossibile recuperare le informazioni dell'ordine da MultiSafepay per la transazione %s.\n" -"\n" -"Riferimento: %s\n" -"Provider: %s\n" -"\n" -"Controlla la connessione internet e riprova più tardi." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_account_bank_statement_line__multisafepay_notified -#: model:ir.model.fields,help:payment_multisafepay_official.field_account_move__multisafepay_notified -msgid "Indicates whether MultiSafepay has been notified about this invoice" -msgstr "Indica se MultiSafepay è stato notificato di questa fattura" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_stock_picking__multisafepay_notified -msgid "Indicates whether MultiSafepay has been notified about this shipment" -msgstr "Indica se MultiSafepay è stato notificato di questa spedizione" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_account_move -msgid "Journal Entry" -msgstr "Registrazione contabile" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "Load available payment methods from your MultiSafepay account." -msgstr "Carica i metodi di pagamento disponibili dal tuo account MultiSafepay." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__main_currency_id -msgid "Main Currency" -msgstr "Valuta Principale" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__maximum_amount -msgid "Maximum Amount" -msgstr "Importo Massimo" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__minimum_amount -msgid "Minimum Amount" -msgstr "Importo Minimo" - -#. module: payment_multisafepay_official -#: model:payment.provider,name:payment_multisafepay_official.payment_provider_multisafepay -msgid "MultiSafepay" -msgstr "MultiSafepay" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "" -"MultiSafepay API error: Transaction not found. Please refresh and try again." -msgstr "" -"Errore API MultiSafepay: Transazione non trovata. Aggiorna la pagina e riprova." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "MultiSafepay Credentials" -msgstr "Credenziali MultiSafepay" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_account_bank_statement_line__multisafepay_notified -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_account_move__multisafepay_notified -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_stock_picking__multisafepay_notified -msgid "MultiSafepay Notified" -msgstr "MultiSafepay Notificato" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "MultiSafepay SDK is not initialized." -msgstr "L'SDK MultiSafepay non è inizializzato." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "MultiSafepay did not return a valid order identifier." -msgstr "MultiSafepay non ha restituito un identificatore ordine valido." - -#. module: payment_multisafepay_official -#: model:ir.model.fields.selection,name:payment_multisafepay_official.selection__payment_provider__code__multisafepay -msgid "Multisafepay" -msgstr "Multisafepay" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "No customer info partner associated." -msgstr "Nessun partner informazioni cliente associato." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"No response received from MultiSafepay when attempting to refund transaction %s.\n" -"\n" -"Please check your internet connection and try again later." -msgstr "" -"Nessuna risposta ricevuta da MultiSafepay nel tentativo di rimborsare la transazione %s.\n" -"\n" -"Controlla la connessione internet e riprova più tardi." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__only_multisafepay -msgid "Only Multisafepay" -msgstr "Solo Multisafepay" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_method -msgid "Payment Method" -msgstr "Metodo di pagamento" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_provider -msgid "Payment Provider" -msgstr "Fornitore di pagamenti" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_transaction -msgid "Payment Transaction" -msgstr "Transazione di pagamento" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Payment provider not found." -msgstr "Fornitore di pagamento non trovato." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Payment reference is missing. Please try again." -msgstr "Riferimento pagamento mancante. Riprova." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "Pull Payment Methods" -msgstr "Recupera Metodi di Pagamento" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"Refund amount exceeds available amount.\n" -"\n" -"Transaction: %s\n" -"Requested Refund: %.2f %s\n" -"Maximum Available: %.2f %s\n" -"Already Refunded: %.2f %s\n" -"\n" -"Please adjust the refund amount." -msgstr "" -"L'importo del rimborso supera l'importo disponibile.\n" -"\n" -"Transazione: %s\n" -"Rimborso Richiesto: %.2f %s\n" -"Massimo Disponibile: %.2f %s\n" -"Già Rimborsato: %.2f %s\n" -"\n" -"Aggiusta l'importo del rimborso." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "Refund failed at MultiSafepay: %s\n" -msgstr "Rimborso fallito su MultiSafepay: %s\n" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_provider__multisafepay_api_key -msgid "" -"The API key for the Multisafepay account. This is used to authenticate " -"requests to the Multisafepay API." -msgstr "" -"La chiave API per l'account Multisafepay. Viene utilizzata per autenticare " -"le richieste all'API Multisafepay." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__main_currency_id -msgid "The main currency of the first provider linked to this payment method." -msgstr "La valuta principale del primo fornitore collegato a questo metodo di pagamento." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__maximum_amount -msgid "" -"The maximum payment amount that this payment provider is available for. " -"Leave blank to make it available for any payment amount." -msgstr "" -"L'importo massimo di pagamento per cui questo fornitore di pagamento è disponibile. " -"Lascia vuoto per renderlo disponibile per qualsiasi importo di pagamento." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__minimum_amount -msgid "" -"The minimum payment amount that this payment provider is available for. " -"Leave blank to make it available for any payment amount." -msgstr "" -"L'importo minimo di pagamento per cui questo fornitore di pagamento è disponibile. " -"Lascia vuoto per renderlo disponibile per qualsiasi importo di pagamento." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_provider__code -msgid "The technical code of this payment provider." -msgstr "Codice tecnico del fornitore di pagamenti." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"This order has already been fully refunded.\n" -"\n" -"Transaction: %s\n" -"Order Status: %s\n" -"Original Amount: %.2f %s\n" -"\n" -"No additional refunds can be processed." -msgstr "" -"Questo ordine è già stato completamente rimborsato.\n" -"\n" -"Transazione: %s\n" -"Stato Ordine: %s\n" -"Importo Originale: %.2f %s\n" -"\n" -"Nessun rimborso aggiuntivo può essere elaborato." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction amount is missing or invalid." -msgstr "L'importo della transazione è mancante o non valido." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction currency is missing or invalid." -msgstr "La valuta della transazione è mancante o non valida." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction not found. Please refresh and try again." -msgstr "Transazione non trovata. Aggiorna la pagina e riprova." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction reference is missing." -msgstr "Riferimento transazione mancante." - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_stock_picking -msgid "Transfer" -msgstr "Trasferimento" - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,auth_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been authorized." -msgstr "Il tuo pagamento è stato autorizzato." - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,cancel_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been cancelled." -msgstr "Il tuo pagamento è stato cancellato." - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,pending_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "" -"Your payment has been successfully processed but is waiting for approval." -msgstr "" -"Il tuo pagamento è stato elaborato con successo ma è in attesa di approvazione." - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,done_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been successfully processed." -msgstr "Il tuo pagamento è stato elaborato con successo." diff --git a/payment_multisafepay_official/i18n/nl_NL.po b/payment_multisafepay_official/i18n/nl_NL.po deleted file mode 100644 index 19bf5b7..0000000 --- a/payment_multisafepay_official/i18n/nl_NL.po +++ /dev/null @@ -1,387 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * payment_multisafepay_official -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 18.0+e\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-09-08 12:12+0000\n" -"PO-Revision-Date: 2025-09-08 14:30+0000\n" -"Last-Translator: GitHub Copilot\n" -"Language-Team: Dutch\n" -"Language: nl_NL\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "" -" \n" -" Action required to complete your payment." -msgstr "" -" \n" -" Actie vereist om uw betaling te voltooien." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Account Holder" -msgstr "Rekeninghouder" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Account ID" -msgstr "Rekening ID" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Amount" -msgstr "Bedrag" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "External Transaction ID" -msgstr "Externe Transactie ID" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "MultiSafepay ID" -msgstr "MultiSafepay ID" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Payment Description" -msgstr "Betalingsbeschrijving" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Payment Method" -msgstr "Betaalmethode" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Status" -msgstr "Status" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Transaction ID" -msgstr "Transactie ID" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_form_error_message -msgid "" -" Payment " -"Error" -msgstr "" -" Betalingsfout" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_provider__multisafepay_api_key -msgid "API Key" -msgstr "API Sleutel" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "An unexpected error occurred. Please try again." -msgstr "Er is een onverwachte fout opgetreden. Probeer het opnieuw." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_form_error_message -msgid "Close" -msgstr "Sluiten" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_provider__code -msgid "Code" -msgstr "Code" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"Could not retrieve order information from MultiSafepay for transaction %s.\n" -"\n" -"Reference: %s\n" -"Provider: %s\n" -"\n" -"Please check your internet connection and try again later." -msgstr "" -"Kon geen bestelinformatie ophalen van MultiSafepay voor transactie %s.\n" -"\n" -"Referentie: %s\n" -"Provider: %s\n" -"\n" -"Controleer uw internetverbinding en probeer het later opnieuw." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_account_bank_statement_line__multisafepay_notified -#: model:ir.model.fields,help:payment_multisafepay_official.field_account_move__multisafepay_notified -msgid "Indicates whether MultiSafepay has been notified about this invoice" -msgstr "Geeft aan of MultiSafepay op de hoogte is gesteld van deze factuur" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_stock_picking__multisafepay_notified -msgid "Indicates whether MultiSafepay has been notified about this shipment" -msgstr "Geeft aan of MultiSafepay op de hoogte is gesteld van deze zending" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_account_move -msgid "Journal Entry" -msgstr "Boeking" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "Load available payment methods from your MultiSafepay account." -msgstr "Laad beschikbare betaalmethoden uit uw MultiSafepay account." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__main_currency_id -msgid "Main Currency" -msgstr "Hoofdvaluta" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__maximum_amount -msgid "Maximum Amount" -msgstr "Maximum Bedrag" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__minimum_amount -msgid "Minimum Amount" -msgstr "Minimum Bedrag" - -#. module: payment_multisafepay_official -#: model:payment.provider,name:payment_multisafepay_official.payment_provider_multisafepay -msgid "MultiSafepay" -msgstr "MultiSafepay" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "" -"MultiSafepay API error: Transaction not found. Please refresh and try again." -msgstr "" -"MultiSafepay API fout: Transactie niet gevonden. Ververs de pagina en probeer opnieuw." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "MultiSafepay Credentials" -msgstr "MultiSafepay Inloggegevens" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_account_bank_statement_line__multisafepay_notified -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_account_move__multisafepay_notified -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_stock_picking__multisafepay_notified -msgid "MultiSafepay Notified" -msgstr "MultiSafepay Geïnformeerd" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "MultiSafepay SDK is not initialized." -msgstr "MultiSafepay SDK is niet geïnitialiseerd." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "MultiSafepay did not return a valid order identifier." -msgstr "MultiSafepay heeft geen geldige bestelling-identificatie geretourneerd." - -#. module: payment_multisafepay_official -#: model:ir.model.fields.selection,name:payment_multisafepay_official.selection__payment_provider__code__multisafepay -msgid "Multisafepay" -msgstr "Multisafepay" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "No customer info partner associated." -msgstr "Geen klantinformatie partner gekoppeld." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"No response received from MultiSafepay when attempting to refund transaction %s.\n" -"\n" -"Please check your internet connection and try again later." -msgstr "" -"Geen reactie ontvangen van MultiSafepay bij poging tot terugbetaling van transactie %s.\n" -"\n" -"Controleer uw internetverbinding en probeer het later opnieuw." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__only_multisafepay -msgid "Only Multisafepay" -msgstr "Alleen Multisafepay" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_method -msgid "Payment Method" -msgstr "Betaalmethode" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_provider -msgid "Payment Provider" -msgstr "Betaalprovider" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_transaction -msgid "Payment Transaction" -msgstr "Betalingstransactie" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Payment provider not found." -msgstr "Betaalprovider niet gevonden." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Payment reference is missing. Please try again." -msgstr "Betalingsreferentie ontbreekt. Probeer het opnieuw." - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "Pull Payment Methods" -msgstr "Betaalmethoden Ophalen" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"Refund amount exceeds available amount.\n" -"\n" -"Transaction: %s\n" -"Requested Refund: %.2f %s\n" -"Maximum Available: %.2f %s\n" -"Already Refunded: %.2f %s\n" -"\n" -"Please adjust the refund amount." -msgstr "" -"Terugbetalingsbedrag overschrijdt beschikbaar bedrag.\n" -"\n" -"Transactie: %s\n" -"Gevraagde Terugbetaling: %.2f %s\n" -"Maximum Beschikbaar: %.2f %s\n" -"Reeds Terugbetaald: %.2f %s\n" -"\n" -"Pas het terugbetalingsbedrag aan." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "Refund failed at MultiSafepay: %s\n" -msgstr "Terugbetaling mislukt bij MultiSafepay: %s\n" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_provider__multisafepay_api_key -msgid "" -"The API key for the Multisafepay account. This is used to authenticate " -"requests to the Multisafepay API." -msgstr "" -"De API sleutel voor het Multisafepay account. Deze wordt gebruikt om " -"verzoeken naar de Multisafepay API te authenticeren." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__main_currency_id -msgid "The main currency of the first provider linked to this payment method." -msgstr "De hoofdvaluta van de eerste provider gekoppeld aan deze betaalmethode." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__maximum_amount -msgid "" -"The maximum payment amount that this payment provider is available for. " -"Leave blank to make it available for any payment amount." -msgstr "" -"Het maximum betalingsbedrag waarvoor deze betaalprovider beschikbaar is. " -"Laat leeg om het beschikbaar te maken voor elk betalingsbedrag." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__minimum_amount -msgid "" -"The minimum payment amount that this payment provider is available for. " -"Leave blank to make it available for any payment amount." -msgstr "" -"Het minimum betalingsbedrag waarvoor deze betaalprovider beschikbaar is. " -"Laat leeg om het beschikbaar te maken voor elk betalingsbedrag." - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_provider__code -msgid "The technical code of this payment provider." -msgstr "De technische code van deze betaalprovider." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"This order has already been fully refunded.\n" -"\n" -"Transaction: %s\n" -"Order Status: %s\n" -"Original Amount: %.2f %s\n" -"\n" -"No additional refunds can be processed." -msgstr "" -"Deze bestelling is al volledig terugbetaald.\n" -"\n" -"Transactie: %s\n" -"Bestellingsstatus: %s\n" -"Oorspronkelijk Bedrag: %.2f %s\n" -"\n" -"Geen aanvullende terugbetalingen kunnen worden verwerkt." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction amount is missing or invalid." -msgstr "Transactiebedrag ontbreekt of is ongeldig." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction currency is missing or invalid." -msgstr "Transactievaluta ontbreekt of is ongeldig." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction not found. Please refresh and try again." -msgstr "Transactie niet gevonden. Ververs de pagina en probeer opnieuw." - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction reference is missing." -msgstr "Transactiereferentie ontbreekt." - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_stock_picking -msgid "Transfer" -msgstr "Verplaatsing" - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,auth_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been authorized." -msgstr "Uw betaling is geautoriseerd." - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,cancel_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been cancelled." -msgstr "Uw betaling is geannuleerd." - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,pending_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "" -"Your payment has been successfully processed but is waiting for approval." -msgstr "" -"Uw betaling is succesvol verwerkt maar wacht op goedkeuring." - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,done_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been successfully processed." -msgstr "Uw betaling is succesvol verwerkt." diff --git a/payment_multisafepay_official/i18n/payment_multisafepay_official.pot b/payment_multisafepay_official/i18n/payment_multisafepay_official.pot deleted file mode 100644 index 71bbf97..0000000 --- a/payment_multisafepay_official/i18n/payment_multisafepay_official.pot +++ /dev/null @@ -1,351 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * payment_multisafepay_official -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 18.0+e\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-09-08 12:10+0000\n" -"PO-Revision-Date: 2025-09-08 12:10+0000\n" -"Last-Translator: \n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: \n" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "" -" \n" -" Action required to complete your payment." -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Account Holder" -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Account ID" -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Amount" -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "External Transaction ID" -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "MultiSafepay ID" -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Payment Description" -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Payment Method" -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Status" -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.shop_confirmation_multisafepay_info -msgid "Transaction ID" -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_form_error_message -msgid "" -" Payment " -"Error" -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_provider__multisafepay_api_key -msgid "API Key" -msgstr "" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "An unexpected error occurred. Please try again." -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_form_error_message -msgid "Close" -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_provider__code -msgid "Code" -msgstr "" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"Could not retrieve order information from MultiSafepay for transaction %s.\n" -"\n" -"Reference: %s\n" -"Provider: %s\n" -"\n" -"Please check your internet connection and try again later." -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_account_bank_statement_line__multisafepay_notified -#: model:ir.model.fields,help:payment_multisafepay_official.field_account_move__multisafepay_notified -msgid "Indicates whether MultiSafepay has been notified about this invoice" -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_stock_picking__multisafepay_notified -msgid "Indicates whether MultiSafepay has been notified about this shipment" -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_account_move -msgid "Journal Entry" -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "Load available payment methods from your MultiSafepay account." -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__main_currency_id -msgid "Main Currency" -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__maximum_amount -msgid "Maximum Amount" -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__minimum_amount -msgid "Minimum Amount" -msgstr "" - -#. module: payment_multisafepay_official -#: model:payment.provider,name:payment_multisafepay_official.payment_provider_multisafepay -msgid "MultiSafepay" -msgstr "" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "" -"MultiSafepay API error: Transaction not found. Please refresh and try again." -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "MultiSafepay Credentials" -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_account_bank_statement_line__multisafepay_notified -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_account_move__multisafepay_notified -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_stock_picking__multisafepay_notified -msgid "MultiSafepay Notified" -msgstr "" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "MultiSafepay SDK is not initialized." -msgstr "" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "MultiSafepay did not return a valid order identifier." -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model.fields.selection,name:payment_multisafepay_official.selection__payment_provider__code__multisafepay -msgid "Multisafepay" -msgstr "" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "No customer info partner associated." -msgstr "" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"No response received from MultiSafepay when attempting to refund transaction %s.\n" -"\n" -"Please check your internet connection and try again later." -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,field_description:payment_multisafepay_official.field_payment_method__only_multisafepay -msgid "Only Multisafepay" -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_method -msgid "Payment Method" -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_provider -msgid "Payment Provider" -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_payment_transaction -msgid "Payment Transaction" -msgstr "" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Payment provider not found." -msgstr "" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Payment reference is missing. Please try again." -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:ir.ui.view,arch_db:payment_multisafepay_official.payment_provider_form_multisafepay -msgid "Pull Payment Methods" -msgstr "" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"Refund amount exceeds available amount.\n" -"\n" -"Transaction: %s\n" -"Requested Refund: %.2f %s\n" -"Maximum Available: %.2f %s\n" -"Already Refunded: %.2f %s\n" -"\n" -"Please adjust the refund amount." -msgstr "" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "Refund failed at MultiSafepay: %s\n" -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_provider__multisafepay_api_key -msgid "" -"The API key for the Multisafepay account. This is used to authenticate " -"requests to the Multisafepay API." -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__main_currency_id -msgid "The main currency of the first provider linked to this payment method." -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__maximum_amount -msgid "" -"The maximum payment amount that this payment provider is available for. " -"Leave blank to make it available for any payment amount." -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_method__minimum_amount -msgid "" -"The minimum payment amount that this payment provider is available for. " -"Leave blank to make it available for any payment amount." -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model.fields,help:payment_multisafepay_official.field_payment_provider__code -msgid "The technical code of this payment provider." -msgstr "" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/models/payment_transaction.py:0 -msgid "" -"This order has already been fully refunded.\n" -"\n" -"Transaction: %s\n" -"Order Status: %s\n" -"Original Amount: %.2f %s\n" -"\n" -"No additional refunds can be processed." -msgstr "" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction amount is missing or invalid." -msgstr "" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction currency is missing or invalid." -msgstr "" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction not found. Please refresh and try again." -msgstr "" - -#. module: payment_multisafepay_official -#. odoo-python -#: code:addons/payment_multisafepay_official/controllers/main.py:0 -msgid "Transaction reference is missing." -msgstr "" - -#. module: payment_multisafepay_official -#: model:ir.model,name:payment_multisafepay_official.model_stock_picking -msgid "Transfer" -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,auth_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been authorized." -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,cancel_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been cancelled." -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,pending_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "" -"Your payment has been successfully processed but is waiting for approval." -msgstr "" - -#. module: payment_multisafepay_official -#: model_terms:payment.provider,done_msg:payment_multisafepay_official.payment_provider_multisafepay -msgid "Your payment has been successfully processed." -msgstr "" diff --git a/payment_multisafepay_official/models/account_move.py b/payment_multisafepay_official/models/account_move.py deleted file mode 100644 index a050aa8..0000000 --- a/payment_multisafepay_official/models/account_move.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (c) MultiSafepay, Inc. All rights reserved. -# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. -# See the LICENSE.md file for more information. -# See the DISCLAIMER.md file for disclaimer details - -from odoo import models, fields, api, _ -import logging - -from multisafepay.api.paths.orders.order_id.update.request.update_request import UpdateOrderRequest - -_logger = logging.getLogger(__name__) - -class AccountMove(models.Model): - _inherit = 'account.move' - - multisafepay_notified = fields.Boolean( - string="MultiSafepay Notified", - default=False, - copy=False, - help="Indicates whether MultiSafepay has been notified about this invoice" - ) - - def action_post(self): - """Override to detect when invoices are validated/posted.""" - result = super().action_post() - - # Check if the invoice is an outgoing invoice - for invoice in self.filtered(lambda m: m.move_type == 'out_invoice'): - - # If already notified, skip - if invoice.multisafepay_notified: - continue - - multisafepay_tx = False - - # Search for MultiSafepay transactions related to this invoice - multisafepay_tx = self.env['payment.transaction'].search([ - ('invoice_ids', 'in', invoice.ids), - ('provider_code', '=', 'multisafepay') - ], limit=1) - - provider = multisafepay_tx.provider_id - - # If no transaction found, log and continue - if not provider: - _logger.warning("No provider found for transaction %s", multisafepay_tx.id) - continue - - multisafepay_sdk = provider.get_multisafepay_sdk() - - # If no SDK found, log and continue - if not multisafepay_sdk: - _logger.warning("No MultiSafepay SDK found for provider %s", provider.name) - continue - - order_manager = multisafepay_sdk.get_order_manager() - - try: - - update_order_request = UpdateOrderRequest().add_invoice_id(invoice.name) - - _logger.debug("MultiSafepay update order request: %s", str(update_order_request.to_dict())) - - if(invoice.access_url): - update_order_request.add_invoice_url(invoice.access_url) - - response = order_manager.update(multisafepay_tx.reference, update_order_request) - - _logger.debug("MultiSafepay update order request response: %s", str(response)) - - if response.status_code == 200: - - # Mark the invoice as notified - invoice.multisafepay_notified = True - - - except Exception as e: - _logger.error("Error retrieving MultiSafepay order: %s", e) - - return result diff --git a/payment_multisafepay_official/models/payment_method.py b/payment_multisafepay_official/models/payment_method.py deleted file mode 100644 index 500b219..0000000 --- a/payment_multisafepay_official/models/payment_method.py +++ /dev/null @@ -1,396 +0,0 @@ -# Copyright (c) MultiSafepay, Inc. All rights reserved. -# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. -# See the LICENSE.md file for more information. -# See the DISCLAIMER.md file for disclaimer details - -import logging - -from odoo import api, fields, models - -try: - from odoo.http import request -except ImportError: - # Graceful handling when http module is not available (e.g., during module installation) - request = None - -_logger = logging.getLogger(__name__) - - -class PaymentMethod(models.Model): - _inherit = 'payment.method' - - # =================================== - # FIELDS - # =================================== - - main_currency_id = fields.Many2one( - 'res.currency', - string="Main Currency", - compute='_compute_main_currency_id', - store=False, - help="The main currency of the first provider linked to this payment method.", - ) - - minimum_amount = fields.Float( - string="Minimum Amount (EUR)", - help="The minimum payment amount in EUR that this payment provider is available for. " - "Leave 0 to make it available for any payment amount.", - default=0.0, - digits=(16, 2), - ) - - maximum_amount = fields.Float( - string="Maximum Amount (EUR)", - help="The maximum payment amount in EUR that this payment provider is available for. " - "Leave 0 to make it available for any payment amount.", - default=0.0, - digits=(16, 2), - ) - - only_multisafepay = fields.Boolean( - compute='_compute_is_multisafepay', - store=False, - help="Technical field to identify if this payment method has MultiSafepay as one of its providers.", - ) - - pricelist_ids = fields.Many2many( - 'product.pricelist', - 'payment_method_pricelist_rel', - 'method_id', - 'pricelist_id', - string='Allowed Pricelists', - help="Restrict this payment method to specific pricelists. " - "If empty, the payment method will be available for all pricelists. " - "When set, this payment method will only be visible for orders using one of the selected pricelists.", - ) - - # =================================== - # COMPUTED FIELDS - # =================================== - - @api.depends('provider_ids.code') - def _compute_is_multisafepay(self): - """Compute if this payment method has MultiSafepay as one of its providers.""" - for method in self: - providers = method.provider_ids - # Check if ANY provider is MultiSafepay (not just if it's the only one) - method.only_multisafepay = any( - provider.code == 'multisafepay' for provider in providers - ) - - @api.depends('provider_ids.main_currency_id') - def _compute_main_currency_id(self): - """Compute the main currency from the first provider.""" - for method in self: - provider = method.provider_ids[:1] - method.main_currency_id = provider.main_currency_id if provider else False - - # =================================== - # ODOO CORE OVERRIDES - # =================================== - - def _get_compatible_payment_methods( - self, provider_ids, partner_id, currency_id=None, force_tokenization=False, - is_express_checkout=False, report=None, **kwargs): - """Override core method to add amount and pricelist filtering for MultiSafepay payment methods. - - This method extends Odoo's standard payment method filtering to support: - - Amount-based filtering (minimum/maximum amount restrictions) - - Pricelist-based filtering (restrict methods to specific pricelists) - - Availability reporting for debugging - - :param provider_ids: List of provider IDs to filter payment methods - :param partner_id: ID of the partner for whom the payment methods are being fetched - :param currency_id: ID of the currency for which the payment methods are being fetched - :param force_tokenization: Boolean indicating if tokenization is forced - :param is_express_checkout: Boolean indicating if the request is for express checkout - :param report: Dictionary to store availability report information - :param kwargs: Additional keyword arguments (amount, sale_order_id, etc.) - :return: Filtered payment methods based on the provided criteria - :rtype: recordset of `payment.method` - """ - - payment_methods = super()._get_compatible_payment_methods( - provider_ids, partner_id, currency_id, force_tokenization, is_express_checkout, - report=report, **kwargs - ) - - amount = kwargs.get('amount') - - sale_order_id = kwargs.get('sale_order_id') - if sale_order_id and 'sale.order' in self.env: - try: - order = self.env['sale.order'].browse(sale_order_id) - if order.exists(): - amount = order.amount_total - _logger.debug("Using amount %s from sale order %s", amount, order.name) - except (ValueError, TypeError, AttributeError) as e: - _logger.error("Error getting amount from sale order: %s", str(e)) - - if amount is None: - _logger.debug("Amount not found in kwargs, checking request parameters") - try: - if request and hasattr(request, 'httprequest') and hasattr(request.httprequest, 'args'): - amount_str = request.httprequest.args.get('amount') - if amount_str: - try: - amount = float(amount_str) - _logger.debug("Using amount %s from URL parameters", amount) - except (ValueError, TypeError): - _logger.error("Could not convert URL amount parameter to float: %s", amount_str) - except ImportError: - _logger.error("Could not import request object to get URL parameters") - except (AttributeError, KeyError) as e: - _logger.error("Error getting amount from request: %s", str(e)) - - # Apply amount filtering - if amount is not None: - methods_before_amount = payment_methods.filtered(lambda m: m.only_multisafepay) - payment_methods = payment_methods._filter_by_amount(amount) - methods_after_amount = payment_methods.filtered(lambda m: m.only_multisafepay) - - # Report filtered methods - filtered_out = methods_before_amount - methods_after_amount - self._report_filtered_methods(report, filtered_out, 'amount', {'amount': amount}) - - # Apply pricelist filtering automatically if we have access to the current order - try: - if request and hasattr(request, 'cart') and request.cart and request.cart.exists(): - pricelist = request.cart.pricelist_id - if pricelist and pricelist.exists(): - multisafepay_methods_before_pricelist = payment_methods.filtered(lambda method: method.only_multisafepay) - payment_methods = payment_methods._filter_by_pricelist(pricelist) - multisafepay_methods_after_pricelist = payment_methods.filtered(lambda method: method.only_multisafepay) - - # Add pricelist filtering information to report - if report is not None: - filtered_out_by_pricelist = multisafepay_methods_before_pricelist - multisafepay_methods_after_pricelist - for method in filtered_out_by_pricelist: - pricelist_reason = f"Not allowed for pricelist '{pricelist.name}'" - self._add_method_to_availability_report( - report, method, pricelist_reason, available=False - ) - - _logger.debug("Applied pricelist filtering for pricelist: %s", pricelist.name) - except (ImportError, AttributeError) as e: - # No request context or cart available, skip pricelist filtering - _logger.debug("No request context available for pricelist filtering: %s", str(e)) - - return payment_methods - - # =================================== - # FILTERING HELPER METHODS - # =================================== - - def _filter_by_amount(self, amount): - """Filter payment methods based on amount restrictions. - - Only applies amount filtering to MultiSafepay payment methods. - Other payment providers are not affected by amount restrictions. - - :param amount: The payment amount to filter by - :return: Filtered payment methods that are allowed for the given amount - :rtype: recordset - """ - if amount is None: - return self - - # Separate MultiSafepay methods from other payment methods - multisafepay_methods = self.filtered(lambda method: method.only_multisafepay) - other_methods = self.filtered(lambda method: not method.only_multisafepay) - - # Apply amount filtering only to MultiSafepay methods - allowed_multisafepay_methods = multisafepay_methods.filtered( - lambda method: (not method.minimum_amount or amount >= method.minimum_amount) and - (not method.maximum_amount or amount <= method.maximum_amount) - ) - - # Log filtering information - filtered_out_methods = multisafepay_methods - allowed_multisafepay_methods - if filtered_out_methods: - filtered_method_names = [m.name for m in filtered_out_methods] - _logger.info( - "Amount filtering: %d MultiSafepay methods filtered out for amount %.2f: %s", - len(filtered_out_methods), - amount, - ', '.join(filtered_method_names) - ) - - # Return all non-MultiSafepay methods plus filtered MultiSafepay methods - return other_methods + allowed_multisafepay_methods - - def _filter_by_pricelist(self, pricelist): - """Filter payment methods based on pricelist restrictions. - - Only applies pricelist filtering to MultiSafepay payment methods. - Other payment providers are not affected by pricelist restrictions. - - :param pricelist: The pricelist to filter by - :return: Filtered payment methods that are allowed for the given pricelist - :rtype: recordset - """ - if not pricelist: - return self - - # Separate MultiSafepay methods from other payment methods - multisafepay_methods = self.filtered(lambda method: method.only_multisafepay) - other_methods = self.filtered(lambda method: not method.only_multisafepay) - - # Apply pricelist filtering only to MultiSafepay methods - # Other methods are always allowed regardless of pricelist - allowed_multisafepay_methods = multisafepay_methods.filtered( - lambda method: not method.pricelist_ids or pricelist in method.pricelist_ids - ) - - # Log filtering information for debugging and reporting - filtered_out_methods = multisafepay_methods - allowed_multisafepay_methods - if filtered_out_methods: - filtered_method_names = [m.name for m in filtered_out_methods] - _logger.info( - "Pricelist filtering: %d MultiSafepay methods filtered out due to pricelist '%s': %s", - len(filtered_out_methods), - pricelist.name, - ', '.join(filtered_method_names) - ) - - if allowed_multisafepay_methods: - allowed_method_names = [m.name for m in allowed_multisafepay_methods] - _logger.info( - "Pricelist filtering: %d MultiSafepay methods allowed for pricelist '%s': %s", - len(allowed_multisafepay_methods), - pricelist.name, - ', '.join(allowed_method_names) - ) - - # Return all non-MultiSafepay methods plus filtered MultiSafepay methods - return other_methods + allowed_multisafepay_methods - - # =================================== - # PUBLIC API METHODS - # =================================== - - @api.model - def _get_compatible_payment_methods_with_pricelist( - self, provider_ids, partner_id, currency_id=None, force_tokenization=False, - is_express_checkout=False, pricelist_id=None, **kwargs): - """Get payment methods compatible with the given criteria, including pricelist filtering. - - This method extends the standard method filtering to include pricelist-based restrictions. - - :param provider_ids: Provider IDs for filtering - :param partner_id: Partner ID for filtering - :param currency_id: Currency ID for filtering - :param force_tokenization: Force tokenization flag - :param is_express_checkout: Express checkout flag - :param pricelist_id: Pricelist ID for filtering - :param kwargs: Additional filtering criteria - :return: Compatible payment methods - :rtype: recordset - """ - # Get standard compatible methods - methods = self._get_compatible_payment_methods( - provider_ids, partner_id, currency_id, force_tokenization, - is_express_checkout, **kwargs - ) - - # Apply pricelist filtering if provided - if pricelist_id: - pricelist = self.env['product.pricelist'].browse(pricelist_id) - methods = methods._filter_by_pricelist(pricelist) - - return methods - - # =================================== - # AVAILABILITY REPORT HELPERS - # =================================== - - def _add_method_to_availability_report(self, report, method, reason, available=False): - """Add or update a payment method entry in the availability report. - - :param report: The availability report dictionary - :param method: The payment method to add/update - :param reason: The reason why the method is/isn't available - :param available: Whether the method is available - """ - if report is None: - return - - if 'payment_methods' not in report: - report['payment_methods'] = {} - - if method in report['payment_methods']: - # Update existing entry - combine reasons - existing_reason = report['payment_methods'][method].get('reason', '') - if existing_reason: - report['payment_methods'][method]['reason'] = f"{existing_reason}; {reason}" - else: - report['payment_methods'][method]['reason'] = reason - report['payment_methods'][method]['available'] = available - else: - # Create new entry - report['payment_methods'][method] = { - 'available': available, - 'reason': reason, - 'supported_providers': [(provider, True) for provider in method.provider_ids] - } - - def _report_filtered_methods(self, report, filtered_methods, filter_type, context): - """Add filtered methods to availability report with appropriate reasons. - - :param report: Availability report dictionary (can be None) - :param filtered_methods: Recordset of methods that were filtered out - :param filter_type: Type of filter ('amount' or 'pricelist') - :param context: Dictionary with context data (e.g., {'amount': 100.0} or {'pricelist': recordset}) - """ - if report is None or not filtered_methods: - return - - for method in filtered_methods: - if filter_type == 'amount': - reason_parts = self._generate_amount_filter_reasons(method, context.get('amount')) - elif filter_type == 'pricelist': - reason_parts = self._generate_pricelist_filter_reasons(method, context.get('pricelist')) - else: - reason_parts = [f"Filtered by {filter_type}"] - - if reason_parts: - self._add_method_to_availability_report( - report, method, '; '.join(reason_parts), available=False - ) - - def _generate_amount_filter_reasons(self, method, amount): - """Generate reason messages for amount filtering. - - :param method: The payment method - :param amount: The payment amount - :return: List of reason strings - :rtype: list - """ - reason_parts = [] - if method.minimum_amount and amount < method.minimum_amount: - reason_parts.append(f"Amount {amount} below minimum {method.minimum_amount}") - if method.maximum_amount and amount > method.maximum_amount: - reason_parts.append(f"Amount {amount} above maximum {method.maximum_amount}") - return reason_parts - - def _generate_pricelist_filter_reasons(self, method, pricelist): - """Generate human-readable reasons why a payment method was filtered by pricelist. - - :param method: Payment method record - :param pricelist: Pricelist record - :return: List of reason strings - :rtype: list - """ - reason_parts = [] - - if not pricelist: - return reason_parts - - if method.pricelist_ids and pricelist not in method.pricelist_ids: - allowed_names = ', '.join(method.pricelist_ids.mapped('name')) - reason_parts.append( - f"Not allowed for pricelist '{pricelist.name}' " - f"(allowed: {allowed_names})" - ) - - return reason_parts diff --git a/payment_multisafepay_official/models/payment_transaction.py b/payment_multisafepay_official/models/payment_transaction.py deleted file mode 100644 index 7f0b45f..0000000 --- a/payment_multisafepay_official/models/payment_transaction.py +++ /dev/null @@ -1,358 +0,0 @@ -# Copyright (c) MultiSafepay, Inc. All rights reserved. -# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. -# See the LICENSE.md file for more information. -# See the DISCLAIMER.md file for disclaimer details - -from datetime import date, datetime, time -from decimal import Decimal -import logging -import pprint -import uuid -from odoo import _, models, fields -from odoo.exceptions import UserError -from pydantic import ValidationError - -from .. import const -from multisafepay.value_object.amount import Amount -from multisafepay.value_object.currency import Currency -from multisafepay.api.shared.description import Description -from multisafepay.api.paths.orders.order_id.refund.request.refund_request import RefundOrderRequest -from multisafepay.api.paths.orders.order_id.refund.response.order_refund import OrderRefund -from multisafepay.api.shared.cart.cart_item import CartItem -from multisafepay.api.paths.orders.order_id.refund.request.components.checkout_data import CheckoutData -from multisafepay.api.base.response.custom_api_response import CustomApiResponse - -_logger = logging.getLogger(__name__) - - -class PaymentTransaction(models.Model): - _inherit = 'payment.transaction' - - # MultiSafepay specific fields - multisafepay_refund_reason = fields.Char( - string="Refund Reason", - help="Reason for the refund (MultiSafepay specific)", - copy=False, - ) - - - def _get_specific_processing_values(self, processing_values): - """ Override to return specific processing values for MultiSafepay. """ - self.ensure_one() - - res = super()._get_specific_processing_values(processing_values) - - if self.provider_code != 'multisafepay': - return res - - _logger.debug("Input processing values: %s", str(processing_values)) - - res.update({ - 'reference': self.reference, - }) - - _logger.debug("Final processing values: %s", str(res)) - - return res - - - def _get_specific_rendering_values(self, processing_values): - """ Override to return specific rendering values for MultiSafepay. """ - self.ensure_one() - - res = super()._get_specific_rendering_values(processing_values) - - if self.provider_code != 'multisafepay': - return res - - return processing_values - - - def _get_tx_from_notification_data(self, provider_code, notification_data): - """ Override of payment to find the transaction based on Stripe data. - - :param str provider_code: The code of the provider that handled the transaction - :param dict notification_data: The notification data sent by the provider - :return: The transaction if found - :rtype: recordset of `payment.transaction` - :raise: ValidationError if inconsistent data were received - :raise: ValidationError if the data match no transaction - """ - tx = super()._get_tx_from_notification_data(provider_code, notification_data) - if provider_code != 'multisafepay' or len(tx) == 1: - return tx - - reference = notification_data.get('reference') - if reference: - tx = self.search([('reference', '=', reference), ('provider_code', '=', 'multisafepay')]) - - return tx - - def _process_notification_data(self, notification_data): - """ Override of `payment` to process the transaction based on MultiSafepay data. """ - self.ensure_one() - - # Fix: Odoo 19 compatibility - parent class doesn't have _process_notification_data - # Handle notification processing directly without calling super() - if self.provider_code != 'multisafepay': - return - - transaction_id = notification_data.get('reference') - if transaction_id and not self.provider_reference: - self.write({ - 'provider_reference': transaction_id - }) - _logger.debug("Updated provider_reference to: %s", transaction_id) - - - status = notification_data.get('status') - # If it's already a native Odoo state, return it directly - odoo_native_states = ['draft', 'pending', 'authorized', 'done', 'cancel', 'error'] - if status in odoo_native_states: - _logger.debug("Status '%s' is native Odoo state - using as-is", status) - odoo_state = status - else: - odoo_state = self._get_multisafepay_status_to_odoo_state(status) - - if odoo_state == 'draft': - _logger.debug("MSP status '%s' → keeping transaction in draft (ref=%s)", status, self.reference) - - elif odoo_state == 'done': - _logger.debug("MSP status '%s' → marking transaction done (ref=%s)", status, self.reference) - # Fix: Odoo 19 compatibility - _set_done() no longer accepts message parameter - self._set_done() - - elif odoo_state == 'cancel': - _logger.debug("MSP status '%s' → canceling transaction (ref=%s)", status, self.reference) - # Fix: Odoo 19 compatibility - _set_canceled() no longer accepts message parameter - self._set_canceled() - - elif odoo_state == 'error': - _logger.debug("MSP status '%s' → setting transaction to error (ref=%s)", status, self.reference) - # Fix: Odoo 19 compatibility - _set_error() no longer accepts message parameter - self._set_error() - - elif odoo_state == 'pending': - _logger.debug("MSP status '%s' → setting transaction to pending (ref=%s)", status, self.reference) - # Fix: Odoo 19 compatibility - _set_pending() no longer accepts message parameter - self._set_pending() - - elif odoo_state == 'partial_refunded': - _logger.debug("MSP status '%s' → payment partially refunded (ref=%s)", status, self.reference) - # Fix: Odoo 19 compatibility - _set_done() no longer accepts message parameter - self._set_done() - - _logger.info("Transaction %s updated to state '%s'", self.reference, odoo_state) - - - - def _get_multisafepay_status_to_odoo_state(self, multisafepay_status: str) -> str: - """ - Maps a MultiSafepay status to its corresponding Odoo transaction state. - - This function performs a reverse lookup on the STATUS_MAPPING constant to find - the Odoo state (key) that corresponds to a given MultiSafepay status (value - within the tuple). - - :param str multisafepay_status: The status received from MultiSafepay (e.g., 'completed', 'uncleared'). - :return: The corresponding Odoo state (e.g., 'done', 'pending'). Defaults to 'error'. - :rtype: str - """ - for odoo_state, msp_statuses in const.STATUS_MAPPING.items(): - if multisafepay_status in msp_statuses: - return odoo_state - - return 'error' - - - def _create_child_transaction(self, amount, is_refund=False, **custom_create_values): - """Override to add the MultiSafepay refund reason when creating a refund transaction. - - The reason can come from context (set by the wizard). - """ - # Get the refund reason from the context (set by the wizard) - if is_refund and self.provider_code == 'multisafepay': - refund_reason = self.env.context.get('multisafepay_refund_reason') - _logger.debug("Creating refund transaction. Provider: %s, Reason from context: %s", - self.provider_code, refund_reason) - if refund_reason: - custom_create_values['multisafepay_refund_reason'] = refund_reason - _logger.debug("Added reason to custom_create_values: %s", refund_reason) - - # Call the parent method with the updated custom_create_values - result = super()._create_child_transaction(amount, is_refund=is_refund, **custom_create_values) - _logger.debug("Child transaction created. ID: %s, Reason field: %s", - result.id, result.multisafepay_refund_reason) - return result - - - def _send_refund_request(self, amount_to_refund=0.0): - """ Override of payment to send a refund request to Multisafepay. """ - self.ensure_one() - - if self.provider_code != 'multisafepay': - return super()._send_refund_request() - - # In Odoo 19, self is already the refund transaction - # Get the original transaction via source_transaction_id - if not self.source_transaction_id: - raise UserError(_("Refunds can only be processed from a refund transaction linked to an original payment transaction.")) - - original_tx = self.source_transaction_id - original_reference = original_tx.reference - amount_to_refund = abs(self.amount) - - provider = self.provider_id - multisafepay_sdk = provider.get_multisafepay_sdk() - order_manager = multisafepay_sdk.get_order_manager() - - try: - # Get current order status from MultiSafepay using original reference - order_response = order_manager.get(original_reference) - order_data = order_response.get_data() - except Exception as api_error: - _logger.error("API error retrieving order %s: %s", original_reference, str(api_error)) - raise UserError(_("Could not connect to MultiSafepay API for transaction %s.\n\nError: %s") % (self.reference, str(api_error))) - - if not order_data: - _logger.error("Could not retrieve order data for %s", self.reference) - raise UserError(_("Could not retrieve order information from MultiSafepay for transaction %s.\n\nReference: %s\nProvider: %s\n\nPlease check your internet connection and try again later.") % ( - self.reference, - original_reference, - self.provider_id.name - )) - - original_amount = abs(original_tx.amount) - decimal_places = self.currency_id.decimal_places or 2 - currency_divisor = 10 ** decimal_places - - # Check order status - order_status = getattr(order_data, 'status', '') - - # Early check: if already refunded, don't proceed - if order_status == 'refunded': - _logger.error("Order %s is already refunded", original_reference) - raise UserError(_("This order has already been fully refunded.\n\nTransaction: %s\nOrder Status: %s\nOriginal Amount: %.2f %s\n\nNo additional refunds can be processed.") % ( - original_reference, - order_status, - abs(amount_to_refund), - self.currency_id.name - )) - - remaining_amount = 0 - if hasattr(order_data, 'amount_refunded') and order_data.amount_refunded: - _logger.warning("Order %s has already been partially refunded", original_reference) - - # Use Decimal for precise monetary calculations to avoid floating-point errors - refunded_decimal = Decimal(str(order_data.amount_refunded)) - divisor_decimal = Decimal(str(currency_divisor)) - refunded_amount = float(refunded_decimal / divisor_decimal) - remaining_amount = original_amount - refunded_amount - - - if remaining_amount and amount_to_refund > remaining_amount: - _logger.error("Refund amount %s exceeds remaining amount %s for order %s", - amount_to_refund, remaining_amount, original_reference) - raise UserError(_("Refund amount exceeds available amount.\n\nTransaction: %s\nRequested Refund: %.2f %s\nMaximum Available: %.2f %s\nAlready Refunded: %.2f %s\n\nPlease adjust the refund amount.") % ( - original_reference, - abs(amount_to_refund), - self.currency_id.name, - remaining_amount, - self.currency_id.name, - refunded_amount, - self.currency_id.name - )) - - try: - # Use Decimal for precise monetary calculations to avoid floating-point errors - amount_decimal = Decimal(str(abs(amount_to_refund))) - divisor_decimal = Decimal(str(currency_divisor)) - amount_in_cents = int(amount_decimal * divisor_decimal) - - refund_response = None - is_bnpl = original_tx.payment_method_code.removeprefix(const.PAYMENT_METHOD_PREFIX) in const.BNPL_METHODS - - try: - if is_bnpl: - refund_request = order_manager.create_refund_request(order_data) - cart_item = (CartItem(**{}) - .add_merchant_item_id(str(uuid.uuid4())) - .add_name(f"Refund for Odoo order {original_reference}") - .add_quantity(1) - .add_unit_price(-amount_to_refund) - .add_tax_table_selector('0') - ) - refund_request.checkout_data.add_item(cart_item) - refund_payload = (RefundOrderRequest(**{}) - .add_checkout_data(refund_request.checkout_data)) - - else: - - refund_payload = (RefundOrderRequest(**{}) - .add_amount(amount_in_cents) - .add_currency(Currency(currency=self.currency_id.name).currency) - .add_description( - Description(**{}).add_description(f'Refund for Odoo order {original_reference}'))) - - - _logger.debug("Refund payload for order %s: %s", original_reference, str(refund_payload.dict())) - - refund_response = order_manager.refund(original_reference, refund_payload) - except Exception: - refund_response = False - - if not refund_response: - _logger.error("No response received from MultiSafepay for refund of order %s", original_reference) - raise UserError(_("No response received from MultiSafepay when attempting to refund transaction %s.\n\nPlease check your internet connection and try again later.") % original_reference) - - refund_data = refund_response.get_data() - _logger.info("Refund response data: %s", pprint.pformat(refund_data)) - - _logger.debug("Refund response data: %s", str(refund_data)) - - success_refund = False - if is_bnpl: - success_refund = refund_response.body.get('success', False) - else: - success_refund = refund_data and hasattr(refund_data, 'transaction_id') - - if not success_refund: - error_msg = None - if refund_data: - for attr in ('error_info', 'message', 'description', 'detail', 'error'): - if hasattr(refund_data, attr): - error_msg = getattr(refund_data, attr) - break - - if not error_msg and hasattr(refund_response, 'body') and refund_response.body: - for attr in ('error_info', 'message', 'description', 'detail', 'error'): - error_value = refund_response.body.get(attr) - if error_value: - error_msg = error_value - break - - if error_msg: - _logger.error("Refund failed for %s: %s", self.reference, error_msg) - raise UserError(_("Refund failed at MultiSafepay: %s\n") % error_msg) - - _logger.error("Something went wrong with refund for order %s", self.reference) - raise UserError(_("Refund failed at MultiSafepay: %s\n") % self.reference) - - # Update refund transaction with provider reference - if refund_data and hasattr(refund_data, 'transaction_id'): - self.provider_reference = refund_data.transaction_id - - # Mark refund as successful - notification_data = { - 'status': 'completed', - 'amount': amount_to_refund, - 'currency': self.currency_id.name - } - if refund_data and hasattr(refund_data, 'transaction_id'): - notification_data['reference'] = refund_data.transaction_id - - self._process_notification_data(notification_data) - - except Exception as e: - _logger.error("Something went wrong with refund for order %s: %s", self.reference, str(e)) - raise UserError(str(e)) diff --git a/payment_multisafepay_official/models/stock_picking.py b/payment_multisafepay_official/models/stock_picking.py deleted file mode 100644 index 37adc62..0000000 --- a/payment_multisafepay_official/models/stock_picking.py +++ /dev/null @@ -1,115 +0,0 @@ -# Copyright (c) MultiSafepay, Inc. All rights reserved. -# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. -# See the LICENSE.md file for more information. -# See the DISCLAIMER.md file for disclaimer details - -from odoo import models, fields, api, _ -import logging - -from multisafepay.api.paths.orders.order_id.update.request.update_request import UpdateOrderRequest - -_logger = logging.getLogger(__name__) - -class StockPicking(models.Model): - _inherit = 'stock.picking' - - multisafepay_notified = fields.Boolean( - string="MultiSafepay Notified", - copy=False, - default=False, - help="Indicates whether MultiSafepay has been notified about this shipment" - ) - - - def _action_done(self): - """Override to detect when pickings are marked as done.""" - # Call original method first - result = super(StockPicking, self)._action_done() - - multisafepay_tx = False - - # Search for MultiSafepay transactions related to this picking - for picking in self: - - # Check if this picking have is related to a multisafepay transaction - if picking.multisafepay_notified: - _logger.info("MultiSafepay already notified for picking %s", picking.name) - continue - - # Get sale order(s) from picking - Odoo 19 compatibility - # In Odoo 19, stock.picking has a direct computed field 'sale_id' - sale_order = picking.sale_id - - if not sale_order: - _logger.info("No sale order found for picking %s", picking.name) - continue - - # Find MultiSafepay transaction through the sale order - multisafepay_tx = self.env['payment.transaction'].search([ - ('sale_order_ids', 'in', [sale_order.id]), - ('provider_code', '=', 'multisafepay') - ], limit=1) - - # If no transaction found, log and continue - if not multisafepay_tx: - _logger.info("No MultiSafepay transaction found for order %s", sale_order.name) - continue - - # Check if this is the last picking (all others are done or cancelled) - # In Odoo 19, use sale_id field directly instead of searching by origin - pending_pickings = self.env['stock.picking'].search([ - ('sale_id', '=', sale_order.id), - ('state', 'not in', ['done', 'cancel']), - ('id', '!=', picking.id) # Exclude current picking - ]) - - # Only notify MultiSafepay if this is the last shipment - if pending_pickings: - _logger.info("Order %s has pending pickings: %s. Not notifying MultiSafepay yet.", - sale_order.name, pending_pickings.mapped('name')) - continue - - # If we have a transaction, check if we need to notify MultiSafepay - if not picking.multisafepay_notified: - _logger.info("Notifying MultiSafepay for picking %s", picking.name) - - try: - - provider = multisafepay_tx.provider_id - multisafepay_sdk = provider.get_multisafepay_sdk() - - if not multisafepay_sdk: - _logger.warning("No MultiSafepay SDK found for provider %s", provider.name) - continue - - order_manager = multisafepay_sdk.get_order_manager() - - update_order_request = UpdateOrderRequest().add_status('shipped') - - if picking.carrier_tracking_ref and picking.carrier_tracking_url and picking.carrier_id and picking.date_done: - update_order_request.add_tracktrace_code(picking.carrier_tracking_ref) - update_order_request.add_tracktrace_url(picking.carrier_tracking_url) - update_order_request.add_carrier(picking.carrier_id.name) - update_order_request.add_ship_date(picking.date_done.strftime('%Y-%m-%dT%H:%M:%S') if picking.scheduled_date else None) - - _logger.debug("MultiSafepay update order request: %s", str(update_order_request.to_dict())) - - response = order_manager.update(multisafepay_tx.reference, update_order_request) - - _logger.debug("MultiSafepay update order request response: %s", str(response)) - - if response.status_code == 200: - _logger.info("MultiSafepay notified successfully for picking %s", picking.name) - # Mark the picking as notified - picking.multisafepay_notified = True - - else: - _logger.error("Failed to notify MultiSafepay for picking %s", picking.name) - - except Exception as e: - _logger.error("Error notifying MultiSafepay for picking %s: %s", picking.name, str(e)) - continue - - picking.multisafepay_notified = True - - return result diff --git a/payment_multisafepay_official/tests/test_payment_transaction.py b/payment_multisafepay_official/tests/test_payment_transaction.py deleted file mode 100644 index 0f6417f..0000000 --- a/payment_multisafepay_official/tests/test_payment_transaction.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (c) MultiSafepay, Inc. All rights reserved. -# This file is licensed under the GNU Affero General Public License (AGPL) version 3.0. -# See the LICENSE.md file for more information. -# See the DISCLAIMER.md file for disclaimer details - -from odoo.tests.common import TransactionCase -from datetime import datetime -import uuid - -class TestPaymentTransaction(TransactionCase): - - def setUp(self): - super().setUp() - self.provider = self.env['payment.provider'].create({ - 'name': 'MultiSafepay Test', - 'code': 'multisafepay', - 'state': 'test', - }) - - self.partner = self.env['res.partner'].create({ - 'name': 'Test Customer', - 'email': 'test@example.com', - }) - - self.currency = self.env['res.currency'].search([('name', '=', 'EUR')], limit=1) - if not self.currency: - self.currency = self.env['res.currency'].create({ - 'name': 'EUR', - 'symbol': '€', - 'rate': 1.0, - }) - - def test_compute_reference(self): - """Test _compute_reference method for MultiSafepay transactions""" - - reference = self.env['payment.transaction']._compute_reference('multisafepay') - - print(f"🔍 Generated reference: {reference}") - - self.assertTrue(reference.startswith('MSP-')) - parts = reference.split('-') - self.assertEqual(len(parts), 3) # MSP-timestamp-uuid - - timestamp = parts[1] - self.assertEqual(len(timestamp), 14) - self.assertTrue(timestamp.isdigit()) - - uuid_part = parts[2] - self.assertEqual(len(uuid_part), 8) diff --git a/payment_multisafepay_official/views/payment_templates.xml b/payment_multisafepay_official/views/payment_templates.xml deleted file mode 100644 index 20edd4b..0000000 --- a/payment_multisafepay_official/views/payment_templates.xml +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - - - - - - - - - - - Payment Method - - - - - - - - Status - - - - - - - - - - Amount - - - - - - - - - - - - - - Transaction ID - - - - MultiSafepay ID - - - - - - - - - - Account Holder - - - - External Transaction ID - - - - - - Payment Description - - - - Account ID - - - - - - - - - Action required to complete your payment. - - - - - - - - - - - - - - - - Payment Error - - - - - - - - - - - - - - - - - - - - diff --git a/bin/release.sh b/release.sh similarity index 97% rename from bin/release.sh rename to release.sh index 2bfce08..8bdc742 100755 --- a/bin/release.sh +++ b/release.sh @@ -36,4 +36,4 @@ rm -rf "$REPOSITORY_SRC" zip -9 -r "$FILENAME_PREFIX""$RELEASE_VERSION".zip "$FOLDER_PREFIX" # Remove the remaining directory -rm -rf "$FOLDER_PREFIX" \ No newline at end of file +rm -rf "$FOLDER_PREFIX" diff --git a/requirements.txt b/requirements.txt index ee01e26..437b249 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -multisafepay==2.1.0 +# generated from manifests external_dependencies +multisafepay