Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for transaction statement download (FRONT-1155) #919

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ export const MembershipDetailEditor = ({
.otherwise(() => null)}

<View style={styles.buttonGroup}>
<LakeButtonGroup>
<LakeButtonGroup paddingBottom={0}>
{match(editingAccountMembership.statusInfo.__typename)
.with(
"AccountMembershipBindingUserErrorStatusInfo",
Expand Down
76 changes: 71 additions & 5 deletions clients/banking/src/components/TransactionDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Result } from "@swan-io/boxed";
import { useQuery } from "@swan-io/graphql-client";
import { Future, Option, Result } from "@swan-io/boxed";
import { useDeferredQuery, useMutation, useQuery } from "@swan-io/graphql-client";
import { Box } from "@swan-io/lake/src/components/Box";
import { Icon } from "@swan-io/lake/src/components/Icon";
import { LakeAlert } from "@swan-io/lake/src/components/LakeAlert";
import { LakeButton, LakeButtonGroup } from "@swan-io/lake/src/components/LakeButton";
import { LakeHeading } from "@swan-io/lake/src/components/LakeHeading";
import { LakeLabel } from "@swan-io/lake/src/components/LakeLabel";
import { LakeText } from "@swan-io/lake/src/components/LakeText";
Expand All @@ -19,22 +20,29 @@ import { TabView } from "@swan-io/lake/src/components/TabView";
import { Tag } from "@swan-io/lake/src/components/Tag";
import { Tile } from "@swan-io/lake/src/components/Tile";
import { commonStyles } from "@swan-io/lake/src/constants/commonStyles";
import { colors, radii, spacings } from "@swan-io/lake/src/constants/design";
import { backgroundColor, colors, radii, spacings } from "@swan-io/lake/src/constants/design";
import { showToast } from "@swan-io/lake/src/state/toasts";
import { filterRejectionsToResult } from "@swan-io/lake/src/utils/gql";
import {
isNotEmpty,
isNotNullish,
isNotNullishOrEmpty,
isNullish,
} from "@swan-io/lake/src/utils/nullish";
import { getCountryByCCA3, isCountryCCA3 } from "@swan-io/shared-business/src/constants/countries";
import { translateError } from "@swan-io/shared-business/src/utils/i18n";
import { printIbanFormat } from "@swan-io/shared-business/src/utils/validation";
import { printFormat } from "iban";
import { useState } from "react";
import { Image, StyleSheet, View } from "react-native";
import { P, match } from "ts-pattern";
import { TransactionDocument } from "../graphql/partner";
import {
GenerateTransactionStatementDocument,
TransactionDocument,
TransactionStatementDocument,
} from "../graphql/partner";
import { NotFoundPage } from "../pages/NotFoundPage";
import { formatCurrency, formatDateTime, t } from "../utils/i18n";
import { formatCurrency, formatDateTime, locale, t } from "../utils/i18n";
import { Router } from "../utils/routes";
import {
getFeesDescription,
Expand Down Expand Up @@ -89,6 +97,11 @@ const styles = StyleSheet.create({
aspectRatio: "1 / 1",
display: "flex",
},
buttonGroup: {
backgroundColor: backgroundColor.default,
position: "sticky",
bottom: 0,
},
});

const formatMaskedPan = (value: string) => value.replace(/X/g, "•").replace(/(.{4})(?!$)/g, "$1 ");
Expand All @@ -114,6 +127,43 @@ export const TransactionDetail = ({
const beneficiariesEnabled = useTgglFlag("beneficiaries").getOr(false);
const suspense = useIsSuspendable();

const [generateTransactionStatement] = useMutation(GenerateTransactionStatementDocument);
const [, { query: queryTransactionStatement }] = useDeferredQuery(TransactionStatementDocument);

const [isGeneratingStatement, setIsGeneratingStatement] = useState(false);

const generateStatement = () => {
setIsGeneratingStatement(true);
generateTransactionStatement({ input: { transactionId, language: locale.language } })
.mapOk(data => data.generateTransactionStatement)
.mapOkToResult(filterRejectionsToResult)
.mapOkToResult(data => Option.fromNullable(data.transactionStatement).toResult(new Error()))
.flatMapOk(({ id }) =>
Future.retry(
() =>
Future.wait(1000).flatMap(() =>
queryTransactionStatement({ id }).mapOkToResult(data =>
match(data.transactionStatement)
.with(
{
statusInfo: {
__typename: "GeneratedTransactionStatementStatusInfo",
url: P.select(),
},
},
url => Result.Ok(url),
)
.otherwise(value => Result.Error(value)),
),
),
{ max: 20 },
),
)
.tap(() => setIsGeneratingStatement(false))
.tapOk(url => window.location.replace(url))
.tapError(error => showToast({ variant: "error", title: translateError(error), error }));
};

const [data] = useQuery(
TransactionDocument,
{
Expand Down Expand Up @@ -864,6 +914,22 @@ export const TransactionDetail = ({
text={truncateTransactionId(transaction.id)}
/>
</ReadOnlyFieldList>

{transaction.statementCanBeGenerated === true ? (
<View style={styles.buttonGroup}>
<LakeButtonGroup paddingBottom={0}>
<LakeButton
size="small"
color="current"
icon="arrow-download-filled"
loading={isGeneratingStatement}
onPress={() => generateStatement()}
>
{t("transaction.transactionConfirmation")}
</LakeButton>
</LakeButtonGroup>
</View>
) : null}
</ScrollView>
))
.with("beneficiary", () => (
Expand Down
27 changes: 27 additions & 0 deletions clients/banking/src/graphql/partner.gql
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ fragment TransactionDetails on Transaction {
counterparty
reference
externalReference
statementCanBeGenerated
payment {
id
createdAt
Expand Down Expand Up @@ -1505,6 +1506,32 @@ query TransactionListPage(
}
}

fragment TransactionStatement on TransactionStatement {
id
statusInfo {
status
... on GeneratedTransactionStatementStatusInfo {
url
}
}
}

mutation GenerateTransactionStatement($input: GenerateTransactionStatementInput!) {
generateTransactionStatement(input: $input) {
... on GenerateTransactionStatementSuccessPayload {
transactionStatement {
...TransactionStatement
}
}
}
}

query TransactionStatement($id: ID!) {
transactionStatement(id: $id) {
...TransactionStatement
}
}

query BeneficiariesList(
$accountId: ID!
$first: Int!
Expand Down
1 change: 1 addition & 0 deletions clients/banking/src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,7 @@
"transaction.tabs.beneficiary": "Empfänger",
"transaction.tabs.details": "Details",
"transaction.tabs.merchantInfo": "Händlerinfo",
"transaction.transactionConfirmation": "Transaktionsbestätigung",
"transaction.website": "Webseite",
"transactionDetail.internationalCreditTransfer.BIC": "Bankleitzahl (BIC/SWIFT)",
"transactionDetail.internationalCreditTransfer.IBAN": "Empfänger-IBAN",
Expand Down
1 change: 1 addition & 0 deletions clients/banking/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,7 @@
"transaction.tabs.beneficiary": "Beneficiary",
"transaction.tabs.details": "Details",
"transaction.tabs.merchantInfo": "Merchant info",
"transaction.transactionConfirmation": "Transaction confirmation",
"transaction.website": "Website",
"transactionDetail.internationalCreditTransfer.BIC": "Bank code (BIC/SWIFT)",
"transactionDetail.internationalCreditTransfer.IBAN": "Recipient's IBAN",
Expand Down
1 change: 1 addition & 0 deletions clients/banking/src/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,7 @@
"transaction.tabs.beneficiary": "Beneficiario",
"transaction.tabs.details": "Detalles",
"transaction.tabs.merchantInfo": "Información del comerciante",
"transaction.transactionConfirmation": "Confirmación de transacción",
"transaction.website": "Sitio web",
"transactionDetail.internationalCreditTransfer.BIC": "Código bancario (BIC/SWIFT)",
"transactionDetail.internationalCreditTransfer.IBAN": "IBAN del destinatario",
Expand Down
1 change: 1 addition & 0 deletions clients/banking/src/locales/fi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,7 @@
"transaction.tabs.beneficiary": "Vastaanottaja",
"transaction.tabs.details": "Yksityiskohdat",
"transaction.tabs.merchantInfo": "Kauppiaan tiedot",
"transaction.transactionConfirmation": "Tapahtuman vahvistus",
"transaction.website": "Verkkosivusto",
"transactionDetail.internationalCreditTransfer.BIC": "Pankin koodi (BIC/SWIFT)",
"transactionDetail.internationalCreditTransfer.IBAN": "Vastaanottajan IBAN",
Expand Down
1 change: 1 addition & 0 deletions clients/banking/src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,7 @@
"transaction.tabs.beneficiary": "Bénéficiaire",
"transaction.tabs.details": "Détails",
"transaction.tabs.merchantInfo": "Infos commerçant",
"transaction.transactionConfirmation": "Confirmation de la transaction",
"transaction.website": "Site web",
"transactionDetail.internationalCreditTransfer.BIC": "Code bancaire (BIC/SWIFT)",
"transactionDetail.internationalCreditTransfer.IBAN": "IBAN du bénéficiaire",
Expand Down
1 change: 1 addition & 0 deletions clients/banking/src/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,7 @@
"transaction.tabs.beneficiary": "Beneficiario",
"transaction.tabs.details": "Dettagli",
"transaction.tabs.merchantInfo": "Informazioni sul commerciante",
"transaction.transactionConfirmation": "Conferma transazione",
"transaction.website": "Sito web",
"transactionDetail.internationalCreditTransfer.BIC": "Codice BIC/SWIFT della banca",
"transactionDetail.internationalCreditTransfer.IBAN": "IBAN del destinatario",
Expand Down
1 change: 1 addition & 0 deletions clients/banking/src/locales/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,7 @@
"transaction.tabs.beneficiary": "Begunstigde",
"transaction.tabs.details": "Gegevens",
"transaction.tabs.merchantInfo": "Handelaarsinfo",
"transaction.transactionConfirmation": "Transactiebevestiging",
"transaction.website": "Website",
"transactionDetail.internationalCreditTransfer.BIC": "Bankcode (BIC/SWIFT)",
"transactionDetail.internationalCreditTransfer.IBAN": "IBAN nummer van de ontvanger",
Expand Down
1 change: 1 addition & 0 deletions clients/banking/src/locales/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,7 @@
"transaction.tabs.beneficiary": "Beneficiário",
"transaction.tabs.details": "Detalhes",
"transaction.tabs.merchantInfo": "Informações do comerciante",
"transaction.transactionConfirmation": "Confirmação da transação",
"transaction.website": "Website",
"transactionDetail.internationalCreditTransfer.BIC": "Código do banco (BIC/SWIFT)",
"transactionDetail.internationalCreditTransfer.IBAN": "IBAN do beneficiário",
Expand Down
68 changes: 45 additions & 23 deletions scripts/graphql/dist/partner-admin-schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,7 @@ type AccountHolderVerifiedVerificationStatusInfo implements AccountHolderVerific
type AccountHolderWaitingForInformationVerificationStatusInfo implements AccountHolderVerificationStatusInfo {
"""Verification Status of the account holder."""
status: VerificationStatus!
verificationRequirements: [VerificationRequirement!]!

"""
ISO Date string at which the account holder status was set to WaitingForInformation
Expand Down Expand Up @@ -3459,22 +3460,6 @@ type IdentificationLevels {
expert: Boolean!
}

"""Sandbox user identification levels input payload."""
input IdentificationLevelsInput {
"""Identity verified by an expert (Ubble-like)"""
expert: Boolean!

"""
Identity verified by an expert with a Remote Identity Verification Service
"""
PVID: Boolean!

"""
Identity verified by an expert and a Qualified Electronic Signature has been done
"""
QES: Boolean!
}

"""
The status and results associated to the available identification processes
"""
Expand Down Expand Up @@ -5944,6 +5929,15 @@ enum PaymentMandateScheme {

"""Internal Direct Debit B2B"""
InternalDirectDebitB2b

"""Card Visa"""
CardVisa

"""Card Mastercard"""
CardMastercard

"""Card Cartes Bancaires"""
CardCartesBancaires
}

"""Payment Mandate Sequence"""
Expand Down Expand Up @@ -6908,8 +6902,11 @@ type SandboxUser {
"""
nationalityCCA3: CCA3

"""Indicates wether the user has verified his/her identity or not"""
idVerified: Boolean
"""
Indicates wether the user has verified his/her identity or not
@deprecated Use the equivalent identificationLevels.expert field instead
"""
idVerified: Boolean @deprecated(reason: "Use the equivalent `identificationLevels.expert` field instead")

"""
Saves you considerable time during the development phase,
Expand Down Expand Up @@ -7560,8 +7557,8 @@ input SimulateCapitalDepositDocumentStatusInput {
"""Status of capital deposit document."""
documentStatus: CapitalDepositDocumentStatus!

"""Document reason code for refused capital deposit document."""
documentReasonCode: DocumentReasonCode
"""Document refusal reason code for refused capital deposit document."""
documentRefusalReasonCode: DocumentReasonCode
}

union SimulateCapitalDepositDocumentStatusPayload = SimulateCapitalDepositDocumentStatusSuccessPayload | CouldNotFindCapitalDepositCaseRejection | CouldNotUpdateCapitalDepositDocumentStatusRejection | CouldNotUploadCapitalDepositDocumentRejection | CouldNotFindCapitalDepositDocumentRejection | CouldNotRejectCapitalDepositDocumentRejection
Expand Down Expand Up @@ -8060,7 +8057,7 @@ input SimulateMerchantProfileRequestOutcomeInput {
}

"""SimulateMerchantProfileRequestOutcomePayload"""
union SimulateMerchantProfileRequestOutcomePayload = SimulateMerchantProfileRequestOutcomeSuccessPayload | NotFoundRejection | MerchantProfileWrongStatusRejection
union SimulateMerchantProfileRequestOutcomePayload = SimulateMerchantProfileRequestOutcomeSuccessPayload | NotFoundRejection | MerchantProfileWrongStatusRejection | ForbiddenRejection | InternalErrorRejection

"""SimulateMerchantProfileRequestOutcomeSuccessPayload"""
type SimulateMerchantProfileRequestOutcomeSuccessPayload {
Expand Down Expand Up @@ -9717,6 +9714,9 @@ type User {
"""last name"""
lastName: String

"""Birth last name"""
birthLastName: String

"""list of first names"""
allFirstNames: [String!]

Expand All @@ -9729,8 +9729,11 @@ type User {
"""birth city"""
birthCity: String

"""`true` if Swan has verified the user's identity"""
idVerified: Boolean!
"""
`true` if Swan has verified the user's identity
@deprecated Use the equivalent identificationLevels.expert field instead
"""
idVerified: Boolean! @deprecated(reason: "Use the equivalent `identificationLevels.expert` field instead")

"""the methods used to authenticate this user"""
authenticators: [Authenticator!]
Expand Down Expand Up @@ -9878,6 +9881,25 @@ enum VerificationFlow {
Progressive
}

"""
Account Holder Verification Requirement

It is a sub status for the Account Holder when his verification status is WaitingForInformation
"""
type VerificationRequirement {
id: ID!
type: VerificationRequirementType!
}

enum VerificationRequirementType {
FirstTransferRequired
SupportingDocumentsRequired
UboDetailsRequired
LegalRepresentativeDetailsRequired
OrganizationDetailsRequired
TaxIdRequired
}

"""Verification status of an account holder"""
enum VerificationStatus {
"""
Expand Down
Loading