From 4d7d527bdb0bdd0aefaad1fc03ee6ed52ed958ff Mon Sep 17 00:00:00 2001 From: Yannick1712 Date: Thu, 20 Oct 2022 09:44:36 +0200 Subject: [PATCH 01/15] [FIX] fix buyCrypto return mails --- src/payment/models/buy-crypto/entities/buy-crypto.entity.ts | 6 +++++- .../buy-crypto/services/buy-crypto-notification.service.ts | 2 +- src/shared/i18n/de/mail.json | 2 +- src/shared/i18n/en/mail.json | 2 +- src/shared/i18n/es/mail.json | 2 +- src/shared/i18n/fr/mail.json | 2 +- src/shared/i18n/it/mail.json | 2 +- src/shared/i18n/pt/mail.json | 2 +- 8 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/payment/models/buy-crypto/entities/buy-crypto.entity.ts b/src/payment/models/buy-crypto/entities/buy-crypto.entity.ts index b09806def5..0977e0675b 100644 --- a/src/payment/models/buy-crypto/entities/buy-crypto.entity.ts +++ b/src/payment/models/buy-crypto/entities/buy-crypto.entity.ts @@ -224,15 +224,19 @@ export class BuyCrypto extends IEntity { return this.buy ? this.buy.user : this.cryptoRoute.user; } - get target(): { address: string; asset: Asset } { + get target(): { address: string; asset: Asset; trimmedReturnAddress: string } { return this.buy ? { address: this.buy.deposit?.address ?? this.buy.user.address, asset: this.buy.asset, + trimmedReturnAddress: this.buy?.iban ? Util.trimIBAN(this.buy.iban) : null, } : { address: this.cryptoRoute.targetDeposit?.address ?? this.cryptoRoute.user.address, asset: this.cryptoRoute.asset, + trimmedReturnAddress: this.cryptoRoute?.user?.address + ? Util.trimBlockchainAddress(this.cryptoRoute.user.address) + : null, }; } } diff --git a/src/payment/models/buy-crypto/services/buy-crypto-notification.service.ts b/src/payment/models/buy-crypto/services/buy-crypto-notification.service.ts index 706c33c955..31c55a6c0b 100644 --- a/src/payment/models/buy-crypto/services/buy-crypto-notification.service.ts +++ b/src/payment/models/buy-crypto/services/buy-crypto-notification.service.ts @@ -137,7 +137,7 @@ export class BuyCryptoNotificationService { returnReason: await this.i18nService.translate(`mail.amlReasonMailText.${entity.amlReason}`, { lang: entity.user.userData.language?.symbol.toLowerCase(), }), - userAddressTrimmed: Util.trimBlockchainAddress(entity.user.address), + userAddressTrimmed: entity.target.trimmedReturnAddress, }, }, }); diff --git a/src/shared/i18n/de/mail.json b/src/shared/i18n/de/mail.json index 068bb6c733..7a20c3bc07 100644 --- a/src/shared/i18n/de/mail.json +++ b/src/shared/i18n/de/mail.json @@ -64,7 +64,7 @@ }, "paybackInitiated":{ "salutation": "Dein Guthaben wurde an dein Bankkonto zurückerstattet", - "body": "
Erstattet{inputAmount} {inputAsset}
Bank Konto***{userAddressTrimmed}
Verwendungszweck{returnTransactionLink}
Grund, warum wir zurückerstattet haben:
{returnReason}
Herzlichen Dank für dein entgegengebrachtes Vertrauen
Dein freundliches DFX Team
Bitcoiners by heart ♥️
", + "body": "
Erstattet{inputAmount} {inputAsset}
Bank Konto{userAddressTrimmed}
Verwendungszweck{returnTransactionLink}
Grund, warum wir zurückerstattet haben:
{returnReason}
Herzlichen Dank für dein entgegengebrachtes Vertrauen
Dein freundliches DFX Team
Bitcoiners by heart ♥️
", "title": "Guthaben wurde erstattet" } }, diff --git a/src/shared/i18n/en/mail.json b/src/shared/i18n/en/mail.json index 43d7573eec..7f74c8f8eb 100644 --- a/src/shared/i18n/en/mail.json +++ b/src/shared/i18n/en/mail.json @@ -64,7 +64,7 @@ }, "paybackInitiated":{ "salutation": "Your funds have been reimbursed to your bank account", - "body": "
Reimbursed amount{inputAmount} {inputAsset}
Bank account***{userAddressTrimmed}
Purpose of payment{returnTransactionLink}
Reason why we reimbursed your funds:
{returnReason}
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", + "body": "
Reimbursed amount{inputAmount} {inputAsset}
Bank account{userAddressTrimmed}
Purpose of payment{returnTransactionLink}
Reason why we reimbursed your funds:
{returnReason}
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", "title": "Funds have been reimbursed" } }, diff --git a/src/shared/i18n/es/mail.json b/src/shared/i18n/es/mail.json index a9a27d29b7..b558056568 100644 --- a/src/shared/i18n/es/mail.json +++ b/src/shared/i18n/es/mail.json @@ -64,7 +64,7 @@ }, "paybackInitiated":{ "salutation": "Tus fondos se han reembolsado a tu cuenta bancaria", - "body": "
Cantidad reembolsada{inputAmount} {inputAsset}
Cuenta bancaria***{userAddressTrimmed}
Propósito de pago{returnTransactionLink}
Motivo por el cual tus fondos se reembolsaron:
{returnReason}
Muchas gracias por tu confianza
Tu Equipo DFX
Bitcoiners by heart ♥️
", + "body": "
Cantidad reembolsada{inputAmount} {inputAsset}
Cuenta bancaria{userAddressTrimmed}
Propósito de pago{returnTransactionLink}
Motivo por el cual tus fondos se reembolsaron:
{returnReason}
Muchas gracias por tu confianza
Tu Equipo DFX
Bitcoiners by heart ♥️
", "title": "Los fondos han sido reembolsados" } }, diff --git a/src/shared/i18n/fr/mail.json b/src/shared/i18n/fr/mail.json index eaac7c3b9c..09bbe8e2ae 100644 --- a/src/shared/i18n/fr/mail.json +++ b/src/shared/i18n/fr/mail.json @@ -64,7 +64,7 @@ }, "paybackInitiated":{ "salutation": "Vos fonds ont été retournés à votre banque", - "body": "
Montant retourné{inputAmount} {inputAsset}
Compte bancaire***{userAddressTrimmed}
Motif du paiement{returnTransactionLink}
Motif du remboursement:
{returnReason}
Merci beaucoup pour votre confiance
Votre équipe DFX
Bitcoiners by heart ♥️
", + "body": "
Montant retourné{inputAmount} {inputAsset}
Compte bancaire{userAddressTrimmed}
Motif du paiement{returnTransactionLink}
Motif du remboursement:
{returnReason}
Merci beaucoup pour votre confiance
Votre équipe DFX
Bitcoiners by heart ♥️
", "title": "Remboursement des fonds" } }, diff --git a/src/shared/i18n/it/mail.json b/src/shared/i18n/it/mail.json index bee00779fa..fd16bcb076 100644 --- a/src/shared/i18n/it/mail.json +++ b/src/shared/i18n/it/mail.json @@ -64,7 +64,7 @@ }, "paybackInitiated":{ "salutation": "I tuoi fondi sono stati rimborsati sul tuo conto corrente", - "body": "
Importo rimborsato{inputAmount} {inputAsset}
Conto corrente***{userAddressTrimmed}
Causale di pagamento{returnTransactionLink}
Ragioni per cui abbiamo rimborsato i tuoi fondi:
{returnReason}
Grazie mille per la tua fiducia
Tuo DFX team
Bitcoiners by heart ♥️
", + "body": "
Importo rimborsato{inputAmount} {inputAsset}
Conto corrente{userAddressTrimmed}
Causale di pagamento{returnTransactionLink}
Ragioni per cui abbiamo rimborsato i tuoi fondi:
{returnReason}
Grazie mille per la tua fiducia
Tuo DFX team
Bitcoiners by heart ♥️
", "title": "Trasferimento bancario effettuato" } }, diff --git a/src/shared/i18n/pt/mail.json b/src/shared/i18n/pt/mail.json index 439b362176..d9ea14f6f2 100644 --- a/src/shared/i18n/pt/mail.json +++ b/src/shared/i18n/pt/mail.json @@ -64,7 +64,7 @@ }, "paybackInitiated":{ "salutation": "Your funds have been reimbursed to your bank account", - "body": "
Reimbursed amount{inputAmount} {inputAsset}
Bank account***{userAddressTrimmed}
Purpose of payment{returnTransactionLink}
Reason why we reimbursed your funds:
{returnReason}
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", + "body": "
Reimbursed amount{inputAmount} {inputAsset}
Bank account{userAddressTrimmed}
Purpose of payment{returnTransactionLink}
Reason why we reimbursed your funds:
{returnReason}
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", "title": "Funds have been reimbursed" } }, From d03a0652fd81a2614570c36726784a61bd9c353e Mon Sep 17 00:00:00 2001 From: Kolibri1990 <66674482+Kolibri1990@users.noreply.github.com> Date: Thu, 20 Oct 2022 12:35:25 +0200 Subject: [PATCH 02/15] [DEV-864] Add part to completed requirement and also to fallback (#458) * [DEV-864] Add part to completed requirement and also to fallback * [DEV-864] Refactoring * [DEV-864] Refactoring * [DEV-864] Removed empty lines Co-authored-by: David May --- .../services/spider/spider-sync.service.ts | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/user/services/spider/spider-sync.service.ts b/src/user/services/spider/spider-sync.service.ts index 926b8ab8cd..65155145a1 100644 --- a/src/user/services/spider/spider-sync.service.ts +++ b/src/user/services/spider/spider-sync.service.ts @@ -290,31 +290,36 @@ export class SpiderSyncService { userData: UserData, documentType: KycContentType, ): Promise<{ document: KycDocument; version: string; part: DocumentVersionPart }> { - const { document, version } = await this.getCompletedIdentDocument(userData); - if (!version) return null; - - const part = await this.spiderApi - .getDocumentVersionParts(userData.id, false, document, version) - .then((parts) => parts.find((p) => p.contentType === documentType)); - - return { document, version, part }; - } - - private async getCompletedIdentDocument(userData: UserData): Promise<{ document: KycDocument; version: string }> { let document = IdentInProgress(userData.kycStatus) ? KycDocuments[userData.kycStatus].document : KycDocument.ONLINE_IDENTIFICATION; - let version = await this.spiderApi.getDocumentVersion(userData.id, false, document, KycDocumentState.COMPLETED); + let result = await this.getCompletedIdentDocument(userData, document, documentType); - if (!version) { + if (!result) { // fallback to other ident method document = document === KycDocument.ONLINE_IDENTIFICATION ? KycDocument.VIDEO_IDENTIFICATION : KycDocument.ONLINE_IDENTIFICATION; - version = await this.spiderApi.getDocumentVersion(userData.id, false, document, KycDocumentState.COMPLETED); + result = await this.getCompletedIdentDocument(userData, document, documentType); } - return { document, version: version?.name }; + return result; + } + + private async getCompletedIdentDocument( + userData: UserData, + document: KycDocument, + documentType: KycContentType, + ): Promise<{ document: KycDocument; version: string; part: DocumentVersionPart }> { + const version = await this.spiderApi.getDocumentVersion(userData.id, false, document, KycDocumentState.COMPLETED); + if (!version) return null; + + const part = await this.spiderApi + .getDocumentVersionParts(userData.id, false, document, version?.name) + .then((parts) => parts.find((p) => p.contentType === documentType)); + if (!part) return null; + + return { document, version: version.name, part }; } } From 7d1eb8fcf68afe874820ff3d8afe2eab969b76c7 Mon Sep 17 00:00:00 2001 From: David May <85513542+davidleomay@users.noreply.github.com> Date: Thu, 20 Oct 2022 15:17:07 +0200 Subject: [PATCH 03/15] Send payment mails to liq address (#464) --- src/config/config.ts | 1 + src/notification/entities/mail/base/mail.ts | 6 +++--- .../entities/mail/error-monitoring-mail.ts | 12 +++++++++++- src/notification/services/mail.service.ts | 1 + 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/config/config.ts b/src/config/config.ts index 018e4d20d9..3d9a1c5fdd 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -149,6 +149,7 @@ export class Configuration { contact: { supportMail: process.env.SUPPORT_MAIL || 'support@dfx.swiss', monitoringMail: process.env.MONITORING_MAIL || 'monitoring@dfx.swiss', + liqMail: process.env.LIQ_MAIL || 'liq@dfx.swiss', noReplyMail: process.env.NOREPLY_MAIL || 'noreply@dfx.swiss', }, }; diff --git a/src/notification/entities/mail/base/mail.ts b/src/notification/entities/mail/base/mail.ts index 5a1e380ed0..debf920e28 100644 --- a/src/notification/entities/mail/base/mail.ts +++ b/src/notification/entities/mail/base/mail.ts @@ -3,7 +3,7 @@ import { NotificationType } from 'src/notification/enums'; import { Notification, NotificationOptions, NotificationMetadata } from '../../notification.entity'; export interface MailParams { - to: string; + to: string | string[]; subject: string; salutation: string; body: string; @@ -26,7 +26,7 @@ export class Mail extends Notification { name: 'DFX.swiss', address: GetConfig().mail.contact.noReplyMail, }; - readonly #to: string; + readonly #to: string | string[]; readonly #cc: string; readonly #bcc: string; readonly #template: string = GetConfig().mail.defaultMailTemplate; @@ -66,7 +66,7 @@ export class Mail extends Notification { return { name, address }; } - get to(): string { + get to(): string | string[] { return this.#to; } diff --git a/src/notification/entities/mail/error-monitoring-mail.ts b/src/notification/entities/mail/error-monitoring-mail.ts index cf848d6691..6b1e52534e 100644 --- a/src/notification/entities/mail/error-monitoring-mail.ts +++ b/src/notification/entities/mail/error-monitoring-mail.ts @@ -1,4 +1,5 @@ import { GetConfig } from 'src/config/config'; +import { MailContext } from 'src/notification/enums'; import { NotificationMetadata, NotificationOptions } from '../notification.entity'; import { Mail } from './base/mail'; @@ -13,8 +14,11 @@ export interface ErrorMonitoringMailParams { export class ErrorMonitoringMail extends Mail { constructor(params: ErrorMonitoringMailParams) { + const to = [GetConfig().mail.contact.monitoringMail]; + ErrorMonitoringMail.isLiqMail(params) && to.push(GetConfig().mail.contact.liqMail); + const _params = { - to: GetConfig().mail.contact.monitoringMail, + to: to, subject: `${params.subject} (${GetConfig().environment.toUpperCase()})`, salutation: 'Hi DFX Tech Support', body: ErrorMonitoringMail.createBody(params.errors), @@ -25,6 +29,12 @@ export class ErrorMonitoringMail extends Mail { super(_params); } + private static isLiqMail(params: ErrorMonitoringMailParams): boolean { + return [MailContext.BUY_CRYPTO, MailContext.DEX, MailContext.PAYOUT, MailContext.PRICING].includes( + params.metadata?.context, + ); + } + static createBody(errors: string[]): string { const env = GetConfig().environment.toUpperCase(); diff --git a/src/notification/services/mail.service.ts b/src/notification/services/mail.service.ts index 3a00e704e7..8fd64eb521 100644 --- a/src/notification/services/mail.service.ts +++ b/src/notification/services/mail.service.ts @@ -9,6 +9,7 @@ export interface MailOptions { contact: { supportMail: string; monitoringMail: string; + liqMail: string; noReplyMail: string; }; } From ae984fc54d8f65e7162a22fe61fdc067e5ca99f5 Mon Sep 17 00:00:00 2001 From: David May <85513542+davidleomay@users.noreply.github.com> Date: Thu, 20 Oct 2022 15:55:08 +0200 Subject: [PATCH 04/15] [DEV-893] BTC Fee (#463) * [DEV-893] Using recommended fee and RBF for BTC send * [DEV-893] Updated environment --- infrastructure/bicep/dfx-api.bicep | 5 ++++ infrastructure/bicep/parameters/dev.json | 3 +++ infrastructure/bicep/parameters/loc.json | 3 +++ infrastructure/bicep/parameters/prd.json | 3 +++ src/blockchain/ain/ain.module.ts | 5 ++-- src/blockchain/ain/node/btc-client.ts | 21 +++++++++++++++++ .../ain/services/btc-fee.service.ts | 23 +++++++++++++++++++ src/config/config.ts | 1 + .../models/crypto-input/btc-input.service.ts | 17 ++++---------- .../payout/services/payout-bitcoin.service.ts | 6 +++-- 10 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 src/blockchain/ain/services/btc-fee.service.ts diff --git a/infrastructure/bicep/dfx-api.bicep b/infrastructure/bicep/dfx-api.bicep index 22882dda66..3253a2fc72 100644 --- a/infrastructure/bicep/dfx-api.bicep +++ b/infrastructure/bicep/dfx-api.bicep @@ -38,6 +38,7 @@ param intWalletAddress string param stakingWalletAddress string param utxoSpenderAddress string param btcCollectorAddress string +param btcOutWalletAddress string param ethWalletAddress string @secure() @@ -565,6 +566,10 @@ resource apiAppService 'Microsoft.Web/sites@2018-11-01' = { name: 'BTC_COLLECTOR_ADDRESS' value: btcCollectorAddress } + { + name: 'BTC_OUT_WALLET_ADDRESS' + value: btcOutWalletAddress + } { name: 'FTP_HOST' value: '138.201.74.234' diff --git a/infrastructure/bicep/parameters/dev.json b/infrastructure/bicep/parameters/dev.json index a60e92d50f..10180af8bf 100644 --- a/infrastructure/bicep/parameters/dev.json +++ b/infrastructure/bicep/parameters/dev.json @@ -110,6 +110,9 @@ "btcCollectorAddress": { "value": "xxx" }, + "btcOutWalletAddress": { + "value": "xxx" + }, "nodeServicePlanSkuName": { "value": "P1v2" }, diff --git a/infrastructure/bicep/parameters/loc.json b/infrastructure/bicep/parameters/loc.json index f2f99b3a3a..09b97f23fc 100644 --- a/infrastructure/bicep/parameters/loc.json +++ b/infrastructure/bicep/parameters/loc.json @@ -110,6 +110,9 @@ "btcCollectorAddress": { "value": "xxx" }, + "btcOutWalletAddress": { + "value": "xxx" + }, "nodeServicePlanSkuName": { "value": "B1" }, diff --git a/infrastructure/bicep/parameters/prd.json b/infrastructure/bicep/parameters/prd.json index d7772428b4..876fe9cd0f 100644 --- a/infrastructure/bicep/parameters/prd.json +++ b/infrastructure/bicep/parameters/prd.json @@ -110,6 +110,9 @@ "btcCollectorAddress": { "value": "xxx" }, + "btcOutWalletAddress": { + "value": "xxx" + }, "nodeServicePlanSkuName": { "value": "P2v2" }, diff --git a/src/blockchain/ain/ain.module.ts b/src/blockchain/ain/ain.module.ts index 1dad110dbb..6dc7ae380e 100644 --- a/src/blockchain/ain/ain.module.ts +++ b/src/blockchain/ain/ain.module.ts @@ -5,11 +5,12 @@ import { NodeController } from './node/node.controller'; import { NodeService } from './node/node.service'; import { WhaleService } from './whale/whale.service'; import { DeFiChainUtil } from './utils/defichain.util'; +import { BtcFeeService } from './services/btc-fee.service'; @Module({ imports: [SharedModule], - providers: [CryptoService, NodeService, WhaleService, DeFiChainUtil], - exports: [CryptoService, NodeService, WhaleService, DeFiChainUtil], + providers: [CryptoService, NodeService, WhaleService, DeFiChainUtil, BtcFeeService], + exports: [CryptoService, NodeService, WhaleService, DeFiChainUtil, BtcFeeService], controllers: [NodeController], }) export class AinModule {} diff --git a/src/blockchain/ain/node/btc-client.ts b/src/blockchain/ain/node/btc-client.ts index dc1ceb4bd5..594b828f3f 100644 --- a/src/blockchain/ain/node/btc-client.ts +++ b/src/blockchain/ain/node/btc-client.ts @@ -1,4 +1,5 @@ import { SchedulerRegistry } from '@nestjs/schedule'; +import { Config } from 'src/config/config'; import { HttpService } from 'src/shared/services/http.service'; import { NodeClient, NodeCommand, NodeMode } from './node-client'; @@ -27,4 +28,24 @@ export class BtcClient extends NodeClient { true, ).then((r) => r.txid); } + + async sendMany(payload: { addressTo: string; amount: number }[], feeRate: number): Promise { + const batch = payload.reduce((acc, p) => ({ ...acc, [p.addressTo]: `${p.amount}` }), {}); + + return await this.callNode<{ txid: string }>( + (c) => + c.call( + NodeCommand.SEND, + [ + batch, + null, + 'unset', + null, + { fee_rate: feeRate, replaceable: true, change_address: Config.blockchain.default.btcOutWalletAddress }, + ], + 'number', + ), + true, + ).then((r) => r.txid); + } } diff --git a/src/blockchain/ain/services/btc-fee.service.ts b/src/blockchain/ain/services/btc-fee.service.ts new file mode 100644 index 0000000000..bb2b380545 --- /dev/null +++ b/src/blockchain/ain/services/btc-fee.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@nestjs/common'; +import { HttpService } from 'src/shared/services/http.service'; + +@Injectable() +export class BtcFeeService { + private readonly btcFeeUrl = 'https://mempool.space/api/v1/fees/recommended'; + + constructor(private readonly http: HttpService) {} + + async getRecommendedFeeRate(): Promise { + const { fastestFee } = await this.http.get<{ + fastestFee: number; + halfHourFee: number; + hourFee: number; + economyFee: number; + minimumFee: number; + }>(this.btcFeeUrl, { + tryCount: 3, + }); + + return fastestFee; + } +} diff --git a/src/config/config.ts b/src/config/config.ts index 3d9a1c5fdd..df89ab278d 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -199,6 +199,7 @@ export class Configuration { intWalletAddress: process.env.INT_WALLET_ADDRESS, stakingWalletAddress: process.env.STAKING_WALLET_ADDRESS, btcCollectorAddress: process.env.BTC_COLLECTOR_ADDRESS, + btcOutWalletAddress: process.env.BTC_OUT_WALLET_ADDRESS, minTxAmount: 0.00000297, minDeposit: { Fiat: { diff --git a/src/payment/models/crypto-input/btc-input.service.ts b/src/payment/models/crypto-input/btc-input.service.ts index 61e6489cb2..7c7cfcb87e 100644 --- a/src/payment/models/crypto-input/btc-input.service.ts +++ b/src/payment/models/crypto-input/btc-input.service.ts @@ -13,27 +13,26 @@ import { NodeNotAccessibleError } from 'src/payment/exceptions/node-not-accessib import { CryptoInputService } from './crypto-input.service'; import { BtcClient } from 'src/blockchain/ain/node/btc-client'; import { CryptoRouteService } from '../crypto-route/crypto-route.service'; -import { HttpService } from 'src/shared/services/http.service'; import { BuyCryptoService } from '../buy-crypto/services/buy-crypto.service'; import { ChainalysisService } from './chainalysis.service'; import { Blockchain } from 'src/blockchain/shared/enums/blockchain.enum'; import { AmlCheck } from '../buy-crypto/enums/aml-check.enum'; +import { BtcFeeService } from 'src/blockchain/ain/services/btc-fee.service'; @Injectable() export class BtcInputService extends CryptoInputService { private readonly lock = new Lock(7200); - private readonly btcFeeUrl = 'https://mempool.space/api/v1/fees/recommended'; private btcClient: BtcClient; constructor( nodeService: NodeService, cryptoInputRepo: CryptoInputRepository, - private readonly http: HttpService, private readonly assetService: AssetService, private readonly cryptoRouteService: CryptoRouteService, private readonly buyCryptoService: BuyCryptoService, private readonly chainalysisService: ChainalysisService, + private readonly feeService: BtcFeeService, ) { super(cryptoInputRepo); nodeService.getConnectedNode(NodeType.BTC_INPUT).subscribe((bitcoinClient) => (this.btcClient = bitcoinClient)); @@ -177,16 +176,8 @@ export class BtcInputService extends CryptoInputService { } private async getFeeRate(amount: number): Promise { - const { fastestFee } = await this.http.get<{ - fastestFee: number; - halfHourFee: number; - hourFee: number; - economyFee: number; - minimumFee: number; - }>(this.btcFeeUrl, { - tryCount: 3, - }); - return Math.floor(Math.max(Math.min(fastestFee, 500 * amount), 1)); + const feeRate = await this.feeService.getRecommendedFeeRate(); + return Math.floor(Math.max(Math.min(feeRate, 500 * amount), 1)); } // --- CONFIRMATION HANDLING --- // diff --git a/src/payment/models/payout/services/payout-bitcoin.service.ts b/src/payment/models/payout/services/payout-bitcoin.service.ts index a6d2d83ab9..e670ddbfda 100644 --- a/src/payment/models/payout/services/payout-bitcoin.service.ts +++ b/src/payment/models/payout/services/payout-bitcoin.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { BtcClient } from 'src/blockchain/ain/node/btc-client'; import { NodeService, NodeType } from 'src/blockchain/ain/node/node.service'; +import { BtcFeeService } from 'src/blockchain/ain/services/btc-fee.service'; import { PayoutOrderContext } from '../entities/payout-order.entity'; import { PayoutGroup, PayoutJellyfishService } from './base/payout-jellyfish.service'; @@ -8,7 +9,7 @@ import { PayoutGroup, PayoutJellyfishService } from './base/payout-jellyfish.ser export class PayoutBitcoinService extends PayoutJellyfishService { #client: BtcClient; - constructor(readonly nodeService: NodeService) { + constructor(readonly nodeService: NodeService, private readonly feeService: BtcFeeService) { super(); nodeService.getConnectedNode(NodeType.BTC_OUTPUT).subscribe((client) => (this.#client = client)); } @@ -22,7 +23,8 @@ export class PayoutBitcoinService extends PayoutJellyfishService { } async sendUtxoToMany(_context: PayoutOrderContext, payout: PayoutGroup): Promise { - return this.#client.sendUtxoToMany(payout); + const feeRate = await this.feeService.getRecommendedFeeRate(); + return this.#client.sendMany(payout, feeRate); } async checkPayoutCompletion(_context: any, payoutTxId: string): Promise { From 9ade950a47c8492a10503e2855ebd63e6ab65e24 Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Fri, 21 Oct 2022 10:04:28 +0200 Subject: [PATCH 05/15] [DEV-770] Offshore kyc v2 (#455) * [DEV-770] reload users with wallet * [DEV-770] add wallet controller * [DEV-770] add kycFailed webhook * [DEV-770] KYC Light for LOCK users * [DEV-770] apiKey Refactoring * [DEV-770] add company method Get kycData * [DEV-770] add Company SignIn challenge token * Squashed commit of the following: commit a5884ed7c083fef9e8e07776ae7905fd47f94f60 Author: TonySemikin Date: Fri Oct 14 11:43:56 2022 +0200 [DEV-726] Improve BuyCrypto Monitoring (#421) * [DEV-726] created notification module structure * [DEV-726] added notification persistence and recurring rules * [DEV-726] added log to suppressed mails * [DEV-726] added notification module to app module * [DEV-726] first working error suppression * [DEV-726] slight improvement of request interface * [DEV-726] simplified mail request parameters * [DEV-726] refactored client calls to notification service * [DEV-726] fixed tests and removed former mail service * [DEV-726] improved correlation of incoming mail requests * [DEV-726] hard limited notification correlationId to 255 chars * [DEV-726] trade off improvement of mails naming * [DEV-726] added contollers for integration tests * [DEV-726] added migration * [DEV-726] after merge fixes * [DEV-726] small notification entity adjustment * [DEV-726] adjusted blockchain scan base urls for buy crypto confirmation * [DEV-726] after merge fixes * [DEV-726] addressed PR comments * [DEV-726] moved mail class to base directory * [DEV-726] after merge fixes commit 40988a79829046ea75d14471d960fab2baa27141 Author: Yannick1712 Date: Fri Oct 14 11:36:33 2022 +0200 [DEV-884] add Pending bankTx type commit 2b176ef3fc84ff9b4f4007b5b1aa7a5973d2b2da Author: TonySemikin Date: Fri Oct 14 10:20:31 2022 +0200 [DEV-622], [DEV-833] buy crypto btc, eth tokens, bsc tokens (#446) * [DEV-622] scaffolded strategies for bitcoin and erc-20 tokens * [DEV-622] restructured dex strategies * [DEV-622] added new strategies to facades * [DEV-622] adapted DexEvmService to token purchase * [DEV-622] restructured payout strategies * [DEV-622] added payout strategies placeholders * [DEV-622] private methds naming cleanup in dex module * [DEV-622] refactored payout service to group orders by strategies * [DEV-622] implemented dex strategies for bitcoin and eth tokens * [DEV-622] implemented prepare strategy for bitcoin * [DEV-622] added erc20 contract integration * [DEV-622] basic test swap for evm * [DEV-622] fixed abi import and denomination of erc20 tokens * [DEV-622] implemented new reference asset rules * [DEV-622] implemented btc to dfi pricing path * [DEV-622] refactored bitcoin payout strategy to use shared jellyfish functionality * [DEV-622] remove redundant method binding * [DEV-622] implemented reference assets rules for evm liquidity strategies * [DEV-622] fixed abi import path * [DEV-622] changed naming in dfi payout strategy * [DEV-622] several adjustments during testing * [DEV-622] fix payout completion check error * [DEV-622] made swap contract and token configurable * [DEV-622] fixed evm client construction * [DEV-622] extended env example file * [DEV-622] created migration * [DEV-622] implemented fallback to currency logic in pricing module * [DEV-622] Switched to ALTER COLUMN * [DEV-622] added env configuration * [DEV-622] addressed PR comments Co-authored-by: David May commit 2da67ba813ce9202602bacea016e6eb7b339dd08 Author: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Wed Oct 12 12:17:34 2022 +0200 [DEV-862] bankTxReturn update method (#450) * [DEV-862] add update method * [DEV-862] Refactoring 1 * [DEV-862] renaming commit aa675a60b7c482843ef40ed7629703c9d5adc64f Author: David May <85513542+davidleomay@users.noreply.github.com> Date: Wed Oct 12 11:07:22 2022 +0200 Improved logging for BuyCrypto and Spider errors (#451) commit e6f3d488250c805473b8f11d5d4614e8af5df9dc Author: Yannick1712 Date: Tue Oct 11 17:25:24 2022 +0200 [FIX] error handling commit 1c540577a415c155e52d8aa4bc70f5979de29e82 Author: Yannick1712 Date: Tue Oct 11 17:02:04 2022 +0200 [FIX] kyc-webhook wallet fix commit 234ee8bac87d06e330cf8a71853be80b40380900 Author: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Tue Oct 11 15:58:16 2022 +0200 [DEV-860] add BankTxReturn Cols + migration (#448) * [DEV-860] add BankTxReturn Cols + migration * [DEV-860] Refactoring * [DEV-860] Refactoring 2 commit 13273b9de396350b6850fb1a1d2d9d22e130130d Author: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Tue Oct 11 15:48:09 2022 +0200 [DEV-847] add buyCrypto Pending Mails (#447) * [DEV-847] add buyCrypto Pending Mails * [DEV-847] Refactoring 1 * [DEV-847] Refactoring 2 * [DEV-847] Refactoring 3 * [DEV-847] remove unused code Co-authored-by: David May commit 1ef41214ac6cbc1e0a8ef6bab31f9e9c568466c4 Author: David May Date: Mon Oct 10 15:33:12 2022 +0200 [DEV-769] Added API key to env commit 5f420d85e5a2fe260ddd1b10fbac12a775f3a0ac Author: Yannick1712 Date: Mon Oct 10 13:15:39 2022 +0200 [FIX] fix unit test commit 37283694e60bf64474b6e1017015dd7889dc913d Author: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Mon Oct 10 12:58:58 2022 +0200 [DEV-801] & [DEV-802]: min Deposit (#443) * [DEV-801] & [DEV-802]: min Deposit * [DEV-801]&[DEV-802] change config * [DEV-801] Refactoring * [DEV-801] move getMinDeposit commit 5c79d350381f5f3d9a841e2f06939438043ab176 Author: Yannick1712 Date: Sat Oct 8 13:54:54 2022 +0200 [FIX] fix unit test commit 3f38798703568ecbb702d3bda24e1ceffc4c87dc Author: Yannick1712 Date: Sat Oct 8 13:06:46 2022 +0200 [FIX] Mail Update commit 5c2a88159ff086b449bb50b98abad675e4814df3 Author: Yannick1712 Date: Sat Oct 8 11:25:30 2022 +0200 [FIX] fix commit 8f7f0547e6145656e3eaa2b03b019e7afc996bdc Author: Yannick1712 Date: Sat Oct 8 11:19:47 2022 +0200 [FIX] remove seed commit 71caf5c43d6ab361c24c79ab169d371c0a374650 Author: Yannick1712 Date: Sat Oct 8 10:08:23 2022 +0200 [FIX] Refactoring commit 598a24278cf7c4ec7e988e4b57f67eb561c71f68 Author: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Thu Oct 6 19:53:30 2022 +0200 [DEV-827] change kyc mail text (#444) * [FIX] fix payback Mail Text * [DEV-827] kyc mail changed text part commit f5bb3748453c11bb93a75e18d76cab220f3d1df4 Author: Yannick1712 Date: Thu Oct 6 17:17:14 2022 +0200 [FIX] fix linter error commit 5255772b35cbc7577c958a3c49d0ac53bb7fe89b Author: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Thu Oct 6 17:10:58 2022 +0200 [DEV-829] bank table (#440) * [DEV-829] add bank + migration * [DEV-829] move bankSelector, refactoring * [DEV-829] Refactoring * [DEV-829] Refactoring 2 * [DEV-829] add unit tests + Refactoring * [DEV-829] remove ibans in config * [DEV-829] Refactoring unit tests commit a78d84c1c637a8e380cd61d659d3e1b381880bda Author: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Thu Oct 6 16:41:11 2022 +0200 [DEV-813] buyCrypto return Mails (#442) * [DEV-813] add buyCrypto return Mails + add amlReasons * [DEV-813] add amlReason Mail texts * [DEV-813] Refactoring * [DEV-851] add buyCryptoReturn Mail Translation commit 6c0aa85441782edc30ebd383f5556940445b1129 Author: Yannick1712 Date: Wed Oct 5 22:47:14 2022 +0200 [DEV-849] fix dto check commit 1b117a82c404d1e3b99fdb90d04d7b06ffb49cf3 Author: Yannick1712 Date: Wed Oct 5 17:34:34 2022 +0200 [DEV-849] rename wallet description col + change kycDataTransferDto commit d301bc38955bc0195eb688a805250b3121a7bbe1 Author: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Wed Oct 5 15:16:00 2022 +0200 [DEV-839] add bankTxRepeat Tabel + migration (#441) commit 5c45c47628103ba6a26d8aea1947630d02a41d8d Author: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Wed Oct 5 09:42:04 2022 +0200 [DEV-832] add bankTxReturn + migration (#439) * [DEV-832] add bankTxReturn + migration * [DEV-832] Refactoring commit c13cce89af642aebf3dddaf033930040adbfdcb2 Author: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Mon Oct 3 11:47:08 2022 +0200 [DEV-36]&[DEV-174] remove crypto sell + crypto buy (#422) * [DEV-36] Remove cryptoSell * [DEV-174] Remove CryptoBuy + Refactoring * [DEV-36] & [DEV-174] remove thunderClient/Collection entries commit 64403f5ad691a4a8b92e4da1a6ecfa37c5585ddd Author: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Sat Oct 1 16:02:15 2022 +0200 [DEV-831] new bankTx types (#438) Co-authored-by: Kolibri1990 <66674482+Kolibri1990@users.noreply.github.com> Co-authored-by: David May <85513542+davidleomay@users.noreply.github.com> Co-authored-by: David May Co-authored-by: TonySemikin * [DEV-770] Refactoring 1 * [DEV-770] add KycWebhookStatus Rejected * [DEV-770] Refactoring 2 * [DEV-770] disable kyc mails for externalUser * [DEV-770] Refactoring 3 * [DEV-770] move challenge null check * [DEV-770] Refactoring 4 * [DEV-770] Refactoring 5 * [DEV-770] change merge check * [DEV-770] Refactoring 6 Co-authored-by: David May --- src/config/config.ts | 14 +++- src/user/models/auth/auth.controller.ts | 5 ++ src/user/models/auth/auth.service.ts | 80 ++++++++++++++----- src/user/models/ident/ident.service.ts | 2 +- src/user/models/kyc/kyc-process.service.ts | 46 ++++++----- src/user/models/kyc/kyc-webhook.service.ts | 49 +++++++----- src/user/models/kyc/kyc.service.ts | 17 ++-- src/user/models/user-data/user-data.entity.ts | 4 + .../models/user-data/user-data.service.ts | 5 +- src/user/models/wallet/dto/kyc-data.dto.ts | 7 ++ src/user/models/wallet/dto/wallet.dto.ts | 3 + src/user/models/wallet/wallet.controller.ts | 64 +++++++++++++++ src/user/models/wallet/wallet.entity.ts | 2 +- src/user/models/wallet/wallet.service.ts | 28 +++++-- .../services/spider/spider-sync.service.ts | 5 +- src/user/user.module.ts | 2 + thunder-tests/thunderCollection.json | 7 ++ thunder-tests/thunderclient.json | 58 ++++++++++++++ 18 files changed, 323 insertions(+), 75 deletions(-) create mode 100644 src/user/models/wallet/dto/kyc-data.dto.ts create mode 100644 src/user/models/wallet/dto/wallet.dto.ts create mode 100644 src/user/models/wallet/wallet.controller.ts diff --git a/src/config/config.ts b/src/config/config.ts index df89ab278d..c4713ad636 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -72,6 +72,14 @@ export class Configuration { expiresIn: process.env.JWT_EXPIRES_IN ?? 172800, }, }, + company: { + signOptions: { + expiresIn: process.env.JWT_EXPIRES_IN_COMPANY ?? 30, + }, + }, + challenge: { + expiresIn: +process.env.CHALLENGE_EXPIRES_IN ?? 10, + }, signMessage: 'By_signing_this_message,_you_confirm_that_you_are_the_sole_owner_of_the_provided_DeFiChain_address_and_are_in_possession_of_its_private_key._Your_ID:_', signMessageWallet: @@ -119,8 +127,10 @@ export class Configuration { apiKey: process.env.FIXER_API_KEY, }; - lock = { - apiKey: process.env.LOCK_API_KEY, + externalKycServices = { + 'LOCK.space': { + apiKey: process.env.LOCK_API_KEY, + }, }; mail: MailOptions = { diff --git a/src/user/models/auth/auth.controller.ts b/src/user/models/auth/auth.controller.ts index b5ee33f577..9bbd735c76 100644 --- a/src/user/models/auth/auth.controller.ts +++ b/src/user/models/auth/auth.controller.ts @@ -30,4 +30,9 @@ export class AuthController { signInCompany(@Body() credentials: AuthCredentialsDto): Promise<{ accessToken: string }> { return this.authService.companySignIn(credentials); } + + @Post('company/challenge') + companyChallenge(@Query('address') address: string): Promise<{ challenge: string }> { + return this.authService.getCompanyChallenge(address); + } } diff --git a/src/user/models/auth/auth.service.ts b/src/user/models/auth/auth.service.ts index 660628bf8f..fda4ce6cb7 100644 --- a/src/user/models/auth/auth.service.ts +++ b/src/user/models/auth/auth.service.ts @@ -21,9 +21,19 @@ import { Blockchain } from 'src/blockchain/shared/enums/blockchain.enum'; import { WalletRepository } from '../wallet/wallet.repository'; import { Wallet } from '../wallet/wallet.entity'; import { UserRole } from 'src/shared/auth/user-role.enum'; +import { Util } from 'src/shared/util'; +import { Interval } from '@nestjs/schedule'; +import { randomUUID } from 'crypto'; + +export interface ChallengeData { + created: Date; + challenge: string; +} @Injectable() export class AuthService { + private challengeList: Map = new Map(); + constructor( private readonly userService: UserService, private readonly userRepo: UserRepository, @@ -33,13 +43,24 @@ export class AuthService { private readonly refService: RefService, ) {} + @Interval(90000) + checkChallengeList() { + for (const [key, challenge] of this.challengeList.entries()) { + if (!this.isChallengeValid(challenge)) { + this.challengeList.delete(key); + } + } + } + + // --- AUTH METHODS --- // async signUp(dto: CreateUserDto, userIp: string): Promise<{ accessToken: string }> { const existingUser = await this.userRepo.getByAddress(dto.address); if (existingUser) { throw new ConflictException('User already exists'); } - if (!this.verifySignature(dto.address, dto.signature)) { + const { message } = this.getSignMessage(dto.address); + if (!this.verifySignature(message, dto.address, dto.signature)) { throw new BadRequestException('Invalid signature'); } @@ -56,7 +77,8 @@ export class AuthService { const user = await this.userRepo.getByAddress(address); if (!user) throw new NotFoundException('User not found'); - const credentialsValid = this.verifySignature(address, signature); + const { message } = this.getSignMessage(address); + const credentialsValid = this.verifySignature(message, address, signature); if (!credentialsValid) throw new UnauthorizedException('Invalid credentials'); // TODO: temporary code to update old wallet signatures @@ -67,17 +89,36 @@ export class AuthService { return { accessToken: this.generateUserToken(user) }; } - async companySignIn({ address, signature }: AuthCredentialsDto): Promise<{ accessToken: string }> { - const wallet = await this.walletRepo.getByAddress(address); + async companySignIn(dto: AuthCredentialsDto): Promise<{ accessToken: string }> { + const wallet = await this.walletRepo.findOne({ where: { address: dto.address } }); if (!wallet || !wallet.isKycClient) throw new NotFoundException('Wallet not found'); - // TODO add challenge response - const credentialsValid = this.verifyCompanySignature(address, signature); + const credentialsValid = this.verifyCompanySignature(dto); if (!credentialsValid) throw new UnauthorizedException('Invalid credentials'); return { accessToken: this.generateCompanyToken(wallet) }; } + async getCompanyChallenge(address: string): Promise<{ challenge: string }> { + const wallet = await this.walletRepo.findOne({ where: { address: address } }); + if (!wallet || !wallet.isKycClient) throw new BadRequestException('Wallet not found/invalid'); + + const challenge = randomUUID(); + + this.challengeList.set(address, { created: new Date(), challenge: challenge }); + + return { challenge: challenge }; + } + + async changeUser(id: number, changeUser: LinkedUserInDto): Promise<{ accessToken: string }> { + const user = await this.getLinkedUser(id, changeUser.address); + if (!user) throw new NotFoundException('User not found'); + if (user.stakingBalance > 0) throw new ForbiddenException('Change user not allowed'); + return { accessToken: this.generateUserToken(user) }; + } + + // --- HELPER METHODS --- // + getSignMessage(address: string): { message: string; blockchains: Blockchain[] } { const blockchains = this.cryptoService.getBlockchainsBasedOn(address); return { @@ -94,13 +135,6 @@ export class AuthService { }; } - async changeUser(id: number, changeUser: LinkedUserInDto): Promise<{ accessToken: string }> { - const user = await this.getLinkedUser(id, changeUser.address); - if (!user) throw new NotFoundException('User not found'); - if (user.stakingBalance > 0) throw new ForbiddenException('Change user not allowed'); - return { accessToken: this.generateUserToken(user) }; - } - private async getLinkedUser(id: number, address: string): Promise { return this.userRepo .createQueryBuilder('user') @@ -112,14 +146,16 @@ export class AuthService { .getRawOne(); } - private verifySignature(address: string, signature: string): boolean { - const signatureMessage = this.getSignMessage(address); - return this.cryptoService.verifySignature(signatureMessage.message, address, signature); + private verifySignature(message: string, address: string, signature: string): boolean { + return this.cryptoService.verifySignature(message, address, signature); } - private verifyCompanySignature(address: string, signature: string): boolean { - const signatureMessage = this.getCompanySignMessage(address); - return this.cryptoService.verifySignature(signatureMessage.message, address, signature); + private verifyCompanySignature(dto: AuthCredentialsDto): boolean { + const challengeData = this.challengeList.get(dto.address); + if (!this.isChallengeValid(challengeData)) throw new UnauthorizedException('Challenge invalid'); + this.challengeList.delete(dto.address); + + return this.verifySignature(challengeData.challenge, dto.address, dto.signature); } private generateUserToken(user: User): string { @@ -138,6 +174,10 @@ export class AuthService { address: wallet.address, role: UserRole.KYC_CLIENT_COMPANY, }; - return this.jwtService.sign(payload); + return this.jwtService.sign(payload, { expiresIn: Config.auth.company.signOptions.expiresIn }); + } + + private isChallengeValid(challenge: ChallengeData): boolean { + return challenge && Util.secondsDiff(challenge.created, new Date()) <= Config.auth.challenge.expiresIn; } } diff --git a/src/user/models/ident/ident.service.ts b/src/user/models/ident/ident.service.ts index 1605b54238..256f733d7c 100644 --- a/src/user/models/ident/ident.service.ts +++ b/src/user/models/ident/ident.service.ts @@ -21,7 +21,7 @@ export class IdentService { spiderData: { identIdentificationIds: Like(`%${result?.identificationprocess?.id}%`) }, }, ], - relations: ['spiderData'], + relations: ['spiderData', 'users', 'users.wallet'], }); if (!user) { diff --git a/src/user/models/kyc/kyc-process.service.ts b/src/user/models/kyc/kyc-process.service.ts index ea96163121..37831ade57 100644 --- a/src/user/models/kyc/kyc-process.service.ts +++ b/src/user/models/kyc/kyc-process.service.ts @@ -24,7 +24,8 @@ export class KycProcessService { // --- GENERAL METHODS --- // async startKycProcess(userData: UserData): Promise { - return await this.goToStatus(userData, KycStatus.CHATBOT); + const lockUser = userData.users.find((e) => e.wallet.name === 'LOCK.space'); + return await this.goToStatus(userData, lockUser ? KycStatus.ONLINE_ID : KycStatus.CHATBOT); } async checkKycProcess(userData: UserData): Promise { @@ -56,7 +57,7 @@ export class KycProcessService { userData.spiderData = await this.updateSpiderData(userData, initiateData); } - if (status === KycStatus.MANUAL) { + if (status === KycStatus.MANUAL && !userData.hasExternalUser) { if (userData.mail) { await this.notificationService.sendMail({ type: MailType.USER, @@ -95,24 +96,29 @@ export class KycProcessService { if (userData.kycStatus === KycStatus.ONLINE_ID) { userData = await this.goToStatus(userData, KycStatus.VIDEO_ID); - await this.notificationService - .sendMail({ - type: MailType.USER, - input: { - userData, - translationKey: 'mail.kyc.failed', - translationParams: { - url: `${Config.payment.url}/kyc?code=${userData.kycHash}`, + if (!userData.hasExternalUser) { + await this.notificationService + .sendMail({ + type: MailType.USER, + input: { + userData, + translationKey: 'mail.kyc.failed', + translationParams: { + url: `${Config.payment.url}/kyc?code=${userData.kycHash}`, + }, }, - }, - }) - .catch(() => null); + }) + .catch(() => null); + } return userData; } // notify support await this.notificationService.sendMail({ type: MailType.KYC_SUPPORT, input: { userData } }); + + //kyc Webhook external Services + await this.kycWebhookService.kycFailed(userData, 'Kyc step failed'); return this.updateKycState(userData, KycState.FAILED); } @@ -167,12 +173,14 @@ export class KycProcessService { async identCompleted(userData: UserData, result: IdentResultDto): Promise { userData = await this.storeIdentResult(userData, result); - await this.notificationService - .sendMail({ - type: MailType.USER, - input: { userData, translationKey: 'mail.kyc.ident', translationParams: {} }, - }) - .catch(() => null); + if (!userData.hasExternalUser) { + await this.notificationService + .sendMail({ + type: MailType.USER, + input: { userData, translationKey: 'mail.kyc.ident', translationParams: {} }, + }) + .catch(() => null); + } return await this.goToStatus(userData, KycStatus.CHECK); } diff --git a/src/user/models/kyc/kyc-webhook.service.ts b/src/user/models/kyc/kyc-webhook.service.ts index 36f24946fa..bf184e0879 100644 --- a/src/user/models/kyc/kyc-webhook.service.ts +++ b/src/user/models/kyc/kyc-webhook.service.ts @@ -1,13 +1,15 @@ import { Injectable } from '@nestjs/common'; import { HttpService } from 'src/shared/services/http.service'; -import { WalletRepository } from '../wallet/wallet.repository'; -import { KycCompleted, UserData } from '../user-data/user-data.entity'; -import { Config } from 'src/config/config'; +import { KycCompleted, KycStatus, UserData } from '../user-data/user-data.entity'; +import { UserRepository } from '../user/user.repository'; +import { SpiderDataRepository } from '../spider-data/spider-data.repository'; +import { WalletService } from '../wallet/wallet.service'; export enum KycWebhookStatus { NA = 'NA', LIGHT = 'Light', FULL = 'Full', + REJECTED = 'Rejected', } export enum KycWebhookResult { @@ -37,7 +39,12 @@ export class KycWebhookDto { @Injectable() export class KycWebhookService { - constructor(private readonly http: HttpService, private readonly walletRepo: WalletRepository) {} + constructor( + private readonly http: HttpService, + private readonly walletService: WalletService, + private readonly userRepo: UserRepository, + private readonly spiderRepo: SpiderDataRepository, + ) {} async kycChanged(userData: UserData): Promise { await this.triggerWebhook(userData, KycWebhookResult.STATUS_CHANGED); @@ -48,19 +55,13 @@ export class KycWebhookService { } private async triggerWebhook(userData: UserData, result: KycWebhookResult, reason?: string): Promise { - if (!userData.users) { - console.info(`Tried to trigger webhook for user ${userData.id}, but users were not loaded`); - return; - } + userData.users = await this.userRepo.find({ where: { userData: { id: userData.id } }, relations: ['wallet'] }); for (const user of userData.users) { try { - if (!user.wallet?.id) { - console.info(`Tried to trigger webhook for user ${userData.id}, but wallet were not loaded`); - continue; - } - const walletUser = await this.walletRepo.findOne({ where: { id: user.wallet.id } }); - if (!walletUser || !walletUser.isKycClient || !walletUser.apiUrl) continue; + if (!user.wallet.isKycClient || !user.wallet.apiUrl) continue; + + const spiderData = await this.spiderRepo.findOne({ where: { userData: { id: userData.id } } }); const data: KycWebhookDto = { id: user.address, @@ -74,19 +75,31 @@ export class KycWebhookService { city: userData.location, zip: userData.zip, phone: userData.phone, - //TODO change for KYC Update v2 - kycStatus: KycCompleted(userData.kycStatus) ? KycWebhookStatus.FULL : KycWebhookStatus.NA, + kycStatus: this.getKycWebhookStatus(userData.kycStatus, spiderData?.chatbotResult), kycHash: userData.kycHash, }, reason: reason, }; - await this.http.post(`${walletUser.apiUrl}/kyc/update`, data, { - headers: { 'x-api-key': Config.lock.apiKey }, + const apiKey = this.walletService.getApiKeyInternal(user.wallet.name); + if (!apiKey) throw new Error(`ApiKey for wallet ${user.wallet.name} not available`); + + await this.http.post(`${user.wallet.apiUrl}/kyc/update`, data, { + headers: { 'x-api-key': apiKey }, }); } catch (error) { console.error(`Exception during KYC webhook (${result}) for user ${userData.id}:`, error); } } } + + getKycWebhookStatus(kycStatus: KycStatus, chatbotResult: string): KycWebhookStatus { + if (KycCompleted(kycStatus)) { + return chatbotResult ? KycWebhookStatus.FULL : KycWebhookStatus.LIGHT; + } else if (kycStatus === KycStatus.REJECTED) { + return KycWebhookStatus.REJECTED; + } else { + return KycWebhookStatus.NA; + } + } } diff --git a/src/user/models/kyc/kyc.service.ts b/src/user/models/kyc/kyc.service.ts index 80fad001b2..1728705832 100644 --- a/src/user/models/kyc/kyc.service.ts +++ b/src/user/models/kyc/kyc.service.ts @@ -28,8 +28,8 @@ import { UpdateKycStatusDto } from '../user-data/dto/update-kyc-status.dto'; import { KycDataTransferDto } from './dto/kyc-data-transfer.dto'; import { WalletRepository } from '../wallet/wallet.repository'; import { HttpService } from 'src/shared/services/http.service'; -import { Config } from 'src/config/config'; import { UserRepository } from '../user/user.repository'; +import { WalletService } from '../wallet/wallet.service'; export interface KycInfo { kycStatus: KycStatus; @@ -51,6 +51,7 @@ export class KycService { private readonly userDataRepo: UserDataRepository, private readonly userRepo: UserRepository, private readonly walletRepo: WalletRepository, + private readonly walletService: WalletService, private readonly spiderService: SpiderService, private readonly spiderSyncService: SpiderSyncService, private readonly countryService: CountryService, @@ -73,7 +74,7 @@ export class KycService { } async updateKycStatus(userDataId: number, dto: UpdateKycStatusDto): Promise { - let userData = await this.userDataRepo.findOne({ where: { id: userDataId } }); + let userData = await this.userDataRepo.findOne({ where: { id: userDataId }, relations: ['users', 'users.wallet'] }); if (!userData) throw new NotFoundException('User data not found'); // update status @@ -137,9 +138,12 @@ export class KycService { if (!user) throw new NotFoundException('DFX user not found'); if (!KycCompleted(user.userData.kycStatus)) throw new ConflictException('KYC required'); + const apiKey = this.walletService.getApiKeyInternal(wallet.name); + if (!apiKey) throw new Error(`ApiKey for wallet ${wallet.name} not available`); + try { result = await this.http.get<{ kycId: string }>(`${wallet.apiUrl}/kyc/check`, { - headers: { 'x-api-key': Config.lock.apiKey }, + headers: { 'x-api-key': apiKey }, params: { address: user.address }, }); @@ -221,7 +225,7 @@ export class KycService { const users = await this.userDataService.getUsersByMail(user.mail); const completedUser = users.find((data) => KycCompleted(data.kycStatus)); - if (completedUser) { + if (completedUser && !user.hasExternalUser) { await this.linkService.createNewLinkAddress(user, completedUser); throw new ConflictException('User already has completed Kyc'); } @@ -287,7 +291,10 @@ export class KycService { } private async getUserByKycCode(code: string): Promise { - const userData = await this.userDataRepo.findOne({ where: { kycHash: code }, relations: ['users', 'spiderData'] }); + const userData = await this.userDataRepo.findOne({ + where: { kycHash: code }, + relations: ['users', 'users.wallet', 'spiderData'], + }); if (!userData) throw new NotFoundException('User not found'); return userData; } diff --git a/src/user/models/user-data/user-data.entity.ts b/src/user/models/user-data/user-data.entity.ts index b16eb62ee9..7d07cf000c 100644 --- a/src/user/models/user-data/user-data.entity.ts +++ b/src/user/models/user-data/user-data.entity.ts @@ -178,6 +178,10 @@ export class UserData extends IEntity { @OneToOne(() => SpiderData, (c) => c.userData, { nullable: true }) spiderData: SpiderData; + + get hasExternalUser(): boolean { + return !!this.users.find((e) => e.wallet.isKycClient === true); + } } export const KycInProgressStates = [KycStatus.CHATBOT, KycStatus.ONLINE_ID, KycStatus.VIDEO_ID]; diff --git a/src/user/models/user-data/user-data.service.ts b/src/user/models/user-data/user-data.service.ts index ab96f57b06..b1f0222a87 100644 --- a/src/user/models/user-data/user-data.service.ts +++ b/src/user/models/user-data/user-data.service.ts @@ -36,10 +36,11 @@ export class UserDataService { async getUserDataByUser(userId: number): Promise { return this.userDataRepo .createQueryBuilder('userData') - .innerJoin('userData.users', 'user') + .leftJoinAndSelect('userData.users', 'user') .leftJoinAndSelect('userData.country', 'country') .leftJoinAndSelect('userData.organizationCountry', 'organizationCountry') .leftJoinAndSelect('userData.language', 'language') + .leftJoinAndSelect('user.wallet', 'wallet') .where('user.id = :id', { id: userId }) .getOne(); } @@ -69,7 +70,7 @@ export class UserDataService { } async updateUserData(userDataId: number, dto: UpdateUserDataDto): Promise { - let userData = await this.userDataRepo.findOne(userDataId); + let userData = await this.userDataRepo.findOne({ where: { id: userDataId }, relations: ['users', 'users.wallet'] }); if (!userData) throw new NotFoundException('User data not found'); userData = await this.updateSpiderIfNeeded(userData, dto); diff --git a/src/user/models/wallet/dto/kyc-data.dto.ts b/src/user/models/wallet/dto/kyc-data.dto.ts new file mode 100644 index 0000000000..8cf4b8f1ee --- /dev/null +++ b/src/user/models/wallet/dto/kyc-data.dto.ts @@ -0,0 +1,7 @@ +import { KycWebhookStatus } from '../../kyc/kyc-webhook.service'; + +export class KycDataDto { + address: string; + kycStatus: KycWebhookStatus; + kycHash: string; +} diff --git a/src/user/models/wallet/dto/wallet.dto.ts b/src/user/models/wallet/dto/wallet.dto.ts new file mode 100644 index 0000000000..31e84d424e --- /dev/null +++ b/src/user/models/wallet/dto/wallet.dto.ts @@ -0,0 +1,3 @@ +export class WalletDto { + name: string; +} diff --git a/src/user/models/wallet/wallet.controller.ts b/src/user/models/wallet/wallet.controller.ts new file mode 100644 index 0000000000..15d25df04a --- /dev/null +++ b/src/user/models/wallet/wallet.controller.ts @@ -0,0 +1,64 @@ +import { Controller, Get, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { RoleGuard } from 'src/shared/auth/role.guard'; +import { AuthGuard } from '@nestjs/passport'; +import { UserRole } from 'src/shared/auth/user-role.enum'; +import { WalletService } from './wallet.service'; +import { WalletDto } from './dto/wallet.dto'; +import { Wallet } from './wallet.entity'; +import { GetJwt } from 'src/shared/auth/get-jwt.decorator'; +import { JwtPayload } from 'src/shared/auth/jwt-payload.interface'; +import { User } from '../user/user.entity'; +import { KycDataDto } from './dto/kyc-data.dto'; +import { KycWebhookService } from '../kyc/kyc-webhook.service'; +import { SpiderDataRepository } from '../spider-data/spider-data.repository'; + +@ApiTags('wallet') +@Controller('wallet') +export class WalletController { + constructor( + private readonly walletService: WalletService, + private readonly spiderRepo: SpiderDataRepository, + private readonly kycWebhookService: KycWebhookService, + ) {} + + @Get() + @ApiBearerAuth() + @UseGuards(AuthGuard(), new RoleGuard(UserRole.USER)) + async getAllExternalService(): Promise { + return this.walletService.getAllExternalServices().then((l) => this.toDtoList(l)); + } + + @Get('kycData') + @ApiBearerAuth() + @UseGuards(AuthGuard(), new RoleGuard(UserRole.KYC_CLIENT_COMPANY)) + async getAllKycData(@GetJwt() jwt: JwtPayload): Promise { + return this.walletService.getAllKycData(jwt.id).then((l) => this.toKycDataDtoList(l)); + } + + // --- DTO --- // + private async toDtoList(wallets: Wallet[]): Promise { + return Promise.all(wallets.map((b) => this.toDto(b))); + } + + private async toKycDataDtoList(users: User[]): Promise { + return Promise.all(users.map((b) => this.toKycDataDto(b))); + } + + private async toKycDataDto(user: User): Promise { + return { + address: user.address, + kycStatus: this.kycWebhookService.getKycWebhookStatus( + user.userData.kycStatus, + user.userData.spiderData?.chatbotResult, + ), + kycHash: user.userData.kycHash, + }; + } + + private async toDto(wallet: Wallet): Promise { + return { + name: wallet.name, + }; + } +} diff --git a/src/user/models/wallet/wallet.entity.ts b/src/user/models/wallet/wallet.entity.ts index fdbb4d7e6f..2036f9a579 100644 --- a/src/user/models/wallet/wallet.entity.ts +++ b/src/user/models/wallet/wallet.entity.ts @@ -23,5 +23,5 @@ export class Wallet extends IEntity { apiUrl: string; @OneToMany(() => User, (user) => user.wallet) - logs: User[]; + users: User[]; } diff --git a/src/user/models/wallet/wallet.service.ts b/src/user/models/wallet/wallet.service.ts index f488f41d31..48592d1884 100644 --- a/src/user/models/wallet/wallet.service.ts +++ b/src/user/models/wallet/wallet.service.ts @@ -1,18 +1,34 @@ import { Injectable } from '@nestjs/common'; +import { Config } from 'src/config/config'; import { WalletRepository } from 'src/user/models/wallet/wallet.repository'; +import { User } from '../user/user.entity'; import { Wallet } from './wallet.entity'; @Injectable() export class WalletService { - constructor(private walletRepo: WalletRepository) {} + constructor(private readonly walletRepo: WalletRepository) {} async getWalletOrDefault(id: number): Promise { return (await this.walletRepo.findOne(id)) ?? (await this.walletRepo.findOne(1)); } - // TODO: remove? - // private verifySignature(address: string, signature: string): boolean { - // const signatureMessage = Config.auth.signMessageWallet + address; - // return this.cryptoService.verifySignature(signatureMessage, address, signature); - // } + async getAllExternalServices(): Promise { + return await this.walletRepo.find({ where: { isKycClient: true } }); + } + + async getAllKycData(walletId: number): Promise { + const wallet = await this.walletRepo.findOne({ + where: { id: walletId }, + relations: ['users', 'users.userData', 'users.userData.spiderData'], + }); + return wallet.users; + } + + public getApiKeyInternal(name: string): string { + return ( + Object.entries(Config.externalKycServices) + .filter(([key, _]) => key === name) + .map(([_, value]) => value)[0]?.apiKey ?? undefined + ); + } } diff --git a/src/user/services/spider/spider-sync.service.ts b/src/user/services/spider/spider-sync.service.ts index 65155145a1..882ea1990e 100644 --- a/src/user/services/spider/spider-sync.service.ts +++ b/src/user/services/spider/spider-sync.service.ts @@ -118,7 +118,10 @@ export class SpiderSyncService { } async syncKycUser(userDataId: number, forceSync = false): Promise { - let userData = await this.userDataRepo.findOne({ where: { id: userDataId }, relations: ['spiderData'] }); + let userData = await this.userDataRepo.findOne({ + where: { id: userDataId }, + relations: ['spiderData', 'users', 'users.wallet'], + }); if (!userData) return; // update KYC data diff --git a/src/user/user.module.ts b/src/user/user.module.ts index 458d1e0ceb..9237b7c1a6 100644 --- a/src/user/user.module.ts +++ b/src/user/user.module.ts @@ -36,6 +36,7 @@ import { LinkAddressRepository } from './models/link/link-address.repository'; import { NotificationModule } from 'src/notification/notification.module'; import { LimitRequestController } from './models/limit-request/limit-request.controller'; import { KycWebhookService } from './models/kyc/kyc-webhook.service'; +import { WalletController } from './models/wallet/wallet.controller'; @Module({ imports: [ @@ -63,6 +64,7 @@ import { KycWebhookService } from './models/kyc/kyc-webhook.service'; KycController, LinkController, LimitRequestController, + WalletController, ], providers: [ UserService, diff --git a/thunder-tests/thunderCollection.json b/thunder-tests/thunderCollection.json index 80d7aac49f..e48b3e3994 100644 --- a/thunder-tests/thunderCollection.json +++ b/thunder-tests/thunderCollection.json @@ -194,6 +194,13 @@ "created": "2022-09-23T14:03:55.908Z", "sortNum": 300000 }, + { + "_id": "9baf5a17-7886-4959-ab65-eea8e3dae524", + "name": "Wallet", + "containerId": "", + "created": "2022-10-14T16:15:32.185Z", + "sortNum": 275000 + }, { "_id": "af415d12-0498-48a6-a81d-4d3ef4497cdf", "name": "Bank TX Return", diff --git a/thunder-tests/thunderclient.json b/thunder-tests/thunderclient.json index d3e345e18a..b7e313debd 100644 --- a/thunder-tests/thunderclient.json +++ b/thunder-tests/thunderclient.json @@ -1867,6 +1867,64 @@ "tests": [] }, { + "_id": "a0a206f0-13eb-493a-9b07-3a140b0eeedf", + "colId": "62eefab7-84cd-496b-8f93-253c15d4f841", + "containerId": "0bbfaa5e-1c48-4a6b-9dd3-bb28b78af825", + "name": "Company Challenge", + "url": "{{url}}/v1/auth/company/challenge?address=8an2Mi4dCV8GfiqbF2i2pJwDetq87AhFa4", + "method": "POST", + "sortNum": 85000, + "created": "2022-10-14T13:12:37.193Z", + "modified": "2022-10-20T10:15:12.521Z", + "headers": [], + "params": [ + { + "name": "address", + "value": "8an2Mi4dCV8GfiqbF2i2pJwDetq87AhFa4", + "isPath": false + } + ], + "auth": { + "type": "none" + }, + "tests": [] + }, + { + "_id": "8384887d-f0e7-43a4-9a28-1859576d0c1c", + "colId": "62eefab7-84cd-496b-8f93-253c15d4f841", + "containerId": "0bbfaa5e-1c48-4a6b-9dd3-bb28b78af825", + "name": "Company SignIn", + "url": "{{url}}/v1/auth/company/signin", + "method": "POST", + "sortNum": 82500, + "created": "2022-10-14T13:12:39.907Z", + "modified": "2022-10-20T10:28:14.156Z", + "headers": [], + "params": [], + "body": { + "type": "json", + "raw": "{\n \"address\": \"8an2Mi4dCV8GfiqbF2i2pJwDetq87AhFa4\",\n \"signature\": \"HzRZfDlmtAp4ywy7MuGpwCEEGoemH6rB82VNJUaKM7P1PCmiBRo2MyuuYfIBKE/nakafSymO8ep+84AGIzeEm/I=\"\n}", + "form": [] + }, + "auth": { + "type": "none" + }, + "tests": [] + }, + { + "_id": "14441e24-4e57-4e7c-88cc-dff408c5837e", + "colId": "62eefab7-84cd-496b-8f93-253c15d4f841", + "containerId": "9baf5a17-7886-4959-ab65-eea8e3dae524", + "name": "KycData", + "url": "{{url}}/v1/wallet/kycData", + "method": "GET", + "sortNum": 640000, + "created": "2022-10-14T16:15:32.185Z", + "modified": "2022-10-14T16:16:42.670Z", + "headers": [], + "params": [], + "tests": [] + },{ "_id": "fb9bc645-856b-46a8-98c3-445abc95a2c4", "colId": "62eefab7-84cd-496b-8f93-253c15d4f841", "containerId": "d74943e9-e28b-4c36-9df3-b6093cf39800", From d9e09b58983a1a7e6206188a1aa33116ad5a81ef Mon Sep 17 00:00:00 2001 From: Yannick1712 Date: Fri, 21 Oct 2022 10:57:45 +0200 Subject: [PATCH 06/15] [FIX] fix unit tests --- src/user/models/kyc/kyc.service.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/user/models/kyc/kyc.service.spec.ts b/src/user/models/kyc/kyc.service.spec.ts index a981bf3209..4bcd57b32a 100644 --- a/src/user/models/kyc/kyc.service.spec.ts +++ b/src/user/models/kyc/kyc.service.spec.ts @@ -19,6 +19,7 @@ import { } from '../user-data/__mocks__/user-data.entity.mock'; import { UserRepository } from '../user/user.repository'; import { WalletRepository } from '../wallet/wallet.repository'; +import { WalletService } from '../wallet/wallet.service'; import { KycUserDataDto } from './dto/kyc-user-data.dto'; import { KycProcessService } from './kyc-process.service'; import { KycInfo, KycService } from './kyc.service'; @@ -36,6 +37,7 @@ describe('KycService', () => { let userRepo: UserRepository; let walletRepo: WalletRepository; let httpService: HttpService; + let walletService: WalletService; const defaultCountry = createDefaultCountry(); @@ -131,6 +133,7 @@ describe('KycService', () => { userRepo = createMock(); walletRepo = createMock(); httpService = createMock(); + walletService = createMock(); const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -145,6 +148,7 @@ describe('KycService', () => { { provide: UserRepository, useValue: userRepo }, { provide: WalletRepository, useValue: walletRepo }, { provide: HttpService, useValue: httpService }, + { provide: WalletService, useValue: walletService }, ], }).compile(); From a06432473bb8d00c0e8e6e9add68e290a84e5796 Mon Sep 17 00:00:00 2001 From: Kolibri1990 <66674482+Kolibri1990@users.noreply.github.com> Date: Sat, 22 Oct 2022 22:24:57 +0200 Subject: [PATCH 07/15] [DEV-808] Add contribution '0' to business account (#465) --- src/user/services/spider/spider.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/user/services/spider/spider.service.ts b/src/user/services/spider/spider.service.ts index 408bdbbea0..507332f84f 100644 --- a/src/user/services/spider/spider.service.ts +++ b/src/user/services/spider/spider.service.ts @@ -126,6 +126,7 @@ export class SpiderService { organisationType: user.accountType === AccountType.SOLE_PROPRIETORSHIP ? 'SOLE_PROPRIETORSHIP' : 'LEGAL_ENTITY', purposeBusinessRelationship: 'Kauf und Verkauf von DeFiChain Assets', bearerShares: 'NO', + contribution: '0', }; await this.uploadDocument( From 2a7995d88244897c2d797679d6aa11ef8a36aaf9 Mon Sep 17 00:00:00 2001 From: David May Date: Mon, 24 Oct 2022 09:48:29 +0200 Subject: [PATCH 08/15] Switched to REF node for staking swaps --- src/payment/models/crypto-staking/crypto-staking.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/payment/models/crypto-staking/crypto-staking.service.ts b/src/payment/models/crypto-staking/crypto-staking.service.ts index c08fc6c17c..674c011110 100644 --- a/src/payment/models/crypto-staking/crypto-staking.service.ts +++ b/src/payment/models/crypto-staking/crypto-staking.service.ts @@ -34,7 +34,7 @@ export class CryptoStakingService { private readonly stakingRefRewardRepo: StakingRefRewardRepository, private readonly stakingRepo: StakingRepository, ) { - nodeService.getConnectedNode(NodeType.INPUT).subscribe((client) => (this.client = client)); + nodeService.getConnectedNode(NodeType.REF).subscribe((client) => (this.client = client)); } // --- CRUD --- // From 383337028c8b21411e5faa49c22123ed78c9b964 Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Mon, 24 Oct 2022 10:10:34 +0200 Subject: [PATCH 09/15] [DEV-292] remove unused user fields + migration (#462) * [DEV-292] remove unused user fields + migration * [DEV-292] fix unit tests --- .../1666094113435-removeUnusedUserFields.js | 61 +++++++++++++++++++ src/user/models/user-data/user-data.entity.ts | 4 -- src/user/models/user/user.entity.ts | 55 ----------------- src/user/models/user/user.service.spec.ts | 2 +- src/user/models/user/user.service.ts | 7 ++- 5 files changed, 66 insertions(+), 63 deletions(-) create mode 100644 migration/1666094113435-removeUnusedUserFields.js diff --git a/migration/1666094113435-removeUnusedUserFields.js b/migration/1666094113435-removeUnusedUserFields.js new file mode 100644 index 0000000000..f5ef8b19e9 --- /dev/null +++ b/migration/1666094113435-removeUnusedUserFields.js @@ -0,0 +1,61 @@ +const { MigrationInterface, QueryRunner } = require("typeorm"); + +module.exports = class removeUnusedUserFields1666094113435 { + name = 'removeUnusedUserFields1666094113435' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP CONSTRAINT "FK_4aaf6d02199282eb8d3931bff31"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP CONSTRAINT "FK_0b294695467ceecc030f95461c1"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP CONSTRAINT "FK_039c54821427d0adca4db8de366"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP CONSTRAINT "FK_19ab0596b1fab6a44be5491ffb4"`); + await queryRunner.query(`ALTER TABLE "dbo"."user_data" DROP CONSTRAINT "DF_45a8d297955d5c3896ea84afa4b"`); + await queryRunner.query(`ALTER TABLE "dbo"."user_data" DROP COLUMN "isMigrated"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "mail"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "firstname"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "surname"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "street"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "houseNumber"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "location"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "zip"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "phone"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "countryId"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "languageId"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP CONSTRAINT "DF_625088799076cba7dffb9947942"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "accountType"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "organizationName"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "organizationStreet"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "organizationHouseNumber"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "organizationLocation"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "organizationZip"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "organizationCountryId"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "currencyId"`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "currencyId" int`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "organizationCountryId" int`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "organizationZip" nvarchar(256)`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "organizationLocation" nvarchar(256)`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "organizationHouseNumber" nvarchar(256)`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "organizationStreet" nvarchar(256)`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "organizationName" nvarchar(256)`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "accountType" nvarchar(256) NOT NULL`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD CONSTRAINT "DF_625088799076cba7dffb9947942" DEFAULT 'Personal' FOR "accountType"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "languageId" int`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "countryId" int`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "phone" nvarchar(256)`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "zip" nvarchar(256)`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "location" nvarchar(256)`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "houseNumber" nvarchar(256)`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "street" nvarchar(256)`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "surname" nvarchar(256)`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "firstname" nvarchar(256)`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "mail" nvarchar(256)`); + await queryRunner.query(`ALTER TABLE "dbo"."user_data" ADD "isMigrated" bit NOT NULL`); + await queryRunner.query(`ALTER TABLE "dbo"."user_data" ADD CONSTRAINT "DF_45a8d297955d5c3896ea84afa4b" DEFAULT 1 FOR "isMigrated"`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD CONSTRAINT "FK_19ab0596b1fab6a44be5491ffb4" FOREIGN KEY ("currencyId") REFERENCES "fiat"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD CONSTRAINT "FK_039c54821427d0adca4db8de366" FOREIGN KEY ("organizationCountryId") REFERENCES "country"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD CONSTRAINT "FK_0b294695467ceecc030f95461c1" FOREIGN KEY ("languageId") REFERENCES "language"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "dbo"."user" ADD CONSTRAINT "FK_4aaf6d02199282eb8d3931bff31" FOREIGN KEY ("countryId") REFERENCES "country"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } +} diff --git a/src/user/models/user-data/user-data.entity.ts b/src/user/models/user-data/user-data.entity.ts index 7d07cf000c..e919d2c896 100644 --- a/src/user/models/user-data/user-data.entity.ts +++ b/src/user/models/user-data/user-data.entity.ts @@ -40,10 +40,6 @@ export enum BlankType { @Entity() export class UserData extends IEntity { - // TODO: remove - @Column({ default: true }) - isMigrated: boolean; - @Column({ default: AccountType.PERSONAL, length: 256 }) accountType: AccountType; diff --git a/src/user/models/user/user.entity.ts b/src/user/models/user/user.entity.ts index 257ace17fc..ad5754ea49 100644 --- a/src/user/models/user/user.entity.ts +++ b/src/user/models/user/user.entity.ts @@ -137,59 +137,4 @@ export class User extends IEntity { @OneToMany(() => StakingRefReward, (reward) => reward.user) stakingRefRewards: StakingRefReward[]; - - // --- TO REMOVE --- // - @Column({ default: AccountType.PERSONAL, length: 256 }) - accountType: AccountType; - - @Column({ length: 256, nullable: true }) - mail: string; - - @Column({ length: 256, nullable: true }) - phone: string; - - @ManyToOne(() => Language) - language: Language; - - @ManyToOne(() => Fiat) - currency: Fiat; - - @Column({ length: 256, nullable: true }) - firstname: string; - - @Column({ length: 256, nullable: true }) - surname: string; - - @Column({ length: 256, nullable: true }) - street: string; - - @Column({ length: 256, nullable: true }) - houseNumber: string; - - @Column({ length: 256, nullable: true }) - location: string; - - @Column({ length: 256, nullable: true }) - zip: string; - - @ManyToOne(() => Country) - country: Country; - - @Column({ length: 256, nullable: true }) - organizationName: string; - - @Column({ length: 256, nullable: true }) - organizationStreet: string; - - @Column({ length: 256, nullable: true }) - organizationHouseNumber: string; - - @Column({ length: 256, nullable: true }) - organizationLocation: string; - - @Column({ length: 256, nullable: true }) - organizationZip: string; - - @ManyToOne(() => Country) - organizationCountry: Country; } diff --git a/src/user/models/user/user.service.spec.ts b/src/user/models/user/user.service.spec.ts index a0958e9138..68157a4d48 100644 --- a/src/user/models/user/user.service.spec.ts +++ b/src/user/models/user/user.service.spec.ts @@ -40,7 +40,7 @@ describe('UserService', () => { ) { jest .spyOn(userRepo, 'findOne') - .mockResolvedValue({ accountType, refFeePercent, buyFee, usedRef, cryptoFee } as User); + .mockResolvedValue({ refFeePercent, buyFee, usedRef, cryptoFee, userData: { accountType: accountType } } as User); } beforeEach(async () => { diff --git a/src/user/models/user/user.service.ts b/src/user/models/user/user.service.ts index 236444ec78..ba3221e6ec 100644 --- a/src/user/models/user/user.service.ts +++ b/src/user/models/user/user.service.ts @@ -221,15 +221,16 @@ export class UserService { // --- FEES --- // async getUserBuyFee(userId: number, annualVolume: number): Promise<{ fee: number; refBonus: number }> { - const { usedRef, accountType, buyFee } = await this.userRepo.findOne({ - select: ['id', 'usedRef', 'accountType', 'buyFee'], + const { usedRef, buyFee, userData } = await this.userRepo.findOne({ + select: ['id', 'usedRef', 'buyFee', 'userData'], where: { id: userId }, + relations: ['userData'], }); if (buyFee != null) return { fee: buyFee * 100, refBonus: 0 }; const baseFee = - accountType === AccountType.PERSONAL + userData.accountType === AccountType.PERSONAL ? // personal annualVolume < 5000 ? Config.buy.fee.private.base From 88ed86f50adbbed0163813fea095583d9412d0fa Mon Sep 17 00:00:00 2001 From: Kolibri1990 <66674482+Kolibri1990@users.noreply.github.com> Date: Mon, 24 Oct 2022 10:10:55 +0200 Subject: [PATCH 10/15] [DEV-759] Round statistic values to 2 decimals (#466) * [DEV-759] Round statistic values to 2 decimals * [DEV-759] Change decimal value to config default value --- src/statistic/statistic.service.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/statistic/statistic.service.ts b/src/statistic/statistic.service.ts index 7b8bdcafb1..1b9a59db51 100644 --- a/src/statistic/statistic.service.ts +++ b/src/statistic/statistic.service.ts @@ -1,11 +1,13 @@ import { Injectable } from '@nestjs/common'; import { Interval } from '@nestjs/schedule'; +import { Config } from 'src/config/config'; import { BuyService } from 'src/payment/models/buy/buy.service'; import { MasternodeService } from 'src/payment/models/masternode/masternode.service'; import { SellService } from 'src/payment/models/sell/sell.service'; import { StakingRewardService } from 'src/payment/models/staking-reward/staking-reward.service'; import { StakingService } from 'src/payment/models/staking/staking.service'; import { SettingService } from 'src/shared/models/setting/setting.service'; +import { Util } from 'src/shared/util'; import { UserService } from 'src/user/models/user/user.service'; @Injectable() @@ -29,12 +31,12 @@ export class StatisticService { try { this.statistic = { totalVolume: { - buy: await this.buyService.getTotalVolume(), - sell: await this.sellService.getTotalVolume(), + buy: Util.round(await this.buyService.getTotalVolume(), Config.defaultVolumeDecimal), + sell: Util.round(await this.sellService.getTotalVolume(), Config.defaultVolumeDecimal), }, totalRewards: { - staking: await this.stakingService.getTotalStakingRewards(), - ref: await this.userService.getTotalRefRewards(), + staking: Util.round(await this.stakingService.getTotalStakingRewards(), Config.defaultVolumeDecimal), + ref: Util.round(await this.userService.getTotalRefRewards(), Config.defaultVolumeDecimal), }, staking: { masternodes: await this.masternodeService.getActiveCount(), From 7706cc834b5ca7fd79a159c8c03188b25e4b8a0e Mon Sep 17 00:00:00 2001 From: Yannick1712 Date: Mon, 24 Oct 2022 11:34:13 +0200 Subject: [PATCH 11/15] [FIX] remove unused imports --- src/user/models/user/user.entity.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/user/models/user/user.entity.ts b/src/user/models/user/user.entity.ts index ad5754ea49..cf42fb3a0b 100644 --- a/src/user/models/user/user.entity.ts +++ b/src/user/models/user/user.entity.ts @@ -1,6 +1,4 @@ import { Buy } from 'src/payment/models/buy/buy.entity'; -import { Country } from 'src/shared/models/country/country.entity'; -import { Language } from 'src/shared/models/language/language.entity'; import { Sell } from 'src/payment/models/sell/sell.entity'; import { UserData } from 'src/user/models/user-data/user-data.entity'; import { Wallet } from 'src/user/models/wallet/wallet.entity'; @@ -8,8 +6,6 @@ import { Entity, Column, OneToMany, ManyToOne, Index } from 'typeorm'; import { UserRole } from 'src/shared/auth/user-role.enum'; import { Staking } from '../../../payment/models/staking/staking.entity'; import { IEntity } from 'src/shared/models/entity'; -import { AccountType } from '../user-data/account-type.enum'; -import { Fiat } from 'src/shared/models/fiat/fiat.entity'; import { RefReward } from 'src/payment/models/ref-reward/ref-reward.entity'; import { StakingRefReward } from 'src/payment/models/staking-ref-reward/staking-ref-reward.entity'; import { BankAccount } from 'src/payment/models/bank-account/bank-account.entity'; From 567e924ec2c180c645ff99cff8d0ec7649308a7c Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Mon, 24 Oct 2022 14:27:10 +0200 Subject: [PATCH 12/15] [DEV-902] add buyFiat Pending Mail (#468) * [DEV-902] add buyFiat Pending Mail * [DEV-902] Refactoring --- .../buy-crypto-notification.service.ts | 3 +- .../buy-fiat/buy-fiat-notification.service.ts | 47 +++++++++++++++++-- .../models/buy-fiat/buy-fiat.entity.ts | 7 +++ 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/payment/models/buy-crypto/services/buy-crypto-notification.service.ts b/src/payment/models/buy-crypto/services/buy-crypto-notification.service.ts index 31c55a6c0b..3cdd54c319 100644 --- a/src/payment/models/buy-crypto/services/buy-crypto-notification.service.ts +++ b/src/payment/models/buy-crypto/services/buy-crypto-notification.service.ts @@ -9,6 +9,7 @@ import { BlockchainExplorerUrls } from 'src/blockchain/shared/enums/blockchain.e import { AmlCheck } from '../enums/aml-check.enum'; import { I18nService } from 'nestjs-i18n'; import { AmlReason } from '../enums/aml-reason.enum'; +import { Config } from 'src/config/config'; @Injectable() export class BuyCryptoNotificationService { @@ -182,7 +183,7 @@ export class BuyCryptoNotificationService { userData: entity.user.userData, translationKey: entity.translationKey, translationParams: { - hashLink: `https://payment.dfx.swiss/kyc?code=${entity.user.userData.kycHash}`, + hashLink: `${Config.payment.url}/kyc?code=${entity.user.userData.kycHash}`, }, }, }); diff --git a/src/payment/models/buy-fiat/buy-fiat-notification.service.ts b/src/payment/models/buy-fiat/buy-fiat-notification.service.ts index 0ce657a4ad..8a9bf16da1 100644 --- a/src/payment/models/buy-fiat/buy-fiat-notification.service.ts +++ b/src/payment/models/buy-fiat/buy-fiat-notification.service.ts @@ -2,12 +2,14 @@ import { Injectable } from '@nestjs/common'; import { Interval } from '@nestjs/schedule'; import { I18nService } from 'nestjs-i18n'; import { BlockchainExplorerUrls } from 'src/blockchain/shared/enums/blockchain.enum'; +import { Config } from 'src/config/config'; import { MailType } from 'src/notification/enums'; import { NotificationService } from 'src/notification/services/notification.service'; import { Lock } from 'src/shared/lock'; import { Util } from 'src/shared/util'; -import { IsNull, Not } from 'typeorm'; +import { IsNull, Not, In } from 'typeorm'; import { AmlCheck } from '../buy-crypto/enums/aml-check.enum'; +import { AmlReason } from '../buy-crypto/enums/aml-reason.enum'; import { BuyFiatRepository } from './buy-fiat.repository'; @Injectable() @@ -28,6 +30,7 @@ export class BuyFiatNotificationService { await this.cryptoExchangedToFiat(); await this.fiatToBankTransferInitiated(); await this.paybackToAddressInitiated(); + await this.pendingBuyFiat(); this.lock.release(); } @@ -76,7 +79,7 @@ export class BuyFiatNotificationService { mail1SendDate: Not(IsNull()), mail2SendDate: IsNull(), outputAmount: Not(IsNull()), - amlCheck: Not(AmlCheck.FAIL), + amlCheck: AmlCheck.PASS, }, relations: ['sell', 'sell.user', 'sell.user.userData'], }); @@ -116,7 +119,7 @@ export class BuyFiatNotificationService { mail2SendDate: Not(IsNull()), mail3SendDate: IsNull(), bankTx: Not(IsNull()), - amlCheck: Not(AmlCheck.FAIL), + amlCheck: AmlCheck.PASS, }, relations: ['sell', 'sell.user', 'sell.user.userData'], }); @@ -194,4 +197,42 @@ export class BuyFiatNotificationService { } } } + + async pendingBuyFiat(): Promise { + const entities = await this.buyFiatRepo.find({ + where: { + mail2SendDate: IsNull(), + outputAmount: IsNull(), + amlReason: In([AmlReason.DAILY_LIMIT, AmlReason.ANNUAL_LIMIT]), + amlCheck: AmlCheck.PENDING, + }, + relations: ['sell', 'sell.user', 'sell.user.userData'], + }); + + entities.length > 0 && console.log(`Sending ${entities.length} 'pending' email(s)`); + + for (const entity of entities) { + try { + if (entity.sell.user.userData.mail) { + await this.notificationService.sendMail({ + type: MailType.USER, + input: { + userData: entity.sell.user.userData, + translationKey: + entity.amlReason === AmlReason.DAILY_LIMIT + ? 'mail.payment.pending.dailyLimit' + : 'mail.payment.pending.annualLimit', + translationParams: { + hashLink: `${Config.payment.url}/kyc?code=${entity.sell.user.userData.kycHash}`, + }, + }, + }); + } + + await this.buyFiatRepo.update(...entity.pendingMail()); + } catch (e) { + console.error(e); + } + } + } } diff --git a/src/payment/models/buy-fiat/buy-fiat.entity.ts b/src/payment/models/buy-fiat/buy-fiat.entity.ts index e395ef76a3..ccaf64cd86 100644 --- a/src/payment/models/buy-fiat/buy-fiat.entity.ts +++ b/src/payment/models/buy-fiat/buy-fiat.entity.ts @@ -130,6 +130,13 @@ export class BuyFiat extends IEntity { return [this.id, { recipientMail: this.recipientMail, mail1SendDate: this.mail1SendDate }]; } + pendingMail(): UpdateResult { + this.recipientMail = this.sell.user.userData.mail; + this.mail2SendDate = new Date(); + + return [this.id, { recipientMail: this.recipientMail, mail2SendDate: this.mail2SendDate }]; + } + cryptoExchangedToFiat(): UpdateResult { this.mail2SendDate = new Date(); From 92d1d0236415adf1f0e043959040e0496c936794 Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Mon, 24 Oct 2022 14:42:08 +0200 Subject: [PATCH 13/15] [DEV-946] add amlReason (#473) * [DEV-946] add amlReason * [DEV-946] add translation --- src/payment/models/buy-crypto/enums/aml-reason.enum.ts | 1 + src/shared/i18n/de/mail.json | 3 ++- src/shared/i18n/en/mail.json | 3 ++- src/shared/i18n/es/mail.json | 3 ++- src/shared/i18n/fr/mail.json | 3 ++- src/shared/i18n/it/mail.json | 3 ++- src/shared/i18n/pt/mail.json | 3 ++- 7 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/payment/models/buy-crypto/enums/aml-reason.enum.ts b/src/payment/models/buy-crypto/enums/aml-reason.enum.ts index 188e017abd..2e692af98f 100644 --- a/src/payment/models/buy-crypto/enums/aml-reason.enum.ts +++ b/src/payment/models/buy-crypto/enums/aml-reason.enum.ts @@ -7,4 +7,5 @@ export enum AmlReason { KYC_REJECTED = 'KycRejected', NAME_CHECK = 'NameCheck', OLKY_NO_KYC = 'OlkyNoKyc', + MIN_DEPOSIT_NOT_REACHED = 'MinDepositNotReached', } diff --git a/src/shared/i18n/de/mail.json b/src/shared/i18n/de/mail.json index 7a20c3bc07..9fa9d0a7c6 100644 --- a/src/shared/i18n/de/mail.json +++ b/src/shared/i18n/de/mail.json @@ -106,6 +106,7 @@ "KycRejected": "Deine Verifizierung wurde abgelehnt", "NameCheck": "Aus rechtlichen Gründen können dich als Kunden leider nicht bedienen", "AnnualLimit": "Du hast das Jahreslimit überschritten", - "OlkyNoKyc": "Für Nutzung der Olkypay Bank musst du dich vorher vollständig verifzieren (KYC)" + "OlkyNoKyc": "Für Nutzung der Olkypay Bank musst du dich vorher vollständig verifzieren (KYC)", + "MinDepositNotReached": "Die Mindesteinzahlungsgrenze wurde nicht erreicht" } } diff --git a/src/shared/i18n/en/mail.json b/src/shared/i18n/en/mail.json index 7f74c8f8eb..b6dc878690 100644 --- a/src/shared/i18n/en/mail.json +++ b/src/shared/i18n/en/mail.json @@ -105,6 +105,7 @@ "KycRejected": "Your KYC Request was rejected", "NameCheck": "For legal reasons we can not serve you as a customer", "AnnualLimit": "You exceeded your annual limit", - "OlkyNoKyc": "You have to complete KYC for DFX in order to use Olkypay Bank" + "OlkyNoKyc": "You have to complete KYC for DFX in order to use Olkypay Bank", + "MinDepositNotReached": "The minimum deposit limit was not reached" } } diff --git a/src/shared/i18n/es/mail.json b/src/shared/i18n/es/mail.json index b558056568..0c9df56ac5 100644 --- a/src/shared/i18n/es/mail.json +++ b/src/shared/i18n/es/mail.json @@ -105,6 +105,7 @@ "KycRejected": "Tu propuesta de registro KYC fue rechazada", "NameCheck": "Por razones legales, no podemos servirle como cliente", "AnnualLimit": "Excedes el límite anual", - "OlkyNoKyc": "Tienes que completar KYC para DFX para poder usar Olkypay Bank" + "OlkyNoKyc": "Tienes que completar KYC para DFX para poder usar Olkypay Bank", + "MinDepositNotReached": "La cantidad de depósito mínima no ha sido alcanzada" } } diff --git a/src/shared/i18n/fr/mail.json b/src/shared/i18n/fr/mail.json index 09bbe8e2ae..b5e5940fb9 100644 --- a/src/shared/i18n/fr/mail.json +++ b/src/shared/i18n/fr/mail.json @@ -105,6 +105,7 @@ "KycRejected": "Votre demande KYC a été rejetée", "NameCheck": "Pour des raisons légales, nous ne pouvons vous servir comme client", "AnnualLimit": "Vous avez dépassé votre limite annuelle", - "OlkyNoKyc": "Vous devez terminer le KYC de DFX pour utiliser Olkypay Bank" + "OlkyNoKyc": "Vous devez terminer le KYC de DFX pour utiliser Olkypay Bank", + "MinDepositNotReached": "La limite de dépôt minimum n'a pas été atteinte" } } diff --git a/src/shared/i18n/it/mail.json b/src/shared/i18n/it/mail.json index fd16bcb076..6eb17c3e6f 100644 --- a/src/shared/i18n/it/mail.json +++ b/src/shared/i18n/it/mail.json @@ -105,6 +105,7 @@ "KycRejected": "La tua richiesta di KYC è stata rigettata", "NameCheck": "Per ragioni legali non possiamo servirti come cliente", "AnnualLimit": "Hai superato il tuo limite annuale", - "OlkyNoKyc": "Devi completare la KYC per DFX per poter utilizzare Olkypay Bank" + "OlkyNoKyc": "Devi completare la KYC per DFX per poter utilizzare Olkypay Bank", + "MinDepositNotReached": "Il limite di deposito minimo non è stato raggiunto" } } diff --git a/src/shared/i18n/pt/mail.json b/src/shared/i18n/pt/mail.json index d9ea14f6f2..bb748341be 100644 --- a/src/shared/i18n/pt/mail.json +++ b/src/shared/i18n/pt/mail.json @@ -105,6 +105,7 @@ "KycRejected": "Your KYC Request was rejected", "NameCheck": "For legal reasons we can not serve you as a customer", "AnnualLimit": "You exceeded your annual limit", - "OlkyNoKyc": "You have to complete KYC for DFX in order to use Olkypay Bank" + "OlkyNoKyc": "You have to complete KYC for DFX in order to use Olkypay Bank", + "MinDepositNotReached": "The minimum deposit limit was not reached" } } From ed4ad2c956febfd5d378bc5d5083754eb8b4448c Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Mon, 24 Oct 2022 14:53:01 +0200 Subject: [PATCH 14/15] [DEV-903] pending mail olky (#471) * [DEV-903] add olky Pending Mail * [DEV-903] add translations --- .../models/buy-crypto/entities/buy-crypto.entity.ts | 1 + .../buy-crypto/services/buy-crypto-notification.service.ts | 3 ++- src/shared/i18n/de/mail.json | 5 +++++ src/shared/i18n/en/mail.json | 5 +++++ src/shared/i18n/es/mail.json | 5 +++++ src/shared/i18n/fr/mail.json | 5 +++++ src/shared/i18n/it/mail.json | 7 ++++++- src/shared/i18n/pt/mail.json | 5 +++++ 8 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/payment/models/buy-crypto/entities/buy-crypto.entity.ts b/src/payment/models/buy-crypto/entities/buy-crypto.entity.ts index 0977e0675b..b91b19096d 100644 --- a/src/payment/models/buy-crypto/entities/buy-crypto.entity.ts +++ b/src/payment/models/buy-crypto/entities/buy-crypto.entity.ts @@ -213,6 +213,7 @@ export class BuyCrypto extends IEntity { } else if (this.amlCheck === AmlCheck.PENDING) { if (this.amlReason === AmlReason.DAILY_LIMIT) return 'mail.payment.pending.dailyLimit'; if (this.amlReason === AmlReason.ANNUAL_LIMIT) return 'mail.payment.pending.annualLimit'; + if (this.amlReason === AmlReason.OLKY_NO_KYC) return 'mail.payment.pending.olkyNoKyc'; } else if (this.amlCheck === AmlCheck.FAIL) { return 'mail.payment.deposit.paybackInitiated'; } diff --git a/src/payment/models/buy-crypto/services/buy-crypto-notification.service.ts b/src/payment/models/buy-crypto/services/buy-crypto-notification.service.ts index 3cdd54c319..18ffeac6f8 100644 --- a/src/payment/models/buy-crypto/services/buy-crypto-notification.service.ts +++ b/src/payment/models/buy-crypto/services/buy-crypto-notification.service.ts @@ -33,6 +33,7 @@ export class BuyCryptoNotificationService { txId: Not(IsNull()), isComplete: true, batch: { status: BuyCryptoBatchStatus.COMPLETE }, + amlCheck: AmlCheck.PASS, }, relations: [ 'bankTx', @@ -158,7 +159,7 @@ export class BuyCryptoNotificationService { outputAmount: IsNull(), chargebackDate: IsNull(), chargebackBankTx: IsNull(), - amlReason: In([AmlReason.DAILY_LIMIT, AmlReason.ANNUAL_LIMIT]), + amlReason: In([AmlReason.DAILY_LIMIT, AmlReason.ANNUAL_LIMIT, AmlReason.OLKY_NO_KYC]), amlCheck: AmlCheck.PENDING, }, relations: [ diff --git a/src/shared/i18n/de/mail.json b/src/shared/i18n/de/mail.json index 9fa9d0a7c6..e30ea93678 100644 --- a/src/shared/i18n/de/mail.json +++ b/src/shared/i18n/de/mail.json @@ -49,6 +49,11 @@ "salutation": "Du hast dein jährliches Volumenlimit überschritten", "body": "
Bitte beantrage daher ein neues Jahreslimit
Sobald das neue Limit genehmigt wurde,
wird deine Transaktion weiter bearbeitet.
Limit erhöhen?
In der DFX App per \"Staking\" die Paymentseite aufrufen.
Bei \"Dein Limit\" auf den roten Pfeil gehen und
ein neues gewünschtes Limit beantragen.
Fragen? - Kontaktiere bitte support@dfx.swiss
Herzlichen Dank für dein entgegengebrachtes Vertrauen
Dein freundliches DFX Team
Bitcoiners by heart ♥️
", "title": "Jährliches Limit überschritten" + }, + "olkyNoKyc": { + "salutation": "Verifizierung für SEPA instant notwendig", + "body": "
Um unser Bankkonto in Luxemburg mit SEPA Instant Funktion zu verwenden, ist eine vollständige Verifizierung leider zwingend erforderlich.
Wir bitten dich daher mit diesem Link mittels Ausweis hier zu verifizieren
Alternativ kannst du auch über die DFX App auf deiner Paymentseite
bei \"Dein Limit\" auf den roten Pfeil klicken
und die Verifizierung starten.
Ohne Verifizierung kannst Du auch unser Bankkonto aus der CH weiterhin bis zu 1'000 EUR pro Tag ohne Verifizierung per Standard Sepa verwenden.
Solltest du die Verifizierung nicht vornehmen wollen, wird das Geld vollautomatisch nach 7 Tagen retourniert.
Bei Fragen stehen wir dir gerne unter support@dfx.swiss zur Seite.
Herzlichen Dank für dein entgegengebrachtes Vertrauen
Dein freundliches DFX Team
Bitcoiners by heart ♥️
", + "title": "Für Nutzung unserer Bank in Luxemburg ist eine Verifzierung notwendig" } }, "deposit": { diff --git a/src/shared/i18n/en/mail.json b/src/shared/i18n/en/mail.json index b6dc878690..4cea54c495 100644 --- a/src/shared/i18n/en/mail.json +++ b/src/shared/i18n/en/mail.json @@ -49,6 +49,11 @@ "salutation": "You have exceeded your yearly volume limit", "body": "
Please apply for a new annual limit.
Once the new limit has been approved,
we proceed with your transaction.
Increase the limit?
Open the payment page via \"Staking\" in the DFX App.
Go to the red arrow under \"Your limit\" and
request a new desired limit.
Questions? - Please contact support@dfx.swiss
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", "title": "Annual limit exceeded" + }, + "olkyNoKyc": { + "salutation": "Verification by KYC is required to use our bank in Luxembourg", + "body": "
In order to use our bank account in Luxembourg with the SEPA Instant function, full verification is unfortunately mandatory.
We therefore ask you to use this link here to perform KYC with your ID card
Alternatively, you can also use the DFX app on your payment page
to click on the red arrow next to \"Your limit\"
and start KYC
Without verification, you can continue to use our bank account from Switzerland up to EUR 1,000 per day without KYC via standard Sepa.
If you do not want to carry out the verification, the money will be returned fully automatically after 7 days.
If you have any questions, we are happy to help you at support@dfx.swiss.
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", + "title": "KYC required to use SEPA instant wire transfers" } }, "deposit": { diff --git a/src/shared/i18n/es/mail.json b/src/shared/i18n/es/mail.json index 0c9df56ac5..12223c7aa4 100644 --- a/src/shared/i18n/es/mail.json +++ b/src/shared/i18n/es/mail.json @@ -49,6 +49,11 @@ "salutation": "Has excedido tu límite de volumen anual", "body": "
Por favor, registra tu interés para un límite anual nuevo.
Una vez el nuevo límite anual de haya aprobado,
empezaremos con tu transacción.
¿Quieres incrementar el límite?
Abre la aplicación DFX a través de la página de pago en \"Staking\".
Ves a la flecha roja debajo de \"Tu límite\" y
pide un límite nuevo.
¿Alguna pregunta? Por favor ponte en contacto con support@dfx.swiss
Muchas gracias por tu confianza
Tu Equipo DFX
Bitcoiners by heart ♥️
", "title": "Has excedido el límite anual" + }, + "olkyNoKyc": { + "salutation": "La verificación de KYC es requerida para usar nuestro banco en Luxemburgo", + "body": "
Para poder usar nuestra cuenta bancaria en Luxemburgo con la función instantánea SEPA, la verificación completa es desafortunadamente obligatoria.
Por ello, les pedimos que usen este enlace aquí para ejecutar el KYC con tu tarjeta de identificación
Alternativamente, puedes usar la app DFX en la página de pagos
y apretar en la flecha roja al lado de \"Tu límite\"
y empiezas el KYC
Sin la verificación, puedes continuar usando nuestro banco en Suiza hasta 1,000 euros al día sin el KYC con transferencias SEPA ordinarias.
Si no quieres pasar el proceso de verificación, el dinero se devolverá en su totalidad a partir del séptimo día.
Si tienes alguna pregunta, nos encantaría ayudarte en support@dfx.swiss.
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", + "title": "Se requiere el registro KYC para usar las transferencias SEPA instantáneas" } }, "deposit": { diff --git a/src/shared/i18n/fr/mail.json b/src/shared/i18n/fr/mail.json index b5e5940fb9..31b3d2f602 100644 --- a/src/shared/i18n/fr/mail.json +++ b/src/shared/i18n/fr/mail.json @@ -49,6 +49,11 @@ "salutation": "Vous avez dépassé votre volume annuel", "body": "
Veuillez demander une nouvelle limite annuelle.
Nous traiterons votre transaction après
l'augmentation de votre limite.
Augmenter la limite ?
Ouvrez la page de paiement via le bouton \"Staking\" dans l'appli DFX.
Rendez-vous sur la flèche rouge sous \"Votre limite\" et
demandez une nouvelle limite.
Des questions ? Veuillez contacter support@dfx.swiss
Merci beaucoup pour votre confiance
Votre équipe DFX
Bitcoiners by heart ♥️
", "title": "Limite annuelle dépassée" + }, + "olkyNoKyc": { + "salutation": "Une vérification KYC est nécessaire pour utiliser notre banque luxembourgeoise", + "body": "
Pour utiliser notre compte bancaire au Luxembourg lors d'un virement instantané, une vérification complète doit être réalisée.
Nous vous demandons donc d'utiliser ce lien ici pour réaliser le KYC avec votre carte d'identité
Autrement, vous pouvez utiliser l'appli DFX et vous rendre sur votre page de paiement
pour cliquer sur la flèche rouge à côté de \"Votre limite\"
et démarrer le KYC
Sans vérification, vous pouvez continuer à utiliser notre compte bancaire suisse jusqu'à 1 000 euros par jour en SEPA standard.
Si vous ne réalisez pas la vérification, l'argent sera retourné automatiquement après 7 jours.
Si vous avez des questions, nous sommes ravis de vous aider sur support@dfx.swiss.
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", + "title": "Le KYC est nécessaire pour utiliser le virement SEPA instantané" } }, "deposit": { diff --git a/src/shared/i18n/it/mail.json b/src/shared/i18n/it/mail.json index 6eb17c3e6f..d5950adc64 100644 --- a/src/shared/i18n/it/mail.json +++ b/src/shared/i18n/it/mail.json @@ -49,6 +49,11 @@ "salutation": "Hai superato il tuo limite di volume annuo", "body": "
Per favore, richiedi un nuovo limite annuale.
Una volta che il nuovo limite è stato approvato,
procederemo con la tua transazione.
Aumentare il limite?
Apri la pagina di pagamento attraverso \"Staking\" nell'app DFX.
Vai sulla freccia rossa sotto \"Il tuo limite\" e
fai richiesta per un nuovo limite desiderato.
Domande? - Per favore, contatta support@dfx.swiss
Grazie mille per la tua fiducia
Tuo DFX team
Bitcoiners by heart ♥️
", "title": "Limite annuale superato" + }, + "olkyNoKyc": { + "salutation": "La verifica KYC è richiesta per usare la nostra banca in Lussemburgo", + "body": "
Per usare il nostro conto corrente in Lussemburgo con la funzionalità SEPA istantanea, una verifica completa è sfortunatamente obbligatoria.
Ti chiediamo quindi di usare questo link qui per eseguire la KYC col tuo documento d'identità
Alternativamente, puoi anche usare l'app DFX nella tua pagina dei pagamenti
per cliccare sulla freccia rossa vicino a \"Il tuo limite\"
ed avviare la KYC
Senza verifica, puoi continuare a usare il nostro conto corrente svizzero fino a 1000€ al giorno senza KYC utilizzando il SEPA standard.
Se non vuoi eseguire la verifica, i soldi saranno pienamente rimborsati in modo automatico dopo 7 giorni.
Se hai domande, siamo felici di aiutarti su support@dfx.swiss.
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", + "title": "KYC richiesta per utilizzare i trasferimenti immediati SEPA" } }, "deposit": { @@ -85,7 +90,7 @@ "title": "Trasferimento bancario effettuato" }, "paybackToAddressInitiated": { - "salutation": "I tuoi fondi sono stati rimborsati all’indirizzo del tuo portafoglio", + "salutation": "I tuoi fondi sono stati rimborsati all'indirizzo del tuo portafoglio", "body": "
Rimborsato{inputAmount} {inputAsset}
Indirizzo portafoglio{userAddressTrimmed}
Qui trovi l’ID della transazione con il tuo prelievo: Link
Ragioni per cui abbiamo rimborsato i tuoi fondi:
{returnReason}
Grazie mille per la tua fiducia
Tuo DFX team
Bitcoiners by heart ♥️
", "title": "I tuoi fondi sono stati rimborsati" } diff --git a/src/shared/i18n/pt/mail.json b/src/shared/i18n/pt/mail.json index bb748341be..f177be484b 100644 --- a/src/shared/i18n/pt/mail.json +++ b/src/shared/i18n/pt/mail.json @@ -49,6 +49,11 @@ "salutation": "You have exceeded your yearly volume limit", "body": "
Please apply for a new annual limit.
Once the new limit has been approved,
we proceed with your transaction.
Increase the limit?
Open the payment page via \"Staking\" in the DFX App.
Go to the red arrow under \"Your limit\" and
request a new desired limit.
Questions? - Please contact support@dfx.swiss
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", "title": "Annual limit exceeded" + }, + "olkyNoKyc": { + "salutation": "Verification by KYC is required to use our bank in Luxembourg", + "body": "
In order to use our bank account in Luxembourg with the SEPA Instant function, full verification is unfortunately mandatory.
We therefore ask you to use this link here to perform KYC with your ID card
Alternatively, you can also use the DFX app on your payment page
to click on the red arrow next to \"Your limit\"
and start KYC
Without verification, you can continue to use our bank account from Switzerland up to EUR 1,000 per day without KYC via standard Sepa.
If you do not want to carry out the verification, the money will be returned fully automatically after 7 days.
If you have any questions, we are happy to help you at support@dfx.swiss.
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", + "title": "KYC required to use SEPA instant wire transfers" } }, "deposit": { From 65bc7a90fffcc7cbb87a50a82bd701495fdf5af1 Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Mon, 24 Oct 2022 15:06:01 +0200 Subject: [PATCH 15/15] [DEV-904] add support Mail (#470) * [DEV-904] add support Mail * [DEV_904] add mail To Link --- src/shared/i18n/de/mail.json | 8 ++++---- src/shared/i18n/en/mail.json | 8 ++++---- src/shared/i18n/es/mail.json | 8 ++++---- src/shared/i18n/fr/mail.json | 8 ++++---- src/shared/i18n/it/mail.json | 8 ++++---- src/shared/i18n/pt/mail.json | 8 ++++---- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/shared/i18n/de/mail.json b/src/shared/i18n/de/mail.json index e30ea93678..c54bafc889 100644 --- a/src/shared/i18n/de/mail.json +++ b/src/shared/i18n/de/mail.json @@ -42,12 +42,12 @@ "pending": { "dailyLimit": { "salutation": "Du hast dein tägliches Volumenlimit überschritten", - "body": "
Bitte verifiziere dich, um dein Limit zu erhöhen.
Nach der vollständigen Verifizierung wird deine Transaktion weiter bearbeitet.
Limit erhöhen mit Verizfierung?
Klicke bitte hier um dich zu verifizieren.
Alternativ kannst du auch über die DFX App auf deiner Paymentseite
bei \"Dein Limit\" auf den roten Pfeil klicken
und die Verifizierung starten.
Herzlichen Dank für dein entgegengebrachtes Vertrauen
Dein freundliches DFX Team
Bitcoiners by heart ♥️
", + "body": "
Bitte verifiziere dich, um dein Limit zu erhöhen.
Nach der vollständigen Verifizierung wird deine Transaktion weiter bearbeitet.
Limit erhöhen mit Verizfierung?
Klicke bitte hier um dich zu verifizieren.
Alternativ kannst du auch über die DFX App auf deiner Paymentseite
bei \"Dein Limit\" auf den roten Pfeil klicken
und die Verifizierung starten.
Bei Fragen stehen wir dir gerne unter support@dfx.swiss zur Seite.
Herzlichen Dank für dein entgegengebrachtes Vertrauen
Dein freundliches DFX Team
Bitcoiners by heart ♥️
", "title": "Tägliches Limit überschritten" }, "annualLimit": { "salutation": "Du hast dein jährliches Volumenlimit überschritten", - "body": "
Bitte beantrage daher ein neues Jahreslimit
Sobald das neue Limit genehmigt wurde,
wird deine Transaktion weiter bearbeitet.
Limit erhöhen?
In der DFX App per \"Staking\" die Paymentseite aufrufen.
Bei \"Dein Limit\" auf den roten Pfeil gehen und
ein neues gewünschtes Limit beantragen.
Fragen? - Kontaktiere bitte support@dfx.swiss
Herzlichen Dank für dein entgegengebrachtes Vertrauen
Dein freundliches DFX Team
Bitcoiners by heart ♥️
", + "body": "
Bitte beantrage daher ein neues Jahreslimit
Sobald das neue Limit genehmigt wurde,
wird deine Transaktion weiter bearbeitet.
Limit erhöhen?
In der DFX App per \"Staking\" die Paymentseite aufrufen.
Bei \"Dein Limit\" auf den roten Pfeil gehen und
ein neues gewünschtes Limit beantragen.
Bei Fragen stehen wir dir gerne unter support@dfx.swiss zur Seite.
Herzlichen Dank für dein entgegengebrachtes Vertrauen
Dein freundliches DFX Team
Bitcoiners by heart ♥️
", "title": "Jährliches Limit überschritten" }, "olkyNoKyc": { @@ -69,7 +69,7 @@ }, "paybackInitiated":{ "salutation": "Dein Guthaben wurde an dein Bankkonto zurückerstattet", - "body": "
Erstattet{inputAmount} {inputAsset}
Bank Konto{userAddressTrimmed}
Verwendungszweck{returnTransactionLink}
Grund, warum wir zurückerstattet haben:
{returnReason}
Herzlichen Dank für dein entgegengebrachtes Vertrauen
Dein freundliches DFX Team
Bitcoiners by heart ♥️
", + "body": "
Erstattet{inputAmount} {inputAsset}
Bank Konto{userAddressTrimmed}
Verwendungszweck{returnTransactionLink}
Grund, warum wir zurückerstattet haben:
{returnReason}
Bei Fragen stehen wir dir gerne unter support@dfx.swiss zur Seite.
Herzlichen Dank für dein entgegengebrachtes Vertrauen
Dein freundliches DFX Team
Bitcoiners by heart ♥️
", "title": "Guthaben wurde erstattet" } }, @@ -91,7 +91,7 @@ }, "paybackToAddressInitiated": { "salutation": "Dein Guthaben wurde an deine Wallet-Adresse zurückerstattet", - "body": "
Erstattet{inputAmount} {inputAsset}
Wallet Adresse{userAddressTrimmed}
Dies ist die Transaktions-ID: Link
Grund, warum wir zurückerstattet haben:
{returnReason}
Herzlichen Dank für dein entgegengebrachtes Vertrauen
Dein freundliches DFX Team
Bitcoiners by heart ♥️
", + "body": "
Erstattet{inputAmount} {inputAsset}
Wallet Adresse{userAddressTrimmed}
Dies ist die Transaktions-ID: Link
Grund, warum wir zurückerstattet haben:
{returnReason}
Bei Fragen stehen wir dir gerne unter support@dfx.swiss zur Seite.
Herzlichen Dank für dein entgegengebrachtes Vertrauen
Dein freundliches DFX Team
Bitcoiners by heart ♥️
", "title": "Guthaben wurde erstattet" } diff --git a/src/shared/i18n/en/mail.json b/src/shared/i18n/en/mail.json index 4cea54c495..d02e656af2 100644 --- a/src/shared/i18n/en/mail.json +++ b/src/shared/i18n/en/mail.json @@ -42,12 +42,12 @@ "pending": { "dailyLimit": { "salutation": "You have exceeded your daily volume limit", - "body": "
Please verify yourself by KYC (know your customer) to increase your limit.
After a successful KYC we proceed with your transaction.
Increase the limit by KYC?
Click here to start the KYC process.
Alternatively, you can also use the DFX app on your payment page,
click on the red arrow next to \"Your limit\"
and start the verification.
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", + "body": "
Please verify yourself by KYC (know your customer) to increase your limit.
After a successful KYC we proceed with your transaction.
Increase the limit by KYC?
Click here to start the KYC process.
Alternatively, you can also use the DFX app on your payment page,
click on the red arrow next to \"Your limit\"
and start the verification.
If you have any questions, we are happy to help you at support@dfx.swiss.
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", "title": "Daily limit exceeded" }, "annualLimit": { "salutation": "You have exceeded your yearly volume limit", - "body": "
Please apply for a new annual limit.
Once the new limit has been approved,
we proceed with your transaction.
Increase the limit?
Open the payment page via \"Staking\" in the DFX App.
Go to the red arrow under \"Your limit\" and
request a new desired limit.
Questions? - Please contact support@dfx.swiss
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", + "body": "
Please apply for a new annual limit.
Once the new limit has been approved,
we proceed with your transaction.
Increase the limit?
Open the payment page via \"Staking\" in the DFX App.
Go to the red arrow under \"Your limit\" and
request a new desired limit.
If you have any questions, we are happy to help you at support@dfx.swiss.
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", "title": "Annual limit exceeded" }, "olkyNoKyc": { @@ -69,7 +69,7 @@ }, "paybackInitiated":{ "salutation": "Your funds have been reimbursed to your bank account", - "body": "
Reimbursed amount{inputAmount} {inputAsset}
Bank account{userAddressTrimmed}
Purpose of payment{returnTransactionLink}
Reason why we reimbursed your funds:
{returnReason}
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", + "body": "
Reimbursed amount{inputAmount} {inputAsset}
Bank account{userAddressTrimmed}
Purpose of payment{returnTransactionLink}
Reason why we reimbursed your funds:
{returnReason}
If you have any questions, we are happy to help you at support@dfx.swiss.
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", "title": "Funds have been reimbursed" } }, @@ -91,7 +91,7 @@ }, "paybackToAddressInitiated": { "salutation": "Your funds have been reimbursed to your wallet address", - "body": "
Reimbursed{inputAmount} {inputAsset}
Wallet address{userAddressTrimmed}
Here you can find the transaction ID with your withdrawal: Link
Reason why we reimbursed your funds:
{returnReason}
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", + "body": "
Reimbursed{inputAmount} {inputAsset}
Wallet address{userAddressTrimmed}
Here you can find the transaction ID with your withdrawal: Link
Reason why we reimbursed your funds:
{returnReason}
If you have any questions, we are happy to help you at support@dfx.swiss.
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", "title": "Funds have been reimbursed" } } diff --git a/src/shared/i18n/es/mail.json b/src/shared/i18n/es/mail.json index 12223c7aa4..8fd7b6f020 100644 --- a/src/shared/i18n/es/mail.json +++ b/src/shared/i18n/es/mail.json @@ -42,12 +42,12 @@ "pending": { "dailyLimit": { "salutation": "Has excedido el límite de volumen diario", - "body": "
Por favor, verificate a través de KYC (conoce a tu cliente) para incrementar tu límite.
Después de que pases el KYC empezaremos con tu transacción.
¿Quieres incrementar el límite a través del KYC?
Aprieta aquí para empezar el proceso KYC.
Alternativamente, puedes usar la app DFX en la página de pago,
apretar la flecha roja al lado de \"Tu límite\"
y empieza la verificación.
Muchas gracias por tu confianza
Tu Equipo DFX
Bitcoiners by heart ♥️
", + "body": "
Por favor, verificate a través de KYC (conoce a tu cliente) para incrementar tu límite.
Después de que pases el KYC empezaremos con tu transacción.
¿Quieres incrementar el límite a través del KYC?
Aprieta aquí para empezar el proceso KYC.
Alternativamente, puedes usar la app DFX en la página de pago,
apretar la flecha roja al lado de \"Tu límite\"
y empieza la verificación.
Si tienes alguna pregunta, nos encantaría ayudarte en support@dfx.swiss.
Muchas gracias por tu confianza
Tu Equipo DFX
Bitcoiners by heart ♥️
", "title": "Límite diario excedido" }, "annualLimit": { "salutation": "Has excedido tu límite de volumen anual", - "body": "
Por favor, registra tu interés para un límite anual nuevo.
Una vez el nuevo límite anual de haya aprobado,
empezaremos con tu transacción.
¿Quieres incrementar el límite?
Abre la aplicación DFX a través de la página de pago en \"Staking\".
Ves a la flecha roja debajo de \"Tu límite\" y
pide un límite nuevo.
¿Alguna pregunta? Por favor ponte en contacto con support@dfx.swiss
Muchas gracias por tu confianza
Tu Equipo DFX
Bitcoiners by heart ♥️
", + "body": "
Por favor, registra tu interés para un límite anual nuevo.
Una vez el nuevo límite anual de haya aprobado,
empezaremos con tu transacción.
¿Quieres incrementar el límite?
Abre la aplicación DFX a través de la página de pago en \"Staking\".
Ves a la flecha roja debajo de \"Tu límite\" y
pide un límite nuevo.
Si tienes alguna pregunta, nos encantaría ayudarte en support@dfx.swiss.
Muchas gracias por tu confianza
Tu Equipo DFX
Bitcoiners by heart ♥️
", "title": "Has excedido el límite anual" }, "olkyNoKyc": { @@ -69,7 +69,7 @@ }, "paybackInitiated":{ "salutation": "Tus fondos se han reembolsado a tu cuenta bancaria", - "body": "
Cantidad reembolsada{inputAmount} {inputAsset}
Cuenta bancaria{userAddressTrimmed}
Propósito de pago{returnTransactionLink}
Motivo por el cual tus fondos se reembolsaron:
{returnReason}
Muchas gracias por tu confianza
Tu Equipo DFX
Bitcoiners by heart ♥️
", + "body": "
Cantidad reembolsada{inputAmount} {inputAsset}
Cuenta bancaria{userAddressTrimmed}
Propósito de pago{returnTransactionLink}
Motivo por el cual tus fondos se reembolsaron:
{returnReason}
Si tienes alguna pregunta, nos encantaría ayudarte en support@dfx.swiss.
Muchas gracias por tu confianza
Tu Equipo DFX
Bitcoiners by heart ♥️
", "title": "Los fondos han sido reembolsados" } }, @@ -91,7 +91,7 @@ }, "paybackToAddressInitiated": { "salutation": "Tus fondos han sido reembolsados a la dirección de tu billetera", - "body": "
Reembolsados{inputAmount} {inputAsset}
Dirección de la billetera{userAddressTrimmed}
Aquí puedes encontrar el identificador de tu retiro: Link
Motivo por el cual tus fondos se reembolsaron:
{returnReason}
Muchas gracias por tu confianza
Tu Equipo DFX
Bitcoiners by heart ♥️
", + "body": "
Reembolsados{inputAmount} {inputAsset}
Dirección de la billetera{userAddressTrimmed}
Aquí puedes encontrar el identificador de tu retiro: Link
Motivo por el cual tus fondos se reembolsaron:
{returnReason}
Si tienes alguna pregunta, nos encantaría ayudarte en support@dfx.swiss.
Muchas gracias por tu confianza
Tu Equipo DFX
Bitcoiners by heart ♥️
", "title": "Los fondos han sido reembolsados" } } diff --git a/src/shared/i18n/fr/mail.json b/src/shared/i18n/fr/mail.json index 31b3d2f602..e78ce04d2f 100644 --- a/src/shared/i18n/fr/mail.json +++ b/src/shared/i18n/fr/mail.json @@ -42,12 +42,12 @@ "pending": { "dailyLimit": { "salutation": "Vous avez dépassé votre volume journalier", - "body": "
Veuillez vous faire vérifier par le KYC (know your customer) pour augmenter votre limite.
Nous traiterons votre transaction après la réussite du KYC.
Augmenter la limite par un KYC ?
Cliquer here pour commencer la procédure de KYC.
Autrement, vous pouvez aussi utiliser l'appli DFX sur votre page de paiement,
cliquer sur la flèche rouge en face de \"Votre limite\"
et commencer la vérification.
Merci beaucoup pour votre confiance
Votre équipe DFX
Bitcoiners by heart ♥️
", + "body": "
Veuillez vous faire vérifier par le KYC (know your customer) pour augmenter votre limite.
Nous traiterons votre transaction après la réussite du KYC.
Augmenter la limite par un KYC ?
Cliquer here pour commencer la procédure de KYC.
Autrement, vous pouvez aussi utiliser l'appli DFX sur votre page de paiement,
cliquer sur la flèche rouge en face de \"Votre limite\"
et commencer la vérification.
Si vous avez des questions, nous sommes ravis de vous aider sur support@dfx.swiss.
Merci beaucoup pour votre confiance
Votre équipe DFX
Bitcoiners by heart ♥️
", "title": "Limite journalière dépassée" }, "annualLimit": { "salutation": "Vous avez dépassé votre volume annuel", - "body": "
Veuillez demander une nouvelle limite annuelle.
Nous traiterons votre transaction après
l'augmentation de votre limite.
Augmenter la limite ?
Ouvrez la page de paiement via le bouton \"Staking\" dans l'appli DFX.
Rendez-vous sur la flèche rouge sous \"Votre limite\" et
demandez une nouvelle limite.
Des questions ? Veuillez contacter support@dfx.swiss
Merci beaucoup pour votre confiance
Votre équipe DFX
Bitcoiners by heart ♥️
", + "body": "
Veuillez demander une nouvelle limite annuelle.
Nous traiterons votre transaction après
l'augmentation de votre limite.
Augmenter la limite ?
Ouvrez la page de paiement via le bouton \"Staking\" dans l'appli DFX.
Rendez-vous sur la flèche rouge sous \"Votre limite\" et
demandez une nouvelle limite.
Si vous avez des questions, nous sommes ravis de vous aider sur support@dfx.swiss.
Merci beaucoup pour votre confiance
Votre équipe DFX
Bitcoiners by heart ♥️
", "title": "Limite annuelle dépassée" }, "olkyNoKyc": { @@ -69,7 +69,7 @@ }, "paybackInitiated":{ "salutation": "Vos fonds ont été retournés à votre banque", - "body": "
Montant retourné{inputAmount} {inputAsset}
Compte bancaire{userAddressTrimmed}
Motif du paiement{returnTransactionLink}
Motif du remboursement:
{returnReason}
Merci beaucoup pour votre confiance
Votre équipe DFX
Bitcoiners by heart ♥️
", + "body": "
Montant retourné{inputAmount} {inputAsset}
Compte bancaire{userAddressTrimmed}
Motif du paiement{returnTransactionLink}
Motif du remboursement:
{returnReason}
Si vous avez des questions, nous sommes ravis de vous aider sur support@dfx.swiss.
Merci beaucoup pour votre confiance
Votre équipe DFX
Bitcoiners by heart ♥️
", "title": "Remboursement des fonds" } }, @@ -91,7 +91,7 @@ }, "paybackToAddressInitiated": { "salutation": "Vos fonds ont été remboursés sur votre adresse de wallet", - "body": "
Remboursés{inputAmount} {inputAsset}
Adresse de wallet{userAddressTrimmed}
Vous trouverez l'ID de la transaction: Link
Motif du remboursement:
{returnReason}
Merci beaucoup pour votre confiance
Votre équipe DFX
Bitcoiners by heart ♥️
", + "body": "
Remboursés{inputAmount} {inputAsset}
Adresse de wallet{userAddressTrimmed}
Vous trouverez l'ID de la transaction: Link
Motif du remboursement:
{returnReason}
Si vous avez des questions, nous sommes ravis de vous aider sur support@dfx.swiss.
Merci beaucoup pour votre confiance
Votre équipe DFX
Bitcoiners by heart ♥️
", "title": "Remboursement des fonds" } } diff --git a/src/shared/i18n/it/mail.json b/src/shared/i18n/it/mail.json index d5950adc64..0fac9d7594 100644 --- a/src/shared/i18n/it/mail.json +++ b/src/shared/i18n/it/mail.json @@ -42,12 +42,12 @@ "pending": { "dailyLimit": { "salutation": "Hai superato il tuo limite di volume giornaliero", - "body": "
Per favore, completa la verifica KYC (conosci il tuo cliente) per incrementare il tuo limite.
Dopo una verifica KYC eseguita con successo procederemo con le tue transazioni.
Incrementare il limite con la KYC?
Clicca qui per avviare il processo di KYC.
Alternativamente, puoi anche usare l'app DFX nella tua pagina di pagamento,
cliccare sulla freccia rossa vicino a \"Il tuo limite\"
e avviare la verifica.
Grazie mille per la tua fiducia
Tuo DFX team
Bitcoiners by heart ♥️
", + "body": "
Per favore, completa la verifica KYC (conosci il tuo cliente) per incrementare il tuo limite.
Dopo una verifica KYC eseguita con successo procederemo con le tue transazioni.
Incrementare il limite con la KYC?
Clicca qui per avviare il processo di KYC.
Alternativamente, puoi anche usare l'app DFX nella tua pagina di pagamento,
cliccare sulla freccia rossa vicino a \"Il tuo limite\"
e avviare la verifica.
Se hai domande, siamo felici di aiutarti su support@dfx.swiss.
Grazie mille per la tua fiducia
Tuo DFX team
Bitcoiners by heart ♥️
", "title": "Limite giornaliero superato" }, "annualLimit": { "salutation": "Hai superato il tuo limite di volume annuo", - "body": "
Per favore, richiedi un nuovo limite annuale.
Una volta che il nuovo limite è stato approvato,
procederemo con la tua transazione.
Aumentare il limite?
Apri la pagina di pagamento attraverso \"Staking\" nell'app DFX.
Vai sulla freccia rossa sotto \"Il tuo limite\" e
fai richiesta per un nuovo limite desiderato.
Domande? - Per favore, contatta support@dfx.swiss
Grazie mille per la tua fiducia
Tuo DFX team
Bitcoiners by heart ♥️
", + "body": "
Per favore, richiedi un nuovo limite annuale.
Una volta che il nuovo limite è stato approvato,
procederemo con la tua transazione.
Aumentare il limite?
Apri la pagina di pagamento attraverso \"Staking\" nell'app DFX.
Vai sulla freccia rossa sotto \"Il tuo limite\" e
fai richiesta per un nuovo limite desiderato.
Se hai domande, siamo felici di aiutarti su support@dfx.swiss.
Grazie mille per la tua fiducia
Tuo DFX team
Bitcoiners by heart ♥️
", "title": "Limite annuale superato" }, "olkyNoKyc": { @@ -69,7 +69,7 @@ }, "paybackInitiated":{ "salutation": "I tuoi fondi sono stati rimborsati sul tuo conto corrente", - "body": "
Importo rimborsato{inputAmount} {inputAsset}
Conto corrente{userAddressTrimmed}
Causale di pagamento{returnTransactionLink}
Ragioni per cui abbiamo rimborsato i tuoi fondi:
{returnReason}
Grazie mille per la tua fiducia
Tuo DFX team
Bitcoiners by heart ♥️
", + "body": "
Importo rimborsato{inputAmount} {inputAsset}
Conto corrente{userAddressTrimmed}
Causale di pagamento{returnTransactionLink}
Ragioni per cui abbiamo rimborsato i tuoi fondi:
{returnReason}
Se hai domande, siamo felici di aiutarti su support@dfx.swiss.
Grazie mille per la tua fiducia
Tuo DFX team
Bitcoiners by heart ♥️
", "title": "Trasferimento bancario effettuato" } }, @@ -91,7 +91,7 @@ }, "paybackToAddressInitiated": { "salutation": "I tuoi fondi sono stati rimborsati all'indirizzo del tuo portafoglio", - "body": "
Rimborsato{inputAmount} {inputAsset}
Indirizzo portafoglio{userAddressTrimmed}
Qui trovi l’ID della transazione con il tuo prelievo: Link
Ragioni per cui abbiamo rimborsato i tuoi fondi:
{returnReason}
Grazie mille per la tua fiducia
Tuo DFX team
Bitcoiners by heart ♥️
", + "body": "
Rimborsato{inputAmount} {inputAsset}
Indirizzo portafoglio{userAddressTrimmed}
Qui trovi l'ID della transazione con il tuo prelievo: Link
Ragioni per cui abbiamo rimborsato i tuoi fondi:
{returnReason}
Se hai domande, siamo felici di aiutarti su support@dfx.swiss.
Grazie mille per la tua fiducia
Tuo DFX team
Bitcoiners by heart ♥️
", "title": "I tuoi fondi sono stati rimborsati" } } diff --git a/src/shared/i18n/pt/mail.json b/src/shared/i18n/pt/mail.json index f177be484b..4b36b2856e 100644 --- a/src/shared/i18n/pt/mail.json +++ b/src/shared/i18n/pt/mail.json @@ -42,12 +42,12 @@ "pending": { "dailyLimit": { "salutation": "You have exceeded your daily volume limit", - "body": "
Please verify yourself by KYC (know your customer) to increase your limit.
After a successful KYC we proceed with your transaction.
Increase the limit by KYC?
Click here to start the KYC process.
Alternatively, you can also use the DFX app on your payment page,
click on the red arrow next to \"Your limit\"
and start the verification.
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", + "body": "
Please verify yourself by KYC (know your customer) to increase your limit.
After a successful KYC we proceed with your transaction.
Increase the limit by KYC?
Click here to start the KYC process.
Alternatively, you can also use the DFX app on your payment page,
click on the red arrow next to \"Your limit\"
and start the verification.
If you have any questions, we are happy to help you at support@dfx.swiss.
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", "title": "Daily limit exceeded" }, "annualLimit": { "salutation": "You have exceeded your yearly volume limit", - "body": "
Please apply for a new annual limit.
Once the new limit has been approved,
we proceed with your transaction.
Increase the limit?
Open the payment page via \"Staking\" in the DFX App.
Go to the red arrow under \"Your limit\" and
request a new desired limit.
Questions? - Please contact support@dfx.swiss
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", + "body": "
Please apply for a new annual limit.
Once the new limit has been approved,
we proceed with your transaction.
Increase the limit?
Open the payment page via \"Staking\" in the DFX App.
Go to the red arrow under \"Your limit\" and
request a new desired limit.
If you have any questions, we are happy to help you at support@dfx.swiss.
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", "title": "Annual limit exceeded" }, "olkyNoKyc": { @@ -69,7 +69,7 @@ }, "paybackInitiated":{ "salutation": "Your funds have been reimbursed to your bank account", - "body": "
Reimbursed amount{inputAmount} {inputAsset}
Bank account{userAddressTrimmed}
Purpose of payment{returnTransactionLink}
Reason why we reimbursed your funds:
{returnReason}
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", + "body": "
Reimbursed amount{inputAmount} {inputAsset}
Bank account{userAddressTrimmed}
Purpose of payment{returnTransactionLink}
Reason why we reimbursed your funds:
{returnReason}
If you have any questions, we are happy to help you at support@dfx.swiss.
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", "title": "Funds have been reimbursed" } }, @@ -91,7 +91,7 @@ }, "paybackToAddressInitiated": { "salutation": "Your funds have been reimbursed to your wallet address", - "body": "
Reimbursed{inputAmount} {inputAsset}
Wallet address{userAddressTrimmed}
Here you can find the transaction ID with your withdrawal: Link
Reason why we reimbursed your funds:
{returnReason}
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", + "body": "
Reimbursed{inputAmount} {inputAsset}
Wallet address{userAddressTrimmed}
Here you can find the transaction ID with your withdrawal: Link
Reason why we reimbursed your funds:
{returnReason}
If you have any questions, we are happy to help you at support@dfx.swiss.
Thank you very much for your trust
Your DFX Team
Bitcoiners by heart ♥️
", "title": "Funds have been reimbursed" } }