"""Schema for bank details parameters.""" from abacus_common_logic.marshalling.custom_fields import ma from marshmallow import validates_schema, ValidationError from marshmallow.validate import Length, OneOf, Regexp from payee.constants.constants import ( BANK_ACCOUNT_TYPE, BANK_DETAILS_PAYEE_TYPE, COUNTRIES_REQUIRED_FIELD_EMAIL, COUNTRIES_REQUIRED_FIELD_PROVINCE, ) from payee.constants.features import is_account_payee_flow from payee.schemas.address import PayoneerWhitelabelAddressSchema from payee.schemas.mixins import SanitizeSpacesMixin from payee.schemas.validators import UnicodeRegexp NAME_REGEX_PATTERN = '^[\\p{L}0-9\\s,\\.\\/_\\-\\)\\(\\\\#`,*":&\']+$' class BankFieldDetailSchema(ma.Schema): name = ma.String(required=True) value = ma.String(required=True) class PaymentMethodSchema(ma.Schema, SanitizeSpacesMixin): """Payment method schema.""" bank_account_type = ma.String(required=True, validate=OneOf(BANK_ACCOUNT_TYPE)) country = ma.String(required=True, validate=Regexp('^[A-Z]{2}$')) currency = ma.String(required=True, validate=Regexp('^[A-Z]{3}$')) bank_field_details = ma.List(ma.Nested(BankFieldDetailSchema), required=True) class ContactSchema(ma.Schema): """Contact schema.""" first_name = ma.String( required=False, validate=[UnicodeRegexp(NAME_REGEX_PATTERN), Length(min=2, max=26)], allow_none=True, ) last_name = ma.String( required=False, validate=[UnicodeRegexp(NAME_REGEX_PATTERN), Length(min=2, max=26)], allow_none=True, ) date_of_birth = ma.String( required=False, validate=Regexp(r'^\d{4}-\d{2}-\d{2}$'), allow_none=True, ) email = ma.Email(required=False, allow_none=True) phone_country = ma.String( required=False, validate=Regexp('^[A-Z]{2}$'), allow_none=True ) phone = ma.Str( required=False, allow_none=True, validate=Regexp(r'^[0-9]\d{1,12}$'), # 13 digits without country code and + ) @validates_schema def validate_email_phone(self, data, **kwargs): """Validate that email and phone are provided.""" if not is_account_payee_flow(): # Generic payees (collaborators) do not require email and phone yet. return data errors = {} if not data.get('email'): errors['email'] = ma.Field.default_error_messages['required'] phone_country = data.get('phone_country') if not phone_country: errors['phone_country'] = ma.Field.default_error_messages['required'] phone = data.get('phone') if not phone: errors['phone'] = ma.Field.default_error_messages['required'] if errors: raise ValidationError(errors) return data class CompanySchema(ma.Schema): name = ma.String( required=True, validate=[ UnicodeRegexp(NAME_REGEX_PATTERN), Length(min=2, max=100), ], ) class BankDetailsInputSchema(ma.Schema): """Bank post details schema.""" type = ma.String(required=True, validate=OneOf(BANK_DETAILS_PAYEE_TYPE)) contact = ma.Nested(ContactSchema) address = ma.Nested(PayoneerWhitelabelAddressSchema, required=True) company = ma.Nested(CompanySchema) payout_method = ma.Nested(PaymentMethodSchema, required=True) @validates_schema def validate_fields_based_on_type(self, data, **kwargs): required_fields = { BANK_DETAILS_PAYEE_TYPE.INDIVIDUAL: ['contact'], BANK_DETAILS_PAYEE_TYPE.COMPANY: ['company'], }.get(data.get('type'), []) missing = set(required_fields) - set(data.keys()) if missing: raise ValidationError( f"Missing required fields for type '{data.get('type')}': {', '.join(missing)}" ) @validates_schema def validate_province_for_us_country(self, data, **kwargs): if data['address'][ 'country_code' ] in COUNTRIES_REQUIRED_FIELD_PROVINCE and not data['address'].get('province'): raise ValidationError('Province (state) field is required for US country') @validates_schema def validate_email_for_country(self, data, **kwargs): if ( data['type'] == BANK_DETAILS_PAYEE_TYPE.INDIVIDUAL and data['address']['country_code'] in COUNTRIES_REQUIRED_FIELD_EMAIL and not data['contact'].get('email') ): raise ValidationError( f'Contact email field is required for {data["address"]["country_code"]} country' ) @validates_schema def validate_individual_required_fields(self, data, **kwargs): """Validate required fields for individual type.""" if data['type'] != BANK_DETAILS_PAYEE_TYPE.INDIVIDUAL or not data.get( 'contact' ): return missed = [ field for field in ('first_name', 'last_name', 'date_of_birth') if not data['contact'].get(field) ] if missed: raise ValidationError( f'Contact {", ".join(missed)} fields are required for individual type' ) class BankDetailsDetailedSchema(BankDetailsInputSchema): """Bank details detailed schema.""" payoneer_client_reference_id = ma.String(required=True) account_payee_id = ma.String(required=True) modified_at = ma.String(required=True)