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_id, 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_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;