sharee.bike/copri4/main/src/Mod/APIshareeio.pm

390 lines
13 KiB
Perl
Executable file

package Mod::APIshareeio;
#
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) Rainer Gümpelein, TeilRad GmbH
#
#use lib qw(/var/www/copri-bike/shareeapp-primary/src);
#
use warnings;
use strict;
use Exporter;
our @ISA = qw (Exporter);
use POSIX;
use CGI;
use Apache2::Const -compile => qw(OK );
use JSON;
use Scalar::Util qw(looks_like_number);
use Config::General;
use Lib::Config;
use Mod::DBtank;
use Mod::Basework;
use Mod::Shareework;
use Mod::APIfunc;
use Mod::Pricing;
use Data::Dumper;
sub handler {
my ($r) = @_;
my $q = new CGI;
my $netloc = $q->url(-base=>1);
$q->import_names('R');
my $json = JSON->new->allow_nonref;
my $cf = new Config;
my $dbt = new DBtank;
my $bw = new Basework;
my $tk = new Shareework;
my %varenv = $cf->envonline();
my $oprefix = $dbt->{operator}->{$varenv{dbname}}->{oprefix};
my $now_dt = strftime "%Y-%m-%d %H:%M:%S", localtime;
my @keywords = $q->param;
my $user_agent = $q->user_agent();
my $aowner = 168;
my $debug=1;
my $api_file = "/var/www/copri4/shareeconf/apikeys.cfg";
my $aconf = Config::General->new($api_file);
my %apikeyconf = $aconf->getall;
my %headers = map { $_ => $q->http($_) } $q->http();
$bw->log("APIshareeio request:\n--> user-agent '$user_agent' ",$q,"");
$bw->log("headers:",\%headers,"");
#for my $header ( keys %headers ) {
# print "$header: $headers{$header}\n";
#}
print $q->header(-type => "application/json", -charset => "utf-8", -'Access-Control-Allow-Origin' => "*");
open(FILE,">>$varenv{logdir}/APIshareeio.log") if($debug);
print FILE "\n*** $now_dt user-agent: '$user_agent' to syshost: $varenv{syshost}\n" if($debug);
print FILE "headers:\n" . Dumper(\%headers) . "\n" if($debug);
print FILE "<=== DUMP query\n " . Dumper($q) . "\n" if($debug);
print FILE "<=== DUMP postdata:\n " . Dumper($q->param('POSTDATA')) . "\n" if($debug);
my $response = {
event => "",
response_state => "Event doesn't match",
};
my $http_sharee_api_key = $headers{HTTP_SHAREE_API_KEY} || $R::HTTP_SHAREE_API_KEY || "";
if(!$apikeyconf{shareeio}->{sharee_api_key} || !$http_sharee_api_key || $apikeyconf{shareeio}->{sharee_api_key} ne $http_sharee_api_key){
$response->{response_state} = "Failure: access denied, api-key doesn't match";
$bw->log("Failure: access denied, api-key doesn't match",$q,"");
my $jrout = $json->pretty->encode({shareeio => $response});
print $jrout;
return Apache2::Const::OK;
exit 0;
}
foreach(@keywords){
if(length($_) > 40 || length($q->param($_)) > 900){
$response->{response_state} = "Failure 9000: amount of characters in $_ exceeds";
$bw->log("Failure 9000: amount of characters in $_ exceeds",$q,"");
my $jrout = $json->pretty->encode({shareeio => $response});
print $jrout;
return Apache2::Const::OK;
exit 0;
}
}
#main
my $POSTDATA = $q->param('POSTDATA');
my $response_out = {};
eval {
my $response_in = {};
$response_in = decode_json($POSTDATA) if($POSTDATA);
if($response_in->{data}->{rentalId}){
$response_out = sig_booking_update($q,\%varenv,$response,$aowner);#sig json post
}
#operator invoice capture request
if($response_in->{request} eq "capture_prepaid"){
$response = {
request => "$response_in->{request}",
response_state => "Init",
sum_paid => "0",
sum_prepaid_available => "0",
};
$response_out = srv_capture_prepaid($q,\%varenv,$response,$response_in,$aowner);
}
#operator payment balance alias p-saldo @all request
if($response_in->{request} eq "collect_prepaid_invoices"){
$response = {
request => "$response_in->{request}",
response_state => "Init",
};
$response_out = srv_collect_prepaid_invoices($q,\%varenv,$response,$response_in,$aowner);
}
};
if ($@){
print FILE "failure! can not decode POST json, POSTDATA:\n" . Dumper($q->param('POSTDATA')) . "\n" if($debug);
#warn $@;
print FILE "warn:" . $@ . "\n" if($debug);
$response_out->{response_state} = "Failure: can not decode POST json";
}
#end main eval
#sharee collect prepaid json api
sub srv_collect_prepaid_invoices {
my $q = shift;
my $varenv = shift;
my $response = shift || {};
my $response_in = shift || {};
my $aowner = shift || "";
my $dbt = new DBtank;
my $apif = new APIfunc;
my $pri = new Pricing;
my $dbh = "";
my $pref_ctt = {
table => "contenttrans",
fetch => "all",
keyfield => "int10",#userID
main_id => 300023,
template_id => 219,#prepaid tpl
state => "is::null",
close_time => "is::null",
};
$response_in->{userID} if($response_in->{userID});
my $ctt_prepaid = { };
$ctt_prepaid = $dbt->fetch_record($dbh,$pref_ctt);
foreach my $id (sort { $ctt_prepaid->{$a}->{c_id} <=> $ctt_prepaid->{$b}->{c_id} } keys (%$ctt_prepaid)){
my $ctadr = { c_id => $ctt_prepaid->{$id}->{int10} };
my $sum_prepaid_available = 0;
$sum_prepaid_available = $pri->primary_sum_prepaid($dbh,$ctadr,$ctt_prepaid);
$response->{users_prepaid_invoices}->{$id}->{invoiceID} = "$ctt_prepaid->{$id}->{c_id}";
$response->{users_prepaid_invoices}->{$id}->{userID} = "$ctt_prepaid->{$id}->{int10}";
$response->{users_prepaid_invoices}->{$id}->{sum_prepaid_available} = "$sum_prepaid_available";
}
$response->{response_state} = "Success: collect_prepaid_invoives";
return $response;
}#end collect_prepaid_invoices
#sharee capture payment json api
#host must be shareeapp-primary.copri.....
sub srv_capture_prepaid {
my $q = shift;
my $varenv = shift;
my $response = shift || {};
my $response_in = shift || {};
my $aowner = shift || "";
my $dbt = new DBtank;
my $apif = new APIfunc;
my $pri = new Pricing;
my $dbh = "";
my $debug=1;
if(looks_like_number($response_in->{userID}) && looks_like_number($response_in->{sum_paid})){
my $ctadr = { c_id => $response_in->{userID} };
my $invoice_reference = $q->escapeHTML($response_in->{invoice_reference}) || "error";
my $pref_ctt = {
table => "contenttrans",
fetch => "one",
main_id => 300023,
template_id => 219,#prepaid tpl
int10 => "$ctadr->{c_id}",
state => "is::null",
close_time => "is::null",
};
my $ctt_prepaid = { c_id => 0 };
$ctt_prepaid = $dbt->fetch_record($dbh,$pref_ctt);
if($ctt_prepaid->{c_id} > 0){
my $sum_prepaid_available = 0;
$sum_prepaid_available = $pri->primary_sum_prepaid($dbh,$ctadr,$ctt_prepaid);
$response->{sum_prepaid_available} = "$sum_prepaid_available";
if($sum_prepaid_available >= $response_in->{sum_paid}){
#pseudo part. not available in content
my $ct = {
c_id => 0,
barcode => 0,
ca_id => $ctadr->{c_id},
ct_id => $ctt_prepaid->{c_id},
int02 => "-$response_in->{sum_paid}",
txt01 => 'Operator prepaid capture',
int16 => 5,#fibumark for operator prepaid capture
template_id => 219,
};
#Gegenbuchung
my $cttpos = { c_id => 0 };
$cttpos->{c_id} = $dbt->insert_pos($dbh,$ctt_prepaid->{c_id},$ct,"",$ctadr,"","","$invoice_reference","0",$aowner,"");
if($cttpos->{c_id}){
$response->{sum_paid} = "$response_in->{sum_paid}";
$response->{response_state} = "Success: Prepaid capture ID $invoice_reference";
}else{
$response->{response_state} = "Failure: Prepaid capture";
}
}else{
$response->{response_state} = "Failure: Users Prepaid-Account not funded";
}
}else{
$response->{response_state} = "Failure: no Prepaid-Invoice available";
}
}else{
$response->{response_state} = "Failure: no userID defined";
}
return $response;
}#end capture_prepaid
#sig json api
sub sig_booking_update {
my $q = shift;
my $varenv = shift;
my $response = shift || {};
my $aowner = shift || "";
$q->import_names('R');
my $dbt = new DBtank;
my $apif = new APIfunc;
my $dbh = "";
my $debug=1;
my $jrout = "seems to be not valid";
#sig using POST JSON
my $POSTDATA = $q->param('POSTDATA');
#REST methode only used for easy testing
if($R::request && $R::request eq "booking_update"){
my $rentalId = $q->escapeHTML($R::rentalId) || "";
my %POSTDATA_hash = (
"event"=>"RENTAL_END",
"data"=>{
"bikeId"=>"test16b5-0522-43da-ab66-477744a731a3",
"lockStatus"=>"locked",
"rentalId"=>"$rentalId",
"reservationState"=>"",
"startTime"=>"2022-06-06T15:00:18.045Z",
"endTime"=>"2022-06-06T15:01:43.118Z",
"distance"=>1000
}
);
$POSTDATA = encode_json(\%POSTDATA_hash);
print FILE "<=== DUMP POSTDATA_hash:\n " . Dumper(\%POSTDATA_hash) . "\n" if($debug);
}
eval {
my $response_in = {};
$response_in = decode_json($POSTDATA) if($POSTDATA);
$response->{event} = "$response_in->{event}";
if($response_in->{event}){
my $bikeId = $q->escapeHTML($response_in->{data}->{bikeId}) || "";#on push, bikeId is bike_id
my $rentalId = $q->escapeHTML($response_in->{data}->{rentalId}) || "";
my $rows = 0;
my $booking_values = {};
print FILE "event: $response_in->{event}\n" if($debug);
if($response_in->{event} eq "BIKE_STATUS" || $response_in->{event} eq "BATTERY_CAPACITY"){
$response->{response_state} = "OK: $response_in->{event} methode not implemented, because we will get it just-in-time on requesting sig GET bikes";
}
if($response_in->{event} eq "RENTAL_START"){
$response->{response_state} = "OK: methode not implemented, because rental will be started by App and success returned by sig-connector";
}
elsif($response_in->{event} eq "RENTAL_END" || $response_in->{event} eq "RESERVATION_END" || ($response_in->{event} eq "SMARTLOCK" && $response_in->{data}->{lockStatus} eq "unlocked")){
if($rentalId){
my $ctpos = {};
my $booking_pos = {
table => "contenttranspos",
fetch => "one",
txt11 => "$rentalId",
int10 => "IN::('2','3')",
#ca_id => "$authraw->{c_id}",#sig doesn't know uid
};
#only reserved alias requested rentals can be canceled
$booking_pos->{int10} = 2 if($response_in->{event} eq "RESERVATION_END");
$ctpos = $dbt->fetch_tablerecord($dbh,$booking_pos);
my $distance = 0;
$distance = $response_in->{data}->{distance} / 1000 if(looks_like_number($response_in->{data}->{distance}) && $response_in->{data}->{distance} > 0);
$distance = sprintf('%.2f',$distance);
if(ref($ctpos) eq "HASH" && $ctpos->{txt22} && $ctpos->{txt11}){
my $sig_book = {
bikeId => $ctpos->{txt22},
rentalId => $ctpos->{txt11},
reservationId => "",
distance => $distance,
};
my $authraw = { c_id => "" };
$authraw->{c_id} = $ctpos->{ca_id} if($ctpos->{ca_id});
$q->param(-name=>'bike',-value=>"$ctpos->{ct_name}") if($ctpos->{ct_name});
$q->param(-name=>'state',-value=>"canceled") if($response_in->{event} eq "RESERVATION_END");
if($response_in->{event} eq "SMARTLOCK" && $response_in->{data}->{lockStatus} eq "unlocked"){
$q->param(-name=>'lock_state',-value=>"unlocked");
}
if($response_in->{event} eq "RENTAL_END"){
$q->param(-name=>'state',-value=>"available");
$q->param(-name=>'lock_state',-value=>"locked");
#addition state in context to get state=returned for miniquery
$sig_book->{station_lock_state} = "3";#set int28 to 3
}
($rows, $booking_values) = $apif->booking_update($q,$varenv,$authraw,$aowner,$sig_book) if($authraw->{c_id});
#response is for sig json after rental-end
$booking_values->{bikeId} = $sig_book->{bikeId};
$booking_values->{rentalId} = $sig_book->{rentalId};
delete $booking_values->{geo_distance};
delete $booking_values->{co2saving};
delete $booking_values->{bike};
delete $booking_values->{response_text};
delete $booking_values->{user_miniquery};
$response->{response_state} = "OK: event matched, but something seems to goes wrong on booking_update" if(!$booking_values->{response_state});
$response = {%$response, %$booking_values};
}else{
$response->{response_state} = "Failure: $response_in->{event} : there is no reservation OR rental with rentalID=$rentalId";
}
}else{
$response->{response_state} = "Failure: no rentalId defined";
}
}#end RENTAL_END
}#end event
};#end eval
if ($@){
print FILE "failure! can not decode POST json, POSTDATA:\n" . Dumper($q->param('POSTDATA')) . "\n" if($debug);
#warn $@;
print FILE "warn:" . $@ . "\n" if($debug);
$response->{response_state} = "Failure: can not decode POST json";
}
return $response;
}#end sig json booking_update
#end RESTful ------------------------------------------------------------
#FINAL JSON response OUTPUT ----------------------------------------------------------
my $jrout = $json->pretty->encode({shareeio => $response_out});
print $jrout;
print FILE "APIshareeio jrout:\n" . Dumper($jrout) . "\n" if($debug);
$bw->log("APIshareeio response by $user_agent mapped aowner:$aowner",$jrout,"");
#end JSON ----------------------------------------------------------------------------
close(FILE) if($debug);
return Apache2::Const::OK;
}#end handler
1;