/* eslint-disable camelcase */ import axios from 'axios'; import debug from 'debug'; import { donationSubscriptionConfig } from '../../../../config/donation-settings'; import keys from '../../../../config/secrets'; const log = debug('fcc:boot:donate'); const paypalverifyWebhookURL = keys.paypal.verifyWebhookURL || `https://api.sandbox.paypal.com/v1/notifications/verify-webhook-signature`; const paypalTokenURL = keys.paypal.tokenUrl || `https://api.sandbox.paypal.com/v1/oauth2/token`; export async function getAsyncPaypalToken() { const res = await axios.post(paypalTokenURL, null, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, auth: { username: keys.paypal.client, password: keys.paypal.secret }, params: { grant_type: 'client_credentials' } }); return res.data.access_token; } export function capitalizeKeys(object) { Object.keys(object).forEach(function (key) { object[key.toUpperCase()] = object[key]; }); } export async function verifyWebHook(headers, body, token, webhookId) { var webhookEventBody = typeof body === 'string' ? JSON.parse(body) : body; capitalizeKeys(headers); const payload = { auth_algo: headers['PAYPAL-AUTH-ALGO'], cert_url: headers['PAYPAL-CERT-URL'], transmission_id: headers['PAYPAL-TRANSMISSION-ID'], transmission_sig: headers['PAYPAL-TRANSMISSION-SIG'], transmission_time: headers['PAYPAL-TRANSMISSION-TIME'], webhook_id: webhookId, webhook_event: webhookEventBody }; const response = await axios.post(paypalverifyWebhookURL, payload, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` } }); if (response.data.verification_status === 'SUCCESS') { return body; } else { throw { message: `Failed token verification.`, type: 'FailedPaypalTokenVerificationError' }; } } export function verifyWebHookType(req) { // check if webhook type for creation const { body: { event_type } } = req; if ( event_type === 'BILLING.SUBSCRIPTION.ACTIVATED' || event_type === 'BILLING.SUBSCRIPTION.CANCELLED' ) return req; else throw { message: 'Webhook type is not supported', type: 'UnsupportedWebhookType' }; } export const createAsyncUserDonation = (user, donation) => { log(`Creating donation:${donation.subscriptionId}`); user .createDonation(donation) .toPromise() .catch(err => { throw new Error(err); }); }; export function createDonationObj(body) { const { resource: { id, status_update_time, subscriber: { email_address } = { email_address: null } } } = body; let donation = { email: email_address, amount: 500, duration: 'month', provider: 'paypal', subscriptionId: id, customerId: email_address, startDate: new Date(status_update_time).toISOString() }; return donation; } export function createDonation(body, app) { const { User } = app.models; const { resource: { subscriber: { email_address } = { email_address: null } } } = body; let donation = createDonationObj(body); let email = email_address; return User.findOne({ where: { email } }, (err, user) => { if (err) throw new Error(err); if (!user) { log(`Creating new user:${email}`); return User.create({ email }) .then(user => { createAsyncUserDonation(user, donation); }) .catch(err => { throw new Error(err); }); } return createAsyncUserDonation(user, donation); }); } export async function cancelDonation(body, app) { const { resource: { id, status_update_time = new Date(Date.now()).toISOString() } } = body; const { Donation } = app.models; Donation.findOne({ where: { subscriptionId: id } }, (err, donation) => { if (err || !donation) throw Error(err); log(`Updating donation record: ${donation.subscriptionId}`); donation.updateAttributes({ endDate: new Date(status_update_time).toISOString() }); }); } export async function updateUser(body, app) { const { event_type } = body; if (event_type === 'BILLING.SUBSCRIPTION.ACTIVATED') { createDonation(body, app); } else if (event_type === 'BILLING.SUBSCRIPTION.CANCELLED') { cancelDonation(body, app); } else throw { message: 'Webhook type is not supported', type: 'UnsupportedWebhookType' }; } export async function createStripeCardDonation(req, res, stripe) { const { body: { token: { id: tokenId }, amount, duration }, user: { name, id: userId, email }, user } = req; if (!tokenId || !amount || !duration || !userId || !email) { throw { message: 'Request is not valid', type: 'InvalidRequest' }; } if (user.isDonating && duration !== 'onetime') { throw { message: `User already has active recurring donation(s).`, type: 'AlreadyDonatingError' }; } let customerId; try { const customer = await stripe.customers.create({ email, card: tokenId, ...(name && { name }) }); customerId = customer?.id; } catch { throw { type: 'customerCreationFailed', message: 'Failed to create stripe customer' }; } log(`Stripe customer with id ${customerId} created`); let subscriptionId; try { const subscription = await stripe.subscriptions.create({ customer: customerId, items: [ { plan: `${donationSubscriptionConfig.duration[ duration ].toLowerCase()}-donation-${amount}` } ] }); subscriptionId = subscription?.id; } catch { throw { type: 'subscriptionCreationFailed', message: 'Failed to create stripe subscription' }; } log(`Stripe subscription with id ${subscriptionId} created`); // save Donation let donation = { email, amount, duration, provider: 'stripe', subscriptionId, customerId, startDate: new Date().toISOString() }; await createAsyncUserDonation(user, donation); return res.status(200).json({ isDonating: true }); }