178 lines
5.7 KiB
Python
178 lines
5.7 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import uuid
|
|
|
|
from odoo.addons.fusion_clover import const
|
|
|
|
|
|
def generate_idempotency_key():
|
|
"""Generate a unique idempotency key for Clover API requests."""
|
|
return str(uuid.uuid4())
|
|
|
|
|
|
def build_ecom_url(endpoint, is_test=False):
|
|
"""Build a full Clover Ecommerce API URL.
|
|
|
|
:param str endpoint: The API endpoint path (e.g., 'v1/charges').
|
|
:param bool is_test: Whether to use the sandbox environment.
|
|
:return: The full API URL.
|
|
:rtype: str
|
|
"""
|
|
base = const.ECOM_BASE_URL_TEST if is_test else const.ECOM_BASE_URL
|
|
return f"{base}/{endpoint}"
|
|
|
|
|
|
def build_platform_url(endpoint, merchant_id=None, is_test=False):
|
|
"""Build a full Clover Platform API URL.
|
|
|
|
:param str endpoint: The API endpoint path.
|
|
:param str merchant_id: The merchant ID (optional).
|
|
:param bool is_test: Whether to use the sandbox environment.
|
|
:return: The full API URL.
|
|
:rtype: str
|
|
"""
|
|
base = const.API_BASE_URL_TEST if is_test else const.API_BASE_URL
|
|
if merchant_id:
|
|
return f"{base}/v3/merchants/{merchant_id}/{endpoint}"
|
|
return f"{base}/{endpoint}"
|
|
|
|
|
|
def build_ecom_headers(api_key, idempotency_key=None):
|
|
"""Build the standard HTTP headers for a Clover Ecommerce API request.
|
|
|
|
:param str api_key: The Clover API key (Bearer token).
|
|
:param str idempotency_key: Optional unique key for idempotency.
|
|
:return: The request headers dict.
|
|
:rtype: dict
|
|
"""
|
|
headers = {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
'Authorization': f'Bearer {api_key}',
|
|
}
|
|
if idempotency_key:
|
|
headers['idempotency-key'] = idempotency_key
|
|
return headers
|
|
|
|
|
|
def format_clover_amount(amount, currency):
|
|
"""Convert a major currency amount to Clover's minor units (cents).
|
|
|
|
:param float amount: The amount in major currency units.
|
|
:param recordset currency: The currency record.
|
|
:return: The amount in minor currency units (integer).
|
|
:rtype: int
|
|
"""
|
|
decimals = const.CURRENCY_DECIMALS.get(currency.name, 2)
|
|
return int(round(amount * (10 ** decimals)))
|
|
|
|
|
|
def parse_clover_amount(minor_amount, currency):
|
|
"""Convert Clover's minor currency units back to major units.
|
|
|
|
:param int minor_amount: The amount in minor currency units.
|
|
:param recordset currency: The currency record.
|
|
:return: The amount in major currency units.
|
|
:rtype: float
|
|
"""
|
|
decimals = const.CURRENCY_DECIMALS.get(currency.name, 2)
|
|
return minor_amount / (10 ** decimals)
|
|
|
|
|
|
def extract_card_details(source):
|
|
"""Extract card details from a Clover charge source object.
|
|
|
|
:param dict source: The Clover source object from a charge response.
|
|
:return: Dict with card brand, last4, expiration.
|
|
:rtype: dict
|
|
"""
|
|
if not source:
|
|
return {}
|
|
|
|
brand_raw = source.get('brand', '')
|
|
brand_code = const.CARD_BRAND_MAPPING.get(brand_raw, 'card')
|
|
|
|
return {
|
|
'brand': brand_code,
|
|
'last4': str(source.get('last4', '')),
|
|
'exp_month': source.get('exp_month'),
|
|
'exp_year': source.get('exp_year'),
|
|
'first6': str(source.get('first6', '')),
|
|
}
|
|
|
|
|
|
def get_clover_status(status_str):
|
|
"""Map a Clover charge status string to an Odoo transaction state.
|
|
|
|
:param str status_str: The Clover charge status.
|
|
:return: The corresponding Odoo payment state.
|
|
:rtype: str
|
|
"""
|
|
for odoo_state, clover_statuses in const.STATUS_MAPPING.items():
|
|
if status_str in clover_statuses:
|
|
return odoo_state
|
|
return 'error'
|
|
|
|
|
|
def build_charge_payload(amount, currency, source_token, capture=True,
|
|
description='', ecomind='ecom',
|
|
external_reference_id='', receipt_email='',
|
|
metadata=None):
|
|
"""Build a Clover charge creation payload.
|
|
|
|
:param float amount: The charge amount in major currency units.
|
|
:param recordset currency: The currency record.
|
|
:param str source_token: The Clover card token.
|
|
:param bool capture: Whether to capture immediately (True) or pre-auth (False).
|
|
:param str description: Optional charge description.
|
|
:param str ecomind: 'ecom' for customer-initiated, 'moto' for merchant-initiated.
|
|
:param str external_reference_id: External reference (max 12 chars).
|
|
:param str receipt_email: Email to send receipt to.
|
|
:param dict metadata: Optional key-value metadata.
|
|
:return: The Clover-formatted charge payload.
|
|
:rtype: dict
|
|
"""
|
|
minor_amount = format_clover_amount(amount, currency)
|
|
|
|
payload = {
|
|
'amount': minor_amount,
|
|
'currency': currency.name.lower(),
|
|
'source': source_token,
|
|
'capture': capture,
|
|
'ecomind': ecomind,
|
|
}
|
|
|
|
if description:
|
|
payload['description'] = description
|
|
if external_reference_id:
|
|
payload['external_reference_id'] = external_reference_id[:12]
|
|
if receipt_email:
|
|
payload['receipt_email'] = receipt_email
|
|
if metadata:
|
|
payload['metadata'] = metadata
|
|
|
|
return payload
|
|
|
|
|
|
def build_refund_payload(charge_id, amount=None, currency=None, reason=''):
|
|
"""Build a Clover refund payload.
|
|
|
|
:param str charge_id: The Clover charge ID to refund.
|
|
:param float amount: Optional partial refund amount in major currency units.
|
|
:param recordset currency: Optional currency record (needed for partial refunds).
|
|
:param str reason: Optional reason for the refund.
|
|
:return: The Clover-formatted refund payload.
|
|
:rtype: dict
|
|
"""
|
|
payload = {
|
|
'charge': charge_id,
|
|
}
|
|
|
|
if amount is not None and currency:
|
|
payload['amount'] = format_clover_amount(amount, currency)
|
|
|
|
if reason:
|
|
payload['reason'] = reason
|
|
|
|
return payload
|