diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml index 0812ef743..4d7ab08b5 100644 --- a/.docker/docker-compose.yml +++ b/.docker/docker-compose.yml @@ -10,6 +10,7 @@ services: - POSTGRES_HOST=amplify_db ports: - 8080:8080 + - 5000:5000 volumes: - type: bind source: '../' diff --git a/.env b/.env index 193e5c6d6..7a551280e 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ ########################### -# Example `.env` file # +# Environment Variables # ########################### # API key for the Cicero API @@ -8,7 +8,11 @@ CICERO_API_KEY= # Test environment API key for the Lob API # This is only required for running the integration tests successfully! -LOB_API_KEY="test_eee78731c29fe718a5322e0cd51e9a02f0e" +LOB_API_KEY=test_eee78731c29fe718a5322e0cd51e9a02f0e +LOB_BASE_URL=https://api.lob.com/v1 + +# Sendgrid +SENDGRID_API_KEY= # Auth0 authentication parameters # For local development, you can just use these literal nonsense values for now @@ -25,19 +29,12 @@ TWILIO_ACCOUNT_SID=your_twilio_account_id # this is user generated TWILIO_AUTH_TOKEN=TWILIO_SECRET_KEY # To run in single-campaign mode, set the following environment vars +# **We always run in single-campaign mode now so this is mandatory!** +# Use campaign seeder file for local dev campaigns. VUE_APP_CAMPAIGN_MODE=single VUE_APP_FEATURED_CAMPAIGN=1 -VUE_APP_LETTER_TEMPLATE=tmpl_951e09eb07bfd24 POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres POSTGRES_PORT=5432 POSTGRES_HOST=amplify_db - -# Enable $0 transactions (set to 'on' to enable, used in both FE and BE) -# If EMPTY_TRANSACTIONS=on then NO_COST_MAIL should = true -VUE_APP_EMPTY_TRANSACTIONS=on -VUE_APP_NO_COST_MAIL=true -VUE_APP_COUPON_CODE= -VUE_APP_SHOW_EXT_DONATION=false -VUE_APP_EXT_DONATION_URL="https://greenaction.org" \ No newline at end of file diff --git a/server/db/migrations/20241013134620_add_tracking_to_letters.js b/server/db/migrations/20241013134620_add_tracking_to_letters.js new file mode 100644 index 000000000..15b478f8b --- /dev/null +++ b/server/db/migrations/20241013134620_add_tracking_to_letters.js @@ -0,0 +1,13 @@ +module.exports = { + async up(knex) { + await knex.schema.alterTable('letters', (table) => { + table.uuid('tracking_number') + }) + }, + + async down(knex) { + await knex.schema.alterTable('letters', (table) => { + table.dropColumn('tracking_number') + }) + } +} diff --git a/server/routes/api/checkout.js b/server/routes/api/checkout.js index 8abe83aaa..a9864db9d 100644 --- a/server/routes/api/checkout.js +++ b/server/routes/api/checkout.js @@ -1,4 +1,5 @@ const express = require('express') +const axios = require('axios') const { v4: uuidv4 } = require('uuid') const { Stripe, StripeError } = require('../../lib/stripe') const { @@ -108,6 +109,7 @@ router.post('/create-checkout-session', async (req, res) => { .json({ url: session.url, sessionId: session.id }) .end() } catch (error) { + console.error(error) let statusCode = 500 if (error instanceof PaymentPresenterError) { @@ -123,11 +125,12 @@ router.post('/create-checkout-session', async (req, res) => { router.post('/process-transaction', async (req, res) => { try { - const stripe = new Stripe() + // const stripe = new Stripe() // If livemode is false, disable signature checking // and event reconstructionfor ease of testing. let event + /* if (stripe.livemode) { const signature = req.headers['stripe-signature'] if (!signature) throw new CheckoutError('No stripe signature on request!') @@ -135,13 +138,19 @@ router.post('/process-transaction', async (req, res) => { event = stripe.validateEvent(signature, req.rawBody) } else { event = req.body - console.log(event) + // console.log(event) } + */ + event = req.body + + if (!event) throw new CheckoutError('Unprocessable message') const data = event.data const { id: paymentIntent, amount } = data.object const [eventType, eventOutcome] = req.body.type.split('.') + console.log(paymentIntent, amount, eventOutcome) + // We are not going to send letters from here just yet // so we will record the transaction no matter the outcome. if (eventType !== 'payment_intent') { @@ -150,12 +159,53 @@ router.post('/process-transaction', async (req, res) => { ) } - await Transaction.query() - .patch({ amount, status: eventOutcome }) - .where({ stripe_transaction_id: paymentIntent }) + const transaction = await Transaction.query().findOne({ stripe_transaction_id: paymentIntent }) + await transaction.$query().patch({ status: eventOutcome }) + + console.log(`transaction = ${transaction.id}, ${transaction.status}`) + + const letter = await Letter.query().where({ transaction_id: transaction.id }).first() + letter.trackingNumber = uuidv4() + const letterTemplate = JSON.parse(letter.letterTemplate) + + console.log(`letter: ${letter.id}, ${letterTemplate}`) + + const lobApiKey = process.env.LOB_API_KEY + const lobCredentials = btoa(`${lobApiKey}:`) + + console.log(letter.addressee, letter.addressLine1) + const lobResponse = await axios.post( + 'https://api.lob.com/v1/letters', + { + to: { + name: letter.addressee, + address_line1: letter.addressLine1, + address_line2: letter.addressLine2, + address_city: letter.city, + address_state: letter.state, + address_zip: letter.zip + }, + from: letter.returnAddress, + color: false, + use_type: 'operational', + file: letterTemplate.latest_template_preview.template_id, + merge_variables: letter.mergeVariables + }, + { + headers: { + Authorization: `Basic ${lobCredentials}`, + 'Idempotency-Key': letter.trackingNumber + } + } + ) + + if (!lobResponse.statusCode === 200) throw new CheckoutError(lobResponse) + + await letter.$query().patch({ sent: true}) - return res.status(200).end() + return res.status(201).end() } catch (error) { + console.error(error.response.data.error) let statusCode = 500 if (error instanceof CheckoutError) {