forked from StellarSplit/StellarSplit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsettlement.service.ts
More file actions
128 lines (110 loc) · 3.57 KB
/
Copy pathsettlement.service.ts
File metadata and controls
128 lines (110 loc) · 3.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import {
Injectable,
NotFoundException,
BadRequestException,
Logger,
} from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository, MoreThan } from "typeorm";
import { SettlementStep, StepStatus } from "./entities/settlement-step.entity";
import { Participant } from "@/entities/participant.entity";
import { User } from "../entities/user.entity";
import { StellarService } from "../stellar/stellar.service";
@Injectable()
export class SettlementService {
private readonly logger = new Logger(SettlementService.name);
constructor(
@InjectRepository(SettlementStep)
private stepRepo: Repository<SettlementStep>,
@InjectRepository(Participant)
private participantRepo: Repository<Participant>,
@InjectRepository(User)
private userRepo: Repository<User>,
private stellarService: StellarService,
) {}
async verifyAndCompleteStep(
stepId: string,
txHash: string,
userWallet: string,
) {
try {
const step = await this.stepRepo.findOne({
where: { id: stepId, fromAddress: userWallet },
relations: ["suggestion"],
});
if (!step) {
throw new NotFoundException("Settlement step not found");
}
if (step.status === StepStatus.COMPLETED) {
return step;
}
const verification = await this.stellarService.verifyTransaction(txHash);
if (!verification || !verification.valid) {
throw new BadRequestException(
"Transaction could not be verified on-chain",
);
}
const isMatch =
verification.sender === userWallet &&
verification.receiver === step.toAddress &&
verification.amount >= Number(step.amount);
if (!isMatch) {
throw new BadRequestException(
"Transaction details do not match the settlement step",
);
}
step.status = StepStatus.COMPLETED;
await this.participantRepo.update(
{ splitId: step.relatedSplitIds[0], walletAddress: userWallet },
{
status: "paid",
amountPaid: () => `amount_paid + ${verification.amount}`,
},
);
return this.stepRepo.save(step);
} catch (error: any) {
this.logger.error(
`Error completing settlement step ${stepId} for wallet ${userWallet}: ${error?.message || error}`,
error,
);
throw error;
}
}
async calculateNetPosition(walletAddress: string) {
const stats = await this.participantRepo
.createQueryBuilder("p")
.select(
"SUM(CASE WHEN p.walletAddress = :wallet THEN (p.amountOwed - p.amountPaid) ELSE 0 END)",
"owes",
)
.addSelect(
"SUM(CASE WHEN p.walletAddress != :wallet THEN (p.amountOwed - p.amountPaid) ELSE 0 END)",
"owed",
)
.innerJoin("p.split", "s")
.where("s.creatorWalletAddress = :wallet OR p.walletAddress = :wallet", {
wallet: walletAddress,
})
.getRawOne();
return {
owes: parseFloat(stats.owes || 0),
owed: parseFloat(stats.owed || 0),
net: parseFloat(stats.owed || 0) - parseFloat(stats.owes || 0),
};
}
async snoozeSuggestions(userId: string) {
const snoozeDate = new Date();
snoozeDate.setDate(snoozeDate.getDate() + 7);
await this.userRepo.update(
userId,
{ snoozedUntil: snoozeDate } as any,
);
return { snoozedUntil: snoozeDate };
}
async isSnoozed(userId: string): Promise<boolean> {
const user = await this.userRepo.findOne({
where: { id: userId, snoozedUntil: MoreThan(new Date()) },
} as any);
return !!user;
}
}