package CF2022::Controller::Orders; use Mojo::Base 'Mojolicious::Controller'; use Mojo::UserAgent; use Mojo::Asset::File; use Image::PNG::QRCode 'qrpng'; use constant TPL_VS => '%d%07d'; use constant TPL_PAYMENT => 'SPD*1.0*ACC:%s*AM:%.2f*MSG:%s, %s*X-ID:%s*X-VS:%d*X-SS:%d*X-KS:%d'; use feature 'signatures'; no warnings qw{ experimental::signatures }; sub create ($c ) { $c->openapi->valid_input or return; my $args = $c->req->json; my $claims; my $group; my @products = (); my $accredited = 0; my $accreditation = 0; # clasifikator typu my $pt = {}; CLASS: foreach my $class ( qw(main nonfree subsidy) ) { foreach my $id ( split /\D+/, $c->config->{"products_$class"} ) { $pt->{$class}{$id} = 1; } } my $url = sprintf ('%s/organizers/%s/events/%s/orders/', $c->config->{pretix_api}, $c->config->{pretix_organizer}, $c->config->{pretix_event}, ); my @answers = map {{ question => $_->{question_id}, answer => $_->{response}, }} @{ $args->{responses} }; # processing tokenu if ( $args->{token} ) { $c->oauth_token($args->{token}); $claims = $c->oauth_claims; return $c->error(403, 'Invalid token') if ! $claims; $group = $c->oauth_main_group; return $c->error(403, 'Invalid token') if ! $group; $args->{name} = $claims->{name}; push @answers, ( { question => $c->config->{pretix_qid_sso}, answer => $claims->{sub}, }, { question => $c->config->{pretix_qid_region}, answer => $group->{region}, }, ); $accreditation = $c->config->{'products_' . $group->{role}}; if ( $group->{role} eq 'member' ) { $args->{email} = $claims->{preferred_username} .'@'. $c->config->{mail_domain}; } else { $args->{email} = $claims->{email}; } } # kontrola duplicity my $exists = $c->schema->resultset('Order')->search({ deleted => undef, email => $args->{email}, api => $url, })->count; $exists ||= $c->schema->resultset('Order')->search({ deleted => undef, sso_uuid => $claims->{sub}, api => $url, })->count if $claims; return $c->error(400, 'Duplicity' ) if $exists && ! $c->cfg->{pretix_testmode}; # korekce akreditace PRODUCT: foreach my $product ( @{ $args->{products} } ) { $product->{id} = $c->mapped_product_id($product->{id}); next PRODUCT if $pt->{subsidy}{$product->{id}} && ! $product->{price}; if ( $pt->{main}{ $product->{id} } ) { next PRODUCT if $accredited++; # neumoznit dvoji akreditace if ( $accreditation ) { $product->{id} = $accreditation; } elsif ( $pt->{nonfree}{ $product->{id} } ) { $product->{id} = $c->config->{products_fallback}; } } push @products, $product; } # fallback akreditace @products = ( { id => $c->config->{products_fallback} }, @products ) if ! $accredited; my $order = { email => $args->{email}, locale => 'en', payment_provider => 'manual', testmode => $c->config->{pretix_testmode} ? 'true' : 'false', positions => [], fees => [], }; # nahrani fotky if ( $group->{role} eq 'member' ) { my $photo_id = $c->_upload_photo($claims->{preferred_username}); if ( $photo_id ) { push @answers, ( { question => $c->config->{pretix_qid_photo} , answer => $photo_id, }, ); } } # ukladani do lokalni databazi my $local_order = $c->schema->resultset('Order')->create({ ip => $c->tx->remote_address, sso_uuid => $claims->{sub}, email => $order->{email}, api => $url, }); # VS push @answers, ( { question => $c->config->{pretix_qid_vs} , answer => $local_order->id, }, ); PRODUCT: foreach my $product ( @products ) { my $item = { item => $product->{id}, variation => $c->mapped_variation_id($product->{variation}), attendee_name => $args->{name}, price => $product->{price}, company => $args->{company}, }; $item->{answers} = \@answers if $pt->{main}{$product->{id}}; push @{ $order->{positions} }, $item; } $c->trace($order); # odeslani do pretixu my $ua = Mojo::UserAgent->new; my $rc = $ua->post( $url, { Authorization => 'Token ' . $c->config->{pretix_token} }, json => $order )->result; if (! $rc->is_success) { $local_order->delete; return $c->error(400, $rc->body) } $local_order->update({ request => $order, response => $rc->json, order_id => $rc->json->{code}, }); $c->render( status => 201, json => { %{ $rc->json }, payment_request => $c->_payment_request( $local_order ), } ); } sub exists ($c ) { $c->openapi->valid_input or return; my $key = $c->param('key'); my $field = ( $key =~ /\@/ ) ? 'email' : 'sso_uuid'; my $exists = $c->schema->resultset('Order')->search({ deleted => undef, $field => $key, })->count; $c->render( status => 200, openapi => { exists => $exists }, ); } sub get ($c ) { $c->openapi->valid_input or return; $c->oauth_token($c->tx->req->content->headers->header('X-Token')); my $claims = $c->oauth_claims; return $c->error(403, 'Invalid token') if ! $claims; my $order = $c->schema->resultset('Order')->search({ deleted => undef, sso_uuid => $claims->{sub}, })->first; return $c->error(404, 'NOT FOUND') if ! $order; # data z pretixu (kvuli stavu, jinak je to v databazi???) $c->app->log->error($order->api . $order->order_id); my $ua = Mojo::UserAgent->new; my $rc = $ua->get( $order->api . $order->order_id . '/', { Authorization => 'Token ' . $c->config->{pretix_token} }, )->result; return $c->error(400, $rc->body) if ! $rc->is_success; $c->render( status => 200, openapi => { order => $rc->json }, ); } sub payment ($c ) { my $order; if ( $c->stash->{id} =~ /\D/) { $order = $c->schema->resultset('Order')->search({ order_id => {ilike => $c->stash->{id}}, deleted => undef, })->first; } else { $order = $c->schema->resultset('Order')->find({ id => $c->stash->{id}, deleted => undef, }); } return $c->error(404, 'NOT FOUND') if ! $order; my $url = sprintf ('%s/organizers/%s/events/%s/orders/%s/', $c->config->{pretix_api}, $c->config->{pretix_organizer}, $c->config->{pretix_event}, $order->order_id, ); my $ua = Mojo::UserAgent->new; my $rc = $ua->get( $url, { Authorization => 'Token ' . $c->config->{pretix_token} }, )->result; my $order_pretix = $rc->json; $order->update({ response => $order_pretix }); my $pr = $c->_payment_request($order); if ( $order_pretix->{status} eq 'p' ) { $pr->{payed} = $order_pretix->{payments}[0]{payment_date}; } $c->render( status => 200, json => $pr, ); } sub qr ($c) { my $order; if ( $c->stash->{id} =~ /\D/) { $order = $c->schema->resultset('Order')->search({ order_id => {ilike => $c->stash->{id}}, deleted => undef, })->first; } else { $order = $c->schema->resultset('Order')->find({ id => $c->stash->{id}, deleted => undef, }); } return $c->error(404, 'NOT FOUND') if ! $order; my $pr = $c->_payment_request($order); my $payment = sprintf(TPL_PAYMENT, $pr->{iban}, $pr->{amount}, $pr->{message}, $pr->{payer}, $order->order_id, $pr->{vs}, $pr->{ss}, $pr->{ks}, ); $c->trace($payment); my $png = qrpng (text => $payment, level => 4); $c->res->headers->content_type('image/png'); $c->render( data => $png ); } sub _upload_photo ($c, $username ) { my $ua = Mojo::UserAgent->new; my $rc = $ua->get( $c->config->{piratar_url} . $username . '.jpg' )->result; return if ! $rc->is_success; my $photo = "/tmp/$username.jpg"; $rc->save_to($photo); my $tx = $ua->build_tx(POST => $c->config->{pretix_api} . '/upload', { Authorization => 'Token ' . $c->config->{pretix_token}, 'Content-type' => 'image/jpeg', 'Content-Disposition' => qq{attachment; filename="$username.jpg"}, }); $tx->req->content->asset(Mojo::Asset::File->new(path => $photo)); $rc = $ua->start($tx)->result; return if ! $rc->is_success; return $rc->json->{id}; } sub _payment_request($c, $order) { my $ks_bits = 0; PRODUCT: foreach my $pos ( @{ $order->response->{positions}} ) { $ks_bits |= $c->payment_ks_bit($pos->{item}); } return { account => $c->cfg->{payment_account}, iban => $c->cfg->{payment_iban}, amount => $order->response->{payments}[0]{amount}, message => $c->cfg->{payment_msg}, payer => $order->request->{positions}[0]{attendee_name}, vs => sprintf(TPL_VS, $c->cfg->{payment_vs_prefix}, $order->id ), ss => $c->cfg->{payment_ss}, ks => $c->cfg->{payment_ks_base} + $ks_bits, qr => '/api/orders/' . $order->id . '/payment.png', }; } 1;