This is the template project that's checked out and configured when you run the bando-up command from ljsthw-bandolier. This is where the code really lives.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bandolier-template/api/payments/stripe.js

127 lines
3.6 KiB

import { Product, Payment } from '../../lib/models.js';
import { API, developer_admin } from "../../lib/api.js";
import logging from '../../lib/logging.js';
import dayjs from 'dayjs';
import { product as product_config, register_enabled } from "../../client/config.js";
import assert from 'assert';
import * as queues from "../../lib/queues.js";
import stripe_create from "stripe";
import { stripe_private } from "../../lib/config.js";
const log = logging.create("api/payments/stripe.js");
const stripe = stripe_create(stripe_private.secret);
const product = await Product.first({id: product_config.id});
const rules = {
"payment_intent": "required",
"payment_intent_client_secret": "required",
"redirect_status": "required"
};
const create_stripe_payment = async (user) => {
assert(product.price > 1, "Stripe requires the price to be greater than $1.");
const intent = await stripe.paymentIntents.create({
amount: product.price * 100,
currency: product.currency.toLowerCase(),
automatic_payment_methods: {
enabled: true,
},
});
assert(intent, "Failed to create stripe intent.");
let internal_id = Payment.gen_internal_id();
// TODO: see if we can use date-fns instead of dayjs here
let payment = await Payment.insert({
user_id: user.id,
system: 'stripe',
status: 'pending',
status_reason: "started",
internal_id,
sys_primary_id: intent.id,
sys_secondary_id: intent.client_secret,
sys_created_on: new Date()
});
assert(payment && payment.internal_id, "Failed to save payment.");
return [intent, payment];
}
export const post = async (req, res) => {
const api = new API(req, res);
if(!register_enabled) {
return api.error(500, {errors: { main: "Registration is disabled."}});
}
try {
let clientSecret;
const pending = await Payment.first({user_id: api.user.id, status: "pending", system: "stripe"});
if(pending) {
// this already exists so send back the original secret
clientSecret = pending.sys_secondary_id;
} else {
// this is a new request so create it
const [intent, payment] = await create_stripe_payment(api.user);
clientSecret = intent.client_secret;
}
api.reply(200, { clientSecret });
} catch(error) {
log.error(error);
api.error(500, "Fatal error in the server. Tell Zed.");
}
}
post.authenticated = true;
export const get = async (req, res) => {
const api = new API(req, res);
if(!register_enabled) {
return api.error(500, {errors: { main: "Registration is disabled."}});
}
try {
const msg = api.validate(rules);
if(!msg._valid) {
log.error(msg._errors, "Stripe validation failure");
return api.validation_error(res, msg);
} if(msg.redirect_status === "succeeded") {
// find the user's payment attempt using the stripe id
let payment = await Payment.first({
user_id: api.user.id,
system: "stripe",
sys_primary_id: msg.payment_intent
});
// redirect status is succeeded so this is complete
await Payment.update({id: payment.id}, {status: "complete"});
// send out all of the emails and everything
queues.send_welcome(api.user);
queues.send_receipt(api.user, payment.id, product.id);
// downside to stripe is they do a redirect to finish
return api.redirect("/client/#/payment/finished/");
} else {
// need to handle the other stripe redirect states
log.debug("STRIPE ERROR", msg);
return api.error(402, `Stripe error: ${JSON.stringify(msg)}`);
}
} catch(error) {
log.error(error);
return api.error(403, 'Stripe configuration failed');
}
}
get.authenticated = true;