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 path from 'path'
|
||||||
import cprog from 'child_process'
|
import cprog from 'child_process'
|
||||||
import config from '../../scripts/load-config'
|
import config from '../../scripts/load-config'
|
||||||
|
import http from '../../scripts/http'
|
||||||
import models from './models'
|
import models from './models'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import notp from 'notp'
|
import notp from 'notp'
|
||||||
@ -48,6 +49,8 @@ function keysAvailable (object, required) {
|
|||||||
return found
|
return found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let txnStore = []
|
||||||
|
|
||||||
const API = {
|
const API = {
|
||||||
Hash: (len) => {
|
Hash: (len) => {
|
||||||
return crypto.randomBytes(len).toString('hex')
|
return crypto.randomBytes(len).toString('hex')
|
||||||
@ -369,6 +372,92 @@ const API = {
|
|||||||
return true
|
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)
|
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) {
|
if (response.error) {
|
||||||
return JsonData(req, res, 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)
|
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) {
|
if (response.error) {
|
||||||
req.flash('message', {error: true, text: response.error})
|
req.flash('message', {error: true, text: response.error})
|
||||||
return res.redirect(uri)
|
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)
|
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) {
|
if (response.error) {
|
||||||
req.flash('message', {error: true, text: response.error})
|
req.flash('message', {error: true, text: response.error})
|
||||||
return res.redirect(uri)
|
return res.redirect(uri)
|
||||||
@ -399,6 +411,28 @@ router.post('/oauth2/authorized-clients/revoke', wrap(async (req, res, next) =>
|
|||||||
res.status(204).end()
|
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
|
// 404
|
||||||
router.use((req, res) => {
|
router.use((req, res) => {
|
||||||
res.status(404).jsonp({error: 'Not found'})
|
res.status(404).jsonp({error: 'Not found'})
|
||||||
|
@ -43,6 +43,8 @@ router.use(wrap(async (req, res, next) => {
|
|||||||
messages = messages[0]
|
messages = messages[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res.locals.message = messages
|
||||||
|
|
||||||
// Update user session every 30 minutes
|
// Update user session every 30 minutes
|
||||||
if (req.session.user) {
|
if (req.session.user) {
|
||||||
if (!req.session.user.session_refresh) {
|
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()) {
|
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)
|
let udata = await API.User.get(req.session.user.id)
|
||||||
setSession(req, udata)
|
setSession(req, udata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.locals.message = messages
|
|
||||||
next()
|
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})
|
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
|
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.activated === 0) return formError(req, res, 'Please activate your account first.')
|
||||||
if (user.locked === 1) return formError(req, res, 'This account has been locked.')
|
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
|
// Redirect to the verification dialog if 2FA is enabled
|
||||||
let totpRequired = await API.User.Login.totpTokenRequired(user)
|
let totpRequired = await API.User.Login.totpTokenRequired(user)
|
||||||
if (totpRequired) {
|
if (totpRequired) {
|
||||||
@ -346,8 +367,6 @@ router.post('/login', wrap(async (req, res, next) => {
|
|||||||
return res.redirect('/login/verify')
|
return res.redirect('/login/verify')
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Ban checks
|
|
||||||
|
|
||||||
// Set session
|
// Set session
|
||||||
setSession(req, user)
|
setSession(req, user)
|
||||||
|
|
||||||
|
@ -212,6 +212,33 @@ $(document).ready(function () {
|
|||||||
loadAuthorizations()
|
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) {
|
if ($('#newAvatar').length) {
|
||||||
$('#newAvatar').click(function (e) {
|
$('#newAvatar').click(function (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -255,9 +255,15 @@ input:not([type="submit"])
|
|||||||
min-height: 380px
|
min-height: 380px
|
||||||
&#totpcheck
|
&#totpcheck
|
||||||
max-width: 400px
|
max-width: 400px
|
||||||
|
&#donate
|
||||||
|
max-width: 400px
|
||||||
&#error
|
&#error
|
||||||
width: 200px
|
width: 200px
|
||||||
|
|
||||||
|
.check label
|
||||||
|
display: inline-block
|
||||||
|
margin-right: 5px
|
||||||
|
|
||||||
.pgn
|
.pgn
|
||||||
display: inline-block
|
display: inline-block
|
||||||
margin-left: 10px
|
margin-left: 10px
|
||||||
@ -574,6 +580,15 @@ span.load
|
|||||||
color: #fff
|
color: #fff
|
||||||
background-color: #d0d0d0
|
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)
|
@media all and (max-width: 800px)
|
||||||
.navigator
|
.navigator
|
||||||
padding: 0 10px
|
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
|
a(href="/docs/terms-of-service") Terms of Service
|
||||||
span.divider |
|
span.divider |
|
||||||
a(href="/docs/privacy-policy") Privacy Policy
|
a(href="/docs/privacy-policy") Privacy Policy
|
||||||
|
span.divider |
|
||||||
|
a(href="/donate") Donate
|
||||||
|
Reference in New Issue
Block a user