# Creating custom payment component
The integration gives you a raw version of the PaymentMollieProvider.vue
component. You can use it as a base and customize it for your needs.
<template>
<div class="payment-container">
<slot name="loader" v-if="isLoadingPaymentMethods">
<Loader />
</slot>
<slot name="error" v-else-if="isError">
<div class="payment-error-container">
{{ $t('Something went wrong. Please contact website\'s administrator.') }}
</div>
</slot>
<slot name="no-payment-methods" v-else-if="!hasPaymentMethods">
<div class="payment-warn-container">
{{ $t('There are no available payment methods.') }}
</div>
</slot>
<div v-else>
<div class="payment-radio__container">
<h4 class="payment-radio__title">{{ $t('Select payment method') }}</h4>
<SfRadio
v-e2e="'payment-method'"
v-for="paymentMethod in paymentMethods"
:key="paymentMethod.id"
:label="paymentMethod.description"
:value="paymentMethod.id"
:selected ="selectedPaymentMethod && selectedPaymentMethod.id"
name="paymentMethod"
class="form__radio"
@change="selectPaymentMethod(paymentMethod)"
>
<template #label>
<div class="payment-radio__item">
<img :src="paymentMethod.image.size1x" :alt="paymentMethod.description" /> <span class="payment-radio__item__text">{{ $t(paymentMethod.description) }}</span>
</div>
</template>
</SfRadio>
</div>
<div class="payment-radio__container" v-if="selectedPaymentMethod && selectedPaymentMethod.issuers && selectedPaymentMethod.issuers.length">
<h4 class="payment-radio__title">{{ $t('Select payment method\'s issuer') }}</h4>
<SfRadio
v-e2e="'payment-issuer'"
v-for="paymentIssuer in selectedPaymentMethod.issuers"
:key="paymentIssuer.id"
:label="paymentIssuer.name"
:value="paymentIssuer.id"
:selected ="selectedIssuer && selectedIssuer.id"
name="paymentIssuer"
class="form__radio paymentIssuer"
@change="selectIssuer(paymentIssuer)"
>
<template #label>
<div class="payment-radio__item">
<img :src="paymentIssuer.image.size1x" :alt="paymentIssuer.name" /> <span class="payment-radio__item__text">{{ $t(paymentIssuer.name) }}</span>
</div>
</template>
</SfRadio>
</div>
<div class="payment-warn-container" v-if="selectedPaymentMethod && selectedPaymentMethod.issuers && !selectedPaymentMethod.issuers.length">
{{ $t('Selected payment method requires issuer but doesn\'t give any option. Please select a different payment method, sorry.') }}
</div>
<SfButton
:disabled="loading || !canPlaceOrder"
class="mt-10"
@click="placeOrder"
>{{ $t('Place order') }}</SfButton>
</div>
</div>
</template>
<script>
import { computed, onMounted, ref } from '@nuxtjs/composition-api';
import { useMollie } from '@vsf-enterprise/mollie-commercetools';
import Loader from '@vsf-enterprise/mollie-commercetools/src/components/PaymentStripeLoader.vue';
import { SfButton, SfRadio } from '@storefront-ui/vue';
export default {
name: 'PaymentMollieProvider',
components: {
Loader,
SfButton,
SfRadio
},
props: {
locale: {
type: String,
default: 'en_US'
}
},
setup ({ locale }) {
const isLoadingPaymentMethods = ref(true);
const isError = ref(false);
const paymentMethods = ref(null);
const selectedPaymentMethod = ref(null);
const selectPaymentMethod = paymentMethod => {
selectedIssuer.value = null;
selectedPaymentMethod.value = paymentMethod;
};
const selectedIssuer = ref(null);
const selectIssuer = paymentIssuer => selectedIssuer.value = paymentIssuer;
const canPlaceOrder = computed(() => {
if (!selectedPaymentMethod.value?.id) {
return false;
}
return selectedPaymentMethod.value.issuers
? Boolean(selectedIssuer.value)
: true;
});
const {
createContext,
createOrder,
error,
loading
} = useMollie();
const loadPaymentMethods = async () => {
const response = await createContext({
locale
});
if (error.value.createContext) {
isError.value = true;
isLoadingPaymentMethods.value = false;
return;
}
paymentMethods.value = response.methods;
isLoadingPaymentMethods.value = false;
};
const placeOrder = async () => {
const response = await createOrder({
locale,
paymentMethod: selectedPaymentMethod.value,
issuer: selectedIssuer.value
});
if (error.value.createOrder) {
isError.value = true;
return;
}
const { checkoutUrl } = JSON.parse(response.interfaceInteractions[0].fields.response);
window.location.href = checkoutUrl;
};
onMounted(async () => {
if (!process.server) {
await loadPaymentMethods();
}
});
return {
isError,
isLoadingPaymentMethods,
paymentMethods,
hasPaymentMethods: computed(() => Boolean(paymentMethods.value?.length)),
selectPaymentMethod,
selectedPaymentMethod: computed(() => selectedPaymentMethod.value),
selectedPaymentMethodRequiresIssuer: computed(() => Boolean(selectedPaymentMethod.value?.issuers?.length)),
selectIssuer,
selectedIssuer: computed(() => selectedIssuer.value),
canPlaceOrder,
placeOrder,
loading
};
}
};
</script>
<style lang="scss" scoped>
.mt-10 {
margin-top: 10px;
}
.payment-container {
margin: var(--spacer-xl) 0 0 0;
}
.payment-warn-container, .payment-error-container {
border-radius: 5px;
padding: 10px;
margin-bottom: 10px;
}
.payment-warn-container {
background: #ffcc00;
color: #000;
}
.payment-error-container {
background: #d12727;
color: #fff;
}
.payment-radio__item {
display: inline-flex;
flex-wrap: wrap;
align-items: center;
&__text {
margin-left: 10px;
}
}
</style>