feat: Add new payment completion and failure routes, update donation handling in DonationForm and related components
This commit is contained in:
@@ -35,6 +35,16 @@ const routes = [
|
|||||||
path: '',
|
path: '',
|
||||||
name: 'creator',
|
name: 'creator',
|
||||||
component: CreatorHome,
|
component: CreatorHome,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tip-completed',
|
||||||
|
name: 'PaymentCompleted',
|
||||||
|
component: PaymentCompleted,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tip-cancelled',
|
||||||
|
name: 'PaymentFailed',
|
||||||
|
component: PaymentFailed,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -85,16 +95,6 @@ const routes = [
|
|||||||
component: LoginView,
|
component: LoginView,
|
||||||
meta: { notAuthenticated: true },
|
meta: { notAuthenticated: true },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/paymentcompleted/:creatorId',
|
|
||||||
name: 'PaymentCompleted',
|
|
||||||
component: PaymentCompleted,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/paymentfailed/:creatorId',
|
|
||||||
name: 'PaymentFailed',
|
|
||||||
component: PaymentFailed,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/profile',
|
path: '/profile',
|
||||||
name: 'profile',
|
name: 'profile',
|
||||||
|
|||||||
@@ -22,7 +22,11 @@ export const useBrandingStore = defineStore(
|
|||||||
watch(
|
watch(
|
||||||
() => route.params.creator,
|
() => route.params.creator,
|
||||||
async (creator) => {
|
async (creator) => {
|
||||||
await updateBrand(creator);
|
console.log(`creator: ${creator}`)
|
||||||
|
// Extract just the creator name from the path (remove any additional segments)
|
||||||
|
const creatorName = creator ? creator.split('/')[0] : undefined;
|
||||||
|
console.log(`name: ${creatorName}`)
|
||||||
|
await updateBrand(creatorName);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -52,12 +52,9 @@ const route = useRoute();
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
function goBack() {
|
function goBack() {
|
||||||
const returnUrl = route.query.returnUrl;
|
// Navigate back to the creator's page
|
||||||
if (returnUrl) {
|
const creatorName = route.params.creator?.split('/')[0] || '';
|
||||||
router.push(returnUrl);
|
router.push(`/@${creatorName}`);
|
||||||
} else {
|
|
||||||
router.back();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -21,12 +21,8 @@ const route = useRoute();
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
function goBack() {
|
function goBack() {
|
||||||
const returnUrl = route.query.returnUrl;
|
const creatorName = route.params.creator?.split('/')[0] || '';
|
||||||
if (returnUrl) {
|
router.push(`/@${creatorName}`);
|
||||||
router.push(returnUrl);
|
|
||||||
} else {
|
|
||||||
router.back();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ const {t} = useI18n();
|
|||||||
v-if="brandingStore.value?.acceptDonation"
|
v-if="brandingStore.value?.acceptDonation"
|
||||||
:creator-id="brandingStore.value?.id"
|
:creator-id="brandingStore.value?.id"
|
||||||
:creator-name="brandingStore.value?.name"
|
:creator-name="brandingStore.value?.name"
|
||||||
:on-cancelled-url="baseURL + '/paymentfailed/' + brandingStore.value?.id"
|
:on-cancelled-url="baseURL + '/@' + brandingStore.value.slug + '/tip-cancelled'"
|
||||||
:on-success-url="baseURL + '/paymentcompleted/' + brandingStore.value?.id"
|
:on-success-url="baseURL + '/@' + brandingStore.value.slug + '/tip-completed'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ import ActualBanner from "@/views/creators/ActualBanner.vue";
|
|||||||
import BannerActions from "@/views/creators/BannerActions.vue";
|
import BannerActions from "@/views/creators/BannerActions.vue";
|
||||||
|
|
||||||
const brandingStore = useBrandingStore();
|
const brandingStore = useBrandingStore();
|
||||||
const creatorName = window.location.pathname.split('/@').pop();
|
const creatorName = window.location.pathname.split('/@')[1]?.split('/')[0] || '';
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
console.log(`creatorName: ${creatorName}`)
|
||||||
await brandingStore.updateBrand(creatorName);
|
await brandingStore.updateBrand(creatorName);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
{{ t('creator.donation.isupport') }}
|
{{ t('creator.donation.isupport') }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<donation-dialog
|
<DonationDialog
|
||||||
ref="donationDialogRef"
|
ref="donationDialogRef"
|
||||||
:creator-id="creatorId"
|
:creator-id="creatorId"
|
||||||
:creator-name="creatorName"
|
:creator-name="creatorName"
|
||||||
|
|||||||
@@ -2,47 +2,24 @@
|
|||||||
<v-dialog v-model="donationModal">
|
<v-dialog v-model="donationModal">
|
||||||
<DonationForm
|
<DonationForm
|
||||||
:show-cancel-button="showCancelButton"
|
:show-cancel-button="showCancelButton"
|
||||||
|
:creator-id="creatorId"
|
||||||
|
:creator-name="creatorName"
|
||||||
|
:on-success-url="onSuccessUrl"
|
||||||
|
:on-cancelled-url="onCancelledUrl"
|
||||||
@cancel="closeDonationDialog"
|
@cancel="closeDonationDialog"
|
||||||
@submit="handleSubmit"
|
|
||||||
/>
|
/>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
<v-dialog v-model="isPaymentDialogActive" max-width="720" persistent>
|
|
||||||
<template v-slot:default>
|
|
||||||
<v-card :style="{ padding: '20px' }">
|
|
||||||
<div id="checkout"></div>
|
|
||||||
<div v-if="errorMessage" class="error-message">{{ errorMessage }}</div>
|
|
||||||
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
|
|
||||||
<v-card-actions>
|
|
||||||
<v-btn
|
|
||||||
block
|
|
||||||
class="ma-auto"
|
|
||||||
@click="closeDialog()"
|
|
||||||
>{{ t('common.cancel') }}
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
</v-dialog>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {useClient} from '@/plugins/api.js';
|
import {ref} from 'vue';
|
||||||
import {loadStripe} from '@stripe/stripe-js';
|
|
||||||
import {onMounted, ref} from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import DonationForm from './DonationForm.vue';
|
import DonationForm from './DonationForm.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
creatorId: {default: 'missing-creator-id', required: true},
|
creatorId: {default: 'missing-creator-id', required: true},
|
||||||
creatorName: {default: 'missing-creator-name', required: true},
|
creatorName: {default: 'missing-creator-name', required: true},
|
||||||
onSuccessUrl: {default: 'missing-on-success-u', required: true},
|
onSuccessUrl: {default: 'missing-on-success-u', required: true},
|
||||||
onCancelledUrl: {default: 'missing-on-cancelled-url', required: true},
|
onCancelledUrl: {default: 'missing-on-cancelled-url', required: true},
|
||||||
iconColorClass: {default: 'text-black'},
|
|
||||||
showCancelButton: {
|
showCancelButton: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
@@ -51,16 +28,7 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(['close']);
|
const emit = defineEmits(['close']);
|
||||||
|
|
||||||
const errorMessage = ref('');
|
|
||||||
const donationModal = ref(false);
|
const donationModal = ref(false);
|
||||||
const isPaymentDialogActive = ref(false);
|
|
||||||
|
|
||||||
let stripe = null;
|
|
||||||
let checkout;
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
stripe = await loadStripe(import.meta.env.VITE_STRIPE_API_KEY);
|
|
||||||
});
|
|
||||||
|
|
||||||
function openDonationDialog() {
|
function openDonationDialog() {
|
||||||
donationModal.value = true;
|
donationModal.value = true;
|
||||||
@@ -71,101 +39,7 @@ function closeDonationDialog() {
|
|||||||
emit('close');
|
emit('close');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createCheckoutSession(amount, message) {
|
|
||||||
const client = useClient();
|
|
||||||
try {
|
|
||||||
let clientSecret = await client.post(`/api/tips`, {
|
|
||||||
creatorId: props.creatorId,
|
|
||||||
amount: amount * 100,
|
|
||||||
currency: 'CAD',
|
|
||||||
message: message,
|
|
||||||
checkoutSuccessUrl: props.onSuccessUrl,
|
|
||||||
checkoutCancelledUrl: props.onCancelledUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
return clientSecret.data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
errorMessage.value = t('creator.donation.errors.payment');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeDialog() {
|
|
||||||
isPaymentDialogActive.value = false;
|
|
||||||
errorMessage.value = '';
|
|
||||||
|
|
||||||
if (checkout) {
|
|
||||||
checkout.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSubmit({ amount, message }) {
|
|
||||||
isPaymentDialogActive.value = true;
|
|
||||||
|
|
||||||
const response = await createCheckoutSession(amount, message);
|
|
||||||
|
|
||||||
if (response && response.url) {
|
|
||||||
// Redirect to the Stripe Checkout page
|
|
||||||
window.location.href = response.url;
|
|
||||||
} else {
|
|
||||||
errorMessage.value = t('creator.donation.errors.payment');
|
|
||||||
isPaymentDialogActive.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
openDonationDialog
|
openDonationDialog
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.error-message {
|
|
||||||
color: white;
|
|
||||||
background-color: red;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<i18n>
|
|
||||||
{
|
|
||||||
"en": {
|
|
||||||
"common": {
|
|
||||||
"cancel": "Cancel"
|
|
||||||
},
|
|
||||||
"creator": {
|
|
||||||
"donation": {
|
|
||||||
"errors": {
|
|
||||||
"payment": "An error occurred during payment processing"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fr": {
|
|
||||||
"common": {
|
|
||||||
"cancel": "Annuler"
|
|
||||||
},
|
|
||||||
"creator": {
|
|
||||||
"donation": {
|
|
||||||
"errors": {
|
|
||||||
"payment": "Une erreur s'est produite lors du traitement du paiement"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"es": {
|
|
||||||
"common": {
|
|
||||||
"cancel": "Cancelar"
|
|
||||||
},
|
|
||||||
"creator": {
|
|
||||||
"donation": {
|
|
||||||
"errors": {
|
|
||||||
"payment": "Ocurrió un error durante el procesamiento del pago"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</i18n>
|
|
||||||
@@ -33,6 +33,8 @@
|
|||||||
clearable
|
clearable
|
||||||
auto-grow
|
auto-grow
|
||||||
></v-textarea>
|
></v-textarea>
|
||||||
|
|
||||||
|
<div v-if="errorMessage" class="error-message">{{ errorMessage }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
@@ -43,8 +45,10 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="primary"
|
<button class="primary"
|
||||||
@click="$emit('submit', { amount: tipAmountInDollars, message: tipMessage })">
|
@click="handleSubmit"
|
||||||
{{ t('creator.donation.send') }}
|
:disabled="isProcessing">
|
||||||
|
<span v-if="isProcessing" class="spinner mr-2"></span>
|
||||||
|
{{ isProcessing ? t('creator.donation.processing') : t('creator.donation.send') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,13 +57,27 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useClient } from '@/plugins/api.js';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const client = useClient();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
showCancelButton: {
|
showCancelButton: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
},
|
||||||
|
creatorId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
onSuccessUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
onCancelledUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -67,6 +85,8 @@ const emit = defineEmits(['cancel', 'submit']);
|
|||||||
|
|
||||||
const tipAmountInDollars = ref('');
|
const tipAmountInDollars = ref('');
|
||||||
const tipMessage = ref('');
|
const tipMessage = ref('');
|
||||||
|
const errorMessage = ref('');
|
||||||
|
const isProcessing = ref(false);
|
||||||
|
|
||||||
function preventNonNumeric(event) {
|
function preventNonNumeric(event) {
|
||||||
const key = event.key;
|
const key = event.key;
|
||||||
@@ -76,8 +96,57 @@ function preventNonNumeric(event) {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
if (!tipAmountInDollars.value || tipAmountInDollars.value <= 0) {
|
||||||
|
errorMessage.value = t('creator.donation.errors.invalidAmount');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isProcessing.value = true;
|
||||||
|
errorMessage.value = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.post(`/api/tips`, {
|
||||||
|
creatorId: props.creatorId,
|
||||||
|
amount: tipAmountInDollars.value * 100,
|
||||||
|
currency: 'CAD',
|
||||||
|
message: tipMessage.value,
|
||||||
|
checkoutSuccessUrl: props.onSuccessUrl,
|
||||||
|
checkoutCancelledUrl: props.onCancelledUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data?.stripeCheckoutUrl) {
|
||||||
|
window.location.href = response.data.stripeCheckoutUrl;
|
||||||
|
} else {
|
||||||
|
throw new Error('No checkout URL received');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
errorMessage.value = t('creator.donation.errors.payment');
|
||||||
|
isProcessing.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.error-message {
|
||||||
|
@apply text-white bg-red-500;
|
||||||
|
@apply rounded-md text-center w-full p-2;
|
||||||
|
@apply mt-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
@apply inline-block;
|
||||||
|
@apply w-4 h-4;
|
||||||
|
@apply border-2;
|
||||||
|
@apply border-current;
|
||||||
|
@apply border-t-transparent;
|
||||||
|
@apply rounded-full;
|
||||||
|
@apply animate-spin;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<i18n>
|
<i18n>
|
||||||
{
|
{
|
||||||
"en": {
|
"en": {
|
||||||
@@ -89,7 +158,12 @@ function preventNonNumeric(event) {
|
|||||||
"isupport": "I Support",
|
"isupport": "I Support",
|
||||||
"amount": "Amount ($)",
|
"amount": "Amount ($)",
|
||||||
"message": "Message (optional)",
|
"message": "Message (optional)",
|
||||||
"send": "Send"
|
"send": "Send",
|
||||||
|
"processing": "Processing...",
|
||||||
|
"errors": {
|
||||||
|
"payment": "An error occurred during payment processing",
|
||||||
|
"invalidAmount": "Please enter a valid amount"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -102,7 +176,12 @@ function preventNonNumeric(event) {
|
|||||||
"isupport": "Je Soutiens",
|
"isupport": "Je Soutiens",
|
||||||
"amount": "Montant ($)",
|
"amount": "Montant ($)",
|
||||||
"message": "Message (optionnel)",
|
"message": "Message (optionnel)",
|
||||||
"send": "Envoyer"
|
"send": "Envoyer",
|
||||||
|
"processing": "Traitement en cours...",
|
||||||
|
"errors": {
|
||||||
|
"payment": "Une erreur s'est produite lors du traitement du paiement",
|
||||||
|
"invalidAmount": "Veuillez entrer un montant valide"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -115,7 +194,12 @@ function preventNonNumeric(event) {
|
|||||||
"isupport": "Apoyo",
|
"isupport": "Apoyo",
|
||||||
"amount": "Cantidad ($)",
|
"amount": "Cantidad ($)",
|
||||||
"message": "Mensaje (opcional)",
|
"message": "Mensaje (opcional)",
|
||||||
"send": "Enviar"
|
"send": "Enviar",
|
||||||
|
"processing": "Procesando...",
|
||||||
|
"errors": {
|
||||||
|
"payment": "Ocurrió un error durante el procesamiento del pago",
|
||||||
|
"invalidAmount": "Por favor ingrese un monto válido"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user