// 결제 취소
async cancelPurchase(workspaceId: number, paymentId: number, userId: number): Promise<Object> {
const entityManager = this.paymentRepository.manager;
const targetPayment = await this.paymentRepository.findOne({
where: { id: paymentId, workspaceId, user: { id: userId } },
});
if (!targetPayment) throw new HttpException('해당 결제 내역을 찾을 수 없습니다.', HttpStatus.NOT_FOUND);
const targetMembership = await this.membershipService.getMyMembership(workspaceId);
const milliSecondPerDay = 24 * 60 * 60 * 1000;
const refundRequestDate = new Date();
// 남은기간 계산
const remainingTime = targetMembership.end_date.getTime() - refundRequestDate.getTime();
const remainingDays = Math.floor(remainingTime / milliSecondPerDay);
// 멤버십 금액의 일할 계산
const membershipPeriod = targetMembership.end_date.getTime() - targetMembership.created_at.getTime();
const daysInMembership = Math.floor(membershipPeriod / milliSecondPerDay);
const dailyPrice = Math.floor(targetMembership.package_price / daysInMembership);
const refundPrice = Math.floor(remainingDays) * dailyPrice;
const roundedRefundPrice = Math.floor(refundPrice / 100) * 100;
await entityManager.transaction(async (transactionEntityManager: EntityManager) => {
await this.membershipService.cancelMembership(workspaceId);
targetPayment.status = false;
await transactionEntityManager.save(targetPayment);
const user = await this.userService.findUserById(userId);
const refundPoint = roundedRefundPrice;
const remainPoint = (user.points += refundPoint);
await transactionEntityManager.save(User, { ...user, points: remainPoint });
});
return { remainingDays, roundedRefundPrice };
}
생성과 마찬가지로 트랜잭션을 생성, 취소하려는 결제와 멤버십을 조회하고 해당 멤버십의 남은 기간을 계산해줌
남은 기간에 일할계산 된 금액을 곱하고 환불금액을 유저포인트에 다시 넣어준 뒤 커밋
console.log(remainingDays); // 179
console.log(dailyPrice); // 173
console.log(refundPrice); // 30967
console.log(roundedRefundPrice); // 30900
금액이 정확히 일치하진 않지만 정상적으로 환불되는 것을 확인
// 멤버십 취소
async cancelMembership(workspaceId: number): Promise<IResult> {
const targetMembership = await this.membershipRepository.findOne({ where: { workspace: { id: workspaceId } } });
if (!targetMembership) throw new HttpException('결제된 멤버십이 없습니다.', HttpStatus.NOT_FOUND);
await this.membershipRepository.remove(targetMembership);
return { result: true };
}
결제내역 조회를 위해 payment는 status만 false로 바꿔주고 삭제하지 않음
// 나의 결제내역 조회
async getMyPayments(userId: number): Promise<Payment[]> {
const payments = await this.paymentRepository.find({ where: { user: { id: userId } }, relations: ['user'] });
const paymentHistory = [];
for (const payment of payments) {
const workspaceId = payment.workspaceId;
const workspace = await this.workspaceService.getWorkspaceDetail(workspaceId);
if (workspace.memberships.length > 0) {
const membership = workspace.memberships[0];
const paymentInfo = {
paymentId: payment.id,
paymentCreatedAt: payment.created_at,
workspaceId,
workspaceName: workspace.name,
membershipCreatedAt: membership.created_at,
membershipEndDate: membership.end_date,
membershipPrice: membership.package_price,
};
paymentHistory.push(paymentInfo);
} else {
const paymentInfo = {
paymentId: payment.id,
paymentCreatedAt: payment.created_at,
workspaceId,
workspaceName: workspace.name,
};
paymentHistory.push(paymentInfo);
}
}
return paymentHistory;
}
paymentHistory 빈배열을 만들고 워크스페이스에 멤버십이 있다면 멤버십의 정보를 출력해주고 아니라면 취소된 결제라 판단
발생한 문제
취소된 결제가 존재할 때 새로운 멤버십을 결제하는 경우
아래 취소되었던 멤버십도 같이 활성화되는 문제를 발견