Skip to content

Commit aae4c85

Browse files
authored
Merge pull request #3508 from DFXswiss/develop
Release: develop -> main
2 parents 4f427a8 + 0e8683b commit aae4c85

13 files changed

Lines changed: 190 additions & 48 deletions

File tree

src/shared/models/ip-log/ip-log.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export class IpLogService {
5353
return this.ipLogRepo.save(ipLog);
5454
}
5555

56-
async getByUserDataId(userDataId: number, limit = 50): Promise<IpLog[]> {
56+
async getByUserDataId(userDataId: number, limit = 100): Promise<IpLog[]> {
5757
return this.ipLogRepo.find({
5858
where: { userData: { id: userDataId } },
5959
order: { created: 'DESC' },

src/subdomains/core/buy-crypto/routes/swap/swap.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ export class SwapService {
290290
type = 'signed transaction';
291291
payIn = await this.transactionUtilService.handleSignedTxInput(route, request, dto.signedTxHex);
292292
} else if (dto.txHash) {
293-
type = 'EIP-5792 sponsored transfer';
293+
type = 'txHash';
294294
payIn = await this.transactionUtilService.handleTxHashInput(route, request, dto.txHash);
295295
} else {
296296
throw new BadRequestException('Either permit, signedTxHex, txHash, or authorization must be provided');

src/subdomains/core/history/controllers/transaction.controller.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { isFiatDto } from 'src/shared/models/active';
2929
import { AssetDtoMapper } from 'src/shared/models/asset/dto/asset-dto.mapper';
3030
import { FiatService } from 'src/shared/models/fiat/fiat.service';
3131
import { DfxCron } from 'src/shared/utils/cron';
32-
import { Util } from 'src/shared/utils/util';
32+
import { AmountType, Util } from 'src/shared/utils/util';
3333
import { BankDataService } from 'src/subdomains/generic/user/models/bank-data/bank-data.service';
3434
import { UserData } from 'src/subdomains/generic/user/models/user-data/user-data.entity';
3535
import { UserDataStatus } from 'src/subdomains/generic/user/models/user-data/user-data.enum';
@@ -573,7 +573,10 @@ export class TransactionController {
573573
return this.bankTxReturnService.refundBankTx(targetEntity, {
574574
refundIban: dto.refundTarget ?? refundData.refundTarget,
575575
creditorData: dto.creditorData,
576-
chargebackReferenceAmount: refundData.refundPrice.invert().convert(refundData.refundAmount),
576+
chargebackReferenceAmount: Util.roundReadable(
577+
refundData.refundPrice.invert().convert(refundData.refundAmount),
578+
AmountType.FIAT,
579+
),
577580
...refundDto,
578581
});
579582
}
@@ -601,7 +604,10 @@ export class TransactionController {
601604
return this.buyCryptoService.refundBankTx(targetEntity, {
602605
refundIban: refundData.refundTarget ?? dto.refundTarget,
603606
creditorData: dto.creditorData,
604-
chargebackReferenceAmount: refundData.refundPrice.invert().convert(refundData.refundAmount),
607+
chargebackReferenceAmount: Util.roundReadable(
608+
refundData.refundPrice.invert().convert(refundData.refundAmount),
609+
AmountType.FIAT,
610+
),
605611
...refundDto,
606612
});
607613
}

src/subdomains/core/sell-crypto/route/dto/confirm.dto.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export class ConfirmDto {
5757
@IsString()
5858
signedTxHex?: string;
5959

60-
@ApiPropertyOptional({ description: 'Transaction hash from wallet_sendCalls (EIP-5792 gasless transfer)' })
60+
@ApiPropertyOptional({ description: 'Transaction hash of a user-sent transaction (plain transfer or EIP-5792)' })
6161
@IsOptional()
6262
@IsString()
6363
txHash?: string;

src/subdomains/core/sell-crypto/route/sell.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ export class SellService {
315315
type = 'signed transaction';
316316
payIn = await this.transactionUtilService.handleSignedTxInput(route, request, dto.signedTxHex);
317317
} else if (dto.txHash) {
318-
type = 'EIP-5792 sponsored transfer';
318+
type = 'txHash';
319319
payIn = await this.transactionUtilService.handleTxHashInput(route, request, dto.txHash);
320320
} else {
321321
throw new BadRequestException('Either permit, signedTxHex, txHash, or authorization must be provided');

src/subdomains/core/transaction/transaction-util.service.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,8 @@ export class TransactionUtilService {
215215
}
216216

217217
/**
218-
* Handle transaction hash from EIP-5792 wallet_sendCalls (gasless/sponsored transfer)
219-
* The frontend sends the transaction via wallet_sendCalls and provides the txHash
218+
* Handle transaction hash provided by the client
219+
* The client sends the transaction themselves and provides the txHash for tracking
220220
*/
221221
async handleTxHashInput(route: Swap | Sell, request: TransactionRequest, txHash: string): Promise<CryptoInput> {
222222
const asset = await this.assetService.getAssetById(request.sourceId);
@@ -225,14 +225,14 @@ export class TransactionUtilService {
225225
const client = this.blockchainRegistry.getEvmClient(asset.blockchain);
226226
const blockHeight = await client.getCurrentBlock();
227227

228-
// The transaction was already sent by the frontend via wallet_sendCalls
228+
// The transaction was already sent by the client
229229
// We just need to create a PayIn record to track it
230230
return this.payInService.createPayIn(
231231
request.user.address,
232232
route.deposit.address,
233233
asset,
234234
txHash,
235-
PayInType.SPONSORED_TRANSFER,
235+
PayInType.CONFIRMED_DEPOSIT,
236236
blockHeight,
237237
request.amount,
238238
);

src/subdomains/generic/support/dto/user-data-support.dto.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export class BankTxSupportInfo {
3434
export class UserSupportInfo {
3535
id: number;
3636
address: string;
37+
ref?: string;
3738
role: string;
3839
status: string;
3940
created: Date;
@@ -44,7 +45,10 @@ export class TransactionSupportInfo {
4445
uid: string;
4546
type?: string;
4647
sourceType: string;
48+
inputAmount?: number;
49+
inputAsset?: string;
4750
amountInChf?: number;
51+
amountInEur?: number;
4852
amlCheck?: string;
4953
chargebackDate?: Date;
5054
amlReason?: string;

src/subdomains/generic/support/support.controller.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ export class SupportController {
7272
return this.supportService.getRecommendationGraph(+id);
7373
}
7474

75+
@Get(':id/ip-log-pdf')
76+
@ApiBearerAuth()
77+
@ApiExcludeEndpoint()
78+
@UseGuards(AuthGuard(), RoleGuard(UserRole.COMPLIANCE), UserActiveGuard())
79+
async getIpLogPdf(@Param('id') id: string): Promise<{ pdfData: string }> {
80+
const pdfData = await this.supportService.generateIpLogPdf(+id);
81+
return { pdfData };
82+
}
83+
7584
@Get(':id')
7685
@ApiBearerAuth()
7786
@ApiExcludeEndpoint()

src/subdomains/generic/support/support.service.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { BadRequestException, Inject, Injectable, NotFoundException, forwardRef } from '@nestjs/common';
2+
import PDFDocument from 'pdfkit';
3+
import { PdfUtil } from 'src/shared/utils/pdf.util';
24
import { isIP } from 'class-validator';
35
import * as IbanTools from 'ibantools';
46
import { Config } from 'src/config/config';
@@ -109,6 +111,116 @@ export class SupportService {
109111
private readonly supportIssueService: SupportIssueService,
110112
) {}
111113

114+
async generateIpLogPdf(userDataId: number): Promise<string> {
115+
const ipLogs = await this.ipLogService.getByUserDataId(userDataId);
116+
117+
return new Promise<string>((resolve, reject) => {
118+
try {
119+
const pdf = new PDFDocument({ size: 'A4', margin: 50 });
120+
const chunks: Buffer[] = [];
121+
122+
pdf.on('data', (chunk) => chunks.push(chunk));
123+
pdf.on('end', () => resolve(Buffer.concat(chunks).toString('base64')));
124+
125+
PdfUtil.drawLogo(pdf);
126+
127+
// Header
128+
const marginX = 50;
129+
pdf.moveDown(2);
130+
pdf.fontSize(18).font('Helvetica-Bold').fillColor('#072440');
131+
pdf.text('IP Log Report', marginX);
132+
pdf.moveDown(0.5);
133+
pdf.fontSize(10).font('Helvetica').fillColor('#333333');
134+
pdf.text(`User Data ID: ${userDataId}`, marginX);
135+
pdf.text(`Date: ${new Date().toISOString().split('T')[0]}`, marginX);
136+
pdf.text(`Total Entries: ${ipLogs.length}`, marginX);
137+
pdf.moveDown(1);
138+
139+
// Table
140+
this.drawIpLogTable(pdf, ipLogs);
141+
142+
// Footer
143+
pdf.moveDown(2);
144+
pdf.fontSize(8).font('Helvetica').fillColor('#999999');
145+
pdf.text(`Generated by DFX - ${new Date().toISOString()}`, marginX);
146+
147+
pdf.end();
148+
} catch (e) {
149+
reject(e);
150+
}
151+
});
152+
}
153+
154+
private drawIpLogTable(pdf: InstanceType<typeof PDFDocument>, ipLogs: IpLog[]): void {
155+
const marginX = 50;
156+
const { width } = pdf.page;
157+
const tableWidth = width - marginX * 2;
158+
159+
const cols = [
160+
{ header: 'Date', width: tableWidth * 0.2 },
161+
{ header: 'IP', width: tableWidth * 0.2 },
162+
{ header: 'Country', width: tableWidth * 0.12 },
163+
{ header: 'Endpoint', width: tableWidth * 0.36 },
164+
{ header: 'Status', width: tableWidth * 0.12 },
165+
];
166+
167+
let y = pdf.y;
168+
169+
// Headers
170+
pdf.fontSize(10).font('Helvetica-Bold').fillColor('#072440');
171+
let x = marginX;
172+
for (const col of cols) {
173+
pdf.text(col.header, x, y, { width: col.width - 5 });
174+
x += col.width;
175+
}
176+
177+
y += 18;
178+
pdf
179+
.moveTo(marginX, y)
180+
.lineTo(width - marginX, y)
181+
.stroke('#CCCCCC');
182+
y += 8;
183+
184+
// Rows
185+
pdf.fontSize(9).font('Helvetica').fillColor('#333333');
186+
187+
if (ipLogs.length === 0) {
188+
pdf.text('No IP logs found', marginX, y);
189+
} else {
190+
for (const log of ipLogs) {
191+
if (y > pdf.page.height - 80) {
192+
pdf.addPage();
193+
y = 50;
194+
}
195+
196+
x = marginX;
197+
const date = log.created ? new Date(log.created).toISOString().replace('T', ' ').substring(0, 19) : '-';
198+
const endpoint = log.url?.replace('/v1/', '') ?? '-';
199+
200+
pdf.fillColor('#333333');
201+
pdf.text(date, x, y, { width: cols[0].width - 5 });
202+
x += cols[0].width;
203+
pdf.text(log.ip ?? '-', x, y, { width: cols[1].width - 5 });
204+
x += cols[1].width;
205+
pdf.text(log.country ?? '-', x, y, { width: cols[2].width - 5 });
206+
x += cols[2].width;
207+
pdf.text(endpoint, x, y, { width: cols[3].width - 5 });
208+
x += cols[3].width;
209+
210+
pdf.fillColor(log.result ? '#28a745' : '#dc3545');
211+
pdf.text(log.result ? 'Pass' : 'Fail', x, y, { width: cols[4].width - 5 });
212+
213+
y += 20;
214+
}
215+
}
216+
217+
pdf
218+
.moveTo(marginX, y)
219+
.lineTo(width - marginX, y)
220+
.stroke('#CCCCCC');
221+
pdf.y = y + 10;
222+
}
223+
112224
async getUserDataDetails(id: number): Promise<UserDataSupportInfoDetails> {
113225
const userData = await this.userDataService.getUserData(id, { wallet: true, bankDatas: true });
114226
if (!userData) throw new NotFoundException(`User not found`);
@@ -328,7 +440,10 @@ export class SupportService {
328440
uid: tx.uid,
329441
type: tx.type,
330442
sourceType: tx.sourceType,
443+
inputAmount: tx.buyCrypto?.inputAmount ?? tx.buyFiat?.inputAmount,
444+
inputAsset: tx.buyCrypto?.inputAsset ?? tx.buyFiat?.inputAsset,
331445
amountInChf: tx.amountInChf,
446+
amountInEur: tx.buyCrypto?.amountInEur ?? tx.buyFiat?.amountInEur,
332447
amlCheck: tx.amlCheck,
333448
chargebackDate:
334449
tx.buyCrypto?.chargebackDate ??
@@ -344,6 +459,7 @@ export class SupportService {
344459
return {
345460
id: user.id,
346461
address: user.address,
462+
ref: user.ref,
347463
role: user.role,
348464
status: user.status,
349465
created: user.created,

src/subdomains/supporting/bank/bank-account/is-dfx-iban.validator.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ export class IsDfxIbanValidator implements ValidatorConstraintInterface {
8888
)
8989
.map((b) => b.value);
9090

91-
this.currentBIC = await this.bankAccountService.getOrCreateIbanBankAccountInternal(args.value).then((b) => b.bic);
91+
this.currentBIC = args.value
92+
? await this.bankAccountService.getOrCreateIbanBankAccountInternal(args.value).then((b) => b.bic)
93+
: undefined;
9294

9395
return this.defaultMessage(args) == null;
9496
}

0 commit comments

Comments
 (0)