Skip to content
Draft
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
7 changes: 4 additions & 3 deletions src/__tests__/payment-link-wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
jest.mock('src/dto/payment-link.dto', () => ({}));

import { Wallet } from '../util/payment-link-wallet';
import { TransferInfo, WalletInfo } from 'src/dto/payment-link.dto';
import { TransferAmount } from '@dfx.swiss/react';
import { WalletInfo } from 'src/dto/payment-link.dto';

describe('Wallet', () => {
// Test data
Expand All @@ -11,11 +12,11 @@ describe('Wallet', () => {
supportedAssets: assets,
} as WalletInfo);

const createTransferInfo = (method: string, assets: string[], available = true): TransferInfo => ({
const createTransferInfo = (method: string, assets: string[], available = true): TransferAmount => ({
method,
assets: assets.map(name => ({ asset: name })),
available,
} as TransferInfo);
} as TransferAmount);

describe('filterTransferInfoByWallet', () => {
it('should return empty array if no methods match', () => {
Expand Down
8 changes: 2 additions & 6 deletions src/contexts/payment-link-pos.context.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { ApiError, PaymentLink, PaymentLinkPaymentStatus, useApi } from '@dfx.swiss/react';
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import {
ExtendedPaymentLinkStatus,
NoPaymentLinkPaymentStatus,
PaymentLinkHistory,
PaymentLinkPayRequest,
} from 'src/dto/payment-link.dto';
import { PaymentLinkHistory, PaymentLinkPayRequest } from '@dfx.swiss/react';

Check failure on line 4 in src/contexts/payment-link-pos.context.tsx

View workflow job for this annotation

GitHub Actions / review

'"@dfx.swiss/react"' has no exported member named 'PaymentLinkPayRequest'. Did you mean 'PaymentLinkPayment'?

Check failure on line 4 in src/contexts/payment-link-pos.context.tsx

View workflow job for this annotation

GitHub Actions / review

'"@dfx.swiss/react"' has no exported member named 'PaymentLinkHistory'. Did you mean 'PaymentLinksUrl'?
import { ExtendedPaymentLinkStatus, NoPaymentLinkPaymentStatus } from 'src/dto/payment-link.dto';
import { Lnurl } from 'src/util/lnurl';
import { fetchJson, url } from 'src/util/utils';

Expand Down Expand Up @@ -161,7 +157,7 @@

return {
...response[0],
payments: response[0].payments.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()),

Check failure on line 160 in src/contexts/payment-link-pos.context.tsx

View workflow job for this annotation

GitHub Actions / review

Parameter 'b' implicitly has an 'any' type.

Check failure on line 160 in src/contexts/payment-link-pos.context.tsx

View workflow job for this annotation

GitHub Actions / review

Parameter 'a' implicitly has an 'any' type.
};
})
.catch(unauthorizedResponse);
Expand Down
19 changes: 10 additions & 9 deletions src/contexts/payment-link.context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@
import { CloseType, useAppHandlingContext } from 'src/contexts/app-handling.context';
import { AssetBalance } from 'src/contexts/balance.context';
import {
Amount,
ExtendedPaymentLinkStatus,
MetaMaskInfo,
NoPaymentLinkPaymentStatus,
PaymentAmount,

Check failure on line 26 in src/contexts/payment-link.context.tsx

View workflow job for this annotation

GitHub Actions / review

'"@dfx.swiss/react"' has no exported member named 'PaymentAmount'. Did you mean 'PaymentRoute'?
PaymentLinkPayRequest,

Check failure on line 27 in src/contexts/payment-link.context.tsx

View workflow job for this annotation

GitHub Actions / review

'"@dfx.swiss/react"' has no exported member named 'PaymentLinkPayRequest'. Did you mean 'PaymentLinkPayment'?
PaymentLinkPayResponse,

Check failure on line 28 in src/contexts/payment-link.context.tsx

View workflow job for this annotation

GitHub Actions / review

'"@dfx.swiss/react"' has no exported member named 'PaymentLinkPayResponse'. Did you mean 'PaymentLinkPayment'?
PaymentLinkPayTerminal,

Check failure on line 29 in src/contexts/payment-link.context.tsx

View workflow job for this annotation

GitHub Actions / review

'"@dfx.swiss/react"' has no exported member named 'PaymentLinkPayTerminal'. Did you mean 'PaymentLinkPayment'?
PaymentStandard,

Check failure on line 30 in src/contexts/payment-link.context.tsx

View workflow job for this annotation

GitHub Actions / review

'"@dfx.swiss/react"' has no exported member named 'PaymentStandard'. Did you mean 'PaymentStandardType'?
} from 'src/dto/payment-link.dto';
hasPaymentQuote as hasQuoteField,

Check failure on line 31 in src/contexts/payment-link.context.tsx

View workflow job for this annotation

GitHub Actions / review

Module '"@dfx.swiss/react"' has no exported member 'hasPaymentQuote'.
} from '@dfx.swiss/react';
import { ExtendedPaymentLinkStatus, MetaMaskInfo, NoPaymentLinkPaymentStatus } from 'src/dto/payment-link.dto';
import { usePolling } from 'src/hooks/polling';
import { useSessionStore } from 'src/hooks/session-store.hook';
import { Evm } from 'src/util/evm';
Expand Down Expand Up @@ -237,14 +237,14 @@
try {
const urlObj = new URL(url);
urlObj.searchParams.set('timeout', '0');
const payRequest = await fetchJson<PaymentLinkPayRequest>(urlObj);
const payRequest = await fetchJson<PaymentLinkPayResponse>(urlObj);

if (merchantMode) {
setPayRequest(payRequest);
return;
}

if (payRequest.statusCode === 400 && payRequest.message?.includes('not assigned')) {
if (!hasQuoteField(payRequest) && payRequest.statusCode === 400 && payRequest.message.includes('not assigned')) {
setPayRequest(payRequest);
navigate('/pl/assign');
return;
Expand All @@ -255,7 +255,8 @@
setPayRequest(payRequest);
setPaymentStatus(status);

if (status === PaymentLinkPaymentStatus.PENDING) {
// A pending status is only ever derived from a quoted response, but the type cannot know that.
if (status === PaymentLinkPaymentStatus.PENDING && hasQuoteField(payRequest)) {
return waitPayment(payRequest);
}

Expand Down Expand Up @@ -470,7 +471,7 @@
async function findAssetWithBalance(
address: string,
blockchain: Blockchain,
transferAmounts: Amount[],
transferAmounts: PaymentAmount[],
): Promise<AssetBalance | undefined> {
transferAmounts.sort((a, b) => (a.asset === 'dEURO' ? -1 : b.asset === 'dEURO' ? 1 : 0));

Expand Down
104 changes: 8 additions & 96 deletions src/dto/payment-link.dto.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,4 @@
import { Asset, Blockchain, PaymentLinkMode, PaymentLinkPaymentStatus, PaymentStandardType } from '@dfx.swiss/react';

export interface PaymentStandard {
id: PaymentStandardType;
label: string;
description: string;
paymentIdentifierLabel?: string;
blockchain?: Blockchain;
}

export interface Quote {
id: string;
expiration: Date;
payment: string;
}

export interface Amount {
asset: string;
amount?: number;
}

export enum C2BPaymentMethod {
BINANCE_PAY = 'BinancePay',
KUCOINPAY = 'KucoinPay',
}

export type TransferMethod = Blockchain | C2BPaymentMethod;

export interface TransferInfo {
method: TransferMethod;
minFee: number;
assets: Amount[];
available?: boolean;
}

export interface RecipientInfo {
address?: {
city: string;
country: string;
houseNumber: string;
street: string;
zip: string;
};
name?: string;
mail?: string;
phone?: string;
website?: string;
}

export interface PaymentLinkPayTerminal {
id: string;
externalId?: string;
tag: string;
displayName: string;
standard: PaymentStandardType;
possibleStandards: PaymentStandardType[];
displayQr: boolean;
mode: PaymentLinkMode;
route: string;
currency: string;
recipient: RecipientInfo;
transferAmounts: TransferInfo[];

// error fields
statusCode?: number;
message?: string;
error?: string;
}

export enum NoPaymentLinkPaymentStatus {
NO_PAYMENT = 'NoPayment',
}

export type ExtendedPaymentLinkStatus = PaymentLinkPaymentStatus | NoPaymentLinkPaymentStatus;

export interface PaymentStatus {
status: PaymentLinkPaymentStatus;
}

export interface PaymentLinkPayRequest extends PaymentLinkPayTerminal {
quote: Quote;
callback: string;
metadata: string;
minSendable: number;
maxSendable: number;
requestedAmount: Amount;
}
import { Asset, PaymentLinkPaymentStatus, TransferMethod } from '@dfx.swiss/react';

export interface WalletInfo {
id: number;
Expand All @@ -109,16 +23,14 @@ export interface MetaMaskInfo {
minFee: number;
}

export interface PaymentLinkHistory extends PaymentLinkPayRequest {
payments: PaymentLinkHistoryPayment[];
totalCompletedAmount: number;
/** Screen state for a terminal with no active payment. Not a status the API ever sends. */
export enum NoPaymentLinkPaymentStatus {
NO_PAYMENT = 'NoPayment',
}

export interface PaymentLinkHistoryPayment {
id: string;
export type ExtendedPaymentLinkStatus = PaymentLinkPaymentStatus | NoPaymentLinkPaymentStatus;

/** Shape of the LNURL wait response, which reports the status on its own. */
export interface PaymentStatus {
status: PaymentLinkPaymentStatus;
amount: number;
currency: string;
date: Date;
externalId: string;
}
6 changes: 3 additions & 3 deletions src/hooks/payment-link-wallets.hook.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Blockchain, PaymentLinkMode } from '@dfx.swiss/react';
import { Blockchain, C2BPaymentMethod, PaymentLinkMode, TransferMethod } from '@dfx.swiss/react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Api } from 'src/config/api';
import { usePaymentLinkContext } from 'src/contexts/payment-link.context';
import { C2BPaymentMethod, TransferMethod, WalletInfo } from 'src/dto/payment-link.dto';
import { WalletInfo } from 'src/dto/payment-link.dto';
import { Wallet } from 'src/util/payment-link-wallet';
import { fetchJson, url } from 'src/util/utils';

Expand Down Expand Up @@ -118,7 +118,7 @@ export const usePaymentLinkWallets = (): PaymentLinkWalletsProps => {

case 'KuCoin Pay':
const { uri: kucoinUri } =
(await fetchCallbackUrlForTransferMethod<{ uri: string }>(C2BPaymentMethod.KUCOINPAY)) ?? {};
(await fetchCallbackUrlForTransferMethod<{ uri: string }>(C2BPaymentMethod.KUCOIN_PAY)) ?? {};
return kucoinUri;

default:
Expand Down
4 changes: 2 additions & 2 deletions src/screens/payment-link-assign.screen.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApiError, Utils, Validations } from '@dfx.swiss/react';
import { ApiError, Utils, Validations, hasPaymentQuote } from '@dfx.swiss/react';
import { Form, StyledButton, StyledButtonWidth, StyledInput, StyledVerticalStack } from '@dfx.swiss/react-components';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
Expand All @@ -25,7 +25,7 @@ export default function PaymentLinkAssignScreen(): JSX.Element {
useEffect(() => {
if (!payRequest) {
navigate('/');
} else if (payRequest.statusCode !== 400) {
} else if (hasPaymentQuote(payRequest) || payRequest.statusCode !== 400) {
navigate('/pl');
}
}, [payRequest]);
Expand Down
2 changes: 1 addition & 1 deletion src/screens/payment-link-pos.screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { Modal } from 'src/components/modal';
import { QrBasic } from 'src/components/payment/qr-code';
import { usePaymentPosContext } from 'src/contexts/payment-link-pos.context';
import { useSettingsContext } from 'src/contexts/settings.context';
import { PaymentLinkHistory } from 'src/dto/payment-link.dto';
import { PaymentLinkHistory } from '@dfx.swiss/react';
import { useClipboard } from 'src/hooks/clipboard.hook';
import { useLayoutOptions } from 'src/hooks/layout-config.hook';
import { useNavigation } from 'src/hooks/navigation.hook';
Expand Down
30 changes: 17 additions & 13 deletions src/screens/payment-link.screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ import { usePaymentLinkContext } from 'src/contexts/payment-link.context';
import { useSettingsContext } from 'src/contexts/settings.context';
import { useWindowContext } from 'src/contexts/window.context';
import {
NoPaymentLinkPaymentStatus,
PaymentLinkPayRequest,
PaymentLinkPayTerminal,
PaymentLinkPayResponse,
PaymentStandard,
WalletInfo,
} from 'src/dto/payment-link.dto';
hasPaymentQuote,
} from '@dfx.swiss/react';
import { NoPaymentLinkPaymentStatus, WalletInfo } from 'src/dto/payment-link.dto';
import { useNavigation } from 'src/hooks/navigation.hook';
import { usePaymentLinkWallets } from 'src/hooks/payment-link-wallets.hook';
import { useWeb3 } from 'src/hooks/web3.hook';
Expand Down Expand Up @@ -364,10 +364,14 @@ export default function PaymentLinkScreen(): JSX.Element {
label: translate('screens/home', 'Mode'),
text: payRequest.mode,
},
{
label: translate('screens/payment', 'Tag'),
text: payRequest.tag,
},
...(hasPaymentQuote(payRequest)
? [
{
label: translate('screens/payment', 'Tag'),
text: payRequest.tag,
},
]
: []),
{
label: translate('screens/payment', 'Route'),
text: payRequest.route,
Expand Down Expand Up @@ -878,7 +882,7 @@ function WalletLogo({ wallet, size }: { wallet: WalletInfo; size: number }): JSX
);
}

function CreatePublicPaymentForm({ paymentRequest }: { paymentRequest: PaymentLinkPayTerminal }): JSX.Element {
function CreatePublicPaymentForm({ paymentRequest }: { paymentRequest: PaymentLinkPayResponse }): JSX.Element {
const { call } = useApi();
const { translate, translateError } = useSettingsContext();
const [isActivating, setIsActivating] = useState(false);
Expand All @@ -900,7 +904,7 @@ function CreatePublicPaymentForm({ paymentRequest }: { paymentRequest: PaymentLi
setIsActivating(true);
const params = new URLSearchParams({
externalLinkId: paymentRequest.externalId as string,
route: paymentRequest.route,
...(paymentRequest.route ? { route: paymentRequest.route } : {}),
});

return call<PaymentLink>({
Expand Down Expand Up @@ -931,7 +935,7 @@ function CreatePublicPaymentForm({ paymentRequest }: { paymentRequest: PaymentLi
</p>
<StyledInput
label={translate('screens/payment', 'Amount in {{currencyName}}', {
currencyName: paymentRequest.currency,
currencyName: paymentRequest.currency ?? '',
})}
name="amount"
control={control}
Expand All @@ -955,7 +959,7 @@ function CreatePublicPaymentForm({ paymentRequest }: { paymentRequest: PaymentLi
);
}

function EditPublicPaymentForm({ paymentRequest }: { paymentRequest: PaymentLinkPayTerminal }): JSX.Element {
function EditPublicPaymentForm({ paymentRequest }: { paymentRequest: PaymentLinkPayResponse }): JSX.Element {
const { call } = useApi();
const { translate, translateError } = useSettingsContext();
const [isEditing, setIsEditing] = useState(false);
Expand All @@ -969,7 +973,7 @@ function EditPublicPaymentForm({ paymentRequest }: { paymentRequest: PaymentLink
setIsEditing(true);
const params = new URLSearchParams({
externalLinkId: paymentRequest.externalId as string,
route: paymentRequest.route,
...(paymentRequest.route ? { route: paymentRequest.route } : {}),
});

return call<PaymentLink>({
Expand Down
13 changes: 7 additions & 6 deletions src/util/payment-link-wallet.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { TransferInfo, WalletInfo } from 'src/dto/payment-link.dto';
import { TransferAmount } from '@dfx.swiss/react';
import { WalletInfo } from 'src/dto/payment-link.dto';

export class Wallet {
static filterTransferInfoByWallet(wallet: WalletInfo, transferInfoList: TransferInfo[]): TransferInfo[] {
return transferInfoList.map((ta) => this.filterCompatible(wallet, ta)).filter(Boolean) as TransferInfo[];
static filterTransferInfoByWallet(wallet: WalletInfo, transferInfoList: TransferAmount[]): TransferAmount[] {
return transferInfoList.map((ta) => this.filterCompatible(wallet, ta)).filter(Boolean) as TransferAmount[];
}

static qualifiesForPayment(wallet: WalletInfo, transferInfoList: TransferInfo[]): boolean {
static qualifiesForPayment(wallet: WalletInfo, transferInfoList: TransferAmount[]): boolean {
return transferInfoList.some((ta) => this.filterCompatible(wallet, ta));
}

private static filterCompatible(
wallet: WalletInfo,
transferInfo: TransferInfo,
transferInfo: TransferAmount,
isAvailable = true,
): TransferInfo | undefined {
): TransferAmount | undefined {
const { method, assets, available } = transferInfo;
if (isAvailable && available === false) return undefined;

Expand Down
Loading