diff --git a/docs/README.md b/docs/README.md index ebf20798..e0cf1965 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1097,6 +1097,36 @@ size: "sm" size = "sm" ``` + +## donation-rate + +> **The ‘donationRate’ parameter represents the amount that will be donated to the donation address.** + +?> The donationRate parameter is optional values accepted are integers between 0-100. Default value is 2. + + +**Example:** + + +#### ** HTML ** + +```html +donation-rate="10" +``` + +#### ** JavaScript ** + +```javascript +donationRate: 10 +``` + +#### ** React ** + +```react +donationRate = 10 +``` + + # Contribute PayButton is a community-driven open-source initiative. Contributions from the community are _crucial_ to the success of the project. diff --git a/docs/_sidebar.md b/docs/_sidebar.md index ccb5ad35..940e9fdb 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -33,6 +33,8 @@ - [auto-close](/?id=auto-close) - [disable-sound](/?id=disable-sound) - [size](/?id=size) + - [donation-rate](/?id=donation-rate) + - [Contribute](/?id=contribute) - [Developer Quick Start](/?id=developer-quick-start) - [Getting Started](/?id=getting-started) diff --git a/docs/zh-cn/README.md b/docs/zh-cn/README.md index 88359d78..2e5addaa 100644 --- a/docs/zh-cn/README.md +++ b/docs/zh-cn/README.md @@ -1097,6 +1097,36 @@ size = "sm" ``` +## donation-rate + +> **「donationRate」参数用于设置将捐赠到捐赠地址的金额。** + +?> donationRate 参数是可选的,接受的值为 0 到 100 之间的整数。默认值为 2。 + + +**Example:** + + +#### ** HTML ** + +```html +donation-rate="10" +``` + +#### ** JavaScript ** + +```javascript +donationRate: 10 +``` + +#### ** React ** + +```react +donationRate = 10 +``` + + + # 贡献 PayButton是一个社群主导的开放源代码促进会。此项目的成功关键在于对社群的贡献。 diff --git a/docs/zh-cn/_sidebar.md b/docs/zh-cn/_sidebar.md index d357fd36..6acc45ce 100644 --- a/docs/zh-cn/_sidebar.md +++ b/docs/zh-cn/_sidebar.md @@ -32,6 +32,7 @@ - [auto-close](/zh-cn/?id=auto-close) - [disable-sound](/zh-cn/?id=disable-sound) - [size](/zh-cn/?id=size) + - [donation-rate](/zh-cn/?id=donation-rate) - [贡献](/zh-cn/?id=贡献) - [开发人员快速入门](/zh-cn/?id=开发人员快速入门) - [入门](/zh-cn/?id=入门) diff --git a/docs/zh-tw/README.md b/docs/zh-tw/README.md index d97ff142..c93271f1 100644 --- a/docs/zh-tw/README.md +++ b/docs/zh-tw/README.md @@ -1093,6 +1093,35 @@ disableSound: false disableSound = false ``` +## donation-rate + +> **「donationRate」參數用於設定將捐贈到捐贈地址的金額。** + +?> donationRate 參數是可選的,接受的值為 0 到 100 之間的整數。預設值為 2。 + + +**Example:** + + +#### ** HTML ** + +```html +donation-rate="10" +``` + +#### ** JavaScript ** + +```javascript +donationRate: 10 +``` + +#### ** React ** + +```react +donationRate = 10 +``` + + # 貢獻 diff --git a/paybutton/src/index.tsx b/paybutton/src/index.tsx index 29f597a2..d6d911bc 100644 --- a/paybutton/src/index.tsx +++ b/paybutton/src/index.tsx @@ -104,7 +104,8 @@ const allowedProps = [ 'autoClose', 'disableSound', 'transactionText', - 'size' + 'size', + 'donationRate', ]; const requiredProps = [ diff --git a/react/lib/components/PayButton/PayButton.tsx b/react/lib/components/PayButton/PayButton.tsx index e33a406e..0824c486 100644 --- a/react/lib/components/PayButton/PayButton.tsx +++ b/react/lib/components/PayButton/PayButton.tsx @@ -3,7 +3,7 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; import { Theme, ThemeName, ThemeProvider, useTheme } from '../../themes'; import Button, { ButtonProps } from '../Button/Button'; import { Socket } from 'socket.io-client'; - +import config from '../../paybutton-config.json' import { Transaction, Currency, @@ -19,7 +19,8 @@ import { setupAltpaymentSocket, setupChronikWebSocket, CryptoCurrency, - ButtonSize + ButtonSize, + DEFAULT_DONATION_RATE } from '../../util'; import { PaymentDialog } from '../PaymentDialog'; import { AltpaymentCoin, AltpaymentError, AltpaymentPair, AltpaymentShift } from '../../altpayment'; @@ -56,6 +57,8 @@ export interface PayButtonProps extends ButtonProps { contributionOffset?:number size?: ButtonSize; sizeScaleAlreadyApplied?: boolean; + donationAddress?: string; + donationRate?: number; } export const PayButton = ({ @@ -88,6 +91,8 @@ export const PayButton = ({ contributionOffset, size = 'md', sizeScaleAlreadyApplied = false, + donationRate = DEFAULT_DONATION_RATE, + donationAddress = config.donationAddress }: PayButtonProps): React.ReactElement => { const [dialogOpen, setDialogOpen] = useState(false); const [disabled, setDisabled] = useState(false); @@ -111,7 +116,6 @@ export const PayButton = ({ const cryptoAmountRef = useRef(cryptoAmount); - const [paymentId] = useState(!disablePaymentId ? generatePaymentId(8) : undefined); const [addressType, setAddressType] = useState( getCurrencyTypeFromAddress(to), @@ -204,6 +208,7 @@ export const PayButton = ({ expectedOpReturn: opReturn, expectedPaymentId: paymentId, currencyObj, + donationRate } }) } @@ -347,6 +352,8 @@ export const PayButton = ({ newTxs={newTxs} disableSound={disableSound} transactionText={transactionText} + donationAddress={donationAddress} + donationRate={donationRate} /> {errorMsg && (

{ const [success, setSuccess] = useState(false); const [internalDisabled, setInternalDisabled] = useState(false); @@ -247,6 +251,8 @@ export const PaymentDialog = ({ newTxs={newTxs} disableSound={disableSound} transactionText={transactionText} + donationAddress={donationAddress} + donationRate={donationRate} foot={success && ( = props => { altpaymentError, setAltpaymentError, isChild, - } = props - - const [loading, setLoading] = useState(true) + donationAddress = config.donationAddress, + donationRate = DEFAULT_DONATION_RATE + } = props; + const [loading, setLoading] = useState(true); // websockets if standalone const [internalTxsSocket, setInternalTxsSocket] = useState(undefined) @@ -249,6 +256,12 @@ export const Widget: React.FunctionComponent = props => { const theme = useTheme(props.theme, isValidXecAddress(to)) + const [thisAmount, setThisAmount] = useState(props.amount) + const [hasPrice, setHasPrice] = useState(props.price !== undefined && props.price > 0) + const [thisCurrencyObject, setThisCurrencyObject] = useState(props.currencyObject) + + const blurCSS = isPropsTrue(disabled) ? { filter: 'blur(5px)' } : {} + const [donationAmount, setDonationAmount] = useState(null) // inject keyframes once (replacement for @global in makeStyles) useEffect(() => { const id = 'paybutton-widget-keyframes' @@ -388,12 +401,6 @@ export const Widget: React.FunctionComponent = props => { } }, [success, loading, theme, recentlyCopied, copied]) - const [thisAmount, setThisAmount] = useState(props.amount) - const [hasPrice, setHasPrice] = useState(props.price !== undefined && props.price > 0) - const [thisCurrencyObject, setThisCurrencyObject] = useState(props.currencyObject) - - const blurCSS = isPropsTrue(disabled) ? { filter: 'blur(5px)' } : {} - const bchSvg = useMemo((): string => { const color = theme.palette.logo ?? theme.palette.primary return `data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg version='1.1' viewBox='0 0 34 34' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform='translate(1,1)'%3E%3Ccircle cx='16' cy='16' r='17' fill='%23fff' stroke-width='1.0625'/%3E%3C/g%3E%3Cg transform='translate(1,1)' fill-rule='evenodd'%3E%3Ccircle cx='16' cy='16' r='16' fill='${window.encodeURIComponent( @@ -565,11 +572,33 @@ export const Widget: React.FunctionComponent = props => { ? getCurrencyObject(convertedAmount, thisAddressType, randomSatoshis) : null if (convertedObj) { + let amountToDisplay = thisCurrencyObject.string; + let convertedAmountToDisplay = convertedObj.string + if ( donationRate && donationRate >= DONATION_RATE_FIAT_THRESHOLD){ + const thisDonationAmount = thisCurrencyObject.float * (donationRate / 100) + const amountWithDonation = thisCurrencyObject.float + thisDonationAmount + const amountWithDonationObj = getCurrencyObject( + amountWithDonation, + currency, + false, + ) + amountToDisplay = amountWithDonationObj.string + + const convertedDonationAmount = convertedObj.float * (donationRate / 100) + const convertedAmountWithDonation = convertedObj.float + convertedDonationAmount + const convertedAmountWithDonationObj = getCurrencyObject( + convertedAmountWithDonation, + thisAddressType, + randomSatoshis, + ) + convertedAmountToDisplay = convertedAmountWithDonationObj.string + setDonationAmount(convertedAmountWithDonationObj.float) + } setText( - `Send ${thisCurrencyObject.string} ${thisCurrencyObject.currency} = ${convertedObj.string} ${thisAddressType}`, + `Send ${amountToDisplay} ${thisCurrencyObject.currency} = ${convertedAmountToDisplay} ${thisAddressType}`, ) - nextUrl = resolveUrl(thisAddressType, convertedObj.float) - setUrl(nextUrl ?? '') + const url = resolveUrl(thisAddressType, convertedObj.float) + setUrl(url ?? "") } } else { const notZeroValue = @@ -660,20 +689,38 @@ export const Widget: React.FunctionComponent = props => { setRecentlyCopied(true) }, [disabled, to, url, setCopied, setRecentlyCopied]) - const resolveUrl = useCallback( - (currencyCode: string, amount?: number) => { - if (disabled || !to) return - const prefix = CURRENCY_PREFIXES_MAP[currencyCode.toLowerCase() as (typeof CRYPTO_CURRENCIES)[number]] - if (!prefix) return - let thisUrl = `${prefix}:${to.replace(/^.*:/, '')}` - if (amount) { + const resolveUrl = useCallback((currency: string, amount?: number) => { + if (disabled || !to) return; + + const prefix = CURRENCY_PREFIXES_MAP[currency.toLowerCase() as typeof CRYPTO_CURRENCIES[number]]; + if (!prefix) return; + + let thisUrl = `${prefix}:${to.replace(/^.*:/, '')}`; + + if (amount) { + if (donationAddress && donationRate && Number(donationRate)) { + const network = Object.entries(CURRENCY_PREFIXES_MAP).find( + ([, value]) => value === prefix + )?.[0]; + const decimals = network ? DECIMALS[network.toUpperCase()] : undefined; + const donationPercent = donationRate / 100 + const thisDonationAmount = donationAmount ? donationAmount : amount * donationPercent + const minimumDonationAmount = network ? DEFAULT_MINIMUM_DONATION_AMOUNT[network.toUpperCase()] : 0; + thisUrl += `?amount=${amount}` + if(thisDonationAmount > minimumDonationAmount){ + thisUrl += `&addr=${donationAddress}&amount=${thisDonationAmount.toFixed(decimals)}`; + } + }else{ thisUrl += `?amount=${amount}` } - if (opReturn) { - const separator = thisUrl.includes('?') ? '&' : '?' - thisUrl += `${separator}op_return_raw=${opReturn}` - } - return thisUrl + } + + if (opReturn) { + const separator = thisUrl.includes('?') ? '&' : '?'; + thisUrl += `${separator}op_return_raw=${opReturn}`; + } + + return thisUrl; }, [disabled, to, opReturn] ) diff --git a/react/lib/components/Widget/WidgetContainer.tsx b/react/lib/components/Widget/WidgetContainer.tsx index 37ace822..af95d88c 100644 --- a/react/lib/components/Widget/WidgetContainer.tsx +++ b/react/lib/components/Widget/WidgetContainer.tsx @@ -19,6 +19,7 @@ import { resolveNumber, shouldTriggerOnSuccess, isPropsTrue, + DEFAULT_DONATION_RATE, } from '../../util'; import Widget, { WidgetProps } from './Widget'; @@ -51,6 +52,8 @@ export interface WidgetContainerProps setNewTxs: Function disableSound?: boolean transactionText?: string + donationAddress?: string + donationRate?: number } const snackbarOptionsSuccess: OptionsObject = { @@ -131,6 +134,8 @@ export const WidgetContainer: React.FunctionComponent = isChild, disableSound, transactionText, + donationAddress, + donationRate, ...widgetProps } = props; const [internalCurrencyObj, setInternalCurrencyObj] = useState(); @@ -139,7 +144,9 @@ export const WidgetContainer: React.FunctionComponent = const [internalNewTxs, setInternalNewTxs] = useState(); const thisNewTxs = setNewTxs ? newTxs : internalNewTxs; const thisSetNewTxs = setNewTxs ?? setInternalNewTxs; - + if (donationRate === undefined){ + donationRate = DEFAULT_DONATION_RATE + } const [thisPaymentId, setThisPaymentId] = useState(); const [thisPrice, setThisPrice] = useState(0); const [usdPrice, setUsdPrice] = useState(0); @@ -238,7 +245,8 @@ export const WidgetContainer: React.FunctionComponent = altpaymentShift, thisPrice, currencyObj, - randomSatoshis + randomSatoshis, + donationRate ], ); @@ -316,6 +324,8 @@ export const WidgetContainer: React.FunctionComponent = disableAltpayment={disableAltpayment} contributionOffset={contributionOffset} transactionText={transactionText} + donationAddress={donationAddress} + donationRate={donationRate} /> ); diff --git a/react/lib/example-config.json b/react/lib/example-config.json index 728432f0..96c153f1 100644 --- a/react/lib/example-config.json +++ b/react/lib/example-config.json @@ -15,5 +15,6 @@ "https://chronik.pay2stay.com/xec2" ], "bitcoincash": ["https://bch.paybutton.org", "https://chronik.pay2stay.com/bch"] - } + }, + "donationAddress":"ecash:qp2v7kemclu7mv5y3h9qprwp0mrevkqt9gprvmm7yl" } diff --git a/react/lib/util/constants.ts b/react/lib/util/constants.ts index a9d873e9..448b32c2 100644 --- a/react/lib/util/constants.ts +++ b/react/lib/util/constants.ts @@ -16,4 +16,14 @@ export const CURRENCY_PREFIXES_MAP: Record