Hi ππΌ, in this guide, I will show you how to connect Stripe to Vue. This integration is for shopping carts only, not subscriptions. Our working environment is:
- Stripe latest 2022
- Vue.js options API
Due to the extensiveness of this tutorial, I'm going to link my website blog; It has better visual representations.
- If you don't have a project, you can copy mine here: django_store
1. Set Up Stripe.
- To get started with Stripe, you need to open a Stripe account; After opening your account, go to the developer's section and access your API keys. In the developer's section, you will find two keys: publishable key y secret key; copy them in your .env file.
VUE_APP_STRIPE_KEY=<YOUR-PUBLISHABLE-KEY>
2. Integration.
- Once you have opened your account with Stripe, copy the script tag into the head of index.html.
<head>
<script src="https://js.stripe.com/v3/"></script>
</head>
- Immediately after getting the subtotal from the customer, create the onclick event to call the server and request the payment intent. Use the paymentIntent function in different parts of your app, place it in the login form and the guest form.
<!-- template -->
<v-btn
v-if="isCart >= 1"
block
color="#385F73"
class="mt-3"
elevation="2"
:loading=loading
rounded
dark
@click="paymentIntent"
>
Subtotal
</v-btn>
// place this function in different parts of your app
// like the login form and the guest form
// methods
paymentIntent () {
if (this.isLoggedIn) {
this.$store.dispatch('paymentIntent')
.catch((_err) => {
this.loading = false
const show = true
const color = 'red darken-3'
const text = 'Server Error, try again later!'
this.$store.commit('cartSnack', {
show, color, text
})
})
} else {
this.$router.push('/pre-checkout')
}
}
- To send the payment intent from Vue to the server, we are going to use Vuex and Axios. Vuex is the state management for Vuejs applications, and Axios is the http requests manager. In the following code, retrieve the user object and the shopping cart. Send the shopping cart to calculate the transaction total based on the product id. After sending these objects, get the response from the server with the client secret and the tax calculation, send them to the mutations and store them in the state.
// vuex actions
async paymentIntent ({ commit, state }) {
const res = await axios.post('users/secret/', {
user: state.user,
cart: state.cart
})
if (res.status === 201) {
commit('CLIENT_SECRET', {
client_secret: res.data.client_secret,
tax: res.data.tax
})
router.push('/checkout')
}
},
// vuex mutations
CLIENT_SECRET (state, payload) {
const secret = state.secret
secret.clientSecret = payload.client_secret
secret.tax = payload.tax
},
- Initialize the state with the client secret in null and tax in 0.
// vuex state
state: {
secret: {
clientSecret: null,
tax: 0
},
user: {},
cart: []
},
3. Mounting The Card Element.
- Once we have the client secret store in the state, we can use it to mount the Stripe card element. This card element will show to the customer the Stripe form in which the customer will insert his credit card number. Remember Stripe has the transaction total. With this card element, the customer is authorizing and finalizing the transaction.
<!-- template -->
<form
id="payment-form"
v-if="isCart >= 1"
>
<br>
<div ref="paymentElement" id="payment-element">
<!-- Elements will create form elements here -->
</div>
<br>
<div id="error-message">
<!-- Display error message to your customers -->
</div>
</form>
<!-- end -->
<v-card-actions>
<v-btn
v-if="isCart >= 1"
id="submit"
:loading="loading"
block
color="success"
class="mt-3"
elevation="2"
rounded
dark
@click="Submit"
>
Pay
<v-icon class="ml-2">mdi-basket</v-icon>
</v-btn>
</v-card-actions>
- Inside the script tag of Vue, import the store from vuex so we can access the client secret. Use your public secret key and window.Stripe() function to get the form element from Stripe. Your public secret key must be stored in the .env file; never put it directly in the code! Once we get the payment element, we can modify its appearance Stripe appearance-api.
// import mapState and mapGetters to access
// aditional customer variables
// script
import store from '../store'
const stripe = window.Stripe(
process.env.VUE_APP_STRIPE_KEY
)
const options = {
clientSecret: store.state.secret.clientSecret,
// Stripe themes are fully customizable
appearance: { theme: 'stripe' }
}
const elements = stripe.elements(options)
const paymentElement = elements.create('payment')
- Inside export default, mount the Stripe Element using Vue's mounted function.
// export default
mounted () {
paymentElement.mount(this.$refs.paymentElement)
paymentElement.on('change', (event) => {
this.displayError(event)
})
},
- The DisplayError() function will show the customer if his card has errors such as insufficient funds.
// methods
displayError (event) {
const displayError = document.getElementById(
'error-message'
)
if (event.error) {
displayError.textContent = event.error.message
} else {
displayError.textContent = ''
}
},
- Finally, send the transaction through the Submit function. It is important to note that /thankyou/ is the URL parameter of our last route. This is located in the directory under Thankyou.vue. this project uses Vue Router for easy moving between pages. Use mapState to extract properties from the state and send them with the address object.
// methods
async Submit () {
this.loading = true
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// return_url: project final route thankyou.vue
return_url: 'http://localhost:8080/#/thankyou/',
shipping: {
address: {
city: this.city,
line1: this.address,
postal_code: this.zipcode,
state: this.state,
country: 'USA'
},
name: `${this.first_name} ${this.last_name}`,
phone: this.phone
}
}
})
if (error) {
this.loading = false
const messageContainer = document.querySelector(
'#error-message'
)
messageContainer.textContent = error.message
} else {
// Customer redirection to return_url.
}
}
4. Extracting Properties from Stripe.
- If you need to retrieve customer information from Stripe, you should do so at the bottom of the Thankyou.vue page. Through the javascript function URLSearchParams(), we retrieve the payment_intent_client_secret from the URL, this will be used by the Stripe retrievePaymentIntent() function to access the transaction object and be able to send the properties to our server. Once the values have been sent, retrieve the response, and if everything is ok, use COMMIT_CHECKOUT and CLIENT_SECRET environment variables to clear the state.
// mounted
async mounted () {
const stripe = window.Stripe(
process.env.VUE_APP_STRIPE_KEY
)
const clientSecret = new URLSearchParams(
window.location.search
).get(
'payment_intent_client_secret'
)
const {
paymentIntent, error
} = await stripe.retrievePaymentIntent(
clientSecret
)
if (error) {
console.log(error)
this.message = 'An error has ocurred'
}
if (paymentIntent.status === 'succeeded') {
this.message = paymentIntent.status
const payment_id = paymentIntent.id
this.$store.dispatch('checkout', { payment_id })
.catch((_err) => {
this.loading = false
const show = true
const color = 'red darken-3'
const text = 'An error has ocurred'
this.$store.commit('cartSnack',
{
show, color, text
})
})
} else {
this.message = paymentIntent.status
}
}
// actions
async checkout ({ commit, state, getters }, payload) {
const res = await axios.post('orders/order/', {
order: {
stripe_id: payload.payment_id,
subtotal: getters.subTotal,
total: getters.grandTotal,
tax: state.secret.tax,
cart: state.cart,
rating: state.stars,
first_name: state.user.first_name,
last_name: state.user.last_name,
email: state.user.email,
phone: state.user.phone,
address: state.user.address,
city: state.user.city,
state: state.user.state,
zipcode: state.user.zipcode
}
})
if (res.status === 200) {
// Clear the state except for the user
commit('CHECKOUT_SUCCESS')
commit('CLIENT_SECRET', {
client_secret: null,
tax: 0
})
}
},
- Clear the state on susccesful checkout. For the server-side code of this Stripe implementation checkout this blog: How to Connect Stripe to Django Rest Framework?
// mutations
CHECKOUT_SUCCESS (state) {
state.stars = []
state.cart = []
},
Top comments (0)