Skip to content

Commit

Permalink
Partially revert 7dde7aa
Browse files Browse the repository at this point in the history
This refactor will be handled by another PR
  • Loading branch information
sashko9807 committed Jun 25, 2024
1 parent 5c73683 commit e80f63f
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 198 deletions.
228 changes: 226 additions & 2 deletions apps/api/src/campaign/campaign.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ export class CampaignService {
return campaign
}

async getCampaignBySlug(slug: string) {
async getCampaignBySlug(slug: string): Promise<Campaign> {
const includeFilter = {
campaignType: {
select: { name: true, slug: true, category: true },
Expand Down Expand Up @@ -409,7 +409,6 @@ export class CampaignService {
},
},
},
vaults: true,
campaignFiles: true,
}

Expand Down Expand Up @@ -446,6 +445,11 @@ export class CampaignService {
campaign['summary'] = this.getVaultAndDonationSummaries(campaign.id, campaignSums)
campaign['campaignNews'] = await this.getCampaignNews(campaign.id)

const vault = await this.getCampaignVault(campaign.id)
if (vault) {
campaign['defaultVault'] = vault?.id
}

return campaign
}

Expand Down Expand Up @@ -574,6 +578,226 @@ export class CampaignService {
return this.prisma.payment.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: PaymentStatus,
): Promise<string | undefined> {
const campaignId = campaign.id
Logger.debug('Update donation to status: ' + newDonationStatus, {
campaignId,
paymentIntentId: paymentData.paymentIntentId,
})

//Update existing donation or create new in a transaction that
//also increments the vault amount and marks campaign as completed
//if target amount is reached
return await this.prisma.$transaction(async (tx) => {
let donationId
// Find donation by extPaymentIntentId
const existingDonation = await this.findExistingDonation(tx, paymentData)

//if missing create the donation with the incoming status
if (!existingDonation) {
const newDonation = await this.createIncomingDonation(
tx,
paymentData,
newDonationStatus,
campaign,
)
donationId = newDonation.id
}
//donation exists, so check if it is safe to update it
else {
const updatedDonation = await this.updateDonationIfAllowed(
tx,
existingDonation,
newDonationStatus,
paymentData,
)
donationId = updatedDonation?.id
}

return donationId
}) //end of the transaction scope
}

private async updateDonationIfAllowed(
tx: Prisma.TransactionClient,
payment: PaymentWithDonation,
newDonationStatus: PaymentStatus,
paymentData: PaymentData,
) {
if (shouldAllowStatusChange(payment.status, newDonationStatus)) {
try {
const updatedDonation = await tx.payment.update({
where: {
id: payment.id,
},
data: {
status: newDonationStatus,
amount: paymentData.netAmount,
extCustomerId: paymentData.stripeCustomerId,
extPaymentMethodId: paymentData.paymentMethodId,
extPaymentIntentId: paymentData.paymentIntentId,
billingName: paymentData.billingName,
billingEmail: paymentData.billingEmail,
donations: {
updateMany: {
where: { paymentId: payment.id },
data: {
amount: paymentData.netAmount,
},
},
},
},
select: donationNotificationSelect,
})

//if donation is switching to successful, increment the vault amount and send notification
if (
payment.status != PaymentStatus.succeeded &&
newDonationStatus === PaymentStatus.succeeded
) {
await this.vaultService.incrementVaultAmount(
payment.donations[0].targetVaultId,
paymentData.netAmount,
tx,
)
this.notificationService.sendNotification('successfulDonation', {
...updatedDonation,
person: updatedDonation.donations[0].person,
})
} else if (
payment.status === PaymentStatus.succeeded &&
newDonationStatus === PaymentStatus.refund
) {
await this.vaultService.decrementVaultAmount(
payment.donations[0].targetVaultId,
paymentData.netAmount,
tx,
)
this.notificationService.sendNotification('successfulRefund', {
...updatedDonation,
person: updatedDonation.donations[0].person,
})
}
return updatedDonation
} 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: ${payment.status}`,
)
}
}

private async createIncomingDonation(
tx: Prisma.TransactionClient,
paymentData: PaymentData,
newDonationStatus: PaymentStatus,
campaign: Campaign,
) {
Logger.debug(
'No donation exists with extPaymentIntentId: ' +
paymentData.paymentIntentId +
' Creating new donation with status: ' +
newDonationStatus,
)

const vault = await tx.vault.findFirstOrThrow({ where: { campaignId: campaign.id } })
const targetVaultData = { connect: { id: vault.id } }

try {
const donation = await tx.payment.create({
data: {
amount: paymentData.netAmount,
chargedAmount: paymentData.chargedAmount,
currency: campaign.currency,
provider: paymentData.paymentProvider,
type: PaymentType.single,
status: newDonationStatus,
extCustomerId: paymentData.stripeCustomerId ?? '',
extPaymentIntentId: paymentData.paymentIntentId,
extPaymentMethodId: paymentData.paymentMethodId ?? '',
billingName: paymentData.billingName,
billingEmail: paymentData.billingEmail,
donations: {
create: {
amount: paymentData.netAmount,
type: paymentData.type as DonationType,
person: paymentData.personId ? { connect: { email: paymentData.billingEmail } } : {},
targetVault: targetVaultData,
},
},
},
select: donationNotificationSelect,
})

if (newDonationStatus === PaymentStatus.succeeded) {
await this.vaultService.incrementVaultAmount(
donation.donations[0].targetVaultId,
donation.amount,
tx,
)
this.notificationService.sendNotification('successfulDonation', donation)
}

return donation
} catch (error) {
Logger.error(
`Error while creating donation with paymentIntentId: ${paymentData.paymentIntentId} and status: ${newDonationStatus} . Error is: ${error}`,
)
throw new InternalServerErrorException(error)
}
}

private async findExistingDonation(tx: Prisma.TransactionClient, paymentData: PaymentData) {
//first try to find by paymentIntentId
let donation = await tx.payment.findUnique({
where: { extPaymentIntentId: paymentData.paymentIntentId },
include: { donations: true },
})

// if not found by paymentIntent, check for if this is payment on subscription
// 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 tx.payment.findFirst({
where: {
status: PaymentStatus.initial,
chargedAmount: paymentData.chargedAmount,
extPaymentMethodId: 'subscription',
donations: {
some: {
personId: paymentData.personId,
},
},
},
include: { donations: true },
})
Logger.debug('Donation found by subscription: ', donation)
}
return donation
}

async createDonationWish(wish: string, donationId: string, campaignId: string) {
const person = await this.prisma.donation.findUnique({ where: { id: donationId } }).person()
await this.prisma.donationWish.upsert({
Expand Down
Loading

0 comments on commit e80f63f

Please sign in to comment.