Skip to content
This repository has been archived by the owner on Jan 3, 2025. It is now read-only.

Commit

Permalink
Add button to refund Payments to Edit Registrations and show payment …
Browse files Browse the repository at this point in the history
…status (#296)

* refund frontend

* useMutation for refunds

* fix lint

* make sure modal doesn't get accidentally closed

* add missing loading check

* show payment status in registration table

* correct order of arguments

* safe payment access

* shorten it to Paid on

* correctly get payment status in registration editor

* fix typo

* correctly check for error

* correctly send the refund request

* fix registration admin list not displaying correctly

* display payment updated_at more nicely

* show number of registrations on each list

* make admin comment less comically large
  • Loading branch information
FinnIckler authored Nov 3, 2023
1 parent e30ab95 commit 7d7d76f
Show file tree
Hide file tree
Showing 12 changed files with 317 additions and 167 deletions.
12 changes: 12 additions & 0 deletions Frontend/src/api/helper/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ export const permissionsRoute = `${process.env.WCA_URL}/api/v0/users/me/permissi
export const paymentConfigRoute = `${process.env.WCA_URL}/payment/config`
export const paymentFinishRoute = (competitionId: string, userId: string) =>
`${process.env.WCA_URL}/payment/finish?attendee_id=${competitionId}-${userId}`

export const availableRefundsRoute = (competitionId: string, userId: string) =>
`${process.env.WCA_URL}/payment/refunds?attendee_id=${competitionId}-${userId}`

export const refundRoute = (
competitionId: string,
userId: string,
paymentId: string,
amount: number
) =>
`${process.env.WCA_URL}/payment/refund?attendee_id=${competitionId}-${userId}&payment_id=${paymentId}&refund_amount=${amount}`

export const pollingRoute = (userId: string, competitionId: string) =>
`${process.env.POLL_URL}?attendee_id=${competitionId}-${userId}`
export const meRoute = `${process.env.WCA_URL}/api/v0/users/me`
Expand Down
11 changes: 11 additions & 0 deletions Frontend/src/api/payment/get/get_available_refunds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import externalServiceFetch from '../../helper/external_service_fetch'
import { availableRefundsRoute } from '../../helper/routes'

export default async function getAvailableRefunds(
competitionId: string,
userId: string
): Promise<{
charges: { payment_id: string; amount: number }[]
}> {
return externalServiceFetch(availableRefundsRoute(competitionId, userId))
}
15 changes: 15 additions & 0 deletions Frontend/src/api/payment/get/refund_payment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import externalServiceFetch from '../../helper/external_service_fetch'
import { refundRoute } from '../../helper/routes'

export default async function refundPayment(body: {
competitionId: string
userId: string
paymentId: string
amount: number
}): Promise<{
status: string
}> {
return externalServiceFetch(
refundRoute(body.competitionId, body.userId, body.paymentId, body.amount)
)
}
3 changes: 1 addition & 2 deletions Frontend/src/api/registration/get/get_registrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ export async function getAllRegistrations(
for (const registration of data!) {
const user = (await getCompetitorInfo(registration.user_id)).user
regList.push({
user_id: registration.user_id,
competing: registration.competing,
...registration,
user,
})
}
Expand Down
2 changes: 1 addition & 1 deletion Frontend/src/pages/register/components/Processing.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function Processing({ onProcessingComplete }) {
}
}, [data, onProcessingComplete])
return (
<Modal dimmer="blurring">
<Modal open={data?.status?.competing !== 'pending'} dimmer="blurring">
<Modal.Header>Your registration is processing...</Modal.Header>
<Modal.Content>
{pollCounter > 3 && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query'
import { FlagIcon, UiIcon } from '@thewca/wca-components'
import React, { useContext, useMemo, useReducer } from 'react'
import { Link } from 'react-router-dom'
import { Checkbox, Popup, Table } from 'semantic-ui-react'
import { Checkbox, Header, Popup, Table } from 'semantic-ui-react'
import { CompetitionContext } from '../../../api/helper/context/competition_context'
import { getAllRegistrations } from '../../../api/registration/get/get_registrations'
import { BASE_ROUTE } from '../../../routes'
Expand Down Expand Up @@ -169,37 +169,38 @@ export default function RegistrationAdministrationList() {
[registrations]
)
return isLoading ? (
<div className={styles.listContainer}>
<LoadingMessage />
</div>
<LoadingMessage />
) : (
<>
<div className={styles.listContainer}>
<div className={styles.listHeader}> Pending registrations </div>
<Header> Pending registrations ({pending.length}) </Header>
<RegistrationAdministrationTable
registrations={pending}
add={(attendee) => dispatch({ type: 'add-pending', attendee })}
remove={(attendee) => dispatch({ type: 'remove-pending', attendee })}
competition_id={competitionInfo.id}
selected={selected.pending}
/>
<div className={styles.listHeader}> Approved registrations </div>
<Header>
Approved registrations ({accepted.length}/
{competitionInfo.competitor_limit})
</Header>
<RegistrationAdministrationTable
registrations={accepted}
add={(attendee) => dispatch({ type: 'add-accepted', attendee })}
remove={(attendee) => dispatch({ type: 'remove-accepted', attendee })}
competition_id={competitionInfo.id}
selected={selected.accepted}
/>
<div className={styles.listHeader}> Waitlisted registrations </div>
<Header> Waitlisted registrations ({waiting.length}) </Header>
<RegistrationAdministrationTable
registrations={waiting}
add={(attendee) => dispatch({ type: 'add-waiting', attendee })}
remove={(attendee) => dispatch({ type: 'remove-waiting', attendee })}
competition_id={competitionInfo.id}
selected={selected.waiting}
/>
<div className={styles.listHeader}> Cancelled registrations </div>
<Header> Cancelled registrations ({cancelled.length}) </Header>
<RegistrationAdministrationTable
registrations={cancelled}
add={(attendee) => dispatch({ type: 'add-cancelled', attendee })}
Expand Down Expand Up @@ -228,8 +229,9 @@ function RegistrationAdministrationTable({
competition_id,
selected,
}) {
const { competitionInfo } = useContext(CompetitionContext)
return (
<Table textAlign="left" className={styles.list} singleLine>
<Table striped textAlign="left">
<Table.Header>
<Table.Row>
<Table.HeaderCell>
Expand All @@ -248,7 +250,13 @@ function RegistrationAdministrationTable({
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>Citizen of</Table.HeaderCell>
<Table.HeaderCell>Registered on</Table.HeaderCell>
<Table.HeaderCell>Number of Events</Table.HeaderCell>
{competitionInfo['using_stripe_payments?'] && (
<>
<Table.HeaderCell>Payment status</Table.HeaderCell>
<Table.HeaderCell>Paid on</Table.HeaderCell>
</>
)}
<Table.HeaderCell># Events</Table.HeaderCell>
<Table.HeaderCell>Guests</Table.HeaderCell>
<Table.HeaderCell>Comment</Table.HeaderCell>
<Table.HeaderCell>Administrative notes</Table.HeaderCell>
Expand Down Expand Up @@ -312,6 +320,31 @@ function RegistrationAdministrationTable({
}
/>
</Table.Cell>
{competitionInfo['using_stripe_payments?'] && (
<>
<Table.Cell>
{registration.payment.payment_status ?? 'not paid'}
</Table.Cell>
<Table.Cell>
{registration.payment.updated_at ? (
<Popup
content={new Date(
registration.payment.updated_at
).toTimeString()}
trigger={
<span>
{new Date(
registration.payment.updated_at
).toLocaleDateString()}
</span>
}
/>
) : (
''
)}
</Table.Cell>
</>
)}
<Table.Cell>
{registration.competing.event_ids.length}
</Table.Cell>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
@import '../../../style/font-sizes';
@import '../../../style/margins';
@import '../../../style/colors';
.list {
border-radius: 23px;
background-color: $background-grey;
border-width: 10px;
padding: 10px;
}

.list-container{
margin-top: $double-margin;
}
.list-header{
font-size: $header-size;
margin-top: $triple-margin;
margin-bottom: $triple-margin;
}
94 changes: 94 additions & 0 deletions Frontend/src/pages/registration_edit/components/Refunds.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { useMutation, useQuery } from '@tanstack/react-query'
import React, { useState } from 'react'
import { Button, Input, Label, Modal, Table } from 'semantic-ui-react'
import getAvailableRefunds from '../../../api/payment/get/get_available_refunds'
import refundPayment from '../../../api/payment/get/refund_payment'
import { setMessage } from '../../../ui/events/messages'
import LoadingMessage from '../../../ui/messages/loadingMessage'

export default function Refunds({ open, onExit, userId, competitionId }) {
const [refundAmount, setRefundAmount] = useState(0)
const {
data: refunds,
isLoading: refundsLoading,
isError: refundError,
} = useQuery({
queryKey: ['refunds', competitionId, userId],
queryFn: () => getAvailableRefunds(competitionId, userId),
refetchOnWindowFocus: false,
refetchOnReconnect: false,
staleTime: Infinity,
refetchOnMount: 'always',
})
const { mutate: refundMutation, isLoading: isMutating } = useMutation({
mutationFn: refundPayment,
onError: (data) => {
setMessage(
'Refund payment failed with error: ' + data.errorCode,
'negative'
)
},
onSuccess: () => {
setMessage('Refund succeeded', 'positive')
onExit()
},
})
return refundsLoading ? (
<LoadingMessage />
) : (
!refundError && (
<Modal open={open} dimmer="blurring">
<Modal.Header>Available Refunds:</Modal.Header>
<Modal.Content>
<Table>
<Table.Header>
<Table.Header> Amount </Table.Header>
<Table.Header> </Table.Header>
</Table.Header>
<Table.Body>
{refunds.charges.map((refund) => (
<Table.Row key={refund.payment_id}>
<Table.Cell>
<Input
labelPosition="right"
type="text"
placeholder={refund.amount}
>
<Label basic>$</Label>
<input
value={refundAmount}
max={refund.amount}
onChange={(event) =>
setRefundAmount(event.target.value)
}
/>
</Input>
</Table.Cell>
<Table.Cell>
<Button
onClick={() =>
refundMutation({
competitionId,
userId,
paymentId: refund.payment_id,
amount: refundAmount,
})
}
>
Refund amount
</Button>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</Modal.Content>
<Modal.Actions>
<Button disabled={isMutating} onClick={onExit}>
Go Back
</Button>
</Modal.Actions>
</Modal>
)
)
}
Loading

0 comments on commit 7d7d76f

Please sign in to comment.