diff --git a/server/__tests__/integration/lob.test.js b/server/__tests__/integration/lob.test.js index 9f4206723..7acae5a4e 100644 --- a/server/__tests__/integration/lob.test.js +++ b/server/__tests__/integration/lob.test.js @@ -353,9 +353,13 @@ describe('POST /api/lob/createLetter', () => { } const template_id = 'tmpl_1057bb6f50f81fb' + + // A test payment intent that should return status of 'succeeded' + const paymentIntentId = 'pi_3L7VXGFqipIA40A31qbflVvO' + const response = await request(app) .post(route) - .send({ description, to, from, template_id }) + .send({ description, to, from, template_id, paymentIntentId }) expect(response.status).toBe(200) expect(response.body).toEqual({ expected_delivery_date: expect.any(String) diff --git a/server/routes/api/checkout.js b/server/routes/api/checkout.js index 5bfbcd278..9d37324a2 100644 --- a/server/routes/api/checkout.js +++ b/server/routes/api/checkout.js @@ -7,37 +7,40 @@ const db = createClient() const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY) router.post('/create-transaction', async (req, res) => { - const { transaction, email, campaignId, donationId } = req.body || {} - if (!transaction || !email) { - return null + const { session_id /*, email, campaignId, donationId */ } = req.body || {} + if (!session_id /*|| !email*/) { + return res.status(400).send({ error: 'No session ID' }) } + const session = await stripe.checkout.sessions.retrieve(session_id) + const customer = await stripe.customers.retrieve(session.customer) const formattedTransaction = { - stripe_transaction_id: transaction.id, - amount: transaction.amount, - stripe_client_secret: transaction.client_secret, - currency: transaction.currency, - payment_method: transaction.payment_method, - payment_method_type: transaction.payment_method_types[0], - email // to-do: get user email from the server auth, if possible + stripe_transaction_id: session_id, + amount: session.amount_total, + currency: session.currency, + payment_method: 'something not empty', // Not sure what this is for + payment_method_type: session.payment_method_types[0], + email: session.customer_details.email // to-do: get user email from the server auth, if possible } try { + // Expire session? await db('transactions').insert(formattedTransaction) - res.send({ - status: 'ok' - }) + res.status(200).send(formattedTransaction) } catch (error) { console.log({ error }) + res.status(400).send() } }) // 1. send a request to `/create-payment-intent` // with a `donationAmount` as string or integer // If user doesn't select any particular `donationAmount`, send `1` in the donationAmount -// 2. This API will return the client secret. Use it to complete the transaction in the UI +// 2. This API will redirect the client to a Stripe Checkout page +// 3. Once user completes payment, will redirect back to `success_url` with +// a Stripe session_id included in the URL. -router.post('/create-payment-intent', async (req, res) => { +router.post('/create-checkout-session', async (req, res) => { try { const acceptableCharges = [1, 2, 20, 50] const { donationAmount } = req.body || {} @@ -47,13 +50,28 @@ router.post('/create-payment-intent', async (req, res) => { return res.status(400).send({ error: 'Invalid Amount' }) } - const paymentIntent = await stripe.paymentIntents.create({ - amount: parsedDonationAmount * 100, // in cents - currency: 'usd' - }) - res.send({ - clientSecret: paymentIntent.client_secret + const origin = req.get('origin') + + const session = await stripe.checkout.sessions.create({ + line_items: [ + { + price_data: { + currency: 'usd', + product_data: { + name: 'Donation' + }, + unit_amount: parsedDonationAmount * 100 + }, + quantity: 1 + } + ], + mode: 'payment', + allow_promotion_codes: true, + success_url: origin + '/complete?session_id={CHECKOUT_SESSION_ID}', + cancel_url: origin }) + + res.json({ url: session.url }) } catch (error) { console.log({ error }) } diff --git a/server/routes/api/lob.js b/server/routes/api/lob.js index a5d306365..861e0dee1 100644 --- a/server/routes/api/lob.js +++ b/server/routes/api/lob.js @@ -145,13 +145,25 @@ router.post('/createAddress', async (req, res) => { }) router.post('/createLetter', async (req, res) => { - // Get description, to, and template_id from request body - const { description, to, from, template_id, charge } = req.body || {} + // Get description, to, and template_id, and paymentIntent id from request body + const { description, to, from, template_id, paymentIntentId } = req.body || {} const lobApiKey = getLobApiKey() const lob = new Lob({ apiKey: lobApiKey }) const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY) try { + // Check for completed payment before creating letter. Status can be succeeded, failed, or pending. Return server error if failure or pending. + const paymentVerification = await stripe.paymentIntents.retrieve( + paymentIntentId + ) + + if (paymentVerification.status !== 'succeeded') { + return res + .status(500) + .json({ msg: 'Payment is still pending or has failed.' }) + .end() + } + // Create Lob address using variables passed into route via post body const letter = await lob.letters.create({ description: description, @@ -174,7 +186,7 @@ router.post('/createLetter', async (req, res) => { } catch (error) { // We'll need a stripe test env key to test this in our integration tests const refund = await stripe.refunds.create({ - charge: charge + payment_intent: paymentIntentId }) // TODO handle error for refund error. Not doing this currently because chance of // user making it this far in the process and both LOB API and Stripe failing is very small. diff --git a/src/components/ActionComplete.vue b/src/components/ActionComplete.vue index 81b0295f0..49468b04b 100644 --- a/src/components/ActionComplete.vue +++ b/src/components/ActionComplete.vue @@ -1,28 +1,43 @@ diff --git a/src/components/DonateMoney.vue b/src/components/DonateMoney.vue index 438f332dc..d6f15d78c 100644 --- a/src/components/DonateMoney.vue +++ b/src/components/DonateMoney.vue @@ -8,45 +8,48 @@ representatives, from the comfort of your home or on the go.

- - 2 + + 2 - 20 + 20 - 50 + 50
- Submit + Submit
- + diff --git a/src/components/SignName.vue b/src/components/SignName.vue index 80c9bd786..c9858a6c4 100644 --- a/src/components/SignName.vue +++ b/src/components/SignName.vue @@ -11,7 +11,7 @@ label="Full Name" placeholder="John Doe" required - > + /> + /> + /> + /> + /> + /> {{ message }} - + Cancel - + -