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 @@
action-complete Component
+
+ Thank you {{ data.email }} for your ${{ data.amount * 0.01 }} donation!
+