add payrexx as payment provider

This commit is contained in:
2023-08-12 12:47:35 +02:00
parent 2c7e0f39bc
commit e634ac5381
17 changed files with 463 additions and 44 deletions

View File

@@ -74,6 +74,10 @@ class GETHandler {
} );
} else if ( call === 'getCurrency' ) {
resolve( this.settings.currency );
} else if ( call === 'getAdminAccounts' ) {
// TODO: Finish
} else if ( call === 'getSettings' ) {
resolve( this.settings );
} else {
reject( { 'code': 404, 'error': 'Route not found' } );
}

View File

@@ -106,6 +106,15 @@ class POSTHandler {
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'createAdminAccount' ) {
// TODO: Finish
} else if ( call === 'updateAdminAccount' ) {
// TODO: Finish
} else if ( call === 'deleteAdminAccount' ) {
// TODO: Finish
} else if ( call === 'updateSettings' ) {
// TODO: Finish
// TODO: Parse all events and update currency
} else {
reject( { 'code': 404, 'error': 'Route not found' } );
}

View File

@@ -91,7 +91,8 @@ if ( settings.init ) {
console.log( '[ Server ] loading plugins' );
// TODO: load dynamically
require( './backend/plugins/payments/stripe/stripeRoutes.js' )( app, settings ); // stripe routes
// require( './backend/plugins/payments/stripe/stripeRoutes.js' )( app, settings ); // stripe routes
require( './backend/plugins/payments/payrexx/payrexxRoutes.js' )( app, settings ); // payrexx routes
require( './backend/payments/paymentRoutes.js' )( app, settings ); // payment routes
app.use( ( request, response ) => {

View File

@@ -17,6 +17,10 @@ module.exports = ( app, settings ) => {
res.sendFile( path.join( __dirname + '/../../ui/en/payments/canceled.html' ) );
} );
app.get( '/payments/failed', ( req, res ) => {
res.sendFile( path.join( __dirname + '/../../ui/en/payments/failed.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 ) => {

View File

@@ -4,28 +4,7 @@ All payment gateway integration plugins have to be in their own folders. This fo
You will also need to add documentation for the user to set up the payment gateway. Read on below to find out how.
## Setting up the routes.js file
In the routes.js file you should have at least the following code:
```
module.exports = ( app, settings ) => {
app.post( '/payments/prepare', ( req, res ) => {
} );
app.get( '/payments/status', ( request, response ) => {
response.writeHead( 200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
} );
response.status( 200 );
response.flushHeaders();
response.write( 'data: connected\n\n' );
} );
}
```
Take some inspiration of the stripe or adyen setup as these are officially supported by the system and have been developed by the original creator.
Take some inspiration of the stripe or payrexx setup as these are officially supported by the system and have been developed by the original creator.
The express.js routes it has to expose are the following:
@@ -37,3 +16,7 @@ It can contain any number of (not interfering) routes. Please always use the /pa
## The plugin.json file
The plugin.json file should look as follows:
## configOption.json
This file contains the settings that should be available in the settings page of libreevent. It should contain the following fields, as required by the settings.vue module.

View File

@@ -0,0 +1,58 @@
/*
* libreevent - module.payrexx.js
*
* Created by Janis Hutz 08/12/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const fs = require( 'fs' );
const path = require( 'path' );
const qs = require( 'qs' );
const axios = require( 'axios' );
const Base64 = require( 'crypto-js/enc-base64' );
const hmacSHA256 = require( 'crypto-js/hmac-sha256' );
const payrexxConfig = JSON.parse( fs.readFileSync( path.join( __dirname + '/../../../../config/payments.config.secret.json' ) ) )[ 'payrexx' ];
const baseUrl = 'https://api.payrexx.com/v1.0/';
const instance = payrexxConfig.instance;
const secret = payrexxConfig.APISecret;
exports.init = function () {
function buildSignature ( query = '' ) {
return Base64.stringify( hmacSHA256( query, secret ) );
}
function buildBaseUrl ( path ) {
return baseUrl + path + '?instance=' + instance;
}
return {
getGateway: function ( id ) {
const baseUrl = buildBaseUrl( 'Gateway/' + id + '/' );
const url = baseUrl + '&ApiSignature=' + buildSignature();
return axios.get( url );
},
createGateway: function ( params ) {
if ( !params.amount ) {
throw new Error( 'Amount required!' );
}
const defaultParams = {
currency: 'CHF'
};
let queryParams = Object.assign( {}, defaultParams, params );
const queryStr = qs.stringify( queryParams, { format: 'RFC1738' } );
const signature = buildSignature( queryStr );
queryParams.ApiSignature = signature;
const queryStrSigned = qs.stringify( queryParams );
const baseUrl = buildBaseUrl( 'Gateway/' );
return axios.post( baseUrl, queryStrSigned );
}
};
};

View File

@@ -0,0 +1,177 @@
/*
* libreevent - payrexxRoutes.js
*
* Created by Janis Hutz 08/12/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const fs = require( 'fs' );
const path = require( 'path' );
const db = require( '../../../db/db.js' );
const payrexxConfig = JSON.parse( fs.readFileSync( path.join( __dirname + '/../../../../config/payments.config.secret.json' ) ) )[ 'payrexx' ];
const bodyParser = require( 'body-parser' );
const ticket = require( '../../../tickets/ticketGenerator.js' );
const payrexxModule = require( './module.payrexx.js' );
const payrexx = payrexxModule.init();
const TicketGenerator = new ticket();
let sessionReference = {};
let waitingClients = {};
let pendingPayments = {};
let paymentOk = {};
module.exports = ( app, settings ) => {
app.post( '/payments/prepare', bodyParser.json(), ( req, res ) => {
if ( req.session.loggedInUser ) {
let purchase = {
'successRedirectUrl': settings.yourDomain + '/payments/success',
'cancelRedirectUrl': settings.yourDomain + '/payments/canceled',
'failedRedirectUrl': settings.yourDomain + '/payments/failed',
'currency': settings.currency,
'basket': [],
'pm': payrexxConfig.paymentMethods,
'amount': 0,
};
db.getDataSimple( 'temp', 'user_id', req.session.id ).then( dat => {
if ( dat[ 0 ] ) {
db.getJSONData( 'events' ).then( events => {
let data = JSON.parse( dat[ 0 ].data );
( async () => {
for ( let event in data ) {
for ( let item in data[ event ] ) {
purchase[ 'basket' ].push( {
'name': data[ event ][ item ].name,
'quantity': data[ event ][ item ].count ?? 1,
'amount': Math.round( parseFloat( events[ event ][ 'categories' ][ data[ event ][ item ].category ].price[ data[ event ][ item ][ 'ticketOption' ] ] ) * 100 ),
} );
purchase[ 'amount' ] += Math.round( parseFloat( events[ event ][ 'categories' ][ data[ event ][ item ].category ].price[ data[ event ][ item ][ 'ticketOption' ] ] ) * 100 ) * ( data[ event ][ item ].count ?? 1 );
}
}
const response = await payrexx.createGateway( purchase );
if ( response.status === 200 ) {
const session = response.data.data[ 0 ];
sessionReference[ session.id ] = { 'tok': req.session.id, 'email': req.session.username };
pendingPayments[ req.session.id ] = true;
res.send( session.link );
} else {
res.status( 500 ).send( 'ERR_PAYMENT' );
}
} )();
} );
} else {
res.status( 400 ).send( 'ERR_UID_NOT_FOUND' );
}
} ).catch( error => {
console.error( '[ STRIPE ] DB ERROR: ' + error );
res.status( 500 ).send( 'ERR_DB' );
} );
} else {
res.status( 403 ).send( 'ERR_UNAUTHORIZED' );
}
} );
app.get( '/payments/status', ( request, response ) => {
response.writeHead( 200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
} );
response.status( 200 );
response.flushHeaders();
response.write( 'data: connected\n\n' );
waitingClients[ request.session.id ] = response;
const ping = setInterval( () => {
if ( !pendingPayments[ request.session.id ] ) {
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.id ] === 'ok' ) {
delete paymentOk[ request.session.id ];
response.send( { 'status': 'paymentOk' } );
} else {
if ( !pendingPayments[ request.session.id ] ) {
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( '' );
}
} else {
response.send( '' );
}
}
} );
app.post( '/payments/webhook', bodyParser.raw( { type: 'application/json' } ), async ( req, res ) => {
console.error( req.body );
const response = await payrexx.getGateway( req.body.id );
if ( response.status === 200 ) {
const gateway = response.data.data[ 0 ];
res.status( 200 ).end();
if ( gateway.status === 'confirmed' ) {
setTimeout( () => {
if ( waitingClients[ sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ] ) {
waitingClients[ sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ].write( 'data: paymentOk\n\n' );
}
}, 1000 );
db.getDataSimple( 'temp', 'user_id', sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ).then( dat => {
db.getDataSimple( 'users', 'email', sessionReference[ response.data.data[ 0 ].id ][ 'email' ] ).then( user => {
if ( user[ 0 ] ) {
const tickets = JSON.parse( dat[ 0 ].data );
db.writeDataSimple( 'orders', 'account_id', user[ 0 ].account_id, { 'account_id': user[ 0 ].account_id, 'tickets': dat[ 0 ].data, 'order_name': sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] } ).then( () => {
console.log( sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] );
TicketGenerator.generateTickets( sessionReference[ response.data.data[ 0 ].id ] );
db.getJSONData( 'booked' ).then( ret => {
let booked = ret ?? {};
for ( let event in tickets ) {
if ( !booked[ String( event ) ] ) {
booked[ String( event ) ] = {};
}
for ( let tik in tickets[ event ] ) {
booked[ event ][ tik ] = tickets[ event ][ tik ];
}
}
db.writeJSONData( 'booked', booked ).then( () => {
delete pendingPayments[ sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ];
} );
db.deleteDataSimple( 'temp', 'user_id', sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ).catch( error => {
console.error( '[ STRIPE ] ERROR whilst deleting data from DB: ' + error );
} );
} );
} );
} else {
console.log( sessionReference[ response.data.data[ 0 ].id ][ 'email' ] );
console.error( 'user not found' );
}
} );
} ).catch( err => {
console.error( err );
} );
}
}
} );
};

View File

@@ -10,21 +10,19 @@
const fs = require( 'fs' );
const path = require( 'path' );
const db = require( '../../../db/db.js' );
const stripConfig = JSON.parse( fs.readFileSync( path.join( __dirname + '/../../../../config/payments.config.secret.json' ) ) )[ 'stripe' ];
const stripe = require( 'stripe' )( stripConfig[ 'APIKey' ] );
const stripeConfig = JSON.parse( fs.readFileSync( path.join( __dirname + '/../../../../config/payments.config.secret.json' ) ) )[ 'stripe' ];
const stripe = require( 'stripe' )( stripeConfig[ 'APIKey' ] );
const bodyParser = require( 'body-parser' );
const ticket = require( '../../../tickets/ticketGenerator.js' );
const TicketGenerator = new ticket();
const endpointSecret = stripConfig[ 'endpointSecret' ];
const endpointSecret = stripeConfig[ 'endpointSecret' ];
let sessionReference = {};
let waitingClients = {};
let pendingPayments = {};
let paymentOk = {};
// TODO: Remove all selected tickets if timestamp more than user defined amount ago
module.exports = ( app, settings ) => {
app.post( '/payments/prepare', bodyParser.json(), ( req, res ) => {
if ( req.session.loggedInUser ) {

View File

@@ -1,5 +1,11 @@
{
"stripe": {
"APIKey": ""
"APIKey": "",
"endpointSecret": ""
},
"payrexx": {
"APISecret": "",
"instance": "",
"pm": []
}
}

View File

@@ -4,7 +4,7 @@
"setupKey": "hello world",
"twoFAMode": "enhanced",
"db": "mysql",
"payments": "stripe",
"payments": "payrexx",
"name": "libreevent",
"yourDomain": "http://localhost:8080",
"mailSender": "libreevent <info@libreevent.janishutz.com>",

View File

@@ -11,9 +11,11 @@
"dependencies": {
"@pdfme/generator": "^1.2.6",
"@seald-io/nedb": "^4.0.2",
"axios": "^1.4.0",
"bcrypt": "^5.0.1",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
"crypto-js": "^4.1.1",
"express": "^4.18.2",
"express-session": "^1.17.3",
"html-to-text": "^9.0.5",
@@ -21,6 +23,7 @@
"mysql": "^2.18.1",
"nodemailer": "^6.9.3",
"pdf-lib": "^1.17.1",
"qs": "^6.11.2",
"serve-favicon": "^2.5.0",
"serve-static": "^1.15.0",
"stripe": "^12.14.0",
@@ -432,6 +435,11 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/atob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
@@ -454,6 +462,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/axios": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -535,6 +553,20 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/body-parser/node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"dependencies": {
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -665,6 +697,17 @@
"color-support": "bin.js"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
@@ -774,6 +817,11 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"node_modules/crypto-js": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
"integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
},
"node_modules/css-b64-images": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/css-b64-images/-/css-b64-images-0.2.5.tgz",
@@ -820,6 +868,14 @@
"node": ">=0.10.0"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
@@ -934,6 +990,16 @@
"node": ">= 0.8"
}
},
"node_modules/encoding": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
"integrity": "sha512-bl1LAgiQc4ZWr++pNYUdRe/alecaHFeHxIJ/pNciqGdKXghaTCOwKkbKp6ye7pKZGu/GcaSXFk8PBVhgs+dJdA==",
"optional": true,
"peer": true,
"dependencies": {
"iconv-lite": "~0.4.13"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
@@ -1087,6 +1153,20 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/express/node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"dependencies": {
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/express/node_modules/raw-body": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
@@ -1152,6 +1232,25 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/fontkit": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.2.tgz",
@@ -1176,6 +1275,19 @@
"is-callable": "^1.1.3"
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -2158,10 +2270,15 @@
"node": ">= 0.10"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"version": "6.11.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
"integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
"dependencies": {
"side-channel": "^1.0.4"
},

View File

@@ -44,9 +44,11 @@
"dependencies": {
"@pdfme/generator": "^1.2.6",
"@seald-io/nedb": "^4.0.2",
"axios": "^1.4.0",
"bcrypt": "^5.0.1",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
"crypto-js": "^4.1.1",
"express": "^4.18.2",
"express-session": "^1.17.3",
"html-to-text": "^9.0.5",
@@ -54,6 +56,7 @@
"mysql": "^2.18.1",
"nodemailer": "^6.9.3",
"pdf-lib": "^1.17.1",
"qs": "^6.11.2",
"serve-favicon": "^2.5.0",
"serve-static": "^1.15.0",
"stripe": "^12.14.0",

View File

@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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>
<body>
<div class="content">
<h1>Payment Failed</h1>
<p>Your payment has failed! Please try again or use a different payment method</p>
<button onclick="location.href = '/cart'" class="submit">Back to the cart</button>
</div>
</body>
</html>

View File

@@ -9,6 +9,9 @@
<template>
<div>
<!-- <h2>Setup check</h2> -->
<!-- TODO: Maybe add -->
<!-- TODO: call config check of payment + check if events are deployed -->
<h2>Settings</h2>
<p>Changing any of these settings requires a restart of libreevent.</p>
<p>Currency codes used must be valid ISO 4217 codes. Read more on <a href="https://libreevent.janishutz.com/docs/admin-panel/settings#currency" target="_blank">this page</a> of the documentation <!-- https://en.wikipedia.org/wiki/ISO_4217#List_of_ISO_4217_currency_codes"--></p>
@@ -171,9 +174,9 @@
},
showPaymentSettings () {
this.$refs.popup.openPopup( 'Payment gateway settings', {
'link': '/payments/settings',
}
, 'iframe' );
'link': '/payments/settings',
}
, 'iframe' );
},
createAccount() {
this.$refs.popup.openPopup( 'Create new admin user', {
@@ -224,6 +227,7 @@
},
handlePopupReturns( data ) {
console.log( data );
// TODO: Delete user
if ( data.status === 'cancel' ) {
console.log( 'user canceled' );
return;

View File

@@ -3,13 +3,11 @@ Including many payment methods can help the user choose the one that fits them b
## Advantages / Disadvantages of each payment gateway
### payrexx
Payrexx is a lesser known payment gateway and it is not ideal for you if you expect to have a low sales volume, as their basic plan costs you EUR 15 a month. If your sales volume is fairly high, it may be worth is as their per-transaction fees are quite a bit lower than Stripe's and they offer more payment options.
See payrexx [pricing here](https://www.payrexx.com/en/pricing/) and sign up [here](https://signup.payrexx.com). You can find a full list of supported payment methods as they should be entered in the GUI [here](https://docs.payrexx.com/developer/general-info/payment-method)
### Stripe
Stripe is one of the most well known payment gateways out there, but is also fairly expensive, but easy to set up.
Stripe is one of the most well known payment gateways out there and it is really easy to get started.
See [here](https://stripe.com/en-gb/pricing) for pricing information and sign up [here](https://dashboard.stripe.com/register)
### adyen
Adyen is used by small and huge businesses alike, but you have to email their support team for account creation, which is easy to do, but takes longer to complete. adyen is fairly cheap, "*only*" charging 0.10$ per transaction plus payment method costs. Detailed pricing information can be found [here](https://www.adyen.com/pricing) and you may sign up [here](https://www.adyen.com/contact/sales)
### payrexx
https://www.payrexx.com/en/pricing/