Skip to content

Commit

Permalink
Feature/update donation person (#348)
Browse files Browse the repository at this point in the history
* #958 added update of a donation's donor

* #958 changed keycloakid to personid

* code-review fixes
  • Loading branch information
stann1 authored Nov 2, 2022
1 parent dce2692 commit 78c3c95
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 19 deletions.
115 changes: 113 additions & 2 deletions apps/api/src/donations/donations.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ import { STRIPE_CLIENT_TOKEN } from '@golevelup/nestjs-stripe'
import { NotAcceptableException } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { Test, TestingModule } from '@nestjs/testing'
import { Campaign, CampaignState } from '@prisma/client'
import {
Campaign,
CampaignState,
Currency,
DonationStatus,
DonationType,
PaymentProvider,
Prisma,
} from '@prisma/client'
import { CampaignService } from '../campaign/campaign.service'
import { PersonService } from '../person/person.service'
import { MockPrismaService, prismaMock } from '../prisma/prisma-client.mock'
Expand All @@ -26,6 +34,32 @@ describe('DonationsController', () => {
cancelUrl: 'http://test.com',
isAnonymous: true,
} as CreateSessionDto
const vaultMock = {
incrementVaultAmount: jest.fn(),
}

const mockDonation = {
id: '123',
provider: PaymentProvider.bank,
currency: Currency.BGN,
type: DonationType.donation,
status: DonationStatus.succeeded,
amount: 10,
extCustomerId: 'gosho',
extPaymentIntentId: 'pm1',
extPaymentMethodId: 'bank',
billingEmail: '[email protected]',
billingName: 'gosho1',
targetVaultId: '1000',
chargedAmount: 10.5,
createdAt: new Date('2022-01-01'),
updatedAt: new Date('2022-01-02'),
personId: '1',
person: {
id: '1',
keycloakId: '00000000-0000-0000-0000-000000000015',
},
}

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
Expand All @@ -39,7 +73,10 @@ describe('DonationsController', () => {
},
CampaignService,
DonationsService,
VaultService,
{
provide: VaultService,
useValue: vaultMock,
},
MockPrismaService,
{
provide: STRIPE_CLIENT_TOKEN,
Expand Down Expand Up @@ -109,4 +146,78 @@ describe('DonationsController', () => {
expect(prismaMock.campaign.findFirst).toHaveBeenCalled()
expect(stripeMock.checkout.sessions.create).toHaveBeenCalled()
})

it('should update a donations donor, when it is changed', async () => {
const updatePaymentDto = {
type: DonationType.donation,
amount: 10,
targetPersonId: '2',
}

const existingDonation = { ...mockDonation }
const existingTargetPerson = {
id: '2',
firstName: 'string',
lastName: 'string',
email: 'string',
phone: 'string',
company: 'string',
createdAt: new Date('2022-01-01'),
updatedAt: new Date('2022-01-01'),
newsletter: false,
address: 'string',
birthday: new Date('2002-07-07'),
emailConfirmed: true,
personalNumber: 'string',
keycloakId: '00000000-0000-0000-0000-000000000012',
stripeCustomerId: 'string',
picture: 'string',
}

prismaMock.donation.findFirst.mockResolvedValueOnce(existingDonation)
prismaMock.person.findFirst.mockResolvedValueOnce(existingTargetPerson)

// act
await controller.update('123', updatePaymentDto)

// assert
expect(prismaMock.donation.update).toHaveBeenCalledWith({
where: { id: '123' },
data: {
status: existingDonation.status,
personId: '2',
},
})
expect(vaultMock.incrementVaultAmount).toHaveBeenCalledTimes(0)
})

it('should update a donation status, when it is changed', async () => {
const updatePaymentDto = {
type: DonationType.donation,
amount: 10,
status: DonationStatus.succeeded,
}

const existingDonation = { ...mockDonation, status: DonationStatus.initial }
const expectedUpdatedDonation = {...existingDonation, status: DonationStatus.succeeded }

prismaMock.donation.findFirst.mockResolvedValueOnce(existingDonation)
prismaMock.donation.update.mockResolvedValueOnce(expectedUpdatedDonation)

// act
await controller.update('123', updatePaymentDto)

// assert
expect(prismaMock.donation.update).toHaveBeenCalledWith({
where: { id: '123' },
data: {
status: DonationStatus.succeeded,
personId: '1',
},
})
expect(vaultMock.incrementVaultAmount).toHaveBeenCalledWith(
existingDonation.targetVaultId,
existingDonation.amount,
)
})
})
63 changes: 47 additions & 16 deletions apps/api/src/donations/donations.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Stripe from 'stripe'
import { ConfigService } from '@nestjs/config'
import { InjectStripeClient } from '@golevelup/nestjs-stripe'
import { Injectable, Logger, NotFoundException } from '@nestjs/common'
import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common'
import {
Campaign,
Donation,
Expand Down Expand Up @@ -301,36 +301,67 @@ export class DonationsService {
}
}

/**
* Updates the donation's status or donor. Note: completed donations cannot have status updates.
* @param id
* @param updatePaymentDto
* @returns
*/
async update(id: string, updatePaymentDto: UpdatePaymentDto): Promise<Donation> {
try {
const oldDonationStatus = (
await this.prisma.donation.findFirst({
where: { id },
select: { status: true },
})
)?.status
const currentDonation = await this.prisma.donation.findFirst({
where: { id },
})
if (!currentDonation) {
throw new NotFoundException(`Update failed. No donation found with ID: ${id}`)
}

if (oldDonationStatus === DonationStatus.succeeded) {
throw new Error('Succeded donations cannot be updated.')
if (
currentDonation.status === DonationStatus.succeeded &&
updatePaymentDto.status &&
updatePaymentDto.status !== DonationStatus.succeeded
) {
throw new BadRequestException('Succeeded donations cannot be updated.')
}

const status = updatePaymentDto.status || currentDonation.status
let donorId = currentDonation.personId
if (
updatePaymentDto.targetPersonId &&
currentDonation.personId !== updatePaymentDto.targetPersonId
) {
const targetDonor = await this.prisma.person.findFirst({
where: { id: updatePaymentDto.targetPersonId },
})
if (!targetDonor) {
throw new NotFoundException(
`Update failed. No person found with ID: ${updatePaymentDto.targetPersonId}`,
)
}
donorId = targetDonor.id
}

const donation = await this.prisma.donation.update({
where: { id },
data: {
status: updatePaymentDto.status,
status: status,
personId: donorId,
},
})

if (updatePaymentDto.status === DonationStatus.succeeded) {
await this.vaultService.incrementVaultAmount(donation.targetVaultId, donation.amount)
if (currentDonation.status !== DonationStatus.succeeded
&& updatePaymentDto.status === DonationStatus.succeeded
&& donation.status === DonationStatus.succeeded) {
await this.vaultService.incrementVaultAmount(
currentDonation.targetVaultId,
currentDonation.amount,
)
}

return donation
} catch (err) {
const msg = `Update failed. No Donation found with ID: ${id}`

Logger.warn(msg)
throw new NotFoundException(msg)
Logger.warn(err.message || err)
throw err
}
}

Expand Down
11 changes: 10 additions & 1 deletion apps/api/src/donations/dto/update-payment.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { PartialType } from '@nestjs/mapped-types'
import { ApiProperty } from '@nestjs/swagger'
import { Expose } from 'class-transformer'
import { IsOptional, IsUUID } from 'class-validator'
import { CreatePaymentDto } from './create-payment.dto'

export class UpdatePaymentDto extends PartialType(CreatePaymentDto) {}
export class UpdatePaymentDto extends PartialType(CreatePaymentDto) {
@Expose()
@ApiProperty()
@IsOptional()
@IsUUID()
targetPersonId?: string
}

1 comment on commit 78c3c95

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report

St.
Category Percentage Covered / Total
🟡 Statements 71.93% 1901/2643
🔴 Branches 46.36% 248/535
🔴 Functions 44.76% 222/496
🟡 Lines 70.02% 1686/2408

Test suite run success

180 tests passing in 64 suites.

Report generated by 🧪jest coverage report action from 78c3c95

Please sign in to comment.