- Install with your package manager:
pnpm add nuxt-mollie-payments-components
- Add the new nuxt module to the modules section of your nuxt.config.ts:
{
modules: [
'nuxt-mollie-payments-components',
],
}
useMollie
&useMollieCreditCard
composable functionMollieCreditCardComponent.vue
component to use in a Vue projectShopwareFrontendsCreditCard.vue
component to use in a Nuxt Shopware Powered projectShopwareFrontendsIdeal.vue
component to use in a Nuxt Shopware Powered project- WIP:
ShopwareFrontendsPos.vue
component to use in a Nuxt Shopware Powered project
- Nuxt 3
- installed: @shopware-pwa/nuxt3-module / @shopware-pwa/composables-next - optional if you want to use
ShopwareFrontendsCreditCard
component which sends additional request to Mollie's endpoint in order to store Credit Card token.
# Install dependencies
pnpm install
# Generate type stubs
pnpm dev:prepare
# Develop with the playground
pnpm dev
# Build the playground
pnpm dev:build
# Run ESLint
pnpm lint
# Run Vitest
pnpm test
pnpm test:watch
# Release new version
pnpm release
vue-demo-store checkout page: https://github.com/shopware/frontends/blob/main/templates/vue-demo-store/pages/checkout/index.vue
Create a custom placeOrder function in your checkout page and react to the paymentUrl change:
<script setup lang="ts">
// ...
const mollieConfig = ref()
const creditCardError = ref()
const creditCardToken = ref()
const creditCardSaveCardChange = ref(false)
const creditCardActiveMandate = ref()
const creditCardFormMounted = ref(false)
const isMollieCreditCardPaymentMethod = computed(() => paymentMethod.value?.shortName === 'credit_card_payment')
const placeOrder = async () => {
placeOrderTriggered.value = true
if (!terms.tos) {
termsBox.value.scrollIntoView()
return
}
isLoading['placeOrder'] = true
if (isMollieCreditCardPaymentMethod.value) {
// save mandate id if needed
if (creditCardActiveMandate.value) {
try {
await apiClient.invoke(
`saveMandateId post /mollie/creditcard/store-mandate-id/${user.value?.id}/${creditCardActiveMandate.value}`,
)
} catch (e) {
throw new Error('Error: could not save credit card mandate')
}
// save credit card details if needed (this is not needed if the credit card form uses it's own save button)
} else if (!creditCardToken.value) {
try {
const { getToken } = useMollie(mollieConfig.value)
creditCardToken.value = await getToken()
await apiClient.invoke(
`saveCardToken post /mollie/creditcard/store-token/${user.value?.id}/${creditCardToken.value}`,
{ shouldSaveCardDetail: creditCardSaveCardChange.value },
)
} catch (e) {
throw new Error('Error: could not save credit card details')
}
}
}
if (userComment.text !== '') {
order.value = await createOrder({ customerComment: userComment.text })
} else {
order.value = await createOrder()
}
if (!isMollieCreditCardPaymentMethod.value) {
await push(localePath('/checkout/success/' + order.value.id))
refreshCart()
} else {
const SUCCESS_PAYMENT_URL = `${window?.location?.origin}/checkout/success/${order.value.id}/paid`
const FAILURE_PAYMENT_URL = `${window?.location?.origin}/checkout/success/${order.value.id}/unpaid`
await handlePayment(SUCCESS_PAYMENT_URL, FAILURE_PAYMENT_URL)
}
isLoading['placeOrder'] = false
}
watchDebounced(
paymentUrl,
(paymentUrl) => {
if (typeof paymentUrl !== 'string') {
return
}
try {
new URL(paymentUrl as string)
window.location.href = paymentUrl
} catch (error) {
throw new Error('error, redirect')
}
},
{ debounce: 100 },
)
// ...
</script>
<template>
<div
v-for="paymentMethodItem in paymentMethods"
v-else
:key="paymentMethodItem.id"
class="flex flex-col"
>
<!-- PAYMENT RADIO BUTTON -->
<div
v-if="paymentMethod?.id === paymentMethodItem.id && isMollieCreditCardPaymentMethod"
class="pt-6 pb-3"
>
<!-- YOUR LOADER COMPONENT -->
<SharedLoader v-if="!creditCardFormMounted" />
<ShopwareFrontendsCreditCard
submit-button-label="Save"
:locale="'en_US'"
:submit-disabled="!!creditCardToken"
:hide-save-button="true"
@should-save-card-details="
(value: boolean) => {
creditCardSaveCardChange = value
}
"
@config-loaded="
(config: MollieConfig) => {
mollieConfig = config
}
"
@error="
(message: string | undefined) => {
creditCardError = `${message}`
}
"
@store-mandate="
(mandateId: string | undefined) => {
creditCardActiveMandate = mandateId
}
"
@mounted="creditCardFormMounted = true"
/>
<div class="demo-mollie-results-credit-card">
<div v-if="creditCardError">Error: {{ creditCardError }}</div>
</div>
</div>
</div>
</template>
Create a custom placeOrder function in your checkout page and react to the paymentUrl change:
<script setup lang="ts">
// ...
const idealError = ref()
const isMollieIdealPaymentMethod = computed(() => paymentMethod.value?.shortName === 'i_deal_payment')
const placeOrder = async () => {
placeOrderTriggered.value = true
if (!terms.tos) {
termsBox.value.scrollIntoView()
return
}
isLoading['placeOrder'] = true
if (userComment.text !== '') {
order.value = await createOrder({ customerComment: userComment.text })
} else {
order.value = await createOrder()
}
if (!isMollieIdealPaymentMethod.value) {
await push(localePath('/checkout/success/' + order.value.id))
refreshCart()
} else {
const SUCCESS_PAYMENT_URL = `${window?.location?.origin}/checkout/success/${order.value.id}/paid`
const FAILURE_PAYMENT_URL = `${window?.location?.origin}/checkout/success/${order.value.id}/unpaid`
await handlePayment(SUCCESS_PAYMENT_URL, FAILURE_PAYMENT_URL)
}
isLoading['placeOrder'] = false
}
watchDebounced(
paymentUrl,
(paymentUrl) => {
if (typeof paymentUrl !== 'string') {
return
}
try {
new URL(paymentUrl as string)
window.location.href = paymentUrl
} catch (error) {
throw new Error('error, redirect')
}
},
{ debounce: 100 },
)
// ...
</script>
<template>
<div
v-for="paymentMethodItem in paymentMethods"
v-else
:key="paymentMethodItem.id"
class="flex flex-col"
>
<!-- PAYMENT RADIO BUTTON -->
<div
v-else-if="paymentMethod?.id === paymentMethodItem.id && isMollieIdealPaymentMethod"
class="pt-6 pb-3"
>
<ShopwareFrontendsIdeal
:locale="'en_US'"
select-label="Please choose your bank:"
select-disabled-option="Select your bank"
@save-issuer-success="
() => {
idealError = null
}
"
@save-issuer-error="
(message: string | undefined) => {
idealError = `${message}`
}
"
/>
<div class="demo-mollie-results-ideal">
<div v-if="idealError">Error: {{ idealError }}</div>
</div>
</div>
</div>
</template>
Create a custom placeOrder function in your checkout page and react to the paymentUrl change:
<script setup lang="ts">
// ...
const posError = ref()
const isMolliePosPaymentMethod = computed(() => paymentMethod.value?.shortName === 'pos_payment')
const placeOrder = async () => {
placeOrderTriggered.value = true
if (!terms.tos) {
termsBox.value.scrollIntoView()
return
}
isLoading['placeOrder'] = true
if (userComment.text !== '') {
order.value = await createOrder({ customerComment: userComment.text })
} else {
order.value = await createOrder()
}
if (!isMolliePosPaymentMethod.value) {
await push(localePath('/checkout/success/' + order.value.id))
refreshCart()
} else {
const SUCCESS_PAYMENT_URL = `${window?.location?.origin}/checkout/success/${order.value.id}/paid`
const FAILURE_PAYMENT_URL = `${window?.location?.origin}/checkout/success/${order.value.id}/unpaid`
await handlePayment(SUCCESS_PAYMENT_URL, FAILURE_PAYMENT_URL)
}
isLoading['placeOrder'] = false
}
watchDebounced(
paymentUrl,
(paymentUrl) => {
if (typeof paymentUrl !== 'string') {
return
}
try {
new URL(paymentUrl as string)
window.location.href = paymentUrl
} catch (error) {
throw new Error('error, redirect')
}
},
{ debounce: 100 },
)
// ...
</script>
<template>
<div
v-for="paymentMethodItem in paymentMethods"
v-else
:key="paymentMethodItem.id"
class="flex flex-col"
>
<!-- PAYMENT RADIO BUTTON -->
<div
v-else-if="paymentMethod?.id === paymentMethodItem.id && isMolliePosPaymentMethod"
class="pt-6 pb-3"
>
<ShopwareFrontendsPos
:locale="'en_US'"
select-label="Please choose your terminal:"
select-disabled-option="Select your terminal"
@save-terminal-success="
() => {
posError = null
}
"
@save-terminal-error="
(message: string | undefined) => {
posError = `${message}`
}
"
/>
<div class="demo-mollie-results-pos">
<div v-if="posError">Error: {{ posError }}</div>
</div>
</div>
</div>
</template>