Skip to content

benjamin-ott/Shopware6Composables

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Vue components for Mollie Payments (Nuxt module)

Usage

  1. Install with your package manager:
pnpm add nuxt-mollie-payments-components
  1. Add the new nuxt module to the modules section of your nuxt.config.ts:
{
  modules: [
    'nuxt-mollie-payments-components',
  ],
}

Features

  • useMollie & useMollieCreditCard composable function
  • MollieCreditCardComponent.vue component to use in a Vue project
  • ShopwareFrontendsCreditCard.vue component to use in a Nuxt Shopware Powered project
  • ShopwareFrontendsIdeal.vue component to use in a Nuxt Shopware Powered project
  • WIP: ShopwareFrontendsPos.vue component to use in a Nuxt Shopware Powered project

Requirements

  • 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.

Development

# 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

Integration in vue-demo-store checkout page

vue-demo-store checkout page: https://github.com/shopware/frontends/blob/main/templates/vue-demo-store/pages/checkout/index.vue

Credit card example:

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>

Ideal example:

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>

POS example:

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>

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Vue 63.2%
  • TypeScript 32.4%
  • CSS 2.3%
  • Makefile 2.1%