From 376794a00e837317e35fefd61a29ab58c0303b35 Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Mon, 21 Sep 2015 02:02:13 -0700 Subject: [PATCH] billing event to call web services, RT#35167 --- FS/FS/Misc/DateTime.pm | 22 ++++++++--- FS/FS/cust_pay.pm | 16 ++++++++ FS/FS/part_event.pm | 2 + FS/FS/part_event/Action/http.pm | 85 +++++++++++++++++++++++++++++++++++++++++ FS/FS/part_event/Condition.pm | 1 + FS/FS/part_event_option.pm | 3 +- 6 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 FS/FS/part_event/Action/http.pm diff --git a/FS/FS/Misc/DateTime.pm b/FS/FS/Misc/DateTime.pm index 2fff90647..56baec3ed 100644 --- a/FS/FS/Misc/DateTime.pm +++ b/FS/FS/Misc/DateTime.pm @@ -6,9 +6,10 @@ use Carp; use Time::Local; use Date::Parse; use DateTime::Format::Natural; +use Date::Format; use FS::Conf; -@EXPORT_OK = qw( parse_datetime day_end ); +@EXPORT_OK = qw( parse_datetime day_end iso8601 ); =head1 NAME @@ -65,11 +66,22 @@ same date but 23:59:59 for the time. =cut sub day_end { - my $time = shift; + my $time = shift; - my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = - localtime($time); - timelocal(59,59,23,$mday,$mon,$year); + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = + localtime($time); + timelocal(59,59,23,$mday,$mon,$year); +} + +=item iso8601 TIME + +Parses time as an integer UNIX timestamp and returns the ISO 8601 formatted +date and time. + +=cut + +sub iso8601 { + time2str('%Y-%m-%dT%T', @_); } =back diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm index 59d77742c..cb39d4391 100644 --- a/FS/FS/cust_pay.pm +++ b/FS/FS/cust_pay.pm @@ -409,6 +409,22 @@ sub insert { warn "can't send payment receipt/statement: $error" if $error; } + #run payment events immediately + my $due_cust_event = $self->cust_main->due_cust_event( + 'eventtable' => 'cust_pay', + 'objects' => [ $self ], + ); + if ( !ref($due_cust_event) ) { + warn "Error searching for cust_pay billing events: $due_cust_event\n"; + } else { + foreach my $cust_event (@$due_cust_event) { + next unless $cust_event->test_conditions; + if ( my $error = $cust_event->do_event() ) { + warn "Error running cust_pay billing event: $error\n"; + } + } + } + ''; } diff --git a/FS/FS/part_event.pm b/FS/FS/part_event.pm index d15f35b7d..9a1144c85 100644 --- a/FS/FS/part_event.pm +++ b/FS/FS/part_event.pm @@ -369,6 +369,7 @@ sub eventtable_labels { 'cust_pkg' => 'Package', 'cust_bill' => 'Invoice', 'cust_main' => 'Customer', + 'cust_pay' => 'Payment', 'cust_pay_batch' => 'Batch payment', 'cust_statement' => 'Statement', #too general a name here? "Invoice group"? 'svc_acct' => 'Login service', @@ -408,6 +409,7 @@ sub eventtable_pkey { 'cust_main' => 'custnum', 'cust_bill' => 'invnum', 'cust_pkg' => 'pkgnum', + 'cust_pay' => 'paynum', 'cust_pay_batch' => 'paybatchnum', 'cust_statement' => 'statementnum', 'svc_acct' => 'svcnum', diff --git a/FS/FS/part_event/Action/http.pm b/FS/FS/part_event/Action/http.pm new file mode 100644 index 000000000..b8715a714 --- /dev/null +++ b/FS/FS/part_event/Action/http.pm @@ -0,0 +1,85 @@ +package FS::part_event::Action::http; + +use strict; +use base qw( FS::part_event::Action ); +use LWP::UserAgent; +use HTTP::Request::Common; +use JSON::XS; +use FS::Misc::DateTime qw( iso8601 ); + +#sub description { 'Send an HTTP or HTTPS GET or POST request'; } +sub description { 'Send an HTTP or HTTPS POST request'; } + +sub eventtable_hashref { + { 'cust_bill' => 1, + 'cust_pay' => 1, + }, +} + +sub option_fields { + ( + 'method' => { label => 'Method', + type => 'select', + options => [qw( POST )], #GET )], + }, + 'url' => { label => 'URL', + type => 'text', + size => 120, + }, + 'ssl_no_verify' => { label => 'Skip SSL certificate validation', + type => 'checkbox', + }, + 'encoding' => { label => 'Encoding', + type => 'select', + options => [qw( JSON )], #XML, Form, etc. + }, + 'content' => { label => 'Content', #nneed better inline docs on format + type => 'textarea', + }, + #'response_error_param' => 'Response error parameter', + ); +} + +sub default_weight { 57; } + +our %content_type = ( + 'JSON' => 'application/json', +); + +sub do_action { + my( $self, $object ) = @_; + + my $cust_main = $self->cust_main($object); + + my %content = + map { + /^\s*(\S+)\s+(.*)$/ or /()()/; + my( $field, $value_expression ) = ( $1, $2 ); + my $value = eval $value_expression; + die $@ if $@; + ( $field, $value ); + } split(/\n/, $self->option('content') ); + + my $content = encode_json( \%content ); + + my @lwp_opts = (); + push @lwp_opts, 'ssl_opts'=>{ 'verify_hostname'=>0 } + if $self->option('ssl_no_verify'); + my $ua = LWP::UserAgent->new(@lwp_opts); + + my $req = HTTP::Request::Common::POST( + $self->option('url'), + Content_Type => $content_type{ $self->option('encoding') }, + Content => $content, + ); + + my $response = $ua->request($req); + + die $response->status_line if $response->is_error; + + my $response_json = decode_json( $response->content ); + die $response_json->{error} if $response_json->{error}; #XXX response_error_param + +} + +1; diff --git a/FS/FS/part_event/Condition.pm b/FS/FS/part_event/Condition.pm index 60697c196..36fbe9a0d 100644 --- a/FS/FS/part_event/Condition.pm +++ b/FS/FS/part_event/Condition.pm @@ -52,6 +52,7 @@ sub eventtable_hashref { { 'cust_main' => 1, 'cust_bill' => 1, 'cust_pkg' => 1, + 'cust_pay' => 1, 'cust_pay_batch' => 1, 'cust_statement' => 1, 'svc_acct' => 1, diff --git a/FS/FS/part_event_option.pm b/FS/FS/part_event_option.pm index 09b775609..6df9e84c1 100644 --- a/FS/FS/part_event_option.pm +++ b/FS/FS/part_event_option.pm @@ -183,7 +183,8 @@ sub check { $self->ut_numbern('optionnum') || $self->ut_foreign_key('eventpart', 'part_event', 'eventpart' ) || $self->ut_text('optionname') - || $self->ut_textn('optionvalue') + #|| $self->ut_textn('optionvalue') + || $self->ut_anything('optionvalue') #http.pm content has \n ; return $error if $error; -- 2.11.0