Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

473 - Refactor and change out the donation flow to use Stripe.SetupIntents #479

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3897f3b
473 - remove the use of emails as extCustomerIds
dimitur2204 Mar 20, 2023
533025d
473 - add createSubscriptionDonation method
dimitur2204 Mar 20, 2023
57a7869
473 - add endpoint for subscription donation
dimitur2204 Mar 20, 2023
f1180e9
473 - update donation to now take dynamic `amount` and `currency`
dimitur2204 Mar 21, 2023
d74742d
473 - add default incomplete subscription method
dimitur2204 Mar 21, 2023
234e7f9
add a `@Roles` to the `create-payment` endpoint (#469)
dimitur2204 Mar 20, 2023
1e5a4b7
Added error loging to stripe client (#470)
quantum-grit Mar 21, 2023
018929c
add version to stripe client (#471)
quantum-grit Mar 21, 2023
18fc356
upgrading prisma to remove openssl1.1-compat (#472)
quantum-grit Mar 21, 2023
5f00dfb
Add openssl explicitly for prisma
igoychev Mar 21, 2023
77baff7
473 - separete stripe into it's own module
dimitur2204 Mar 22, 2023
2697d7d
473 - add create setup intent endpoint
dimitur2204 Mar 22, 2023
a2d33cf
473 - fix stripe module imports
dimitur2204 Mar 22, 2023
dabd220
473 - add update setup intent endpoint
dimitur2204 Mar 22, 2023
e32222d
473 - add finalization of setup intent
dimitur2204 Mar 22, 2023
cddf10d
473 - cleanup donation service from old implementation
dimitur2204 Mar 22, 2023
e65cff7
473 - fix setup intent updating of campaignId metadata
dimitur2204 Mar 22, 2023
d692c03
473 - change out flow to use setup intent metadata
dimitur2204 Mar 23, 2023
798960b
473 - remove unneeded `paymentIntent.created` hook
dimitur2204 Mar 23, 2023
fb6955e
473 - fix getPaymentDataFromCharge customerId assignment
dimitur2204 Mar 23, 2023
cfa2ad9
473 - remove creation of wish from the charge.succeeded hook
dimitur2204 Mar 23, 2023
8db3f77
473 - move create donation to the donationService
dimitur2204 Mar 23, 2023
ae0d0f6
473 - remove unneeded endpoint
dimitur2204 Mar 23, 2023
7347068
473 - fix anonymous donations
dimitur2204 Mar 23, 2023
2ed3800
Merge remote-tracking branch 'origin/master' into 473-new-donation-fl…
dimitur2204 Mar 23, 2023
ea68458
473 - fix stripe payment service tests
dimitur2204 Mar 23, 2023
d3827e2
473 - fix strip webhook tests
dimitur2204 Mar 24, 2023
b7f9feb
473 - fix stripe and paypal module tests
dimitur2204 Mar 24, 2023
756c197
473 - fix `jest-mock-extended` import
dimitur2204 Mar 24, 2023
d037416
473 - remove `on_session` setup intent option
dimitur2204 Mar 24, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/api/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { PaypalModule } from '../paypal/paypal.module'
import { ExportModule } from '../export/export.module'
import { JwtModule } from '@nestjs/jwt'
import { NotificationModule } from '../sockets/notifications/notification.module'
import { StripeModule } from '../stripe/stripe.module'

@Module({
imports: [
Expand Down Expand Up @@ -96,6 +97,7 @@ import { NotificationModule } from '../sockets/notifications/notification.module
ExportModule,
JwtModule,
NotificationModule,
StripeModule,
],
controllers: [AppController],
providers: [
Expand Down
189 changes: 1 addition & 188 deletions apps/api/src/campaign/campaign.service.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,8 @@
import {
Prisma,
Campaign,
CampaignState,
CampaignType,
Donation,
DonationStatus,
DonationType,
Vault,
} from '@prisma/client'
import { Prisma, Campaign, CampaignState, CampaignType, Donation, Vault } from '@prisma/client'
import {
forwardRef,
Inject,
Injectable,
InternalServerErrorException,
Logger,
NotAcceptableException,
NotFoundException,
Expand All @@ -21,7 +11,6 @@ import {
import { PersonService } from '../person/person.service'
import { PrismaService } from '../prisma/prisma.service'
import { VaultService } from '../vault/vault.service'
import { shouldAllowStatusChange } from '../donations/helpers/donation-status-updates'
import { PaymentData } from '../donations/helpers/payment-intent-helpers'
import { CreateCampaignDto } from './dto/create-campaign.dto'
import { UpdateCampaignDto } from './dto/update-campaign.dto'
Expand All @@ -32,17 +21,11 @@ import {
CampaignListItem,
CampaignListItemSelect,
} from './dto/list-campaigns.dto'
import {
NotificationService,
donationNotificationSelect,
} from '../sockets/notifications/notification.service'
import { DonationMetadata } from '../donations/dontation-metadata.interface'

@Injectable()
export class CampaignService {
constructor(
private prisma: PrismaService,
private notificationService: NotificationService,
@Inject(forwardRef(() => VaultService)) private vaultService: VaultService,
@Inject(forwardRef(() => PersonService)) private personService: PersonService,
) {}
Expand Down Expand Up @@ -409,174 +392,6 @@ export class CampaignService {
return this.prisma.donation.findFirst({ where: { extPaymentIntentId: paymentIntentId } })
}

/**
* Creates or Updates an incoming donation depending on the newDonationStatus attribute
* @param campaign
* @param paymentData
* @param newDonationStatus
* @param metadata
* @returns donation.id of the created/updated donation
*/
async updateDonationPayment(
campaign: Campaign,
paymentData: PaymentData,
newDonationStatus: DonationStatus,
metadata?: DonationMetadata,
): Promise<string> {
const campaignId = campaign.id
Logger.debug('Update donation to status: ' + newDonationStatus, {
campaignId,
paymentIntentId: paymentData.paymentIntentId,
})

/**
* Create or connect campaign vault
*/
const vault = await this.prisma.vault.findFirst({ where: { campaignId } })
const targetVaultData = vault
? // Connect the existing vault to this donation
{ connect: { id: vault.id } }
: // Create new vault for the campaign
{ create: { campaignId, currency: campaign.currency, name: campaign.title } }

// Find donation by extPaymentIntentId and update if status allows

let donation = await this.prisma.donation.findUnique({
where: { extPaymentIntentId: paymentData.paymentIntentId },
select: donationNotificationSelect,
})

// check for UUID length of personId
// subscriptions always have a personId
if (!donation && paymentData.personId && paymentData.personId.length === 36) {
// search for a subscription donation
// for subscriptions, we don't have a paymentIntentId
donation = await this.prisma.donation.findFirst({
where: {
status: DonationStatus.initial,
personId: paymentData.personId,
chargedAmount: paymentData.chargedAmount,
extPaymentMethodId: 'subscription',
},
select: donationNotificationSelect,
})

if (donation) {
donation.status = newDonationStatus
this.notificationService.sendNotification('successfulDonation', donation)
}

Logger.debug('Donation found by subscription: ', donation)
}

//if missing create the donation with the incoming status
if (!donation) {
Logger.debug(
'No donation exists with extPaymentIntentId: ' +
paymentData.paymentIntentId +
' Creating new donation with status: ' +
newDonationStatus,
)

try {
donation = await this.prisma.donation.create({
data: {
amount: paymentData.netAmount,
chargedAmount: paymentData.chargedAmount,
currency: campaign.currency,
targetVault: targetVaultData,
provider: paymentData.paymentProvider,
type: DonationType.donation,
status: newDonationStatus,
extCustomerId: paymentData.stripeCustomerId ?? '',
extPaymentIntentId: paymentData.paymentIntentId,
extPaymentMethodId: paymentData.paymentMethodId ?? '',
billingName: paymentData.billingName,
billingEmail: paymentData.billingEmail,
person: paymentData.personId ? { connect: { id: paymentData.personId } } : {},
},
select: donationNotificationSelect,
})

this.notificationService.sendNotification('successfulDonation', donation)
} catch (error) {
Logger.error(
`Error while creating donation with paymentIntentId: ${paymentData.paymentIntentId} and status: ${newDonationStatus} . Error is: ${error}`,
)
throw new InternalServerErrorException(error)
}
}
//donation exists, so check if it is safe to update it
else if (shouldAllowStatusChange(donation.status, newDonationStatus)) {
try {
const updatedDonation = await this.prisma.donation.update({
where: {
id: donation.id,
},
data: {
status: newDonationStatus,
amount: paymentData.netAmount,
extCustomerId: paymentData.stripeCustomerId,
extPaymentMethodId: paymentData.paymentMethodId,
extPaymentIntentId: paymentData.paymentIntentId,
billingName: paymentData.billingName,
billingEmail: paymentData.billingEmail,
},
select: donationNotificationSelect,
})

this.notificationService.sendNotification('successfulDonation', {
...updatedDonation,
person: donation.person,
})
} catch (error) {
Logger.error(
`Error wile updating donation with paymentIntentId: ${paymentData.paymentIntentId} in database. Error is: ${error}`,
)
throw new InternalServerErrorException(error)
}
}
//donation exists but we need to skip because previous status is from later event than the incoming
else {
Logger.warn(
`Skipping update of donation with paymentIntentId: ${paymentData.paymentIntentId}
and status: ${newDonationStatus} because the event comes after existing donation with status: ${donation.status}`,
)
}

//For successful donations we will also need to link them to user and add donation wish:
if (newDonationStatus === DonationStatus.succeeded) {
Logger.debug('metadata?.isAnonymous = ' + metadata?.isAnonymous)

if (metadata?.isAnonymous != 'true') {
await this.prisma.donation.update({
where: { id: donation.id },
data: {
person: {
connect: {
email: paymentData.billingEmail,
},
},
},
})
}
}

return donation.id
}

async createDonationWish(wish: string, donationId: string, campaignId: string) {
const person = await this.prisma.donation.findUnique({ where: { id: donationId } }).person()
await this.prisma.donationWish.create({
data: {
message: wish,
donationId,
campaignId,
personId: person?.id,
},
})
}

async donateToCampaign(campaign: Campaign, paymentData: PaymentData) {
Logger.debug('Update amounts with successful donation', {
campaignId: campaign.id,
Expand All @@ -588,8 +403,6 @@ export class CampaignService {
const vault = await this.getCampaignVault(campaign.id)
if (vault) {
await this.vaultService.incrementVaultAmount(vault.id, paymentData.netAmount)
} else {
//vault is already checked and created if not existing in updateDonationPayment() above
}
}

Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/config/validation.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const globalValidationPipe = new ValidationPipe({
excludeExtraneousValues: true,
},
stopAtFirstError: false,
forbidUnknownValues: true,
forbidUnknownValues: false,
disableErrorMessages: false,
exceptionFactory: (errors) => new BadRequestException(errors),
validationError: { target: false, value: false },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ApiProperty } from '@nestjs/swagger'
export class CreateDonationDto {
@ApiProperty({ enum: DonationType })
type: DonationType
extCustomerId: string
extCustomerId?: string
extPaymentIntentId: string
extPaymentMethodId: string
billingEmail?: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class Donation {
status: DonationStatus
provider: PaymentProvider
targetVaultId: string
extCustomerId: string
extCustomerId: string | null
extPaymentIntentId: string
extPaymentMethodId: string
createdAt: Date
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ export class CreateRecurringDonationDto {
@ApiProperty({ enum: RecurringDonationStatus })
status: RecurringDonationStatus
extSubscriptionId: string
extCustomerId?: string
extCustomerId: string
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class RecurringDonation {
vaultId: string
personId: string
extSubscriptionId: string
extCustomerId: string | null
extCustomerId: string
createdAt: Date
updatedAt: Date | null
amount: number
Expand Down
Loading