Skip to content

Commit 86080c5

Browse files
Merge pull request #523 from ProgramEquity/407-custom-donation-input-2
2 parents df290a6 + 1cdb2df commit 86080c5

4 files changed

Lines changed: 184 additions & 58 deletions

File tree

server/routes/api/checkout.js

Lines changed: 46 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
/* eslint-disable no-unused-vars */
2-
31
const express = require('express')
42
const { createClient } = require('../../db')
53
const router = express.Router()
64
const db = createClient()
75
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY)
6+
const { formatDonationAmount } = require('../../../util/format')
7+
const { validateDonationAmount } = require('../../../util/validate')
88

99
router.post('/create-transaction', async (req, res) => {
1010
const { sessionId /*, email /*, campaignId, donationId */ } = req.body || {}
@@ -42,48 +42,56 @@ router.post('/create-transaction', async (req, res) => {
4242
// a Stripe session_id included in the URL.
4343

4444
router.post('/create-checkout-session', async (req, res) => {
45-
try {
46-
const acceptableCharges = [1, 2, 20, 50]
47-
const { donationAmount } = req.body || {}
48-
const parsedDonationAmount = parseInt(donationAmount, 10)
45+
const { donationAmount } = req.body || {}
46+
const origin = req.get('origin')
4947

50-
let donation
48+
const input = formatDonationAmount(donationAmount)
49+
const inputIsValid = validateDonationAmount(input)
5150

52-
if (parsedDonationAmount < 2) {
53-
// TODO: Change to something better later.
54-
donation = 150
55-
} else {
56-
donation = parsedDonationAmount * 100
57-
}
51+
if (inputIsValid) {
52+
const donationAmountForStripe = input * 100 // Stripe accepts values in cents
53+
let session
5854

59-
if (!acceptableCharges.includes(parsedDonationAmount)) {
60-
return res.status(400).send({ error: 'Invalid Amount' })
61-
}
55+
try {
56+
session = await stripe.checkout.sessions.create({
57+
line_items: [
58+
{
59+
price_data: {
60+
currency: 'usd',
61+
product_data: {
62+
name: 'Donation'
63+
},
64+
unit_amount: donationAmountForStripe
65+
},
66+
quantity: 1
67+
}
68+
],
69+
mode: 'payment',
70+
allow_promotion_codes: true,
71+
success_url: origin + '/complete?session_id={CHECKOUT_SESSION_ID}',
72+
cancel_url: origin
73+
})
74+
} catch (error) {
75+
const data = {
76+
type: error.type,
77+
code: error.raw.code,
78+
url: error.raw.doc_url,
79+
message: 'An error occurred with Stripe checkout',
80+
entire_error_object: error
81+
}
6282

63-
const origin = req.get('origin')
83+
console.log(data)
84+
return res.status(500).json(data)
85+
}
86+
// console.log('session:', session)
6487

65-
const session = await stripe.checkout.sessions.create({
66-
line_items: [
67-
{
68-
price_data: {
69-
currency: 'usd',
70-
product_data: {
71-
name: 'Donation'
72-
},
73-
unit_amount: donation
74-
},
75-
quantity: 1
76-
}
77-
],
78-
mode: 'payment',
79-
allow_promotion_codes: true,
80-
success_url: origin + '/complete?session_id={CHECKOUT_SESSION_ID}',
81-
cancel_url: origin
88+
// the redirection happens within `DonateMoney.vue`
89+
return res.status(200).json({ url: session.url, sessionId: session.id })
90+
} else {
91+
return res.status(400).send({
92+
error: 'Bad request: did not create Stripe checkout session',
93+
message: 'Check backend console for possible failing reasons'
8294
})
83-
84-
res.json({ url: session.url, sessionId: session.id })
85-
} catch (error) {
86-
console.log({ error })
8795
}
8896
})
8997

src/components/DonateMoney.vue

Lines changed: 109 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,68 @@
88
representatives, from the comfort of your home or on the go.
99
</p>
1010

11-
<v-btn-toggle v-model="donation" tile color="deep-purple accent-3" group>
12-
<v-btn elevation="2" raised :value="2"> 2 </v-btn>
11+
<v-btn-toggle
12+
v-model="donationAmount"
13+
class="d-flex flex-wrap justify-center"
14+
tile
15+
color="deep-purple accent-3"
16+
group
17+
>
18+
<v-btn
19+
elevation="2"
20+
raised
21+
:value="2"
22+
@click="unsetCustomAmountSelection"
23+
>
24+
2
25+
</v-btn>
1326

14-
<v-btn elevation="2" raised :value="20"> 20 </v-btn>
27+
<v-btn
28+
elevation="2"
29+
raised
30+
:value="20"
31+
@click="unsetCustomAmountSelection"
32+
>
33+
20
34+
</v-btn>
1535

16-
<v-btn elevation="2" :value="50"> 50 </v-btn>
36+
<v-btn
37+
elevation="2"
38+
raised
39+
:value="50"
40+
@click="unsetCustomAmountSelection"
41+
>
42+
50
43+
</v-btn>
44+
45+
<v-btn
46+
elevation="2"
47+
raised
48+
:value="customDonationAmount"
49+
@click="handleCustomAmountSelection"
50+
>
51+
Custom Amount
52+
</v-btn>
1753
</v-btn-toggle>
54+
55+
<div class="d-flex justify-center flex-column align-center :width=100%">
56+
<v-text-field
57+
v-if="customAmountSelected"
58+
ref="input"
59+
v-model="customDonationAmount"
60+
:rules="inputRule"
61+
class="custom-donation-amount-textfield"
62+
inputmode="numeric"
63+
label="Donation Amount"
64+
type="number"
65+
:max="10000"
66+
:min="1.5"
67+
:precision="2"
68+
:step="0.01"
69+
:value="customDonationAmount"
70+
required
71+
/>
72+
</div>
1873
</v-col>
1974
<div>
2075
<v-btn outlined color="primary" text @click="submit()"> Submit </v-btn>
@@ -24,33 +79,67 @@
2479

2580
<script lang="js">
2681
import axios from 'axios'
82+
import { formatDonationAmount } from '../../util/format.js'
83+
import { validateDonationAmount } from '../../util/validate.js'
2784
export default {
28-
name: 'DonateMoney',
29-
data () {
30-
return {
31-
donation: 1.50
32-
}
85+
name: 'DonateMoney',
86+
props: [],
87+
data () {
88+
return {
89+
donationAmount: 2,
90+
customAmountSelected: false,
91+
customDonationAmount: undefined,
92+
inputRule: [
93+
(val) => validateDonationAmount(formatDonationAmount(val)) || 'Invalid amount: acceptable value ranges between $1.50 and $10,000.00'
94+
]
95+
}
96+
},
97+
computed: {
98+
},
99+
mounted () {
100+
},
101+
methods: {
102+
unsetCustomAmountSelection() {
103+
this.customAmountSelected = false;
33104
},
34-
computed: {
105+
handleCustomAmountSelection() {
106+
this.customAmountSelected = !this.customAmountSelected;
35107
},
36-
mounted () {
108+
submit() {
109+
const value = this.customAmountSelected ?
110+
this.customDonationAmount : this.donationAmount;
111+
const input = formatDonationAmount(value);
112+
113+
if (this.customAmountSelected) {
114+
// inputRule provides user feedback on input, but actual validation occurs on submit
115+
if (this.$refs.input.validate()) this.createCheckoutSession(input);
116+
}
117+
118+
if (validateDonationAmount(input)) this.createCheckoutSession(input);
119+
120+
return;
37121
},
38-
methods: {
39-
submit () {
40-
axios.post('/api/checkout/create-checkout-session', {donationAmount: this.donation})
122+
createCheckoutSession(donationAmount) {
123+
axios.post('/api/checkout/create-checkout-session', {donationAmount})
41124
.then((response) => {
42-
console.log(response);
43125
// Dump state to local storage before redirect
44126
this.$store.dispatch('dumpStateToLocalStorage', response.data.sessionId)
45-
// Redirect to stripe
127+
// Redirect to Stripe
46128
location.href = response.data.url
47129
})
48-
.catch(function (error) {
49-
console.log(error)
130+
.catch((error) => {
131+
// Bring custom error message to top-level for ease of debugging
132+
const { data } = error.response;
133+
console.log(data, error)
50134
})
51-
}
52135
}
136+
}
53137
}
54138
</script>
55139

56-
<style scoped lang="less"></style>
140+
<style scoped lang="less">
141+
.custom-donation-amount-textfield {
142+
margin-top: 1em;
143+
width: 10em;
144+
}
145+
</style>

util/format.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// format input value
2+
function formatDonationAmount(value) {
3+
// separating parameter assignment and parseFloat operation for consistent outcome
4+
value = parseFloat(value) // outputs: number
5+
value = value.toFixed(2) // outputs: string
6+
value = parseFloat(value) // outputs: number
7+
return value // number
8+
}
9+
10+
module.exports = { formatDonationAmount }

util/validate.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// validate input value, expects a number as parameter
2+
function validateDonationAmount(value) {
3+
let message = ''
4+
5+
if (value > 1.49 && value < 10000.01) return true
6+
7+
if (isNaN(value)) message = 'Please select or enter a valid amount'
8+
if (value < 1.5) message = 'Please enter a donation amount higher than $1.50'
9+
if (value > 10000)
10+
message =
11+
'Amplify currently only accept donation amounts less than $10,000.00'
12+
13+
// logs message to help with debugging
14+
if (process.env.NODE_ENV === 'development') console.log(message)
15+
16+
return false
17+
}
18+
19+
module.exports = { validateDonationAmount }

0 commit comments

Comments
 (0)