Skip to content

Commit

Permalink
Complete Stripe 3D Secure Integration (#158)
Browse files Browse the repository at this point in the history
  • Loading branch information
gfyre authored May 11, 2021
1 parent 11fadc8 commit 0bee6a8
Show file tree
Hide file tree
Showing 11 changed files with 417 additions and 123 deletions.
27 changes: 27 additions & 0 deletions shopping-server/controllers/orderController.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,33 @@ export default {
}
},

async stripePaymentInstant(req, res, next){
let response;
//console.log(req.user);
try {
response = await orderService.stripePaymentInstant(req.body.checkoutId, req.user);
console.log(response);
return res.status(response.httpStatus).send(response);
}
catch(err) {
logger.error("Error in stripePaymentInstant Controller", {meta: err});
return res.status(httpStatus.INTERNAL_SERVER_ERROR).send({httpStatus: httpStatus.INTERNAL_SERVER_ERROR, status: "failed", errorDetails: err});
}
},

async completeCheckoutUsingStripePaymentInstant(req, res, next) {
let response;
try {
response = await orderService.completeCheckoutUsingStripePaymentInstant(req.body.checkoutId, req.body.paymentToken, req.user);
return res.status(response.httpStatus).send(response);
}
catch(err) {
logger.error("Error in completeCheckoutUsingStripePaymentInstant Controller", {meta: err});
return res.status(httpStatus.INTERNAL_SERVER_ERROR).send({httpStatus: httpStatus.INTERNAL_SERVER_ERROR, status: "failed", errorDetails: err});
}
},


async completeCheckoutUsingCard(req, res, next) {
let response;
try {
Expand Down
4 changes: 4 additions & 0 deletions shopping-server/routes/orders.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ router.post('/createPaymentToken', orderController.createPaymentToken);

router.post('/completeCheckout', orderController.completeCheckout);

router.post('/stripePaymentInstant', orderController.stripePaymentInstant);

router.post('/completeCheckoutUsingStripePaymentInstant', orderController.completeCheckoutUsingStripePaymentInstant);

module.exports = router;
312 changes: 223 additions & 89 deletions shopping-server/services/orderService.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export default {
this.$store.commit('cartStore/resetOrders');
this.$router.push(`/orders/${data.order_id}`);
notification.success(this, 'Payment was successful.');
console.log('payWithStripe token', token);
} catch (error) {
console.log('Error with Veniqa payment', error);
notification.error(
Expand Down
120 changes: 105 additions & 15 deletions shopping-webclient/src/components/checkout/stripe/Stripe.vue
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
<template>
<div>
<!-- <stripe-payment/> -->
<card class='stripe-card'
:class='{ complete }'
:stripe='stripeKey'
:options='stripeOptions'
:paymentReqOptions='reqParams'
@change='complete = $event.complete'
@token='payWithGpay'
<card
class="stripe-card"
:class="{ complete }"
:stripe="stripeKey"
:options="stripeOptions"
:paymentReqOptions="reqParams"
@change="complete = $event.complete"
@token="payWithGpay"
/>
<br>
<b-btn class='pay-with-stripe' @click='pay' :disabled='!complete'>Pay with credit card</b-btn>
<br />
<b-btn
class="pay-with-stripe"
@click="pay($event)"
:disabled="!complete"
style="font-family: Libre Baskerville; font-size: 14px"
>Pay by card</b-btn
>
</div>
</template>

<script>
import _ from 'lodash';
import { Card, Stripe } from './index';
import PaymentRequestDTO from './StripePaymentRequestDTO.json';
import { mapGetters } from 'vuex';
import Vue from 'vue';
import ProxyUrl from '@/constants/ProxyUrls';
export default {
components: {
Expand Down Expand Up @@ -52,15 +62,94 @@ export default {
},
methods: {
async pay() {
async pay(event) {
// createToken returns a Promise which resolves in a result object with
// either a token or an error key.
// See https://stripe.com/docs/api#tokens for the token object.
// See https://stripe.com/docs/api#errors for the error object.
// More general https://stripe.com/docs/stripe.js#stripe-create-token.
try {
const data = await Stripe.createToken();
this.$emit('pay', data.token);
// const data = await Stripe.createToken();
// this.$emit('pay', data.token);
event.target.disabled = true;
let checkoutId = this.checkoutId;
const { data } = await Vue.prototype.$axios({
url: ProxyUrl.stripeInstantPay,
method: 'post',
data: {
checkoutId,
},
});
if (data && data.httpStatus === 200) {
console.log(data.responseData);
if (data.responseData) {
const paymentMethodReq = await Stripe.createPaymentMethod(
'card',
{}
);
console.log(paymentMethodReq);
if (paymentMethodReq.paymentMethod.id) {
const confirmPayment = await Stripe.confirmCardPayment(
data.responseData.client_secret,
{
payment_method: paymentMethodReq.paymentMethod.id,
}
);
console.log(confirmPayment);
if (confirmPayment) {
console.log('inside confirmPayment', confirmPayment);
if (confirmPayment.paymentIntent.status == 'succeeded') {
console.log('inside confirmPayment status', confirmPayment);
if (confirmPayment.hasOwnProperty('error')) {
this.$notify({
group: 'toast',
type: 'error',
text: `${confirmPayment.error.code}, ${confirmPayment.error.message}. Please try again later`,
});
event.target.disabled = false;
}
const { data } = await Vue.prototype.$axios({
url: ProxyUrl.stripeInstantPayment,
method: 'post',
data: {
checkoutId,
paymentToken: confirmPayment.paymentIntent.id,
},
});
if (data && data.httpStatus === 200) {
return this.$router.push('/orders');
//return data.responseData;
}
//this.createPayment(confirmPayment.paymentIntent.id);
} else {
event.target.disabled = false;
throw new Error('Card details are invalid !');
}
}
}
}
// const { data } = await Vue.prototype.$axios({
// url: ProxyUrl.stripePay,
// method: 'post',
// data: {
// checkoutId,
// paymentToken: token.id,
// },
// });
/**
* Response data:
* {
* orderId: 'xxx'
* }
*/
//return data.responseData;
}
// const data = await Stripe.createToken();
console.log('stripe payWithStripe token', this.checkoutId);
//this.$emit('pay', data.token);
} catch (error) {
console.log('Stripe error', error.message);
}
Expand All @@ -73,19 +162,20 @@ export default {
},
computed: {
...mapGetters({
checkoutId: 'cartStore/checkoutId',
}),
// reqParams() {
// let params = _.cloneDeep(PaymentRequestDTO);
// params.total.amount = this.totalCost;
// return params;
// }
},
};
</script>

<style lang="scss" scoped>
.pay-with-stripe{
.pay-with-stripe {
padding: 5px;
background-image: linear-gradient(to right, #267871, #136a8a) !important;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export default {
event.updateWith({ status: 'success' });
});
console.log('stripe element payWithStripe token');
this._paymentRequest.on('token', ev => this.$emit('token', ev));
this._paymentBtn = createPayButton();
Expand Down
2 changes: 1 addition & 1 deletion shopping-webclient/src/components/checkout/stripe/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ export {
PostalCode,
StripeElement,
baseStyle,
Stripe,
Stripe
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ export const Stripe = {
retrieveSource: null,
elements: null,
paymentRequest: null,
redirectToCheckout: null,
retrievePaymentIntent: null,
handleCardPayment: null,
handleCardSetup: null,
handleCardAction: null,
confirmPaymentIntent: null,
createPaymentMethod: null,
confirmCardPayment: null
};

export const baseStyle = {
Expand Down Expand Up @@ -47,10 +55,24 @@ export function create(elementType, keyOrStripe, options = {}) {
options.style = Object.assign(baseStyle, options.style || {});

const element = Stripe.elements.create(elementType, options);
console.log(element);

Stripe.createToken = opts => Stripe.instance.createToken(element, opts);
Stripe.createSource = opts => Stripe.instance.createSource(element, opts);
Stripe.retrieveSource = opts => Stripe.instance.retrieveSource(opts);
Stripe.redirectToCheckout = (options) => Stripe.instance.redirectToCheckout(options)
Stripe.retrievePaymentIntent = (clientSecret) => Stripe.instance.retrievePaymentIntent(clientSecret)
Stripe.handleCardPayment = (clientSecret, data) => Stripe.instance.handleCardPayment(clientSecret, element, data)
Stripe.handleCardSetup = (clientSecret, data) => Stripe.instance.handleCardSetup(clientSecret, element, data)
Stripe.handleCardAction = (clientSecret) => Stripe.instance.handleCardAction(clientSecret)
Stripe.confirmPaymentIntent = (clientSecret, data) => Stripe.instance.confirmPaymentIntent(clientSecret, element, data)
Stripe.createPaymentMethod = (cardType, data) => Stripe.instance.createPaymentMethod(cardType, element, data)
Stripe.confirmCardPayment = (clientSecret, data) => Stripe.instance.confirmCardPayment(clientSecret, {
payment_method:{
card:element,
billing_details: {}
}
}, data)

return element;
}
Expand Down Expand Up @@ -80,4 +102,12 @@ export function destroy() {
Stripe.createToken = null;
Stripe.createSource = null;
Stripe.retrieveSource = null;
Stripe.redirectToCheckout = null;
Stripe.retrievePaymentIntent = null;
Stripe.handleCardPayment = null;
Stripe.handleCardSetup = null;
Stripe.handleCardAction = null;
Stripe.confirmPaymentIntent = null;
Stripe.createPaymentMethod = null;
Stripe.confirmCardPayment = null;
}
4 changes: 4 additions & 0 deletions shopping-webclient/src/constants/ProxyUrls.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export default {
// Payment URLS
stripePay: '/orders/completeCheckoutUsingCard',
khaltiPay: '/orders/completeCheckoutUsingKhalti',

// Stripe Payment Intents
stripeInstantPay: '/orders/stripePaymentInstant',
stripeInstantPayment: '/orders/completeCheckoutUsingStripePaymentInstant',

// Categories List
categoriesUrl: '/ui/productCategoryList',
Expand Down
36 changes: 18 additions & 18 deletions shopping-webclient/src/services/paymentService.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,26 @@ import Vue from 'vue';
export default {
async payWithStripe(token, checkoutId) {
try {
const { data } = await Vue.prototype.$axios({
url: ProxyUrl.stripePay,
method: 'post',
data: {
checkoutId,
paymentToken: token.id,
},
});
// const { data } = await Vue.prototype.$axios({
// url: ProxyUrl.stripePay,
// method: 'post',
// data: {
// checkoutId,
// paymentToken: token.id,
// },
// });

if (data && data.httpStatus === 200) {
/**
* Response data:
* {
* orderId: 'xxx'
* }
*/
return data.responseData;
}
// if (data && data.httpStatus === 200) {
// /**
// * Response data:
// * {
// * orderId: 'xxx'
// * }
// */
// return data.responseData;
// }

throw new Error('Error occured during Strip transactions');
// throw new Error('Error occured during Strip transactions');
} catch (error) {
throw error;
}
Expand Down
2 changes: 2 additions & 0 deletions shopping-webclient/src/store/cart.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default {
* @param {*} { state }
*/
async pay({ state, commit }) {
console.log('pay in cart');
const { data } = await Vue.prototype.$axios({
url: ProxyUrl.createPaymentToken,
method: 'post',
Expand Down Expand Up @@ -54,6 +55,7 @@ export default {
addressId: address._id
};
try {
console.log('createCheckout');
const { data } = await Vue.prototype.$axios({
url: ProxyUrl.createCheckout,
method: 'post',
Expand Down

0 comments on commit 0bee6a8

Please sign in to comment.