"""Schema validation for payoneer.""" import uuid from abacus_common_logic.marshalling.custom_fields import ma from marshmallow import validate, ValidationError from marshmallow.decorators import pre_load, validates_schema from payee.constants.constants import BANK_ACCOUNT_TYPE from payee.constants.error import ( ERROR_MASS_PAYOUT_BOTH_PAYEE_IDS, ERROR_MASS_PAYOUT_INVALID_PAYEE_ID, ERROR_MASS_PAYOUT_MISSING_PAYEE_ID, ) class PayoneerPayeeSchema(ma.Schema): """Schema for Payoneer payee object.""" first_name = ma.NonemptyString(required=True) last_name = ma.NonemptyString(required=True) email = ma.NonemptyString(required=True) class CreatePayoneerRegistrationLinkSchema(ma.Schema): """Payoneer create registration link body.""" account_payee_id = ma.IntegerId(required=True) payoneer_program_id = ma.IntegerId(required=True) payoneer_session_id = ma.NonemptyString(required=False) payee = ma.Nested(PayoneerPayeeSchema, required=False) class CreatePayoneerRegistrationLinkResponseSchema(ma.Schema): """Payoneer create registration link response schema.""" account_payee_id = ma.IntegerId(required=True) registration_link = ma.NonemptyString(required=True) class CreateMassPayoutsSchema(ma.Schema): """Schema for Payoneer mass payouts request. Each entry must contain `account_payee_id` (for account-payee entries) or `payee_id` (for collaborator / Payee-model entries). Including both or neither raises a `ValidationError`. """ client_reference_id = ma.NonemptyString(required=True) account_payee_id = ma.NonemptyString( required=False, allow_none=True, load_default=None, ) payee_id = ma.NonemptyString( required=False, allow_none=True, load_default=None, ) @pre_load def coerce_payee_id_to_string(self, data, **kwargs): """Accept both integer and UUID string payee_id values. Existing consumers send a non-negative integer payee_id (matching the original `ma.IntegerId` field); collaborator payouts send a UUID string (payoneer_client_reference_id). Coerce integers to strings so NonemptyString validation passes for both callers, and reject negative integers or strings that aren't valid UUIDs. """ payee_id = data.get('payee_id') if isinstance(payee_id, int): if payee_id < 0: raise ValidationError(ERROR_MASS_PAYOUT_INVALID_PAYEE_ID, 'payee_id') data = {**data, 'payee_id': str(payee_id)} elif isinstance(payee_id, str): try: uuid.UUID(payee_id) except ValueError: raise ValidationError(ERROR_MASS_PAYOUT_INVALID_PAYEE_ID, 'payee_id') return data description = ma.NonemptyString( required=False, allow_none=True, description=( 'The mass payouts description ' '(Provided description will be overridden by the account payment ' 'description if it exists)' ), ) currency = ma.NonemptyString(required=True) amount = ma.MoneyAmount(required=True) @validates_schema def validate_exactly_one_payee_identifier(self, data, **kwargs): """Only one of the two fields must be non-None.""" account_payee_id = data.get('account_payee_id') payee_id = data.get('payee_id') if account_payee_id is not None and payee_id is not None: raise ValidationError(ERROR_MASS_PAYOUT_BOTH_PAYEE_IDS, '_schema') if account_payee_id is None and payee_id is None: raise ValidationError(ERROR_MASS_PAYOUT_MISSING_PAYEE_ID, '_schema') class MovePayoneerProgramSchema(ma.Schema): """Schema for moving Payoneer program.""" existing_payoneer_program_id = ma.IntegerId(required=True) new_payoneer_program_id = ma.IntegerId(required=True) class ReleasePayoneerProgramSchema(ma.Schema): """Release payee from Payoneer program request schema.""" payoneer_program_id = ma.IntegerId(required=False, allow_none=True) class ReleasePayoneerProgramResponseSchema(ma.Schema): """Release payee from Payoneer program response schema.""" account_payee_id = ma.IntegerId(required=True) class PayeeRegistrationFormatGetSchema(ma.Schema): """Payee registration format query params schema.""" bank_account_type = ma.String( required=True, validate=validate.OneOf(BANK_ACCOUNT_TYPE) ) bank_country = ma.String(required=True, validate=validate.Regexp('[A-Z]{2}')) bank_currency = ma.String(required=True, validate=validate.Regexp('[A-Z]{3}')) @pre_load def trim_strings(self, data, **kwargs): return {k: v.strip() for k, v in data.items()} class PayeeRegistrationFormatOptionsItemSchema(ma.Schema): """Payee registration format response options item schema.""" description = ma.String() value = ma.String() class PayeeRegistrationFormatOptionsSchema(ma.Schema): """Payee registration format response fields item options schema.""" items = ma.List(ma.Nested(PayeeRegistrationFormatOptionsItemSchema)) class PayeeRegistrationFormatDependentOptionsItemSchema(ma.Schema): """Payee registration format response dependent options item schema.""" name = ma.String() options = ma.Nested(PayeeRegistrationFormatOptionsSchema) class PayeeRegistrationFormatDependentOptionsSchema(ma.Schema): """Payee registration format response fields item dependent options schema.""" items = ma.List(ma.Nested(PayeeRegistrationFormatDependentOptionsItemSchema)) class PayeeRegistrationFormatItemSchema(ma.Schema): """Payee registration format response item schema.""" field_name = ma.String() field_id = ma.Integer() field_type = ma.String() label = ma.String() description = ma.String() watermark = ma.String() required = ma.Boolean() min_length = ma.Integer() max_length = ma.Integer() regex = ma.String() options_contains_other = ma.Boolean() options = ma.Nested(PayeeRegistrationFormatOptionsSchema) dependent_options_contains_others = ma.Boolean() dependent_options = ma.Nested(PayeeRegistrationFormatDependentOptionsSchema) parent_field_id = ma.Integer() parent_list_item = ma.String() class PayeeRegistrationFormatFieldsSchema(ma.Schema): """Payee registration format response fields schema.""" items = ma.List(ma.Nested(PayeeRegistrationFormatItemSchema)) class PayeeRegistrationFormatSchema(ma.Schema): """Payee registration format response schema.""" type = ma.String() country = ma.String(data_key='bank_country') currency = ma.String(data_key='bank_currency') bank_account_type = ma.String() fields = ma.Nested(PayeeRegistrationFormatFieldsSchema) class PayoneerStatusResponseSchema(ma.Schema): """Payoneer service status response schema.""" status_code = ma.Integer(required=True) status_desc = ma.String(required=True)