accept PayPal IPNs and add them to the database
This commit is contained in:
parent
0d04fb69cf
commit
d083aaa346
@ -1,6 +1,7 @@
|
||||
import path from 'path'
|
||||
import cprog from 'child_process'
|
||||
import config from '../../scripts/load-config'
|
||||
import http from '../../scripts/http'
|
||||
import models from './models'
|
||||
import crypto from 'crypto'
|
||||
import notp from 'notp'
|
||||
@ -48,6 +49,8 @@ function keysAvailable (object, required) {
|
||||
return found
|
||||
}
|
||||
|
||||
let txnStore = []
|
||||
|
||||
const API = {
|
||||
Hash: (len) => {
|
||||
return crypto.randomBytes(len).toString('hex')
|
||||
@ -369,6 +372,92 @@ const API = {
|
||||
return true
|
||||
}
|
||||
}
|
||||
},
|
||||
Payment: {
|
||||
handleIPN: async function (body) {
|
||||
let sandboxed = body.test_ipn === '1'
|
||||
let url = 'https://ipnpb.' + (sandboxed ? 'sandbox.' : '') + 'paypal.com/cgi-bin/webscr'
|
||||
|
||||
console.debug('Incoming payment')
|
||||
let verification = await http.POST(url, {}, Object.assign({
|
||||
cmd: '_notify-validate'
|
||||
}, body))
|
||||
|
||||
if (verification !== 'VERIFIED') return null
|
||||
|
||||
if (sandboxed) {
|
||||
console.debug('Sandboxed payment:', body)
|
||||
} else {
|
||||
console.debug('IPN Verified Notification:', body)
|
||||
}
|
||||
|
||||
// TODO: add database field for this
|
||||
if (body.txn_id) {
|
||||
if (txnStore.indexOf(body.txn_id) !== -1) return true
|
||||
txnStore.push(body.txn_id)
|
||||
}
|
||||
|
||||
let user
|
||||
let source = []
|
||||
if (sandboxed) {
|
||||
source.push('virtual')
|
||||
}
|
||||
|
||||
// TODO: add hooks
|
||||
let custom = body.custom.split(',')
|
||||
for (let i in custom) {
|
||||
let str = custom[i]
|
||||
if (str.indexOf('userid:') === 0) {
|
||||
body.user_id = parseInt(str.split(':')[1])
|
||||
} else if (str.indexOf('mcu:') === 0) {
|
||||
source.push('mcu:' + str.split(':')[1])
|
||||
}
|
||||
}
|
||||
|
||||
if (body.user_id != null) {
|
||||
user = await API.User.get(body.user_id)
|
||||
} else if (body.payer_email != null) {
|
||||
user = await API.User.get(body.payer_email)
|
||||
}
|
||||
|
||||
let donation = {
|
||||
user_id: user ? user.id : null,
|
||||
amount: (body.mc_gross || body.payment_gross || 'Unknown') + ' ' + (body.mc_currency || 'EUR'),
|
||||
source: source.join(';'),
|
||||
note: body.memo || '',
|
||||
read: 0,
|
||||
created_at: new Date(body.payment_date)
|
||||
}
|
||||
|
||||
console.log('Server receieved a successful PayPal IPN message.')
|
||||
|
||||
return models.Donation.query().insert(donation)
|
||||
},
|
||||
userContributions: async function (user) {
|
||||
user = await API.User.ensureObject(user)
|
||||
|
||||
let dbq = await models.Donation.query().where('user_id', user.id)
|
||||
let contribs = []
|
||||
|
||||
for (let i in dbq) {
|
||||
let contrib = dbq[i]
|
||||
let obj = {
|
||||
amount: contrib.amount,
|
||||
donated: contrib.created_at
|
||||
}
|
||||
|
||||
let srcs = contrib.source.split(';')
|
||||
for (let j in srcs) {
|
||||
if (srcs[j].indexOf('mcu') === 0) {
|
||||
obj.minecraft_username = srcs[j].split(':')[1]
|
||||
}
|
||||
}
|
||||
|
||||
contribs.push(obj)
|
||||
}
|
||||
|
||||
return contribs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,6 +88,10 @@ router.post('/external/facebook/callback', wrap(async (req, res, next) => {
|
||||
|
||||
let response = await APIExtern.Facebook.callback(req.session.user, sane)
|
||||
|
||||
if (response.banned) {
|
||||
return res.render('user/banned', {bans: response.banned, ipban: response.ip})
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
return JsonData(req, res, response.error)
|
||||
}
|
||||
@ -153,6 +157,10 @@ router.get('/external/twitter/callback', wrap(async (req, res) => {
|
||||
}
|
||||
|
||||
let response = await APIExtern.Twitter.callback(req.session.user, accessTokens, req.realIP)
|
||||
if (response.banned) {
|
||||
return res.render('user/banned', {bans: response.banned, ipban: response.ip})
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
req.flash('message', {error: true, text: response.error})
|
||||
return res.redirect(uri)
|
||||
@ -223,6 +231,10 @@ router.get('/external/discord/callback', wrap(async (req, res) => {
|
||||
}
|
||||
|
||||
let response = await APIExtern.Discord.callback(req.session.user, accessToken.accessToken, req.realIP)
|
||||
if (response.banned) {
|
||||
return res.render('user/banned', {bans: response.banned, ipban: response.ip})
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
req.flash('message', {error: true, text: response.error})
|
||||
return res.redirect(uri)
|
||||
@ -399,6 +411,28 @@ router.post('/oauth2/authorized-clients/revoke', wrap(async (req, res, next) =>
|
||||
res.status(204).end()
|
||||
}))
|
||||
|
||||
/* ==================
|
||||
* Donation Store
|
||||
* ==================
|
||||
*/
|
||||
|
||||
router.post('/paypal/ipn', wrap(async (req, res) => {
|
||||
let content = req.body
|
||||
|
||||
if (content && content.payment_status && content.payment_status === 'Completed') {
|
||||
await API.Payment.handleIPN(content)
|
||||
}
|
||||
|
||||
res.status(204).end()
|
||||
}))
|
||||
|
||||
router.get('/user/donations', wrap(async (req, res, next) => {
|
||||
if (!req.session.user) return next()
|
||||
|
||||
let contribs = await API.Payment.userContributions(req.session.user)
|
||||
res.jsonp(contribs)
|
||||
}))
|
||||
|
||||
// 404
|
||||
router.use((req, res) => {
|
||||
res.status(404).jsonp({error: 'Not found'})
|
||||
|
@ -43,6 +43,8 @@ router.use(wrap(async (req, res, next) => {
|
||||
messages = messages[0]
|
||||
}
|
||||
|
||||
res.locals.message = messages
|
||||
|
||||
// Update user session every 30 minutes
|
||||
if (req.session.user) {
|
||||
if (!req.session.user.session_refresh) {
|
||||
@ -50,12 +52,20 @@ router.use(wrap(async (req, res, next) => {
|
||||
}
|
||||
|
||||
if (req.session.user.session_refresh < Date.now()) {
|
||||
// Check for ban
|
||||
let banStatus = await API.User.getBanStatus(req.session.user.id)
|
||||
|
||||
if (banStatus.length) {
|
||||
delete req.session.user
|
||||
return next()
|
||||
}
|
||||
|
||||
// Update user session
|
||||
let udata = await API.User.get(req.session.user.id)
|
||||
setSession(req, udata)
|
||||
}
|
||||
}
|
||||
|
||||
res.locals.message = messages
|
||||
next()
|
||||
}))
|
||||
|
||||
@ -207,6 +217,11 @@ router.get('/user/manage/email', ensureLogin, wrap(async (req, res) => {
|
||||
res.render('user/email_change', {email: obfuscated, password: socialStatus.password})
|
||||
}))
|
||||
|
||||
router.get('/donate', wrap(async (req, res, next) => {
|
||||
if (!config.donations || !config.donations.business) return next()
|
||||
res.render('donate', config.donations)
|
||||
}))
|
||||
|
||||
/*
|
||||
=================
|
||||
POST HANDLING
|
||||
@ -339,6 +354,12 @@ router.post('/login', wrap(async (req, res, next) => {
|
||||
if (user.activated === 0) return formError(req, res, 'Please activate your account first.')
|
||||
if (user.locked === 1) return formError(req, res, 'This account has been locked.')
|
||||
|
||||
// Check if the user is banned
|
||||
let banStatus = await API.User.getBanStatus(user.id)
|
||||
if (banStatus.length) {
|
||||
return res.render('user/banned', {bans: banStatus, ipban: false})
|
||||
}
|
||||
|
||||
// Redirect to the verification dialog if 2FA is enabled
|
||||
let totpRequired = await API.User.Login.totpTokenRequired(user)
|
||||
if (totpRequired) {
|
||||
@ -346,8 +367,6 @@ router.post('/login', wrap(async (req, res, next) => {
|
||||
return res.redirect('/login/verify')
|
||||
}
|
||||
|
||||
// TODO: Ban checks
|
||||
|
||||
// Set session
|
||||
setSession(req, user)
|
||||
|
||||
|
@ -212,6 +212,33 @@ $(document).ready(function () {
|
||||
loadAuthorizations()
|
||||
}
|
||||
|
||||
if ($('#mcinclude').length) {
|
||||
var customDef = $('#custominfo').val()
|
||||
$('#mcinclude').change(function () {
|
||||
$('.mcuname').slideToggle()
|
||||
|
||||
if (!this.checked) {
|
||||
$('#custominfo').val(customDef)
|
||||
} else {
|
||||
if ($('#mcusername').val()) {
|
||||
var mcname = 'mcu:' + $('#mcusername').val()
|
||||
$('#custominfo').val(customDef ? customDef + ',' + mcname : mcname)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
$('#mcusername').on('keyup', function () {
|
||||
var mcname = 'mcu:' + $(this).val()
|
||||
|
||||
if ($(this).val() === '') {
|
||||
$('#custominfo').val(customDef)
|
||||
return
|
||||
}
|
||||
|
||||
$('#custominfo').val(customDef ? customDef + ',' + mcname : mcname)
|
||||
})
|
||||
}
|
||||
|
||||
if ($('#newAvatar').length) {
|
||||
$('#newAvatar').click(function (e) {
|
||||
e.preventDefault()
|
||||
|
@ -255,9 +255,15 @@ input:not([type="submit"])
|
||||
min-height: 380px
|
||||
&#totpcheck
|
||||
max-width: 400px
|
||||
&#donate
|
||||
max-width: 400px
|
||||
&#error
|
||||
width: 200px
|
||||
|
||||
.check label
|
||||
display: inline-block
|
||||
margin-right: 5px
|
||||
|
||||
.pgn
|
||||
display: inline-block
|
||||
margin-left: 10px
|
||||
@ -574,6 +580,15 @@ span.load
|
||||
color: #fff
|
||||
background-color: #d0d0d0
|
||||
|
||||
select
|
||||
width: 80px
|
||||
background-color: #f5f5f5
|
||||
border: 1px solid #c1c1c1
|
||||
padding: 8px
|
||||
vertical-align: top
|
||||
margin-left: 5px
|
||||
border-radius: 5px
|
||||
|
||||
@media all and (max-width: 800px)
|
||||
.navigator
|
||||
padding: 0 10px
|
||||
|
45
views/donate.pug
Normal file
45
views/donate.pug
Normal file
@ -0,0 +1,45 @@
|
||||
extends layout.pug
|
||||
block title
|
||||
|Icy Network - Donate
|
||||
|
||||
block body
|
||||
.wrapper
|
||||
.boxcont
|
||||
.box#donate
|
||||
h1 Donate
|
||||
p Donating any amount would be highly appreciated!
|
||||
|
||||
- var formurl = "https://www.paypal.com/cgi-bin/webscr"
|
||||
if sandbox
|
||||
- formurl = "https://www.sandbox.paypal.com/cgi-bin/webscr"
|
||||
|
||||
form(action=formurl, method="post")
|
||||
input(type="hidden" name="cmd" value="_xclick")
|
||||
input(type="hidden" name="business" value=business)
|
||||
input(type="hidden" name="item_name" value=name)
|
||||
input(type="hidden" name="item_number" value="1")
|
||||
input(type="hidden" name="no_shipping" value="1")
|
||||
input(type="hidden" name="quantity" value="1")
|
||||
input(type="hidden" name="tax" value="0")
|
||||
input(type="hidden" name="notify_url" value=ipn_url)
|
||||
label(for="amount") Amount
|
||||
input(type="number", name="amount" value="1.00")
|
||||
select(name="currency_code")
|
||||
option(value="EUR") EUR
|
||||
option(value="USD") USD
|
||||
if user
|
||||
input#custominfo(type="hidden", name="custom", value="userid:" + user.id)
|
||||
else
|
||||
input#custominfo(type="hidden", name="custom", value="")
|
||||
if minecraft
|
||||
.check
|
||||
label(for="mcinclude") Include Minecraft Username
|
||||
input(id="mcinclude" type="checkbox")
|
||||
.mcuname(style="display: none;")
|
||||
input(id="mcusername")
|
||||
.buttoncont
|
||||
a.button.donate(name="submit", onclick="$(this).closest('form').submit()")
|
||||
i.fa.fa-fw.fa-paypal
|
||||
|Donate
|
||||
br
|
||||
b Currently you can only donate using a PayPal account.
|
@ -84,3 +84,5 @@ html
|
||||
a(href="/docs/terms-of-service") Terms of Service
|
||||
span.divider |
|
||||
a(href="/docs/privacy-policy") Privacy Policy
|
||||
span.divider |
|
||||
a(href="/donate") Donate
|
||||
|
Reference in New Issue
Block a user