diff --git a/public/locales/bg/donations.json b/public/locales/bg/donations.json
index 036db7acb..1e34bafac 100644
--- a/public/locales/bg/donations.json
+++ b/public/locales/bg/donations.json
@@ -32,6 +32,7 @@
"edit": "Дарението беше редактирано успешно!",
"delete": "Дарението беше изтрито успешно!",
"refundSuccess": "Беше създадена успешна заявка за връщане на парите към Stripe.",
+ "invalidate": "Дарението беше маркирано като невалидно!",
"editDonor": "Дарителят беше редактиран успешно!",
"error": "Възникна грешка! Моля опитайте отново по-късно.",
"requiredError": "Полето е задължително."
@@ -66,5 +67,14 @@
"amount": "Сума:",
"email": "Е-mail на дарител:",
"confirm-button": "Възстанови парите от дарение"
+ },
+ "invalidate": {
+ "icon": "Маркирай като невалидно",
+ "title": "Маркиране на дарение като невалидно",
+ "confirmation": "Сигурни ли сте, че искате да маркирате като невалидно дарението:",
+ "number": "Номер:",
+ "amount": "Сума:",
+ "email": "Е-mail на дарител:",
+ "confirm-button": "Маркирай като невалидно"
}
}
diff --git a/public/locales/en/donations.json b/public/locales/en/donations.json
index a87b2076f..f912bfdb4 100644
--- a/public/locales/en/donations.json
+++ b/public/locales/en/donations.json
@@ -32,6 +32,7 @@
"edit": "Document has been edited successfully!",
"delete": "Document has been deleted successfully!",
"refundSuccess": "A successful refund request was created with Stripe.",
+ "invalidate": "Document has been marked as invalid successfully!",
"editDonor": "Donation donor has been edited successfully!",
"error": "An error has occured! Please try again later.",
"requiredError": "Fields is required!"
@@ -66,5 +67,14 @@
"amount": "Amount:",
"email": "Donator's email:",
"confirm-button": "Refund a donation"
+ },
+ "invalidate": {
+ "icon": "Mark as invalid",
+ "title": "Mark as invalid",
+ "confirmation": "Are you sure you want to refund the donation:",
+ "number": "Number:",
+ "amount": "Amount:",
+ "email": "Е-mail",
+ "confirm-button": "Mark as invalid"
}
}
diff --git a/src/components/admin/donations/DonationsPage.tsx b/src/components/admin/donations/DonationsPage.tsx
index 88e1a041f..b4678295d 100644
--- a/src/components/admin/donations/DonationsPage.tsx
+++ b/src/components/admin/donations/DonationsPage.tsx
@@ -9,6 +9,7 @@ import { RefundStoreImpl } from './store/RefundStore'
export const ModalStore = new ModalStoreImpl()
export const RefundStore = new RefundStoreImpl()
+export const InvalidateStore = new ModalStoreImpl()
export default function DocumentsPage() {
const { t } = useTranslation()
diff --git a/src/components/admin/donations/grid/Grid.tsx b/src/components/admin/donations/grid/Grid.tsx
index 5899f3c6f..539073350 100644
--- a/src/components/admin/donations/grid/Grid.tsx
+++ b/src/components/admin/donations/grid/Grid.tsx
@@ -14,8 +14,8 @@ import { observer } from 'mobx-react'
import { useDonationsList } from 'common/hooks/donation'
import DetailsModal from '../modals/DetailsModal'
-import DeleteModal from '../modals/DeleteModal'
-import { ModalStore, RefundStore } from '../DonationsPage'
+import InvalidateModal from '../modals/InvalidateModal'
+import { ModalStore, RefundStore, InvalidateStore } from '../DonationsPage'
import { getExactDateTime } from 'common/util/date'
import { useRouter } from 'next/router'
import { money } from 'common/util/money'
@@ -26,8 +26,11 @@ import RenderEditPersonCell from './RenderEditPersonCell'
import { useStores } from '../../../../common/hooks/useStores'
import RenderEditBillingEmailCell from './RenderEditBillingEmailCell'
import RestoreIcon from '@mui/icons-material/Restore'
+import CancelIcon from '@mui/icons-material/Cancel'
import RefundModal from '../modals/RefundModal'
import { DonationStatus, PaymentProvider } from '../../../../gql/donations.enums'
+import { useSession } from 'next-auth/react'
+
interface RenderCellProps {
params: GridRenderCellParams
@@ -51,8 +54,16 @@ export default observer(function Grid() {
const router = useRouter()
const { isDetailsOpen } = ModalStore
const { isRefundOpen } = RefundStore
+ const {
+ isDeleteOpen,
+ setSelectedRecord: setInvalidateRecord,
+ showDelete: showInvalidate,
+ } = InvalidateStore
const campaignId = router.query.campaignId as string | undefined
-
+ const { data: session } = useSession()
+ const canEditFinancials = session?.user?.realm_access?.roles.includes(
+ 'account-edit-financials-requests',
+ )
const {
data: { items: donations, total: allDonationsCount } = { items: [], total: 0 },
// error: donationHistoryError,
@@ -147,6 +158,11 @@ export default observer(function Grid() {
showRefund()
}
+ function invalidateClickHandler(id: string) {
+ setInvalidateRecord({ id, name: '' })
+ showInvalidate()
+ }
+
const columns: GridColDef[] = [
{
field: 'actions',
@@ -155,7 +171,8 @@ export default observer(function Grid() {
width: 120,
resizable: false,
renderCell: (params: GridRenderCellParams) => {
- return params.row?.status === DonationStatus.succeeded &&
+ return canEditFinancials &&
+ params.row?.status === DonationStatus.succeeded &&
params.row?.provider === PaymentProvider.stripe ? (
<>
@@ -166,6 +183,14 @@ export default observer(function Grid() {
+
+ invalidateClickHandler(params.row.id)}>
+
+
+
>
) : (
''
@@ -292,7 +317,7 @@ export default observer(function Grid() {
{/* making sure we don't sent requests to the API when not needed */}
{isDetailsOpen && }
{isRefundOpen && }
-
+ {isDeleteOpen && }
>
)
})
diff --git a/src/components/admin/donations/modals/InvalidateModal.tsx b/src/components/admin/donations/modals/InvalidateModal.tsx
new file mode 100644
index 000000000..226ee569d
--- /dev/null
+++ b/src/components/admin/donations/modals/InvalidateModal.tsx
@@ -0,0 +1,48 @@
+import React from 'react'
+import { useRouter } from 'next/router'
+import { useMutation } from '@tanstack/react-query'
+import { observer } from 'mobx-react'
+import { AxiosError, AxiosResponse } from 'axios'
+import { useTranslation } from 'next-i18next'
+
+import { DonationResponse } from 'gql/donations'
+import { ApiErrors } from 'service/apiErrors'
+import { AlertStore } from 'stores/AlertStore'
+import { routes } from 'common/routes'
+import DeleteDialog from 'components/admin/DeleteDialog'
+import { useInvalidateStripeDonation } from 'service/donation'
+
+import { InvalidateStore } from '../DonationsPage'
+
+type Props = {
+ onUpdate: () => void
+}
+
+export default observer(function InvalidateModal({ onUpdate }: Props) {
+ const router = useRouter()
+ const { hideDelete, selectedRecord } = InvalidateStore
+ const { t } = useTranslation()
+
+ const mutationFn = useInvalidateStripeDonation()
+
+ const invalidateMutation = useMutation<
+ AxiosResponse,
+ AxiosError,
+ string
+ >({
+ mutationFn,
+ onError: () => AlertStore.show(t('donations:alerts:error'), 'error'),
+ onSuccess: () => {
+ hideDelete()
+ AlertStore.show(t('donations:alerts:invalidate'), 'success')
+ router.push(routes.admin.donations.index)
+ onUpdate()
+ },
+ })
+
+ function invalidateHandler() {
+ invalidateMutation.mutate(selectedRecord.id)
+ }
+
+ return
+})
diff --git a/src/service/apiEndpoints.ts b/src/service/apiEndpoints.ts
index a18cee857..290b8b5ab 100644
--- a/src/service/apiEndpoints.ts
+++ b/src/service/apiEndpoints.ts
@@ -118,6 +118,8 @@ export const endpoints = {
createBankDonation: { url: '/donation/create-bank-payment', method: 'POST' },
refundStripePayment: (id: string) =>
{ url: `/donation/refund-stripe-payment/${id}`, method: 'POST' },
+ invalidateStripePayment: (id: string) =>
+ { url: `/donation/invalidate-stripe-payment/${id}`, method: 'POST' },
getDonation: (id: string) => { url: `/donation/${id}`, method: 'GET' },
donationsList: (
campaignId?: string,
diff --git a/src/service/donation.ts b/src/service/donation.ts
index 6090471fb..4d34407d2 100644
--- a/src/service/donation.ts
+++ b/src/service/donation.ts
@@ -78,6 +78,16 @@ export const useRefundStripeDonation = () => {
)
}
}
+export const useInvalidateStripeDonation = () => {
+ const { data: session } = useSession()
+ return async (extPaymentId: string) => {
+ return await apiClient.post(
+ endpoints.donation.invalidateStripePayment(extPaymentId).url,
+ '',
+ authConfig(session?.accessToken),
+ )
+ }
+}
export const useUploadBankTransactionsFiles = () => {
const { data: session } = useSession()