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: '',
|
||||
name: 'creator',
|
||||
component: CreatorHome,
|
||||
},
|
||||
{
|
||||
path: 'tip-completed',
|
||||
name: 'PaymentCompleted',
|
||||
component: PaymentCompleted,
|
||||
},
|
||||
{
|
||||
path: 'tip-cancelled',
|
||||
name: 'PaymentFailed',
|
||||
component: PaymentFailed,
|
||||
}
|
||||
],
|
||||
},
|
||||
@@ -85,16 +95,6 @@ const routes = [
|
||||
component: LoginView,
|
||||
meta: { notAuthenticated: true },
|
||||
},
|
||||
{
|
||||
path: '/paymentcompleted/:creatorId',
|
||||
name: 'PaymentCompleted',
|
||||
component: PaymentCompleted,
|
||||
},
|
||||
{
|
||||
path: '/paymentfailed/:creatorId',
|
||||
name: 'PaymentFailed',
|
||||
component: PaymentFailed,
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
name: 'profile',
|
||||
|
||||
@@ -22,7 +22,11 @@ export const useBrandingStore = defineStore(
|
||||
watch(
|
||||
() => route.params.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();
|
||||
|
||||
function goBack() {
|
||||
const returnUrl = route.query.returnUrl;
|
||||
if (returnUrl) {
|
||||
router.push(returnUrl);
|
||||
} else {
|
||||
router.back();
|
||||
}
|
||||
// Navigate back to the creator's page
|
||||
const creatorName = route.params.creator?.split('/')[0] || '';
|
||||
router.push(`/@${creatorName}`);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -21,12 +21,8 @@ const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
function goBack() {
|
||||
const returnUrl = route.query.returnUrl;
|
||||
if (returnUrl) {
|
||||
router.push(returnUrl);
|
||||
} else {
|
||||
router.back();
|
||||
}
|
||||
const creatorName = route.params.creator?.split('/')[0] || '';
|
||||
router.push(`/@${creatorName}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -42,8 +42,8 @@ const {t} = useI18n();
|
||||
v-if="brandingStore.value?.acceptDonation"
|
||||
:creator-id="brandingStore.value?.id"
|
||||
:creator-name="brandingStore.value?.name"
|
||||
:on-cancelled-url="baseURL + '/paymentfailed/' + brandingStore.value?.id"
|
||||
:on-success-url="baseURL + '/paymentcompleted/' + brandingStore.value?.id"
|
||||
:on-cancelled-url="baseURL + '/@' + brandingStore.value.slug + '/tip-cancelled'"
|
||||
:on-success-url="baseURL + '/@' + brandingStore.value.slug + '/tip-completed'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,10 +7,11 @@ import ActualBanner from "@/views/creators/ActualBanner.vue";
|
||||
import BannerActions from "@/views/creators/BannerActions.vue";
|
||||
|
||||
const brandingStore = useBrandingStore();
|
||||
const creatorName = window.location.pathname.split('/@').pop();
|
||||
const creatorName = window.location.pathname.split('/@')[1]?.split('/')[0] || '';
|
||||
const { t } = useI18n();
|
||||
|
||||
onMounted(async () => {
|
||||
console.log(`creatorName: ${creatorName}`)
|
||||
await brandingStore.updateBrand(creatorName);
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
{{ t('creator.donation.isupport') }}
|
||||
</button>
|
||||
|
||||
<donation-dialog
|
||||
<DonationDialog
|
||||
ref="donationDialogRef"
|
||||
:creator-id="creatorId"
|
||||
:creator-name="creatorName"
|
||||
|
||||
@@ -2,47 +2,24 @@
|
||||
<v-dialog v-model="donationModal">
|
||||
<DonationForm
|
||||
:show-cancel-button="showCancelButton"
|
||||
:creator-id="creatorId"
|
||||
:creator-name="creatorName"
|
||||
:on-success-url="onSuccessUrl"
|
||||
:on-cancelled-url="onCancelledUrl"
|
||||
@cancel="closeDonationDialog"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</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>
|
||||
|
||||
<script setup>
|
||||
import {useClient} from '@/plugins/api.js';
|
||||
import {loadStripe} from '@stripe/stripe-js';
|
||||
import {onMounted, ref} from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import {ref} from 'vue';
|
||||
import DonationForm from './DonationForm.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
creatorId: {default: 'missing-creator-id', required: true},
|
||||
creatorName: {default: 'missing-creator-name', required: true},
|
||||
onSuccessUrl: {default: 'missing-on-success-u', required: true},
|
||||
onCancelledUrl: {default: 'missing-on-cancelled-url', required: true},
|
||||
iconColorClass: {default: 'text-black'},
|
||||
showCancelButton: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
@@ -51,16 +28,7 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
const errorMessage = ref('');
|
||||
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() {
|
||||
donationModal.value = true;
|
||||
@@ -71,101 +39,7 @@ function closeDonationDialog() {
|
||||
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({
|
||||
openDonationDialog
|
||||
});
|
||||
</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
|
||||
auto-grow
|
||||
></v-textarea>
|
||||
|
||||
<div v-if="errorMessage" class="error-message">{{ errorMessage }}</div>
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
@@ -43,8 +45,10 @@
|
||||
</button>
|
||||
|
||||
<button class="primary"
|
||||
@click="$emit('submit', { amount: tipAmountInDollars, message: tipMessage })">
|
||||
{{ t('creator.donation.send') }}
|
||||
@click="handleSubmit"
|
||||
:disabled="isProcessing">
|
||||
<span v-if="isProcessing" class="spinner mr-2"></span>
|
||||
{{ isProcessing ? t('creator.donation.processing') : t('creator.donation.send') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -53,13 +57,27 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
|
||||
const { t } = useI18n();
|
||||
const client = useClient();
|
||||
|
||||
const props = defineProps({
|
||||
showCancelButton: {
|
||||
type: Boolean,
|
||||
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 tipMessage = ref('');
|
||||
const errorMessage = ref('');
|
||||
const isProcessing = ref(false);
|
||||
|
||||
function preventNonNumeric(event) {
|
||||
const key = event.key;
|
||||
@@ -76,8 +96,57 @@ function preventNonNumeric(event) {
|
||||
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>
|
||||
|
||||
<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>
|
||||
{
|
||||
"en": {
|
||||
@@ -89,7 +158,12 @@ function preventNonNumeric(event) {
|
||||
"isupport": "I Support",
|
||||
"amount": "Amount ($)",
|
||||
"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",
|
||||
"amount": "Montant ($)",
|
||||
"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",
|
||||
"amount": "Cantidad ($)",
|
||||
"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