From 5cbf624284b9f81036772c163a75e8de2c042dd1 Mon Sep 17 00:00:00 2001 From: Janis Hutz Date: Sun, 6 Aug 2023 20:35:50 +0200 Subject: [PATCH] payments fully integrated --- src/server/backend/db/mysqldb.js | 2 +- src/server/backend/payments/paymentRoutes.js | 10 ++ .../plugins/payments/stripe/stripeRoutes.js | 39 +++++-- src/server/backend/tickets/ticketGenerator.js | 22 +++- src/server/ui/en/errors/404.html | 11 ++ .../views/purchasing/PaymentSuccessView.vue | 106 +++++++++--------- 6 files changed, 129 insertions(+), 61 deletions(-) create mode 100644 src/server/ui/en/errors/404.html diff --git a/src/server/backend/db/mysqldb.js b/src/server/backend/db/mysqldb.js index ad254ee..4fad8a4 100644 --- a/src/server/backend/db/mysqldb.js +++ b/src/server/backend/db/mysqldb.js @@ -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 ) => { 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, 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 ) => { + 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 ), processed TINYTEXT, 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; 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; diff --git a/src/server/backend/payments/paymentRoutes.js b/src/server/backend/payments/paymentRoutes.js index 90e021a..6b1ce5e 100644 --- a/src/server/backend/payments/paymentRoutes.js +++ b/src/server/backend/payments/paymentRoutes.js @@ -8,6 +8,7 @@ */ const path = require( 'path' ); +const fs = require( 'fs' ); // const ph = require( './paymentHandler.js' ); // const paymentHandler = new ph(); @@ -15,4 +16,13 @@ module.exports = ( app, settings ) => { app.get( '/payments/canceled', ( req, res ) => { res.sendFile( path.join( __dirname + '/../../ui/en/payments/canceled.html' ) ); } ); + + app.get( '/tickets/tickets.pdf', ( req, res ) => { + if ( req.session.id ) { + fs.readFile( path.join( __dirname + '/../tickets/store/' + req.session.id + '.pdf' ), ( error, data ) => { + if ( error ) res.sendFile( path.join( __dirname + '/../../ui/en/errors/404.html' ) ); + else res.send( data ); + } ); + } + } ); }; \ No newline at end of file diff --git a/src/server/backend/plugins/payments/stripe/stripeRoutes.js b/src/server/backend/plugins/payments/stripe/stripeRoutes.js index 00ced39..53ccb01 100644 --- a/src/server/backend/plugins/payments/stripe/stripeRoutes.js +++ b/src/server/backend/plugins/payments/stripe/stripeRoutes.js @@ -82,14 +82,37 @@ module.exports = ( app, settings ) => { response.flushHeaders(); response.write( 'data: connected\n\n' ); waitingClients[ request.session.id ] = response; + const ping = setInterval( () => { + const stat = TicketGenerator.getGenerationStatus( request.session.id ); + if ( stat === 'done' ) { + clearInterval( ping ); + setTimeout( () => { + response.write( 'data: ready\n\n' ); + response.end(); + delete waitingClients[ request.session.id ]; + }, 2000 ); + } else if ( stat === 'noTicket' ) { + clearInterval( ping ); + response.write( 'data: noData\n\n' ); + response.end(); + delete waitingClients[ request.session.id ]; + } + }, 2000 ); } ); 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( '' ); + if ( paymentOk[ request.session.id ] === 'ok' ) { + delete paymentOk[ request.session.id ]; + response.send( { 'status': 'paymentOk' } ); + } else { + const stat = TicketGenerator.getGenerationStatus( request.session.id ); + if ( stat === 'done' ) { + response.send( { 'status': 'ticketOk' } ); + } else if ( stat === 'noTicket' ) { + response.send( { 'status': 'noTicket' } ); + } else { + response.send( '' ); + } } } ); @@ -108,8 +131,10 @@ module.exports = ( app, settings ) => { if ( event.type === 'checkout.session.completed' ) { setTimeout( () => { - waitingClients[ sessionReference[ event.data.object.id ][ 'tok' ] ].write( 'data: paymentOk\n\n' ); - }, 2000 ); + if ( waitingClients[ sessionReference[ event.data.object.id ][ 'tok' ] ] ) { + waitingClients[ sessionReference[ event.data.object.id ][ 'tok' ] ].write( 'data: paymentOk\n\n' ); + } + }, 1000 ); 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 ] ) { diff --git a/src/server/backend/tickets/ticketGenerator.js b/src/server/backend/tickets/ticketGenerator.js index f056f05..2948f2f 100644 --- a/src/server/backend/tickets/ticketGenerator.js +++ b/src/server/backend/tickets/ticketGenerator.js @@ -32,16 +32,31 @@ class TicketGenerator { db.getJSONData( 'events' ).then( events => { this.events = events; } ); + this.runningTickets = {}; } // TODO: Save to disk in case of crash of server / reboot / whatever // and continue processing once back online generateTickets ( order ) { - this.ticketQueue [ this.jobId ] = { 'order': order }; + this.ticketQueue[ this.jobId ] = { 'order': order }; + this.runningTickets[ order ] = 'processing'; this.jobId += 1; this.queueHandler(); } + getGenerationStatus ( order ) { + if ( this.runningTickets[ order ] ) { + if ( this.runningTickets[ order ] === 'done' ) { + delete this.runningTickets[ order ]; + return 'done'; + } else { + return 'processing'; + } + } else { + return 'noTicket'; + } + } + // TODO: Maybe move to subprocesses queueHandler () { if ( !this.isRunning ) { @@ -63,7 +78,8 @@ class TicketGenerator { template: '' + fs.readFileSync( path.join( __dirname + '/../../ui/en/payments/ticketMail.html' ) ) } ); - console.log( dat[ 0 ].email ); + this.runningTickets[ res.order ] = 'done'; + mailManager.sendMailWithAttachment( dat[ 0 ].email, await renderToString( app ), 'Thank you for your order', [ { 'filename': 'tickets.pdf', @@ -71,7 +87,7 @@ class TicketGenerator { } ], settings.mailSender ); - // db.writeDataSimple( 'orders', 'order_name', res.order, { 'processed': 'true' } ); + db.writeDataSimple( 'orders', 'order_name', res.order, { 'processed': 'true' } ); } )(); } } ); diff --git a/src/server/ui/en/errors/404.html b/src/server/ui/en/errors/404.html new file mode 100644 index 0000000..5b516ea --- /dev/null +++ b/src/server/ui/en/errors/404.html @@ -0,0 +1,11 @@ + + + + + + 404 + + +

404

+ + \ No newline at end of file diff --git a/src/webapp/main/src/views/purchasing/PaymentSuccessView.vue b/src/webapp/main/src/views/purchasing/PaymentSuccessView.vue index 0208a67..4e735e8 100644 --- a/src/webapp/main/src/views/purchasing/PaymentSuccessView.vue +++ b/src/webapp/main/src/views/purchasing/PaymentSuccessView.vue @@ -37,61 +37,67 @@ }, 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; + 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' ); + source.onmessage = ( e ) => { + console.log( e ); + if ( e.data === 'ready' ) { self.$refs.notification.cancelNotification( startNotification ); - }; - - source.addEventListener( 'error', function( e ) { - if ( e.eventPhase == EventSource.CLOSED ) source.close(); + self.$refs.notification.createNotification( 'Your tickets are ready! Starting download...', 10, 'progress', 'normal' ); + setTimeout( () => { + open( '/tickets/tickets.pdf' ); + source.close(); + }, 500 ); + } else if ( e.data === 'paymentOk' ) { + self.$refs.notification.createNotification( 'Your payment has been marked as completed!', 5, 'ok', 'normal' ); + } + } - 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' ); - } + 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 ); + } + } ); + } 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' ); - } ); - }, 5000 ); - } + } + } ).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 ); + } } }