Skip to content

Commit

Permalink
Stripe 3ds
Browse files Browse the repository at this point in the history
  • Loading branch information
scottyzen committed Mar 15, 2024
1 parent 567d5d1 commit 5da380c
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 89 deletions.
4 changes: 4 additions & 0 deletions functions/stripe.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ exports.handler = async (event, context) => {
};
} catch (error) {
console.log(error);
return {
statusCode: 500,
body: JSON.stringify({ error: error.raw.message }),
};
}
};
44 changes: 44 additions & 0 deletions woonuxt_base/components/shopElements/StripeElement.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script setup>
const { cart } = useCart();
const { stripe } = defineProps(['stripe']);
const emit = defineEmits(['elements']);
let elements = null;
const refreshStripeElements = async () => {
const totalForStripe = Math.round(parseFloat(cart.value?.rawTotal) * 100);
if (!stripe || !cart.value || !totalForStripe) return;
const { client_secret } = await fetch('/api/stripe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount: totalForStripe }),
}).then((res) => res.json());
const stripeEl = document.getElementById('stripe-payment');
elements = stripe?.elements({ clientSecret: client_secret });
const payEl = elements?.create('payment', { layout: 'tabs' });
if (payEl && stripeEl) {
payEl.mount(stripeEl);
emit('updateElements', elements);
}
};
watch(
() => cart.value,
(newVal) => {
if (newVal) refreshStripeElements();
},
);
onMounted(() => {
refreshStripeElements();
});
</script>
<template>
<div id="stripe-payment" class="w-full">
<LoadingIcon />
</div>
</template>
4 changes: 3 additions & 1 deletion woonuxt_base/composables/useCheckout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function useCheckout() {
});
}

const proccessCheckout = async () => {
const proccessCheckout = async (isPaid = false) => {
const { loginUser } = useAuth();
const router = useRouter();
const { replaceQueryParam } = useHelpers();
Expand Down Expand Up @@ -94,6 +94,7 @@ export function useCheckout() {
customerNote: orderInput.value.customerNote,
shipToDifferentAddress: orderInput.value.shipToDifferentAddress,
transactionId: orderInput.value.transactionId,
isPaid,
};

if (orderInput.value.createAccount) {
Expand All @@ -105,6 +106,7 @@ export function useCheckout() {
}

const { checkout } = await GqlCheckout(checkoutPayload);
console.log({ checkout });

if (orderInput.value.createAccount) {
await loginUser({
Expand Down
109 changes: 21 additions & 88 deletions woonuxt_base/pages/checkout.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script setup>
import { StripeElements, StripeElement } from 'vue-stripe-js';
import { loadStripe } from '@stripe/stripe-js';
const { t } = useI18n();
Expand All @@ -9,68 +8,47 @@ const { customer, viewer } = useAuth();
const { orderInput, isProcessingOrder, proccessCheckout } = useCheckout();
const runtimeConfig = useRuntimeConfig();
const stripeKey = runtimeConfig.public?.STRIPE_PUBLISHABLE_KEY;
const stripeCardIsComplete = ref(false);
const buttonText = ref(isProcessingOrder.value ? t('messages.general.processing') : t('messages.shop.checkoutButton'));
const isCheckoutDisabled = computed(() => {
if (orderInput.value.paymentMethod === 'stripe') {
return isProcessingOrder.value || isUpdatingCart.value || !orderInput.value.paymentMethod || !stripeCardIsComplete.value;
}
return isProcessingOrder.value || isUpdatingCart.value || !orderInput.value.paymentMethod;
});
const isCheckoutDisabled = computed(() => isProcessingOrder.value || isUpdatingCart.value || !orderInput.value.paymentMethod);
const emailRegex = new RegExp('^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}$');
const isInvalidEmail = ref(false);
let stripe = stripeKey ? await loadStripe(stripeKey) : null;
let elements = ref(null);
const isPaid = ref(false);
const instanceOptions = ref({});
const elementsOptions = ref({});
const cardOptions = ref({ hidePostalCode: true });
const card = ref();
const elms = ref();
const stripe = stripeKey ? await loadStripe(stripeKey) : null;
// Initialize Stripe.js
onBeforeMount(() => {
onBeforeMount(async () => {
if (query.cancel_order) window.close();
});
const payNow = async () => {
buttonText.value = t('messages.general.processing');
const stripeReturnUrl = window.location.href;
const isStripe = orderInput.value.paymentMethod === 'stripe';
try {
if (orderInput.value.paymentMethod === 'stripe') {
const cardElement = card.value.stripeElement;
const headers = { 'Content-Type': 'application/json' };
// createPaymentIntent
const all = await fetch('/api/stripe', {
method: 'POST',
headers,
body: JSON.stringify({
amount: 1200,
currency: 'usd',
}),
}).then((res) => res.json());
console.log({ all });
// confirmPayment
const data = await elms.value.instance.confirmCardPayment(all.client_secret, {
payment_method: {
card: cardElement,
},
if (isStripe) {
const { paymentIntent } = await stripe?.confirmPayment({
elements: elements.value,
redirect: 'if_required',
confirmParams: { return_url: stripeReturnUrl },
});
console.log({ data });
// orderInput.value.metaData.push({ key: '_stripe_source_id', value: source.id });
// orderInput.value.transactionId = source.created?.toString() || '';
orderInput.value.metaData.push({ key: '_stripe_intent_id', value: paymentIntent.id });
orderInput.value.transactionId = paymentIntent.created?.toString() || '';
isPaid.value = paymentIntent.status === 'succeeded';
}
} catch (error) {
console.error('payNow error:', error);
buttonText.value = t('messages.shop.placeOrder');
}
// proccessCheckout();
proccessCheckout(isPaid.value);
};
const handleStripeElementsChange = (el) => {
elements.value = el;
};
const checkEmailOnBlur = (email) => {
Expand All @@ -81,35 +59,6 @@ const checkEmailOnInput = (email) => {
if (email || isInvalidEmail.value) isInvalidEmail.value = !emailRegex.test(email);
};
/**
* Watch orderInput.paymentMethod for stripe. If is stripe, add and event listener to .StripeElement to check if it's complete.
* It will have the class .StripeElement--complete when it's complete. Then set stripeCardIsComplete to true.
*/
watch(
() => orderInput.value.paymentMethod,
(newVal) => {
if (newVal === 'stripe') {
setTimeout(() => {
const stripeElement = document.querySelector('.StripeElement');
if (stripeElement) {
// Watch .StripeElement dom element. When it has the class .StripeElement--complete, set stripeCardIsComplete to true.
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === 'class') {
const attributeValue = mutation.target.getAttribute(mutation.attributeName);
stripeCardIsComplete.value = attributeValue.includes('StripeElement--complete');
}
});
});
observer.observe(stripeElement, { attributes: true });
}
}, 1000);
} else {
stripeCardIsComplete.value = false;
}
},
);
useSeoMeta({
title: t('messages.shop.checkout'),
});
Expand Down Expand Up @@ -181,18 +130,7 @@ useSeoMeta({
<div v-if="paymentGateways.length" class="mt-2 col-span-full">
<h2 class="mb-4 text-xl font-semibold">{{ $t('messages.billing.paymentOptions') }}</h2>
<PaymentOptions v-model="orderInput.paymentMethod" class="mb-4" :paymentGateways="paymentGateways" />
<Transition name="scale-y" mode="out-in">
<StripeElements
v-show="orderInput.paymentMethod == 'stripe'"
v-slot="{ elements, instance }"
ref="elms"
:stripe-key="stripeKey"
:instance-options="instanceOptions"
:elements-options="elementsOptions">
<StripeElement ref="card" :elements="elements" :options="cardOptions" />
</StripeElements>
</Transition>
<StripeElement v-if="stripe" v-show="orderInput.paymentMethod == 'stripe'" :stripe @updateElements="handleStripeElementsChange" />
</div>
<!-- Order note -->
Expand Down Expand Up @@ -226,7 +164,6 @@ useSeoMeta({
.checkout-form input[type='tel'],
.checkout-form input[type='password'],
.checkout-form textarea,
.checkout-form .StripeElement,
.checkout-form select {
@apply bg-white border rounded-md outline-none border-gray-300 shadow-sm w-full py-2 px-4;
}
Expand All @@ -239,8 +176,4 @@ useSeoMeta({
.checkout-form label {
@apply my-1.5 text-xs text-gray-600 uppercase;
}
.checkout-form .StripeElement {
padding: 1rem 0.75rem;
}
</style>
2 changes: 2 additions & 0 deletions woonuxt_base/queries/checkout.gql
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mutation Checkout(
$shipToDifferentAddress: Boolean = false
$account: CreateAccountInput = { username: "", password: "" }
$transactionId: String = ""
$isPaid: Boolean = false
) {
checkout(
input: {
Expand All @@ -18,6 +19,7 @@ mutation Checkout(
shipToDifferentAddress: $shipToDifferentAddress
account: $account
transactionId: $transactionId
isPaid: $isPaid
}
) {
result
Expand Down
1 change: 1 addition & 0 deletions woonuxt_base/queries/fragments/CartFragment.gql
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

fragment CartFragment on Cart {
total
rawTotal: total(format: RAW)
subtotal
totalTax
discountTotal
Expand Down

0 comments on commit 5da380c

Please sign in to comment.