mirror of
https://github.com/janishutz/libreevent.git
synced 2025-11-25 05:14:23 +00:00
ticket generation working
This commit is contained in:
@@ -37,6 +37,8 @@ Visit our [website](https://libreevent.janishutz.com)
|
|||||||
You may download this project using the GitHub releases page or the direct links on the [libreevent website](https://libreevent.janishutz.com/download) as this only downloads the ready-to-distribute version, not the development version.
|
You may download this project using the GitHub releases page or the direct links on the [libreevent website](https://libreevent.janishutz.com/download) as this only downloads the ready-to-distribute version, not the development version.
|
||||||
Alternatively, you may download the project directly from GitHub (by cloning it or downloading the code) but you'll have to compile and package the project [manually](https://libreevent.janishutz.com/docs/contributing/packaging).
|
Alternatively, you may download the project directly from GitHub (by cloning it or downloading the code) but you'll have to compile and package the project [manually](https://libreevent.janishutz.com/docs/contributing/packaging).
|
||||||
|
|
||||||
|
THIS IS FREE SOFTWARE. IT IS PROVIDED "AS IS" AND AS SUCH COMES WITH ABSOLUTELY NO WARRANTY TO THE EXTENT PERMITTED BY APPLICABLE LAW. If anything does not work, please report it back, but do not expect it to be fixed immediately, as this software is developed by volunteers in their free time.
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
If you want to contribute to this project, please read more [here](https://libreevent.janishutz.com/docs/contributing). Until the end of October 2023, no contributions can be accepted into master.
|
If you want to contribute to this project, please read more [here](https://libreevent.janishutz.com/docs/contributing). Until the end of October 2023, no contributions can be accepted into master.
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ module.exports = ( app ) => {
|
|||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
app.post( '/admin/API/:call', bodyParser.json(), ( req, res ) => {
|
app.post( '/admin/API/:call', bodyParser.json( { limit: '20mb' } ), ( req, res ) => {
|
||||||
if ( req.session.loggedInAdmin ) {
|
if ( req.session.loggedInAdmin ) {
|
||||||
postHandler.handleCall( req.params.call, req.body, req.query.lang ).then( data => {
|
postHandler.handleCall( req.params.call, req.body, req.query.lang ).then( data => {
|
||||||
res.send( data );
|
res.send( data );
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ class POSTHandler {
|
|||||||
} ).catch( error => {
|
} ).catch( error => {
|
||||||
reject( { 'code': 500, 'error': error } );
|
reject( { 'code': 500, 'error': error } );
|
||||||
} );
|
} );
|
||||||
|
} else if ( call === 'saveTickets' ) {
|
||||||
|
db.writeJSONDataSimple( 'tickets', data.location, data.data ).then( resp => {
|
||||||
|
resolve( resp );
|
||||||
|
} ).catch( error => {
|
||||||
|
reject( { 'code': 500, 'error': error } );
|
||||||
|
} );
|
||||||
} else {
|
} else {
|
||||||
reject( { 'code': 404, 'error': 'Route not found' } );
|
reject( { 'code': 404, 'error': 'Route not found' } );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,8 @@ if ( settings.init ) {
|
|||||||
|
|
||||||
console.log( '[ Server ] loading plugins' );
|
console.log( '[ Server ] loading plugins' );
|
||||||
// TODO: load dynamically
|
// TODO: load dynamically
|
||||||
require( './backend/plugins/payments/stripe/stripeRoutes.js' )( app, settings ); // setup routes
|
require( './backend/plugins/payments/stripe/stripeRoutes.js' )( app, settings ); // stripe routes
|
||||||
|
require( './backend/payments/paymentRoutes.js' )( app, settings ); // payment routes
|
||||||
|
|
||||||
app.use( ( request, response ) => {
|
app.use( ( request, response ) => {
|
||||||
response.sendFile( file );
|
response.sendFile( file );
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -33,7 +33,6 @@ module.exports.getDataSimple = ( db, column, searchQuery ) => {
|
|||||||
} ).catch( error => {
|
} ).catch( error => {
|
||||||
reject( error );
|
reject( error );
|
||||||
} );
|
} );
|
||||||
// resolve( '$2b$05$ElMYWoMjk7567lXkIkee.e.6cxCrWU4gkfuNLB8gmGYLQQPm7gT3O' );
|
|
||||||
} );
|
} );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
class DataHelper {
|
class DataHelper {
|
||||||
constructor () {
|
constructor () {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,98 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const fs = require( 'fs' );
|
||||||
|
const path = require( 'path' );
|
||||||
|
|
||||||
class JSONDB {
|
class JSONDB {
|
||||||
constructor () {
|
constructor () {
|
||||||
|
this.db = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
connect ( ) {
|
||||||
|
this.db = JSON.parse( fs.readFileSync( path.join( __dirname + '/data/db.json' ) ) );
|
||||||
|
setInterval( async () => {
|
||||||
|
fs.writeFile( path.join( __dirname + '/data/db.json' ), JSON.stringify( this.db ) );
|
||||||
|
}, 10000 );
|
||||||
|
console.log( '[ JSON-DB ] Database initialized successfully' );
|
||||||
|
return 'connection';
|
||||||
|
}
|
||||||
|
|
||||||
|
query ( operation, table ) {
|
||||||
|
return new Promise( ( resolve, reject ) => {
|
||||||
|
/*
|
||||||
|
Possible operation.command values (all need the table argument of the method call):
|
||||||
|
- getAllData: no additional instructions needed
|
||||||
|
|
||||||
|
- getFilteredData:
|
||||||
|
- operation.property (the column to search for the value),
|
||||||
|
- operation.searchQuery (the value to search for [will be sanitised by method])
|
||||||
|
|
||||||
|
- InnerJoin (Select values that match in both tables):
|
||||||
|
- operation.property (the column to search for the value),
|
||||||
|
- operation.searchQuery (the value to search for [will be sanitised by method])
|
||||||
|
- operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
|
||||||
|
- operation.secondTable (The second table to perform Join operation with)
|
||||||
|
- operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)
|
||||||
|
|
||||||
|
- LeftJoin (Select values in first table and return all corresponding values of second table):
|
||||||
|
- operation.property (the column to search for the value),
|
||||||
|
- operation.searchQuery (the value to search for [will be sanitised by method])
|
||||||
|
- operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
|
||||||
|
- operation.secondTable (The second table to perform Join operation with)
|
||||||
|
- operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)
|
||||||
|
|
||||||
|
- RightJoin (Select values in second table and return all corresponding values of first table):
|
||||||
|
- operation.property (the column to search for the value),
|
||||||
|
- operation.searchQuery (the value to search for [will be sanitised by method])
|
||||||
|
- operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
|
||||||
|
- operation.secondTable (The second table to perform Join operation with)
|
||||||
|
- operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)
|
||||||
|
|
||||||
|
- addData:
|
||||||
|
- operation.data (key-value pair with all data as values and column to insert into as key)
|
||||||
|
|
||||||
|
- updateData:
|
||||||
|
- operation.newValues (a object with keys being the column and value being the value to be inserted into that column, values are being
|
||||||
|
sanitised by the function)
|
||||||
|
- operation.property (the column to search for the value),
|
||||||
|
- operation.searchQuery (the value to search for [will be sanitised by method])
|
||||||
|
|
||||||
|
- checkDataAvailability:
|
||||||
|
- operation.property (the column to search for the value),
|
||||||
|
- operation.searchQuery (the value to search for [will be sanitised by method])
|
||||||
|
|
||||||
|
- fullCustomCommand:
|
||||||
|
- operation.query (the SQL instruction to be executed) --> NOTE: This command will not be sanitised, so use only with proper sanitisation!
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ( operation.command === 'getAllData' ) {
|
||||||
|
resolve( this.db[ table ] );
|
||||||
|
} else if ( operation.command === 'getFilteredData' ) {
|
||||||
|
//
|
||||||
|
} else if ( operation.command === 'fullCustomCommand' ) {
|
||||||
|
//
|
||||||
|
} else if ( operation.command === 'addData' ) {
|
||||||
|
//
|
||||||
|
} else if ( operation.command === 'updateData' ) {
|
||||||
|
if ( !operation.property || !operation.searchQuery ) reject( 'Refusing to run destructive command: Missing Constraints' );
|
||||||
|
else {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
} else if ( operation.command === 'deleteData' ) {
|
||||||
|
if ( !operation.property || !operation.searchQuery ) reject( 'Refusing to run destructive command: Missing Constraints' );
|
||||||
|
else {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
} else if ( operation.command === 'InnerJoin' ) {
|
||||||
|
//
|
||||||
|
} else if ( operation.command === 'LeftJoin' ) {
|
||||||
|
//
|
||||||
|
} else if ( operation.command === 'RightJoin' ) {
|
||||||
|
//
|
||||||
|
} else if ( operation.command === 'checkDataAvailability' ) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ class SQLDB {
|
|||||||
} );
|
} );
|
||||||
this.sqlConnection.query( 'CREATE TABLE libreevent_users ( account_id INT ( 10 ) NOT NULL AUTO_INCREMENT, email TINYTEXT NOT NULL, pass TEXT, name TEXT, first_name TEXT, two_fa TINYTEXT, user_data VARCHAR( 60000 ), mail_confirmed TINYTEXT, marketing_ok TINYTEXT, PRIMARY KEY ( account_id ) ) ENGINE=INNODB;', ( error ) => {
|
this.sqlConnection.query( 'CREATE TABLE libreevent_users ( account_id INT ( 10 ) NOT NULL AUTO_INCREMENT, email TINYTEXT NOT NULL, pass TEXT, name TEXT, first_name TEXT, two_fa TINYTEXT, user_data VARCHAR( 60000 ), mail_confirmed TINYTEXT, marketing_ok TINYTEXT, PRIMARY KEY ( account_id ) ) ENGINE=INNODB;', ( error ) => {
|
||||||
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;
|
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;
|
||||||
this.sqlConnection.query( 'CREATE TABLE libreevent_orders ( order_id INT ( 10 ) NOT NULL AUTO_INCREMENT, account_id INT ( 10 ) NOT NULL, seats VARCHAR( 60000 ), PRIMARY KEY ( order_id ), FOREIGN KEY ( account_id ) REFERENCES libreevent_users( account_id ) ) ENGINE=INNODB;', ( error ) => {
|
this.sqlConnection.query( 'CREATE TABLE libreevent_orders ( order_id INT ( 10 ) NOT NULL AUTO_INCREMENT, order_name TINYTEXT, account_id INT ( 10 ) NOT NULL, tickets VARCHAR( 60000 ), PRIMARY KEY ( order_id ), FOREIGN KEY ( account_id ) REFERENCES libreevent_users( account_id ) ) ENGINE=INNODB;', ( error ) => {
|
||||||
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;
|
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;
|
||||||
this.sqlConnection.query( 'CREATE TABLE libreevent_admin ( account_id INT NOT NULL AUTO_INCREMENT, email TINYTEXT, pass TEXT, permissions VARCHAR( 1000 ), username TINYTEXT, two_fa TINYTEXT, PRIMARY KEY ( account_id ) );', ( error ) => {
|
this.sqlConnection.query( 'CREATE TABLE libreevent_admin ( account_id INT NOT NULL AUTO_INCREMENT, email TINYTEXT, pass TEXT, permissions VARCHAR( 1000 ), username TINYTEXT, two_fa TINYTEXT, PRIMARY KEY ( account_id ) );', ( error ) => {
|
||||||
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;
|
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class MailManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendMailWithAttachment ( recipient, html, subject, attachments, from ) {
|
sendMailWithAttachment ( recipient, html, subject, attachments, from ) {
|
||||||
|
// Attachments have to be an array of objects that have filename and path as their keys
|
||||||
let text = html2text.convert( html, this.options );
|
let text = html2text.convert( html, this.options );
|
||||||
let mailOptions = {
|
let mailOptions = {
|
||||||
from: from,
|
from: from,
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
* libreevent - successHandler.js
|
|
||||||
*
|
|
||||||
* Created by Janis Hutz 08/02/2023, Licensed under the GPL V3 License
|
|
||||||
* https://janishutz.com, development@janishutz.com
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
class PaymentHandler {
|
|
||||||
constructor () {
|
|
||||||
this.canceledTransactions = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleSuccess ( token ) {
|
|
||||||
console.log( token );
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleError ( token ) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = PaymentHandler;
|
|
||||||
@@ -13,57 +13,63 @@ const db = require( '../../../db/db.js' );
|
|||||||
const stripConfig = JSON.parse( fs.readFileSync( path.join( __dirname + '/../../../../config/payments.config.secret.json' ) ) )[ 'stripe' ];
|
const stripConfig = JSON.parse( fs.readFileSync( path.join( __dirname + '/../../../../config/payments.config.secret.json' ) ) )[ 'stripe' ];
|
||||||
const stripe = require( 'stripe' )( stripConfig[ 'APIKey' ] );
|
const stripe = require( 'stripe' )( stripConfig[ 'APIKey' ] );
|
||||||
const bodyParser = require( 'body-parser' );
|
const bodyParser = require( 'body-parser' );
|
||||||
const ph = require( '../../../payments/paymentHandler.js' );
|
const ticket = require( '../../../tickets/ticketGenerator.js' );
|
||||||
const paymentHandler = new ph();
|
const TicketGenerator = new ticket();
|
||||||
|
|
||||||
const endpointSecret = stripConfig[ 'endpointSecret' ];
|
const endpointSecret = stripConfig[ 'endpointSecret' ];
|
||||||
|
|
||||||
let sessionReference = {};
|
let sessionReference = {};
|
||||||
|
let waitingClients = {};
|
||||||
|
let paymentOk = {};
|
||||||
|
|
||||||
// TODO: Remove all selected tickets if timestamp more than user defined amount ago
|
// TODO: Remove all selected tickets if timestamp more than user defined amount ago
|
||||||
|
|
||||||
module.exports = ( app, settings ) => {
|
module.exports = ( app, settings ) => {
|
||||||
app.post( '/payments/prepare', bodyParser.json(), ( req, res ) => {
|
app.post( '/payments/prepare', bodyParser.json(), ( req, res ) => {
|
||||||
let purchase = {
|
if ( req.session.loggedInUser ) {
|
||||||
'line_items': [],
|
let purchase = {
|
||||||
'mode': 'payment',
|
'line_items': [],
|
||||||
'success_url': settings.yourDomain + '/payments/success',
|
'mode': 'payment',
|
||||||
'cancel_url': settings.yourDomain + '/payments/canceled',
|
'success_url': settings.yourDomain + '/payments/success',
|
||||||
'submit_type': 'book',
|
'cancel_url': settings.yourDomain + '/payments/canceled',
|
||||||
'customer_email': req.body.mail
|
'submit_type': 'book',
|
||||||
};
|
'customer_email': req.session.username
|
||||||
|
};
|
||||||
|
|
||||||
db.getDataSimple( 'temp', 'user_id', req.session.id ).then( dat => {
|
db.getDataSimple( 'temp', 'user_id', req.session.id ).then( dat => {
|
||||||
if ( dat[ 0 ] ) {
|
if ( dat[ 0 ] ) {
|
||||||
db.getJSONData( 'events' ).then( events => {
|
db.getJSONData( 'events' ).then( events => {
|
||||||
let data = JSON.parse( dat[ 0 ].data );
|
let data = JSON.parse( dat[ 0 ].data );
|
||||||
( async () => {
|
( async () => {
|
||||||
for ( let event in data ) {
|
for ( let event in data ) {
|
||||||
for ( let item in data[ event ] ) {
|
for ( let item in data[ event ] ) {
|
||||||
purchase[ 'line_items' ].push( {
|
purchase[ 'line_items' ].push( {
|
||||||
'price_data': {
|
'price_data': {
|
||||||
'product_data': {
|
'product_data': {
|
||||||
'name': data[ event ][ item ].name,
|
'name': data[ event ][ item ].name,
|
||||||
|
},
|
||||||
|
'currency': events[ event ].currency,
|
||||||
|
'unit_amount': Math.round( parseFloat( events[ event ][ 'categories' ][ data[ event ][ item ].category ].price[ data[ event ][ item ][ 'ticketOption' ] ] ) * 100 ),
|
||||||
},
|
},
|
||||||
'currency': events[ event ].currency,
|
'quantity': data[ event ][ item ].count ?? 1,
|
||||||
'unit_amount': Math.round( parseFloat( events[ event ][ 'categories' ][ data[ event ][ item ].category ].price[ data[ event ][ item ][ 'ticketOption' ] ] ) * 100 ),
|
} );
|
||||||
},
|
}
|
||||||
'quantity': data[ event ][ item ].count ?? 1,
|
|
||||||
} );
|
|
||||||
}
|
}
|
||||||
}
|
const session = await stripe.checkout.sessions.create( purchase );
|
||||||
const session = await stripe.checkout.sessions.create( purchase );
|
sessionReference[ session.id ] = { 'tok': req.session.id, 'email': req.session.username };
|
||||||
sessionReference[ session.id ] = req.session.id;
|
res.send( session.url );
|
||||||
res.send( session.url );
|
} )();
|
||||||
} )();
|
} );
|
||||||
} );
|
} else {
|
||||||
} else {
|
res.status( 400 ).send( 'ERR_UID_NOT_FOUND' );
|
||||||
res.status( 400 ).send( 'ERR_UID_NOT_FOUND' );
|
}
|
||||||
}
|
} ).catch( error => {
|
||||||
} ).catch( error => {
|
console.error( '[ STRIPE ] DB ERROR: ' + error );
|
||||||
console.error( '[ STRIPE ] DB ERROR: ' + error );
|
res.status( 500 ).send( 'ERR_DB' );
|
||||||
res.status( 500 ).send( 'ERR_DB' );
|
} );
|
||||||
} );
|
} else {
|
||||||
|
res.status( 403 ).send( 'ERR_UNAUTHORIZED' );
|
||||||
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
app.get( '/payments/status', ( request, response ) => {
|
app.get( '/payments/status', ( request, response ) => {
|
||||||
@@ -75,7 +81,16 @@ module.exports = ( app, settings ) => {
|
|||||||
response.status( 200 );
|
response.status( 200 );
|
||||||
response.flushHeaders();
|
response.flushHeaders();
|
||||||
response.write( 'data: connected\n\n' );
|
response.write( 'data: connected\n\n' );
|
||||||
// TODO: Finish up
|
waitingClients[ request.session.id ] = response;
|
||||||
|
} );
|
||||||
|
|
||||||
|
app.get( '/user/2fa/ping', ( request, response ) => {
|
||||||
|
if ( paymentOk[ request.session.token ] === 'ok' ) {
|
||||||
|
delete paymentOk[ request.session.token ];
|
||||||
|
response.send( { 'status': 'ok' } );
|
||||||
|
} else {
|
||||||
|
response.send( '' );
|
||||||
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
app.post( '/payments/webhook', bodyParser.raw( { type: 'application/json' } ), ( req, res ) => {
|
app.post( '/payments/webhook', bodyParser.raw( { type: 'application/json' } ), ( req, res ) => {
|
||||||
@@ -92,7 +107,24 @@ module.exports = ( app, settings ) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( event.type === 'checkout.session.completed' ) {
|
if ( event.type === 'checkout.session.completed' ) {
|
||||||
paymentHandler.handleSuccess( sessionReference[ event.data.object.id ] );
|
setTimeout( () => {
|
||||||
|
waitingClients[ sessionReference[ event.data.object.id ][ 'tok' ] ].write( 'data: paymentOk\n\n' );
|
||||||
|
}, 2000 );
|
||||||
|
db.getDataSimple( 'temp', 'user_id', sessionReference[ event.data.object.id ][ 'tok' ] ).then( dat => {
|
||||||
|
db.getDataSimple( 'users', 'email', sessionReference[ event.data.object.id ][ 'email' ] ).then( user => {
|
||||||
|
if ( user[ 0 ] ) {
|
||||||
|
console.log( sessionReference[ event.data.object.id ][ 'tok' ] );
|
||||||
|
db.writeDataSimple( 'orders', 'account_id', user[ 0 ].account_id, { 'account_id': user[ 0 ].account_id, 'tickets': dat[ 0 ].data, 'order_name': sessionReference[ event.data.object.id ][ 'tok' ] } ).then( () => {
|
||||||
|
TicketGenerator.generateTickets( sessionReference[ event.data.object.id ] );
|
||||||
|
} );
|
||||||
|
} else {
|
||||||
|
console.log( sessionReference[ event.data.object.id ][ 'email' ] );
|
||||||
|
console.error( 'user not found' );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
} ).catch( err => {
|
||||||
|
console.error( err );
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status( 200 ).end();
|
res.status( 200 ).end();
|
||||||
|
|||||||
3
src/server/backend/tickets/store/README.md
Normal file
3
src/server/backend/tickets/store/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Ticket Store
|
||||||
|
|
||||||
|
Here, all the tickets for all the orders are saved. You may delete the tickets here at any point. Should the user request their tickets once again, the tickets can simply be regenerated from the data stored in the database.
|
||||||
24
src/server/backend/tickets/test.js
Normal file
24
src/server/backend/tickets/test.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* libreevent - test.js
|
||||||
|
*
|
||||||
|
* Created by Janis Hutz 08/06/2023, Licensed under the GPL V3 License
|
||||||
|
* https://janishutz.com, development@janishutz.com
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
const express = require( 'express' );
|
||||||
|
let app = express();
|
||||||
|
const ticket = require( './ticketGenerator.js' );
|
||||||
|
const TicketGenerator = new ticket();
|
||||||
|
const http = require( 'http' );
|
||||||
|
|
||||||
|
|
||||||
|
app.get( '/', ( request, response ) => {
|
||||||
|
response.send( 'ok' );
|
||||||
|
TicketGenerator.generateTickets( { 'tok': 'hGids5PVsHm_KiK-Wd-8ekvwxpuUPrUX', 'email': 'info@janishutz.com' } );
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || 8080;
|
||||||
|
http.createServer( app ).listen( PORT );
|
||||||
@@ -10,18 +10,34 @@
|
|||||||
|
|
||||||
const pdfme = require( '@pdfme/generator' );
|
const pdfme = require( '@pdfme/generator' );
|
||||||
const db = require( '../db/db.js' );
|
const db = require( '../db/db.js' );
|
||||||
|
const pdfLib = require( 'pdf-lib' );
|
||||||
|
const fs = require( 'fs' );
|
||||||
|
const path = require( 'path' );
|
||||||
|
const mm = require( '../mail/mailSender.js' );
|
||||||
|
const mailManager = new mm();
|
||||||
|
let createSSRApp = require( 'vue' ).createSSRApp;
|
||||||
|
let renderToString = require( 'vue/server-renderer' ).renderToString;
|
||||||
|
|
||||||
|
const settings = JSON.parse( fs.readFileSync( path.join( __dirname + '/../../config/settings.config.json' ) ) );
|
||||||
|
|
||||||
class TicketGenerator {
|
class TicketGenerator {
|
||||||
constructor () {
|
constructor () {
|
||||||
this.ticketQueue = {};
|
this.ticketQueue = {};
|
||||||
this.jobId = 0;
|
this.jobId = 0;
|
||||||
|
this.currentlyRunningJob = 0;
|
||||||
this.isRunning = false;
|
this.isRunning = false;
|
||||||
|
db.getJSONData( 'tickets' ).then( tickets => {
|
||||||
|
this.tickets = tickets;
|
||||||
|
} );
|
||||||
|
db.getJSONData( 'events' ).then( events => {
|
||||||
|
this.events = events;
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Save to disk in case of crash of server / reboot / whatever
|
// TODO: Save to disk in case of crash of server / reboot / whatever
|
||||||
// and continue processing once back online
|
// and continue processing once back online
|
||||||
generateTicket ( event, data ) {
|
generateTickets ( order ) {
|
||||||
this.ticketQueue [ this.jobId ] = { 'event': event, 'data': data };
|
this.ticketQueue [ this.jobId ] = { 'order': order };
|
||||||
this.jobId += 1;
|
this.jobId += 1;
|
||||||
this.queueHandler();
|
this.queueHandler();
|
||||||
}
|
}
|
||||||
@@ -30,28 +46,78 @@ class TicketGenerator {
|
|||||||
queueHandler () {
|
queueHandler () {
|
||||||
if ( !this.isRunning ) {
|
if ( !this.isRunning ) {
|
||||||
this.isRunning = true;
|
this.isRunning = true;
|
||||||
this.ticketGenerator( this.ticketQueue[ this.jobId ][ 'event' ], this.ticketQueue[ this.jobId ][ 'data' ] ).then( pdf => {
|
if ( this.ticketQueue[ this.currentlyRunningJob ] ) {
|
||||||
console.log( pdf );
|
this.ticketGenerator( this.ticketQueue[ this.currentlyRunningJob ][ 'order' ] ).then( res => {
|
||||||
// TODO: Maybe write to disk
|
this.currentlyRunningJob += 1;
|
||||||
this.isRunning = false;
|
if ( res.status ) {
|
||||||
this.queueHandler();
|
db.getDataSimple( 'users', 'account_id', res.user ).then( dat => {
|
||||||
} ).catch( error => {
|
if ( dat[ 0 ] ) {
|
||||||
console.error( '[ PDF GENERATOR ] ERROR: ' + error );
|
( async () => {
|
||||||
this.isRunning = false;
|
const app = createSSRApp( {
|
||||||
this.queueHandler();
|
data() {
|
||||||
// TODO: Add to FAILED db
|
return {
|
||||||
} );
|
host: settings.yourDomain,
|
||||||
|
pageName: settings.name,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
template: '' + fs.readFileSync( path.join( __dirname + '/../../ui/en/payments/ticketMail.html' ) )
|
||||||
|
} );
|
||||||
|
|
||||||
|
console.log( dat[ 0 ].email );
|
||||||
|
mailManager.sendMailWithAttachment( dat[ 0 ].email, await renderToString( app ), 'Thank you for your order', [
|
||||||
|
{
|
||||||
|
'filename': 'tickets.pdf',
|
||||||
|
'path': res.file,
|
||||||
|
}
|
||||||
|
], settings.mailSender
|
||||||
|
);
|
||||||
|
// db.writeDataSimple( 'orders', 'order_name', res.order, { 'processed': 'true' } );
|
||||||
|
} )();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
this.isRunning = false;
|
||||||
|
this.queueHandler();
|
||||||
|
} ).catch( error => {
|
||||||
|
console.error( '[ PDF GENERATOR ] ERROR: ' + error );
|
||||||
|
this.isRunning = false;
|
||||||
|
// TODO: Add to FAILED db
|
||||||
|
} );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ticketGenerator ( event, data ) {
|
ticketGenerator ( order ) {
|
||||||
return new Promise( ( resolve, reject ) => {
|
return new Promise( ( resolve, reject ) => {
|
||||||
db.getJSONDataSimple( event ).then( template => {
|
db.getDataSimple( 'orders', 'order_name', order.tok ).then( ord => {
|
||||||
pdfme.generate( { template, data } ).then( pdf => {
|
if ( ord[ 0 ] ) {
|
||||||
resolve( pdf );
|
( async () => {
|
||||||
} ).catch( error => {
|
let doc = await pdfLib.PDFDocument.create();
|
||||||
reject( error );
|
let pages = [];
|
||||||
} );
|
const order = JSON.parse( ord[ 0 ].tickets );
|
||||||
|
for ( let event in order ) {
|
||||||
|
const template = this.tickets[ event ];
|
||||||
|
for ( let ticket in order[ event ] ) {
|
||||||
|
const data = [ {
|
||||||
|
'locationAndTime': this.events[ event ][ 'date' ],
|
||||||
|
'ticketName': order[ event ][ ticket ][ 'name' ],
|
||||||
|
'ticketQRCode': ord[ 0 ].order_name + '_' + order[ event ][ ticket ][ 'id' ],
|
||||||
|
} ];
|
||||||
|
const page = await pdfLib.PDFDocument.load( await pdfme.generate( { 'template': template, 'inputs': data } ) );
|
||||||
|
const p = await doc.copyPages( page, page.getPageIndices() );
|
||||||
|
pages.push( p );
|
||||||
|
p.forEach( ( page ) => doc.addPage( page ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const f = path.join( __dirname + '/store/' + ord[ 0 ].order_name + '.pdf' );
|
||||||
|
fs.writeFileSync( f, await doc.save() );
|
||||||
|
resolve( { 'status': true, 'file': f, 'user': ord[ 0 ].account_id, 'order': ord[ 0 ].order_name } );
|
||||||
|
} )();
|
||||||
|
} else {
|
||||||
|
reject( 'ERR_NO_ORDER' );
|
||||||
|
}
|
||||||
|
} ).catch( err => {
|
||||||
|
console.error( err );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,8 @@ module.exports = ( app, settings ) => {
|
|||||||
request.session.loggedInUser = true;
|
request.session.loggedInUser = true;
|
||||||
if ( responseObjects[ request.body.token ] ) {
|
if ( responseObjects[ request.body.token ] ) {
|
||||||
responseObjects[ request.body.token ].write( 'data: authenticated\n\n' );
|
responseObjects[ request.body.token ].write( 'data: authenticated\n\n' );
|
||||||
|
responseObjects[ request.body.token ].end();
|
||||||
|
delete responseObjects[ request.body.token ];
|
||||||
} else {
|
} else {
|
||||||
authOk[ request.body.token ] = 'ok';
|
authOk[ request.body.token ] = 'ok';
|
||||||
}
|
}
|
||||||
@@ -127,6 +129,7 @@ module.exports = ( app, settings ) => {
|
|||||||
|
|
||||||
app.get( '/user/2fa/ping', ( request, response ) => {
|
app.get( '/user/2fa/ping', ( request, response ) => {
|
||||||
if ( authOk[ request.session.token ] === 'ok' ) {
|
if ( authOk[ request.session.token ] === 'ok' ) {
|
||||||
|
delete authOk[ request.session.token ];
|
||||||
response.send( { 'status': 'ok' } );
|
response.send( { 'status': 'ok' } );
|
||||||
} else {
|
} else {
|
||||||
response.send( '' );
|
response.send( '' );
|
||||||
|
|||||||
17
src/server/package-lock.json
generated
17
src/server/package-lock.json
generated
@@ -19,6 +19,7 @@
|
|||||||
"html-to-text": "^9.0.5",
|
"html-to-text": "^9.0.5",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"nodemailer": "^6.9.3",
|
"nodemailer": "^6.9.3",
|
||||||
|
"pdf-lib": "^1.17.1",
|
||||||
"serve-favicon": "^2.5.0",
|
"serve-favicon": "^2.5.0",
|
||||||
"serve-static": "^1.15.0",
|
"serve-static": "^1.15.0",
|
||||||
"stripe": "^12.14.0",
|
"stripe": "^12.14.0",
|
||||||
@@ -1991,6 +1992,22 @@
|
|||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/pdf-lib": {
|
||||||
|
"version": "1.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz",
|
||||||
|
"integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@pdf-lib/standard-fonts": "^1.0.0",
|
||||||
|
"@pdf-lib/upng": "^1.0.1",
|
||||||
|
"pako": "^1.0.11",
|
||||||
|
"tslib": "^1.11.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pdf-lib/node_modules/tslib": {
|
||||||
|
"version": "1.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||||
|
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||||
|
},
|
||||||
"node_modules/peberminta": {
|
"node_modules/peberminta": {
|
||||||
"version": "0.9.0",
|
"version": "0.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz",
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
"html-to-text": "^9.0.5",
|
"html-to-text": "^9.0.5",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"nodemailer": "^6.9.3",
|
"nodemailer": "^6.9.3",
|
||||||
|
"pdf-lib": "^1.17.1",
|
||||||
"serve-favicon": "^2.5.0",
|
"serve-favicon": "^2.5.0",
|
||||||
"serve-static": "^1.15.0",
|
"serve-static": "^1.15.0",
|
||||||
"stripe": "^12.14.0",
|
"stripe": "^12.14.0",
|
||||||
|
|||||||
@@ -4,11 +4,56 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Payment Canceled</title>
|
<title>Payment Canceled</title>
|
||||||
|
<style>
|
||||||
|
body, html {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
background-color: rgb(41, 40, 40);
|
||||||
|
color: white;
|
||||||
|
font-size: 150%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit {
|
||||||
|
margin-top: 2%;
|
||||||
|
background: linear-gradient(90deg, rgb(30, 36, 131), rgb(87, 66, 184), rgb(105, 115, 214), rgb(30, 36, 131), rgb(41, 128, 109), rgb(146, 50, 47));
|
||||||
|
background-size: 300px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 3s;
|
||||||
|
font-size: 75%;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit:hover {
|
||||||
|
background-size: 200%;
|
||||||
|
background-position: -100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Payment Canceled</h1>
|
<div class="content">
|
||||||
<p>You have canceled your payment!</p>
|
<h1>Payment Canceled</h1>
|
||||||
<p>This was a mistake? Head back to the payment page!</p>
|
<p>You have canceled your payment!</p>
|
||||||
<button onclick="history.back()">Back to payment</button>
|
<p>This was a mistake? Head back to the payment page!</p>
|
||||||
|
<button onclick="history.back()" class="submit">Back to payment</button>
|
||||||
|
<button onclick="location.href = '/cart'" class="submit">Back to the cart</button>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
69
src/server/ui/en/payments/ticketMail.html
Normal file
69
src/server/ui/en/payments/ticketMail.html
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Two-Factor Authentication</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
width: 100%;
|
||||||
|
height: 800px;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 80%;
|
||||||
|
height: 90%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip {
|
||||||
|
color: rgb(94, 94, 94);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 70vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify {
|
||||||
|
padding: 20px 30px;
|
||||||
|
background-color: rgb(0, 7, 87);
|
||||||
|
text-decoration: none;
|
||||||
|
color: white;
|
||||||
|
transition: 0.5s all;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify:hover {
|
||||||
|
background-color: rgb(0, 12, 139);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 999px) {
|
||||||
|
.logo {
|
||||||
|
width: 20vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 40vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
<img :src="host + '/otherAssets/logo.png'" alt="Logo" class="logo">
|
||||||
|
<h1>Thank you for your purchase at {{ pageName }}</h1>
|
||||||
|
<p>Attached you may find your tickets. Enjoy the event!</p>
|
||||||
|
<p>You may also find the tickets on your <a :href="host + '/account'">account page</a>.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
- Require user to confirm email before purchasing
|
- Require user to confirm email before purchasing
|
||||||
|
|
||||||
|
- Guest purchase in the future (remove from matura shit)
|
||||||
|
|
||||||
- Create password changing endpoint (to reset forgotten pwd)
|
- Create password changing endpoint (to reset forgotten pwd)
|
||||||
- Add Admin profile (page to change account settings per person like changing pwd)
|
- Add Admin profile (page to change account settings per person like changing pwd)
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,15 @@ export default [
|
|||||||
transition: 'scale'
|
transition: 'scale'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/payments/success',
|
||||||
|
name: 'paymentSuccess',
|
||||||
|
component: () => import( '@/views/purchasing/PaymentSuccessView.vue' ),
|
||||||
|
meta: {
|
||||||
|
title: 'Payment successful - ',
|
||||||
|
transition: 'scale'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/admin/seatplan',
|
path: '/admin/seatplan',
|
||||||
name: 'adminSeatplanEditor',
|
name: 'adminSeatplanEditor',
|
||||||
|
|||||||
@@ -33,7 +33,24 @@
|
|||||||
saveTemplate() {
|
saveTemplate() {
|
||||||
// Save to server instead
|
// Save to server instead
|
||||||
this.$refs.notification.createNotification( 'Saving...', 5, 'progress', 'normal' );
|
this.$refs.notification.createNotification( 'Saving...', 5, 'progress', 'normal' );
|
||||||
this.$refs.notification.createNotification( 'Saved successfully', 5, 'ok', 'normal' );
|
let fetchOptions = {
|
||||||
|
method: 'post',
|
||||||
|
body: JSON.stringify( { 'location': sessionStorage.getItem( 'selectedTicket' ), 'data': this.designer.getTemplate() } ),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'charset': 'utf-8'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch( '/admin/api/saveTickets', fetchOptions ).then( res => {
|
||||||
|
if ( res.status === 200 ) {
|
||||||
|
res.text().then( text => {
|
||||||
|
// TODO: Finish up
|
||||||
|
console.log( text );
|
||||||
|
this.$refs.notification.createNotification( 'Saved successfully', 5, 'ok', 'normal' );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
} );
|
||||||
console.log( this.designer.getTemplate() );
|
console.log( this.designer.getTemplate() );
|
||||||
},
|
},
|
||||||
testNotifications () {
|
testNotifications () {
|
||||||
|
|||||||
133
src/webapp/main/src/views/purchasing/PaymentSuccessView.vue
Normal file
133
src/webapp/main/src/views/purchasing/PaymentSuccessView.vue
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
<!--
|
||||||
|
* libreevent - GuestPurchaseView.vue
|
||||||
|
*
|
||||||
|
* Created by Janis Hutz 05/14/2023, Licensed under the GPL V3 License
|
||||||
|
* https://janishutz.com, development@janishutz.com
|
||||||
|
*
|
||||||
|
*
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="content">
|
||||||
|
<h1 style="font-size: 250%;">Thank you for your purchase!</h1>
|
||||||
|
<p>The system is currently processing your order and you will be able to download your tickets within a moment's notice.</p>
|
||||||
|
<p>You will receive an email with your tickets within the next few minutes</p>
|
||||||
|
<p class="small">If the email does not arrive withing the next 10 minutes, please click <a href="/payments/resendTickets" target="_blank">here</a></p>
|
||||||
|
<button onclick="if ( confirm( 'Do you really want to leave this page? If you want to download the tickets directly, you will need to head to your account page and download the ticket from there or stay on this page and wait for the order to finish processing.' ) ) {
|
||||||
|
location.href = '/' }" class="submit">Back to the home page</button>
|
||||||
|
</div>
|
||||||
|
<notifications ref="notification" location="bottomright" size="bigger"></notifications>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import notifications from '@/components/notifications/notifications.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PaymentSuccessView',
|
||||||
|
components: {
|
||||||
|
notifications
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if ( !!window.EventSource ) {
|
||||||
|
setTimeout( () => {
|
||||||
|
let startNotification = this.$refs.notification.createNotification( 'Connecting to status service...', 20, 'progress', 'normal' );
|
||||||
|
let source = new EventSource( localStorage.getItem( 'url' ) + '/payments/status', { withCredentials: true } );
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
source.onmessage = ( e ) => {
|
||||||
|
if ( e.data === 'ready' ) {
|
||||||
|
open( '/tickets/get' );
|
||||||
|
} else if ( e.data === 'paymentOk' ) {
|
||||||
|
self.$refs.notification.createNotification( 'Your payment has been marked as completed!', 5, 'ok', 'normal' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
source.onopen = e => {
|
||||||
|
self.$refs.notification.createNotification( 'Connected to status service', 5, 'ok', 'normal' );
|
||||||
|
self.$refs.notification.cancelNotification( startNotification );
|
||||||
|
};
|
||||||
|
|
||||||
|
source.addEventListener( 'error', function( e ) {
|
||||||
|
if ( e.eventPhase == EventSource.CLOSED ) source.close();
|
||||||
|
|
||||||
|
if ( e.target.readyState == EventSource.CLOSED ) {
|
||||||
|
self.$refs.notification.cancelNotification( startNotification );
|
||||||
|
self.$refs.notification.createNotification( 'Could not connect to status service', 5, 'error', 'normal' );
|
||||||
|
}
|
||||||
|
}, false)
|
||||||
|
}, 300 );
|
||||||
|
} else {
|
||||||
|
setTimeout( () => {
|
||||||
|
this.$refs.notification.createNotification( 'Unsupported browser detected. Ticket generation might take longer!', 20, 'warning', 'normal' );
|
||||||
|
}, 300 );
|
||||||
|
// ping server every 5s to check if ticket ready
|
||||||
|
this.serverPing = setInterval( () => {
|
||||||
|
fetch( '/payments/status/ping' ).then( res => {
|
||||||
|
if ( res.status === 200 ) {
|
||||||
|
res.json().then( data => {
|
||||||
|
if ( data ) {
|
||||||
|
if ( data.status === 'ready' ) {
|
||||||
|
open( '/tickets/get' );
|
||||||
|
} else if ( data.status === 'paymentOk' ) {
|
||||||
|
this.$refs.notification.createNotification( 'Your payment has been marked as completed!', 5, 'ok', 'normal' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
} else {
|
||||||
|
console.error( 'Request failed' );
|
||||||
|
this.$refs.notification.createNotification( 'We are sorry, but an error occurred. You will not be redirected automatically', 300, 'error', 'normal' );
|
||||||
|
}
|
||||||
|
} ).catch( error => {
|
||||||
|
console.error( error );
|
||||||
|
this.$refs.notification.createNotification( 'We are sorry, but an error occurred. You will not be redirected automatically', 300, 'error', 'normal' );
|
||||||
|
} );
|
||||||
|
}, 5000 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
font-size: 75%;
|
||||||
|
color: rgb(158, 158, 158);
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit {
|
||||||
|
margin-top: 2%;
|
||||||
|
background: linear-gradient(90deg, rgb(30, 36, 131), rgb(87, 66, 184), rgb(105, 115, 214), rgb(30, 36, 131), rgb(41, 128, 109), rgb(146, 50, 47));
|
||||||
|
background-size: 300px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 3s;
|
||||||
|
font-size: 75%;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit:hover {
|
||||||
|
background-size: 200%;
|
||||||
|
background-position: -100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -44,6 +44,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
<!-- TODO: Ask for permission to send emails (Make question sound really optional) -->
|
||||||
</form>
|
</form>
|
||||||
<notifications ref="notification" location="topright" size="bigger"></notifications>
|
<notifications ref="notification" location="topright" size="bigger"></notifications>
|
||||||
<button @click="signup();" class="button">Sign up</button>
|
<button @click="signup();" class="button">Sign up</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user