The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package WWW::eNom::Contact;

use strict;
use warnings;

use Moose;
use MooseX::StrictConstructor;
use MooseX::Params::Validate;
use namespace::autoclean;

use WWW::eNom::Types qw( ContactType EmailAddress HashRef PhoneNumber Str );

use WWW::eNom::PhoneNumber;

use Data::Util qw( is_string );
use Try::Tiny;
use Carp;

our $VERSION = 'v2.3.0'; # VERSION
# ABSTRACT: Representation of eNom Contact

has 'first_name' => (
    is       => 'rw',
    isa      => Str,
    required => 1,
);

has 'last_name' => (
    is       => 'rw',
    isa      => Str,
    required => 1,
);

has 'organization_name' => (
    is        => 'rw',
    isa       => Str,
    predicate => 'has_organization_name',
    clearer   => 'clear_organization_name',
);

has 'job_title' => (
    is        => 'rw',
    isa       => Str,
    predicate => 'has_job_title',
    clearer   => 'clear_job_title',
);

has 'address1' => (
    is       => 'rw',
    isa      => Str,
    required => 1,
);

has 'address2' => (
    is        => 'rw',
    isa       => Str,
    predicate => 'has_address2',
    clearer   => 'clear_address2',
);

has 'city' => (
    is       => 'rw',
    isa      => Str,
    required => 1,
);

has 'state' => (
    is        => 'rw',
    isa       => Str,
    predicate => 'has_state',
    clearer   => 'clear_state',
);

has 'country' => (
    is       => 'rw',
    isa      => Str,
    required => 1,
);

has 'zipcode' => (
    is       => 'rw',
    isa      => Str,
    required => 1,
);

has 'email' => (
    is       => 'rw',
    isa      => EmailAddress,
    required => 1,
);

has 'phone_number' => (
    is       => 'rw',
    isa      => PhoneNumber,
    required => 1,
    coerce   => 1,
);

has 'fax_number' => (
    is        => 'rw',
    isa       => PhoneNumber,
    predicate => 'has_fax_number',
    clearer   => 'clear_fax_number',
    coerce    => 1,
);

sub BUILD {
    my $self = shift;

    if( $self->has_organization_name ) {
        if( !$self->has_job_title || !$self->has_fax_number ) {
            croak 'Contacts with an organization_name require a job_title and fax_number';
        }
    }
}

sub construct_creation_request {
    my $self = shift;
    my ( $contact_type ) = pos_validated_list( \@_, { isa => ContactType, optional => 1 } );

    my $creation_request = {
        FirstName    => $self->first_name,
        LastName     => $self->last_name,
        $self->has_organization_name ? ( OrganizationName => $self->organization_name ) : ( ),
        $self->has_job_title         ? ( JobTitle         => $self->job_title         ) : ( ),
        Address1     => $self->address1,
        $self->has_address2          ? ( Address2         => $self->address2          ) : ( ),
        City         => $self->city,
        $self->has_state             ? ( StateProvince    => $self->state             ) : ( ),
        Country      => $self->country,
        PostalCode   => $self->zipcode,
        EmailAddress => $self->email,
        Phone        => sprintf('+%s.%s', $self->phone_number->country_code, $self->phone_number->number ),
        $self->has_fax_number ? ( Fax => sprintf('+%s.%s', $self->fax_number->country_code, $self->fax_number->number ) ) : ( ),
    };

    if( $contact_type ) {
        for my $key ( keys %{ $creation_request } ) {
            $creation_request->{ $contact_type . $key } = delete $creation_request->{ $key };
        }
    }

    return $creation_request;
}

sub construct_from_response {
    my $self         = shift;
    my ( $response ) = pos_validated_list( \@_, { isa => HashRef } );

    return try {
        return $self->new({
            first_name        => $response->{'FirstName'},
            last_name         => $response->{'LastName'},
            is_string( $response->{'OrganizationName'} ) ? ( organization_name => $response->{'OrganizationName'} ) : ( ),
            is_string( $response->{'JobTitle'}         ) ? ( job_title         => $response->{'JobTitle'}         ) : ( ),
            address1          => $response->{'Address1'},
            is_string( $response->{'Address2'}         ) ? ( address2          => $response->{'Address2'}         ) : ( ),
            city              => $response->{'City'},
            is_string( $response->{'StateProvince'}    ) ? ( state             => $response->{'StateProvince'}    ) : ( ),
            country           => $response->{'Country'},
            zipcode           => $response->{'PostalCode'},
            email             => $response->{'EmailAddress'},
            phone_number      => $response->{'Phone'},
            is_string( $response->{'Fax'}             ) ? ( fax_number        => $response->{'Fax'}              ) : ( ),
        });
    }
    catch {
        croak "Error constructing contact from response: $_";
    };
}

__PACKAGE__->meta->make_immutable;
1;

__END__

=pod

=head1 NAME

WWW::eNom::Contact - Representation of eNom Contact

=head1 SYNOPSIS

    use strict;
    use warnings;

    use WWW::eNom;
    use WWW::eNom::Contact;

    my $api     = WWW::eNom->new( ... );
    my $contact = WWW::eNom::Contact->new( ... );

    # New Contact Object
    my $contact = WWW::eNom::Contact->new(
        first_name        => 'Ada',
        last_name         => 'Byron',
        organization_name => 'Lovelace',                # Optional
        job_title         => 'Countess',                # Optional if no organization_name, otherwise required
        address1          => 'University of London',
        address2          => 'Analytical Engine Dept',  # Optional
        city              => 'London',
        #state            => 'Texas',                   # Optional, primarily used for US Contacts
        country           => 'GB',
        zipcode           => 'WC1E 7HU',
        email             => 'ada.byron@lovelace.com',
        phone_number      => '18005551212',
        fax_number        => '18005551212',             # Optional if no organization_name, otherwise required
    );

    # Contact Creation
    my $registrant_contact_creation_payload = $contact->construct_creation_request('Registrant');
    my $admin_contact_creation_payload      = $contact->construct_creation_request('Admin');
    my $technical_contact_creation_payload  = $contact->construct_creation_request('Tech');
    my $billing_contact_creation_payload    = $contact->construct_creation_request('AuxBilling');

    my $response = $api->submit({
        method => 'Purchase',
        params => {
            ...,
            %{ $registrant_contact_creation_payload },
            %{ $admin_contact_creation_payload },
            %{ $technical_contact_creation_payload },
            %{ $billing_contact_creation_payload },
        }
    });

    # Contact Retrieval
    my $response = $self->submit({
        method => 'GetContacts',
        params => {
            Domain => $domain_name
        }
    });

    my $contacts;
    for my $contact_type (qw( Registrant Admin Tech AuxBilling )) {
        my $raw_contact_response = $response->{GetContacts}{$contact_type};

        my $common_contact_response;
        for my $field ( keys %{ $raw_contact_response } ) {
            if( $field !~ m/$contact_type/ ) {
                next;
            }

            $common_contact_response->{ substr( $field, length( $contact_type ) ) } =
                $raw_contact_response->{ $field } // { };
        }

        $contacts->{ $contact_type } = WWW::eNom::Contact->construct_from_response( $common_contact_response );
    }


=head1 DESCRIPTION

Representation of an L<eNom|http://www.enom.com> Contact.

=head1 ATTRIBUTES

=head2 B<first_name>

=head2 B<last_name>

=head2 organization_name

Predicate of has_organization_name and clearer of clear_organization_name.

B<NOTE> If the organization_name is specified then the previously optional L<job_title|WWW::eNom::Contact/job_title> and L<fax_number|WWW::eNom::Contact/fax_number> attributes become B<required>.

=head2 job_title

Predicate of has_job_title and clearer of clear_job_title.

B<NOTE> this field is B<required> if an L<organization_name|WWW::eNom::Contact/organization_name> was provided.

=head2 B<address1>

=head2 address2

Predicate of has_address2 and clearer of clear_address2

=head2 B<city>

=head2 state

Required for Contacts with a US Address, the full name of the state so Texas rather than TX should be used.

Predicate of has_state and clearer of clear_state.

=head2 B<country>

The L<ISO-3166-1 alpha-2 (two character country code)|https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2> is preferred.  You can use a full country name, just keep in mind your response from eNom will be the country code.

=head2 B<zipcode>

=head2 B<email>

=head2 B<phone_number>

An instance of L<WWW::eNom::PhoneNumber>, but this will coerce from a L<Number::Phone> object or a string based representation of the phone number.  This will also stringify to a human readable phone number.

=head2 fax_number

An instance of L<WWW::eNom::PhoneNumber>, but this will coerce from a L<Number::Phone> object or a string based representation of the phone number.  This will also stringify to a human readable phone number.

Predicate of has_fax_number and clearer of clear_fax_number.

B<NOTE> this field is B<required> if an L<organization_name|WWW::eNom::Contact/organization_name> was provided.

=head1 METHODS

=head2 construct_creation_request

    my $api     = WWW::eNom->new( ... );
    my $contact = WWW::eNom::Contact->new( ... );

    my $registrant_contact_creation_payload = $contact->construct_creation_request('Registrant');
    my $admin_contact_creation_payload      = $contact->construct_creation_request('Admin');
    my $technical_contact_creation_payload  = $contact->construct_creation_request('Tech');
    my $billing_contact_creation_payload    = $contact->construct_creation_request('AuxBilling');

    my $response = $api->submit({
        method => 'Purchase',
        params => {
            ...,
            %{ $registrant_contact_creation_payload },
            %{ $admin_contact_creation_payload },
            %{ $technical_contact_creation_payload },
            %{ $billing_contact_creation_payload },
        }
    });

Converts $self into a HashRef suitable for creation of a contact with L<eNom|https://www.enom.com>.  Accepts a string that must be one of the following:

=over 4

=item Registrant

=item Admin

=item Tech

=item AuxBilling

AuxBilling is what eNom calls the "Billing" contact for WHOIS data since the Billing contact is actually the reseller.

=back

=head2 construct_from_response

    my $response = $api->submit({
        method => 'GetContacts',
        params => {
            Domain => $domain_name
        }
    });

    my $contacts;
    for my $contact_type (qw( Registrant Admin Tech AuxBilling )) {
        my $raw_contact_response = $response->{GetContacts}{$contact_type};

        my $common_contact_response;
        for my $field ( keys %{ $raw_contact_response } ) {
            if( $field !~ m/$contact_type/ ) {
                next;
            }

            $common_contact_response->{ substr( $field, length( $contact_type ) ) } =
                $raw_contact_response->{ $field } // { };
        }

        $contacts->{ $contact_type } = WWW::eNom::Contact->construct_from_response( $common_contact_response );
    }

Getting a contact from a response is a bit more involved then other data marshallers.  This is because the fields are all prefixed with the contact type.  Rather than having just FirstName the response will contain a field like TechFirstName.  This must be processed off before feeding in the HashRef of the response into the construct_from_response method.  Returned is an instance of self.

=cut