From 1c33591c75a889840dc80e0ccd7f57d2c92fe3d7 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Tue, 24 Sep 2024 12:03:00 +0300 Subject: [PATCH 1/3] feat: enable custom fee address and fee amount --- .../components/payment-confirmation.svelte | 38 ++++++++++++++++++- .../src/lib/payment-widget.svelte | 5 +++ .../src/lib/react/PaymentWidget.d.ts | 4 ++ .../payment-widget/src/lib/utils/request.ts | 28 ++++++++++++-- 4 files changed, 69 insertions(+), 6 deletions(-) diff --git a/packages/payment-widget/src/lib/components/payment-confirmation.svelte b/packages/payment-widget/src/lib/components/payment-confirmation.svelte index c718e63c..c1e2a828 100644 --- a/packages/payment-widget/src/lib/components/payment-confirmation.svelte +++ b/packages/payment-widget/src/lib/components/payment-confirmation.svelte @@ -20,6 +20,7 @@ } from "../utils/request"; import Spinner from "./spinner.svelte"; import WalletInfo from "./wallet-info.svelte"; + import { ethers } from "ethers"; export let selectedCurrency: Currency; export let amountInUSD: number; @@ -36,6 +37,8 @@ export let onPaymentSuccess: (request: any) => void; export let onPaymentError: (error: string) => void; export let invoiceNumber: string | undefined; + export let feeAddress: string; + export let feeAmountInUSD: number; const COUNTDOWN_INTERVAL = 30; @@ -44,9 +47,11 @@ let intervalId: NodeJS.Timeout; let exchangeRate: number = 0; let error: string = ""; + let feeAmountInCrypto: number = 0; $: isLoadingPrice = true; $: isPaying = false; + $: totalPayment = amountInCrypto + feeAmountInCrypto; const currencySymbol = selectedCurrency.symbol.includes( selectedCurrency.network @@ -72,6 +77,7 @@ const rate = data.data.rates[lookupSymbol]; exchangeRate = parseFloat(rate); amountInCrypto = amountInUSD * exchangeRate; + feeAmountInCrypto = feeAmountInUSD * exchangeRate; } catch (error) { alert("Unable to fetch exchange rate. Please try again later"); } finally { @@ -161,13 +167,38 @@ + {#if feeAddress !== ethers.constants.AddressZero && feeAmountInUSD > 0} +
+

Fee to

+ + {feeAddress} + + +
+
+

Fee Amount

+ + ${feeAmountInUSD} USD / {trimTrailingDecimalZeros(feeAmountInCrypto)} + {currencySymbol} + +
+ {/if}

Payment network

{NETWORK_LABEL[selectedCurrency.network]}

Total

- {trimTrailingDecimalZeros(amountInCrypto)} {currencySymbol} + {trimTrailingDecimalZeros(totalPayment)} {currencySymbol}
{#if !isPaying} @@ -216,11 +247,14 @@ try { const requestParameters = prepareRequestParameters({ currency: selectedCurrency, + feeAddress, + feeAmountInUSD, + feeAmountInCrypto, productInfo, sellerInfo, buyerInfo, payerAddress, - amountInCrypto, + amountInCrypto: totalPayment, exchangeRate, amountInUSD, builderId, diff --git a/packages/payment-widget/src/lib/payment-widget.svelte b/packages/payment-widget/src/lib/payment-widget.svelte index 6c0e298d..7310ed93 100644 --- a/packages/payment-widget/src/lib/payment-widget.svelte +++ b/packages/payment-widget/src/lib/payment-widget.svelte @@ -22,6 +22,7 @@ import { getSupportedCurrencies } from "./utils/currencies"; import { initWalletConnector } from "./utils/walletConnector"; import BuyerInfoForm from "./components/buyer-info-form.svelte"; + import { ethers } from "ethers"; // Props export let sellerInfo: SellerInfo; @@ -37,6 +38,8 @@ export let buyerInfo: BuyerInfo | undefined = undefined; export let enableBuyerInfo: boolean = true; export let invoiceNumber: string | undefined = undefined; + export let feeAddress: string = ethers.constants.AddressZero; + export let feeAmountInUSD: number = 0; // State let web3Modal: Web3Modal | null = null; @@ -230,6 +233,8 @@ /> {:else if selectedCurrency && currentPaymentStep === "confirmation"} { * console.error(error); * }} + * feeAddress="0x1234567890123456789012345678901234567890 + * feeAmountInUSD={22} * /> */ declare const PaymentWidget: React.FC; diff --git a/packages/payment-widget/src/lib/utils/request.ts b/packages/payment-widget/src/lib/utils/request.ts index e1790424..186f6f34 100644 --- a/packages/payment-widget/src/lib/utils/request.ts +++ b/packages/payment-widget/src/lib/utils/request.ts @@ -10,7 +10,7 @@ import { Utils, } from "@requestnetwork/request-client.js"; import { Web3SignatureProvider } from "@requestnetwork/web3-signature"; -import { providers, utils } from "ethers"; +import { constants, providers, utils } from "ethers"; import type { BuyerInfo, Currency, ProductInfo, SellerInfo } from "../types"; import { chains } from "./chains"; @@ -27,6 +27,9 @@ export const prepareRequestParameters = ({ sellerInfo, buyerInfo, invoiceNumber, + feeAddress, + feeAmountInUSD, + feeAmountInCrypto, }: { currency: Currency; sellerAddress: string; @@ -40,6 +43,9 @@ export const prepareRequestParameters = ({ sellerInfo: SellerInfo; buyerInfo: BuyerInfo; invoiceNumber?: string; + feeAddress: string; + feeAmountInUSD: number; + feeAmountInCrypto: number; }) => { const isERC20 = currency.type === Types.RequestLogic.CURRENCY.ERC20; const currencyValue = isERC20 ? currency.address : "eth"; @@ -47,6 +53,12 @@ export const prepareRequestParameters = ({ .parseUnits(amountInCrypto.toFixed(currency.decimals), currency.decimals) .toString(); + let note = `Sale made with ${currency.symbol} on ${currency.network} for amount of ${amountInUSD} USD with an exchange rate of ${exchangeRate}. `; + + if (feeAddress !== constants.AddressZero && feeAmountInUSD) { + note += `Fee of ${feeAmountInUSD} USD and ${feeAmountInCrypto} ${currency.symbol} was paid by the buyer to ${feeAddress}.`; + } + return { requestInfo: { currency: { @@ -72,8 +84,13 @@ export const prepareRequestParameters = ({ parameters: { paymentNetworkName: currency.network, paymentAddress: sellerAddress, - feeAddress: "0x0000000000000000000000000000000000000000", - feeAmount: "0", + feeAddress: feeAddress, + feeAmount: utils + .parseUnits( + feeAmountInCrypto.toFixed(currency.decimals), + currency.decimals + ) + .toString(), }, }, contentData: { @@ -83,7 +100,7 @@ export const prepareRequestParameters = ({ }, creationDate: new Date().toISOString(), invoiceNumber: invoiceNumber || "receipt", - note: `Sale made with ${currency.symbol} on ${currency.network} for amount of ${amountInUSD} USD with an exchange rate of ${exchangeRate}`, + note: note, sellerInfo: { email: sellerInfo?.email || undefined, firstName: sellerInfo?.firstName || undefined, @@ -136,6 +153,9 @@ export const prepareRequestParameters = ({ exchangeRate: exchangeRate.toString(), amountInUSD: amountInUSD.toString(), createdWith, + feeAddress, + feeAmountInUSD, + feeAmountInCrypto, builderId, paymentCurrency: { type: currency.type, From 78c981e05903d648a2eaa26bd4c57653293bb4de Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Thu, 26 Sep 2024 15:15:57 +0300 Subject: [PATCH 2/3] feat: optimize request content data to accomodate fees --- .../components/payment-confirmation.svelte | 109 +++++++++++------- .../src/lib/payment-widget.svelte | 39 ++++++- .../payment-widget/src/lib/utils/request.ts | 61 +++++++--- 3 files changed, 153 insertions(+), 56 deletions(-) diff --git a/packages/payment-widget/src/lib/components/payment-confirmation.svelte b/packages/payment-widget/src/lib/components/payment-confirmation.svelte index c1e2a828..2ec6cd8a 100644 --- a/packages/payment-widget/src/lib/components/payment-confirmation.svelte +++ b/packages/payment-widget/src/lib/components/payment-confirmation.svelte @@ -151,39 +151,43 @@ > -
+

Payment to

- - {sellerAddress} - - -
- {#if feeAddress !== ethers.constants.AddressZero && feeAmountInUSD > 0} -
-

Fee to

+
- {feeAddress} + {sellerAddress}
+
+ {#if feeAddress !== ethers.constants.AddressZero && feeAmountInUSD > 0} +
+

Fee to

+
+ + {feeAddress} + + +
+

Fee Amount

@@ -254,7 +258,8 @@ sellerInfo, buyerInfo, payerAddress, - amountInCrypto: totalPayment, + amountInCrypto, + totalAmountInCrypto: totalPayment, exchangeRate, amountInUSD, builderId, @@ -320,23 +325,6 @@ width: 100%; gap: 16px; - .payment-confirmation-seller-address { - display: flex; - align-items: center; - gap: 2px; - font-size: 12px; - - button { - background: none; - border: none; - padding: 0; - margin: 0; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - } - } &-amount-info { display: flex; align-items: center; @@ -385,6 +373,49 @@ font-weight: 500; font-size: 14px; } + + &.payment-confirmation-address { + flex-direction: column; + align-items: flex-start; + gap: 8px; + + h4 { + margin: 0; + } + + .address-container { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + + a { + flex-grow: 1; + text-decoration: none; + color: inherit; + font-size: 14px; + + span { + display: inline-block; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + button { + background: none; + border: none; + padding: 0; + margin: 0; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + } + } + } } &-warning { diff --git a/packages/payment-widget/src/lib/payment-widget.svelte b/packages/payment-widget/src/lib/payment-widget.svelte index 7310ed93..52923a9c 100644 --- a/packages/payment-widget/src/lib/payment-widget.svelte +++ b/packages/payment-widget/src/lib/payment-widget.svelte @@ -196,7 +196,7 @@

Pay with crypto

- Pay
{ const isERC20 = currency.type === Types.RequestLogic.CURRENCY.ERC20; const currencyValue = isERC20 ? currency.address : "eth"; const amount = utils - .parseUnits(amountInCrypto.toFixed(currency.decimals), currency.decimals) + .parseUnits( + totalAmountInCrypto.toFixed(currency.decimals), + currency.decimals + ) .toString(); let note = `Sale made with ${currency.symbol} on ${currency.network} for amount of ${amountInUSD} USD with an exchange rate of ${exchangeRate}. `; if (feeAddress !== constants.AddressZero && feeAmountInUSD) { - note += `Fee of ${feeAmountInUSD} USD and ${feeAmountInCrypto} ${currency.symbol} was paid by the buyer to ${feeAddress}.`; + note += `Fee of ${feeAmountInUSD} USD/${feeAmountInCrypto} ${currency.symbol} was paid by the buyer to ${feeAddress}.`; + } + + const invoiceItems = [ + { + name: productInfo?.name || "Unnamed product", + quantity: 1, + unitPrice: utils + .parseUnits( + amountInCrypto.toFixed(currency.decimals), + currency.decimals + ) + .toString(), + discount: "0", + tax: { + type: "percentage", + amount: "0", + }, + currency: isERC20 ? currency.address : currency.symbol, + }, + ]; + + if (feeAmountInCrypto > 0) { + invoiceItems.push({ + name: "Fee", + quantity: 1, + unitPrice: utils + .parseUnits( + feeAmountInCrypto.toFixed(currency.decimals), + currency.decimals + ) + .toString(), + discount: "0", + tax: { + type: "percentage", + amount: "0", + }, + currency: isERC20 ? currency.address : currency.symbol, + }); } return { @@ -133,19 +176,7 @@ export const prepareRequestParameters = ({ : undefined, taxRegistration: buyerInfo?.taxRegistration || undefined, }, - invoiceItems: [ - { - name: productInfo?.name || "Unnamed product", - quantity: 1, - unitPrice: amount, - discount: "0", - tax: { - type: "percentage", - amount: "0", - }, - currency: isERC20 ? currency.address : currency.symbol, - }, - ], + invoiceItems, paymentTerms: { dueDate: new Date().toISOString(), }, From 74f972859fb666bf6dbec0d62681182e122b92b3 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Thu, 26 Sep 2024 15:21:24 +0300 Subject: [PATCH 3/3] chore: remove unused button import --- packages/payment-widget/src/lib/payment-widget.svelte | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/payment-widget/src/lib/payment-widget.svelte b/packages/payment-widget/src/lib/payment-widget.svelte index 52923a9c..c0b502ea 100644 --- a/packages/payment-widget/src/lib/payment-widget.svelte +++ b/packages/payment-widget/src/lib/payment-widget.svelte @@ -1,15 +1,16 @@