Skip to content

Commit

Permalink
Invalidate a successfull donation (#597)
Browse files Browse the repository at this point in the history
* Reduce the cache ttl for public donations and total money collected.
The idea of the cache is to help in extreme scenarios when many requests are being fired.
One request every 2 seconds should be easy to handle by the backend.

* The expense original filenames are encoded in base64. This allows us to upload files with cyrilic names. But it adds a bit of complexity in the backend.

* Add support for making a donation invalid. Sometimes we can have a buggy stripe donation and we would like to sort of remove it.
This is allowed only if one has special credentials, of course.

* Refund payment should also be covered by the account-edit-financials-requests role.

* Rename the post /invalidate-stripe-payment/:id to a patch /:id/invalidate to match the REST guidelines better.
  • Loading branch information
slavcho authored Jan 25, 2024
1 parent d21fea3 commit c0e7cf4
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 3 deletions.
24 changes: 24 additions & 0 deletions apps/api/src/donations/donations.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,4 +297,28 @@ describe('DonationsController', () => {
reason: 'requested_by_customer',
})
})

it('should invalidate a donation and update the vault if needed', async () => {
const existingDonation = { ...mockDonation, status: DonationStatus.succeeded }
jest.spyOn(prismaMock, '$transaction').mockImplementation((callback) => callback(prismaMock))

prismaMock.donation.findFirstOrThrow.mockResolvedValueOnce(existingDonation)

await controller.invalidate('123')

expect(prismaMock.donation.update).toHaveBeenCalledWith({
where: { id: '123' },
data: {
status: DonationStatus.invalid,
},
})
expect(prismaMock.vault.update).toHaveBeenCalledWith({
where: { id: existingDonation.targetVaultId },
data: {
amount: {
decrement: existingDonation.amount,
},
},
})
})
})
18 changes: 15 additions & 3 deletions apps/api/src/donations/donations.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { ApiQuery, ApiTags } from '@nestjs/swagger'
import { DonationStatus } from '@prisma/client'
import { AuthenticatedUser, Public, RoleMatchingMode, Roles } from 'nest-keycloak-connect'
import { RealmViewSupporters, ViewSupporters } from '@podkrepi-bg/podkrepi-types'
import { RealmViewSupporters, ViewSupporters, EditFinancialsRequests } from '@podkrepi-bg/podkrepi-types'

import { isAdmin, KeycloakTokenParsed } from '../auth/keycloak'
import { DonationsService } from './donations.service'
Expand Down Expand Up @@ -221,7 +221,7 @@ export class DonationsController {

@Post('/refund-stripe-payment/:id')
@Roles({
roles: [RealmViewSupporters.role, ViewSupporters.role],
roles: [EditFinancialsRequests.role],
mode: RoleMatchingMode.ANY,
})
refundStripePaymet(@Param('id') paymentIntentId: string) {
Expand All @@ -240,6 +240,16 @@ export class DonationsController {
return this.donationsService.createUpdateBankPayment(bankPaymentDto)
}

@Patch('/:id/invalidate')
@Roles({
roles: [EditFinancialsRequests.role],
mode: RoleMatchingMode.ANY,
})
invalidate(@Param('id') id: string) {
Logger.debug(`Invalidating donation with id ${id}`)
return this.donationsService.invalidate(id)
}

@Patch(':id')
@Roles({
roles: [RealmViewSupporters.role, ViewSupporters.role],
Expand All @@ -251,12 +261,14 @@ export class DonationsController {
@Body()
updatePaymentDto: UpdatePaymentDto,
) {
Logger.debug(`Updating donation with id ${id}`)

return this.donationsService.update(id, updatePaymentDto)
}

@Post('delete')
@Roles({
roles: [RealmViewSupporters.role, ViewSupporters.role],
roles: [EditFinancialsRequests.role],
mode: RoleMatchingMode.ANY,
})
delete(
Expand Down
25 changes: 25 additions & 0 deletions apps/api/src/donations/donations.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,31 @@ export class DonationsService {
}
}

async invalidate(id: string) {
try {
await this.prisma.$transaction(async (tx) => {
const donation = await this.getDonationById(id)

if (donation.status === DonationStatus.succeeded) {
await this.vaultService.decrementVaultAmount(donation.targetVaultId, donation.amount, tx)
}

await this.prisma.donation.update({
where: { id },
data: {
status: DonationStatus.invalid,
},
})
})
} catch (err) {
Logger.warn(err.message || err)
const msg = `Invalidation failed. No Donation found with given ID.`

Logger.warn(msg)
throw new NotFoundException(msg)
}
}

async getDonationsByUser(keycloakId: string, email?: string) {
const donations = await this.prisma.donation.findMany({
where: {
Expand Down
3 changes: 3 additions & 0 deletions libs/podkrepi-types/src/lib/roles/team/edit-financials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class EditFinancialsRequests {
static readonly role = 'realm:account-edit-financials-requests'
}
1 change: 1 addition & 0 deletions libs/podkrepi-types/src/lib/roles/team/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './view-supporters'
export * from './view-contact-requests'
export * from './edit-financials'

0 comments on commit c0e7cf4

Please sign in to comment.