Group
Extension

SMS-API-CMTelecom/lib/SMS/API/CMTelecom.pm

package SMS::API::CMTelecom;

use 5.006;
use strict;
use warnings;
use LWP::UserAgent;
use JSON;

=head1 NAME

SMS::API::CMTelecom - SMS API for cmtelecom.com

=head1 VERSION

Version 0.01

=cut

our $VERSION = '0.05';


=head1 SYNOPSIS

    use SMS::API::CMTelecom;
    my $sms = SMS::API::CMTelecom->new(
        product_token => '00000000-0000-0000-0000-000000000000',
    );

    $sms->send(
        sender     => '00480000111111111',
        message    => 'please call me!',
        recipients => '00490000000000000',
        reference  => 293854,
    );

    # bulk send to many recipients:
    $sms->send(
        sender     => '00480000111111111',
        message    => 'please call me!',
        recipients => ['00490000000000000', '00480000000000', '004300021651202'],
        reference  => 293854,
    );

    my $number = '00480000111111111';
    if ($sms->validate_number($number)) {
        print "$number is a valid phone number.\n"
    } else {
        print "$number is no valid phone number.\n"
    }

    my $number_details = $sms->number_details($number);
    print "$number was ported.\n" if $number_details->{ported};


=head1 METHODS

=head2 new(%options)

Instantiate and initialise object with the following options:

=over 4

=item C<< product_token => $product_token >>

The product token is required to authenticate with the CM Telecom API.

=item C<< sender => $sender >>

Optional. SMS sender number.

=back

=cut

sub new {
    my $class = shift;
    my %params = @_;
    die $class.'->new requires product_token parameter' if not exists $params{product_token};
    my $self = \%params;
    bless $self, $class;

    $self->{_ua} = LWP::UserAgent::->new();
    $self->{_ua}->agent('SMS::API::CMTelecom/'.$VERSION);
    if ($self->{_ua}->can('ssl_opts')) {
        $self->{_ua}->ssl_opts( verify_hostname => 0, );
    }

    return $self;
}

=head2 send

=over 4

=item C<< message => $message >>

Mandatory. Message text to send.

=item C<< recipients => $recipients >>

Mandatory. May be a scalar containing one phone number or an array reference
holding multiple scalars containing one phone number each.

=item C<< sender => $sender >>

Optional if already given as parameter to C<new>. Can also be set globally when construction the object with C<new()>.

=back

If sending fails, C<undef> is returned, otherwise a hashref with some status information:

    {
        messages => [
            {
                messageDetails => undef,
                parts          => 1,
                reference      => 51314,
                status         => "Accepted",
                to             => "0049123456784510",
            },
        ],
    }

You can retrieve the error message via

    my $msg = $sms->error_message();

=cut

sub send {
    my $self = shift;
    my %params = @_;

    $self->_reset_error_message();

    my @recipients = ref $params{recipients} eq 'ARRAY' ? @{ $params{recipients} } : ($params{recipients} || ());

    return $self->_set_error_message(ref($self).'->send requires at least one recipient number') if !@recipients;
    for my $recipient (@recipients) {
        return $self->_set_error_message('recipient may not be undefined') if !defined $recipient;
        return $self->_set_error_message('recipient must be a telephone number') if ref $recipient;
        return $self->_set_error_message('recipient may not be an empty string') if $recipient eq '';
    }

    my $sender = $params{sender} // $self->{sender};
    return $self->_set_error_message(ref($self).'->send requires a sender number') if !defined $sender;

    my $payload = {
        messages => {
            authentication => {
                producttoken => $self->{product_token},
            },
            msg => [
                {
                    from => $sender,
                    to   => [ map { +{ number => $self->_clean_number($_) } } @recipients ],
                    body => {
                        type    => 'AUTO',
                        content => $params{message},
                    },
                    exists $params{reference} ? (reference => $params{reference}) : (),
                },
            ],
        },
    };

    my $req = HTTP::Request->new(
        POST => 'https://gw.cmtelecom.com/v1.0/message',
        ['Content-Type' => 'application/json'],
        encode_json $payload,
    );
    my $res = $self->{_ua}->request( $req );

    if ($res->code == 200) {
        my $result = decode_json $res->content();
        return {
            messages => $result->{messages},
        };
    }

    my $result = eval { decode_json $res->content() };
    return $self->_set_error_message($result->{details}) if ref $result eq 'HASH';

    return $self->_set_error_message('HTTP request returned with status '.$res->code);
}

sub _clean_number {
    my ($self, $number) = @_;

    # strip all non-number chars
    $number =~ s/\D//g;

    return $number;
}

sub _set_error_message {
    my ($self, $message) = @_;
    $self->{error_message} = $message;
    return;
}

sub _reset_error_message {
    my ($self, $message) = @_;
    $self->{error_message} = undef;
    return;
}

=head2 validate_number $number

Checks if the given phone number is valid and provides additional information,
e.g. how the number should be formatted. Returns 1 if the number is valid, a
false value otherwise.

=cut

sub validate_number {
    my ($self, $number) = @_;

    my $req = HTTP::Request->new(
        POST => 'https://api.cmtelecom.com/v1.1/numbervalidation',
        [
            'Content-Type'      => 'application/json',
            'X-CM-PRODUCTTOKEN' => $self->{product_token},
        ],
        encode_json { phonenumber => $number },
    );

    my $res = $self->{_ua}->request( $req );
    if ($res->code == 200) {
        my $result = decode_json $res->content();
        return 1 if JSON::is_bool($result->{valid_number}) and $result->{valid_number};
        return 0;
    }
    return $self->_set_error_message('HTTP request returned with status '.$res->code);
}

=head2 number_details $number

Returns carrier, country, timezone and number type information about the given number.

=cut

sub number_details {
    my ($self, $number) = @_;

    my $req = HTTP::Request->new(
        POST => 'https://api.cmtelecom.com/v1.1/numbervalidation',
        [
            'Content-Type'      => 'application/json',
            'X-CM-PRODUCTTOKEN' => $self->{product_token},
        ],
        encode_json { phonenumber => $number },
    );

    my $res = $self->{_ua}->request( $req );

    if ($res->code == 200) {
        return decode_json $res->content();
    }
    return $self->_set_error_message('HTTP request returned with status '.$res->code);
}

=head2 error_message

Returns the last set error message.

=cut

sub error_message {
    return shift()->{error_message};
}


=head1 AUTHOR

Dominic Sonntag, C<< <dominic at s5g.de> >>

=head1 BUGS AND SUPPORT

Please report any bugs or feature requests on Github: L<https://github.com/sonntagd/SMS-API-CMTelecom/issues>


=head1 LICENSE AND COPYRIGHT

Copyright 2017 Dominic Sonntag.

This program is free software; you can redistribute it and/or modify it
under the terms of the the Artistic License (2.0). You may obtain a
copy of the full license at:

L<http://www.perlfoundation.org/artistic_license_2_0>

=cut

1; # End of SMS::API::CMTelecom


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.