Group
Extension

SMS-Send-KR-APIStore/lib/SMS/Send/KR/APIStore.pm

package SMS::Send::KR::APIStore;
# ABSTRACT: An SMS::Send driver for the apistore.co.kr SMS service

use utf8;
use strict;
use warnings;

our $VERSION = '0.003';

use parent qw( SMS::Send::Driver );

use HTTP::Tiny;
use JSON;

our $URL     = "http://api.apistore.co.kr/ppurio/1";
our $AGENT   = 'SMS-Send-KR-APIStore/' . $SMS::Send::KR::APIStore::VERSION;
our $TIMEOUT = 3;
our $TYPE    = 'SMS';
our $DELAY   = 0;

our %ERROR_CODE = (
    '4100' => 'sms:전달',
    '4421' => 'sms:타임아웃',
    '4426' => 'sms:재시도한도초과',
    '4425' => 'sms:단말기호처리중',
    '4400' => 'sms:음영지역',
    '4401' => 'sms:단말기전원꺼짐',
    '4402' => 'sms:단말기메시지저장초과',
    '4410' => 'sms:잘못된번호',
    '4422' => 'sms:단말기일시정지',
    '4427' => 'sms:기타단말기문제',
    '4405' => 'sms:단말기 busy',
    '4423' => 'sms:단말기착신거부',
    '4412' => 'sms:착신거절',
    '4411' => 'sms:NPDB 에러',
    '4428' => 'sms:시스템에러',
    '4404' => 'sms:가입자위치정보없음',
    '4413' => 'sms:SMSC 형식오류',
    '4414' => 'sms:비가입자,결번,서비스정지',
    '4424' => 'sms:URL SMS 미지원폰',
    '4403' => 'sms:메시지삭제됨',
    '4430' => 'sms:스팸',
    '4431' => 'sms:발송제한 수신거부(스팸)',
    '4432' =>
        'sms:번호도용문자 차단서비스에 가입된 발신번호(개인)사용',
    '4433' =>
        'sms:번호도용문자 차단서비스에 가입된 발신번호(개인)사용',
    '4434' => 'sms:발신번호 사전 등록제에 의한 미등록 차단',
    '4435' => 'sms:KISA 에 스팸 신고된 발신번호 사용',
    '4436' => 'sms:발신번호 사전 등록제 번호규칙 위반',
    '4420' => 'sms:기타에러',

    '6600' => 'mms:전달',
    '6601' => 'mms:타임아웃',
    '6602' => 'mms:핸드폰호처리중',
    '6603' => 'mms:음영지역',
    '6604' => 'mms:전원이꺼져있음',
    '6605' => 'mms:메시지저장개수초과',
    '6606' => 'mms:잘못된번호',
    '6607' => 'mms:서비스일시정지',
    '6608' => 'mms:기타단말기문제',
    '6609' => 'mms:착신거절',
    '6610' => 'mms:기타에러',
    '6611' => 'mms:통신사의 SMC 형식오류',
    '6612' => 'mms:게이트웨이의형식오류',
    '6613' => 'mms:서비스불가단말기',
    '6614' => 'mms:핸드폰호불가상태',
    '6615' => 'mms:SMC 운영자에의해삭제',
    '6616' => 'mms:통신사의메시지큐초과',
    '6617' => 'mms:통신사의스팸처리',
    '6618' => 'mms:공정위의스팸처리',
    '6619' => 'mms:게이트웨이의스팸처리',
    '6620' => 'mms:발송건수초과',
    '6621' => 'mms:메시지의길이초과',
    '6622' => 'mms:잘못된번호형식',
    '6623' => 'mms:잘못된데이터형식',
    '6624' => 'mms:MMS 정보를찾을수없음',
    '6625' => 'mms:NPDB 에러',
    '6626' => 'mms:080 수신거부(SPAM)',
    '6627' => 'mms:발신제한 수신거부(SPAM)',
    '6628' =>
        'mms:번호도용문자 차단서비스에 가입된 발신번호(개인)사용',
    '6629' =>
        'mms:번호도용문자 차단서비스에 가입된 발신번호(개인)사용',
    '6630' => 'mms:서비스 불가 번호',
    '6631' => 'mms:발신번호 사전 등록제에 의한 미등록 차단',
    '6632' => 'mms:KISA 에 스팸 신고된 발신번호 사용',
    '6633' => 'mms:발신번호 사전 등록제 번호규칙 위반',
    '6670' => 'mms:이미지파일크기제한',

    '9903' => '선불사용자 사용금지',
    '9904' => 'Block time(날짜제한)',
    '9082' => '발송해제',
    '9083' => 'IP 차단',
    '9023' => 'Callback error',
    '9905' => 'Block time(요일제한)',
    '9010' => '아이디 틀림',
    '9011' => '비밀번호 틀림',
    '9012' => '중복접속량 많음',
    '9013' => '발송시간 지난 데이터',
    '9014' => '시간제한(리포트 수신대기 timeout)',
    '9020' => 'Wrong Data Format',
    '9021' => 'Wrong Data Format',
    '9022' => 'Wrong Data Format(cinfo가 특수 문자/공백을 포함)',
    '9080' => 'Deny User Ack',
    '9214' => 'Wrong Phone Num',
    '9311' => 'Fax File Not Found',
    '9908' => 'PHONE, FAX 선불사용자 제한기능',
    '9090' => '기타에러',

    '-1' => '잘못된 데이터 형식 발송오류',
);

sub new {
    my $class  = shift;
    my %params = (
        _url           => $SMS::Send::KR::APIStore::URL,
        _agent         => $SMS::Send::KR::APIStore::AGENT,
        _timeout       => $SMS::Send::KR::APIStore::TIMEOUT,
        _from          => q{},
        _type          => $SMS::Send::KR::APIStore::TYPE,
        _delay         => $SMS::Send::KR::APIStore::DELAY,
        _id            => q{},
        _api_store_key => q{},
        @_,
    );

    die "$class->new: _id is needed\n"            unless $params{_id};
    die "$class->new: _api_store_key is needed\n" unless $params{_api_store_key};
    die "$class->new: _from is needed\n"          unless $params{_from};
    die "$class->new: _type is invalid\n"
        unless $params{_type} && $params{_type} =~ m/^(SMS|LMS)$/i;

    my $self = bless \%params, $class;
    return $self;
}

sub send_sms {
    my $self   = shift;
    my %params = (
        _from    => $self->{_from},
        _type    => $self->{_type} || 'SMS',
        _delay   => $self->{_delay} || 0,
        _subject => $self->{_subject},
        _epoch   => q{},
        @_,
    );

    my $text    = $params{text};
    my $to      = $params{to};
    my $from    = $params{_from};
    my $type    = $params{_type};
    my $delay   = $params{_delay};
    my $subject = $params{_subject};
    my $epoch   = $params{_epoch};

    my %ret = (
        success => 0,
        reason  => q{},
        detail  => +{},
    );

    $ret{reason} = 'text is needed', return \%ret unless $text;
    $ret{reason} = 'to is needed',   return \%ret unless $to;
    $ret{reason} = '_type is invalid', return \%ret
        unless $type && $type =~ m/^(SMS|LMS)$/i;

    my $http = HTTP::Tiny->new(
        agent           => $self->{_agent},
        timeout         => $self->{_timeout},
        default_headers => { 'x-waple-authorization' => $self->{_api_store_key} },
    ) or $ret{reason} = 'cannot generate HTTP::Tiny object', return \%ret;
    my $url = sprintf '%s/message/%s/%s', $self->{_url}, lc($type), $self->{_id};

    #
    # delay / send_time: reserve SMS
    #
    my $send_time;
    if ($delay) {
        my $t = DateTime->now( time_zone => 'Asia/Seoul' )->add( seconds => $delay );
        $send_time = $t->ymd(q{}) . $t->hms(q{});
    }
    if ($epoch) {
        my $t = DateTime->from_epoch(
            time_zone => 'Asia/Seoul',
            epoch     => $epoch,
        );
        $send_time = $t->ymd(q{}) . $t->hms(q{});
    }

    #
    # subject
    #
    undef $subject if $type =~ m/SMS/i;

    my %form = (
        dest_phone => $to,
        send_phone => $from,
        subject    => $subject,
        msg_body   => $text,
        send_time  => $send_time,
    );
    $form{$_} or delete $form{$_} for keys %form;

    my $res = $http->post_form( $url, \%form );
    $ret{reason} = 'cannot get valid response for POST request';
    if ( $res && $res->{success} ) {
        $ret{detail} = decode_json( $res->{content} );
        $ret{success} = 1 if $ret{detail}{result_code} eq '200';

        $ret{reason} = 'unknown error';
        $ret{reason} = 'user error' if $ret{detail}{result_code} eq '100';
        $ret{reason} = 'ok' if $ret{detail}{result_code} eq '200';
        $ret{reason} = 'parameter error' if $ret{detail}{result_code} eq '300';
        $ret{reason} = 'etc error' if $ret{detail}{result_code} eq '400';
        $ret{reason} = 'prevent unregistered caller identification'
            if $ret{detail}{result_code} eq '500';
        $ret{reason} = 'not enough pre-payment charge' if $ret{detail}{result_code} eq '600';
    }
    else {
        $ret{detail} = $res;
        $ret{reason} = 'unknown error';
    }

    return \%ret;
}

sub report {
    my ( $self, $cmid_obj ) = @_;

    my %ret = (
        success     => 0,
        reason      => q{},
        cmid        => q{},
        call_status => q{},
        dest_phone  => q{},
        report_time => q{},
        umid        => q{},
    );

    $ret{reason} = 'cmid is needed', return \%ret unless defined $cmid_obj;

    my $cmid;
    if ( !ref($cmid_obj) ) {
        $cmid = $cmid_obj;
    }
    elsif ( ref($cmid_obj) eq 'HASH' ) {
        $cmid = $cmid_obj->{detail}{cmid};
    }
    else {
        $ret{reason} = 'invalid cmid';
        return \%ret;
    }
    $ret{cmid} = $cmid;

    my $http = HTTP::Tiny->new(
        agent           => $self->{_agent},
        timeout         => $self->{_timeout},
        default_headers => { 'x-waple-authorization' => $self->{_api_store_key} },
    ) or $ret{reason} = 'cannot generate HTTP::Tiny object', return \%ret;
    my $url = sprintf '%s/message/%s/%s', $self->{_url}, 'report', $self->{_id};

    my %form = ( cmid => $cmid );
    $form{$_} or delete $form{$_} for keys %form;
    my $params = $http->www_form_urlencode( \%form );

    my $res = $http->get("$url?$params");
    $ret{reason} = 'cannot get valid response for GET request';
    if ( $res && $res->{success} ) {
        my $detail = decode_json( $res->{content} );

        $ret{success}     = 1 if $detail->{call_status} =~ m/^(4100|6600)$/;
        $ret{reason}      = $ERROR_CODE{ $detail->{call_status} };
        $ret{call_status} = $detail->{call_status};
        $ret{dest_phone}  = $detail->{dest_phone};
        $ret{report_time} = $detail->{report_time};
        $ret{umid}        = $detail->{umid};
    }
    else {
        $ret{detail} = $res;
        $ret{reason} = 'unknown error';
    }

    return \%ret;
}

sub cid {
    my ( $self, $cid, $cid_desc ) = @_;

    if ($cid) {
        my %ret = (
            success => 0,
            reason  => q{},
        );

        my $http = HTTP::Tiny->new(
            agent           => $self->{_agent},
            timeout         => $self->{_timeout},
            default_headers => { 'x-waple-authorization' => $self->{_api_store_key} },
        ) or $ret{reason} = 'cannot generate HTTP::Tiny object', return \%ret;

        my $url = sprintf '%s/sendnumber/%s/%s', $self->{_url}, 'save', $self->{_id};

        my %form = (
            sendnumber => $cid,
            comment    => $cid_desc,
        );
        $form{$_} or delete $form{$_} for keys %form;

        my $res = $http->post_form( $url, \%form );
        $ret{reason} = 'cannot get valid response for POST request';

        if ( $res && $res->{success} ) {
            $ret{detail} = decode_json( $res->{content} );

            if ( $ret{detail}{result_code} eq '200' ) {
                $ret{success} = 1;
                $ret{reason}  = 'ok';
            }
            else {
                $ret{reason} = 'unknown error';
                $ret{reason} = 'user error' if $ret{detail}{result_code} eq '100';
                $ret{reason} = 'parameter error' if $ret{detail}{result_code} eq '300';
                $ret{reason} = 'etc error' if $ret{detail}{result_code} eq '400';
                $ret{reason} = 'prevent unregistered caller identification'
                    if $ret{detail}{result_code} eq '500';
                $ret{reason} = 'not enough pre-payment charge' if $ret{detail}{result_code} eq '600';
            }
        }
        else {
            $ret{detail} = $res;
            $ret{reason} = 'unknown error';
        }

        return \%ret;
    }
    else {
        my %ret = (
            success     => 0,
            reason      => q{},
            number_list => q{},
        );

        my $http = HTTP::Tiny->new(
            agent           => $self->{_agent},
            timeout         => $self->{_timeout},
            default_headers => { 'x-waple-authorization' => $self->{_api_store_key} },
        ) or $ret{reason} = 'cannot generate HTTP::Tiny object', return \%ret;

        my $url = sprintf '%s/sendnumber/%s/%s', $self->{_url}, 'list', $self->{_id};

        my $res = $http->get($url);
        $ret{reason} = 'cannot get valid response for GET request';

        if ( $res && $res->{success} ) {
            $ret{detail} = decode_json( $res->{content} );

            if ( $ret{detail}{result_code} eq '200' ) {
                $ret{success}     = 1;
                $ret{reason}      = 'ok';
                $ret{number_list} = $ret{detail}{numberList} unless $cid;
            }
            else {
                $ret{reason} = 'unknown error';
                $ret{reason} = 'user error' if $ret{detail}{result_code} eq '100';
                $ret{reason} = 'parameter error' if $ret{detail}{result_code} eq '300';
                $ret{reason} = 'etc error' if $ret{detail}{result_code} eq '400';
                $ret{reason} = 'prevent unregistered caller identification'
                    if $ret{detail}{result_code} eq '500';
                $ret{reason} = 'not enough pre-payment charge' if $ret{detail}{result_code} eq '600';
            }
        }
        else {
            $ret{detail} = $res;
            $ret{reason} = 'unknown error';
        }

        return \%ret;
    }
}

1;

#
# This file is part of SMS-Send-KR-APIStore
#
# This software is copyright (c) 2017 by Keedi Kim.
#
# This is free software; you can redistribute it and/or modify it under
# the same terms as the Perl 5 programming language system itself.
#

__END__

=pod

=encoding UTF-8

=head1 NAME

SMS::Send::KR::APIStore - An SMS::Send driver for the apistore.co.kr SMS service

=head1 VERSION

version 0.003

=head1 SYNOPSIS

    use SMS::Send;

    # create the sender object
    my $sender = SMS::Send->new('KR::APIStore',
        _id            => 'keedi',
        _api_store_key => 'XXXXXXXX',
        _from          => '01025116893',
    );

    # send a message
    my $sent = $sender->send_sms(
        text  => 'You message may use up to 80 chars and must be utf8',
        to    => '01012345678',
    );

    unless ( $sent->{success} ) {
        warn "failed to send sms: $sent->{reason}\n";

        # if you want to know detail more, check $sent->{detail}
        use Data::Dumper;
        warn Dumper $sent->{detail};
    }

    # Of course you can send LMS
    my $sender = SMS::Send->new('KR::APIStore',
        _id            => 'keedi',
        _api_store_key => 'XXXXXXXX',
        _type          => 'lms',
        _from          => '01025116893',
    );

    # You can override _from or _type

    #
    # send a message
    #
    my $sent = $sender->send_sms(
        text     => 'You LMS message may use up to 2000 chars and must be utf8',
        to       => '01025116893',
        _from    => '02114',             # you can override $self->_from
        _type    => 'LMS',               # you can override $self->_type
        _subject => 'This is a subject', # subject is optional & up to 40 chars
    );

    #
    # check the result
    #
    my $result = $sender->report("20130314163439459");
    printf "success:     %s\n", $result->{success} ? 'success' : 'fail';
    printf "reason:      %s\n", $result->{reason};
    printf "call_status: %s\n", $result->{call_status};
    printf "dest_phone:  %s\n", $result->{dest_phone};
    printf "report_time: %s\n", $result->{report_time};
    printf "cmid:        %s\n", $result->{cmid};
    printf "umid:        %s\n", $result->{umid};

    # you can use cmid of the send_sms() result
    my $sent = $sender->send_sms( ... );
    my $result = $sender->report( $sent->{detail}{cmid} );

    # or you can use the send_sms() result itself
    my $sent = $sender->send_sms( ... );
    my $result = $sender->report($sent);

    #
    # set caller id
    #

    # set caller id only
    my $ret = $sender->cid( "0XXXXXXXXX" );

    # set caller id and its description
    my $ret = $sender->cid( "0XXXXXXXXX", "Office #201" );

    #
    # get caller id list
    #
    my $ret = $sender->cid;
    if ( $ret->{success} ) {
        my $cids = $ret->{number_list};
        my $idx = 0;
        for my $cid (@$cids) {
            say "$idx:";
            say "     client_id: " . $cid->{client_id};
            say "       comment: " . ( $cid->{comment} || q{} );
            say "    sendnumber: " . $cid->{sendnumber};
            ++$idx;
        }
    }
    else {
        say "failed to get cid info: $ret->{reason}"
    }

=head1 DESCRIPTION

SMS::Send driver for sending SMS messages with the L<APIStore SMS service|http://www.apistore.co.kr/api/apiView.do?service_seq=151>.
Current version of APIStore SMS service DOES NOT support HTTPS,
so you have to use this module at your own risk.

=head1 ATTRIBUTES

=head2 _url

DO NOT change this value except for testing purpose.
Default is C<"http://api.openapi.io/ppurio/1/message">.

=head2 _agent

The agent value is sent as the "User-Agent" header in the HTTP requests.
Default is C<"SMS-Send-KR-APIStore/#.###">.

=head2 _timeout

HTTP request timeout seconds.
Default is C<3>.

=head2 _id

B<Required>.
APIStore API id for REST API.

=head2 _api_store_key

B<Required>.
APIStore API key for REST API.

=head2 _from

B<Required>.
Source number to send sms.

=head2 _type

Type of sms.
Currently C<SMS> and C<LMS> are supported.
Default is C<"SMS">.

=head2 _delay

Delay second between sending sms.
Default is C<0>.

=head1 METHODS

=head2 new

This constructor should not be called directly. See L<SMS::Send> for details.

Available parameters are:

=over 4

=item *

_url

=item *

_agent

=item *

_timeout

=item *

_from

=item *

_type

=item *

_delay

=item *

_id

=item *

_api_store_key

=back

=head2 send_sms

This method should not be called directly. See L<SMS::Send> for details.

Available parameters are:

=over 4

=item *

text

=item *

to

=item *

_from

=item *

_type

=item *

_delay

=item *

_subject

=item *

_epoch

=back

=head2 report

This method checks the result of the request.

=head2 cid

This method gets/sets the caller id information.

=head1 SEE ALSO

=over 4

=item *

L<SMS::Send>

=item *

L<SMS::Send::Driver>

=item *

L<APIStore REST API|http://www.apistore.co.kr/api/apiView.do?service_seq=151>

=back

=for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan

=head1 SUPPORT

=head2 Bugs / Feature Requests

Please report any bugs or feature requests through the issue tracker
at L<https://github.com/keedi/SMS-Send-KR-APIStore/issues>.
You will be notified automatically of any progress on your issue.

=head2 Source Code

This is open source software.  The code repository is available for
public review and contribution under the terms of the license.

L<https://github.com/keedi/SMS-Send-KR-APIStore>

  git clone https://github.com/keedi/SMS-Send-KR-APIStore.git

=head1 AUTHOR

김도형 - Keedi Kim <keedi@cpan.org>

=head1 CONTRIBUTOR

=for stopwords Hyungsuk Hong

Hyungsuk Hong <aanoaa@gmail.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2017 by Keedi Kim.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut


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