Refactor monero subscription component

Using computed properties and atomic units.
This commit is contained in:
silverpill 2022-09-18 12:38:41 +00:00
parent df7748ca72
commit b65ad1057c
6 changed files with 78 additions and 46 deletions

View file

@ -1,7 +1,34 @@
import { BigNumber, FixedNumber } from "@ethersproject/bignumber"
import { BACKEND_URL } from "@/constants"
import { floatToBigNumber, roundBigNumber } from "@/utils/numbers"
import { http } from "./common"
import { Profile, User } from "./users"
const SECONDS_IN_DAY = 3600 * 24
const SECONDS_IN_MONTH = SECONDS_IN_DAY * 30
export function getPricePerSec(
pricePerMonth: number,
tokenDecimals: number,
): BigNumber {
const pricePerMonthInt = floatToBigNumber(pricePerMonth, tokenDecimals)
return pricePerMonthInt.div(SECONDS_IN_MONTH)
}
export function getPricePerMonth(
pricePerSec: BigNumber,
): BigNumber {
return roundBigNumber(pricePerSec.mul(SECONDS_IN_MONTH), 4)
}
export function formatAmount(
value: BigNumber,
tokenDecimals: number,
): FixedNumber {
return FixedNumber.fromValue(value, tokenDecimals)
}
export interface SubscriptionOption {
type: string;
price: number | null;

View file

@ -7,12 +7,14 @@ import { ethereumAddressMatch, EthereumSignature } from "@/utils/ethereum"
import { floatToBigNumber, roundBigNumber } from "@/utils/numbers"
import { http } from "./common"
import { Contracts, getContract } from "./contracts"
import { registerSubscriptionOption } from "./subscriptions-common"
import {
formatAmount,
getPricePerMonth,
getPricePerSec,
registerSubscriptionOption,
} from "./subscriptions-common"
import { Profile, User } from "./users"
const SECONDS_IN_DAY = 3600 * 24
const SECONDS_IN_MONTH = SECONDS_IN_DAY * 30
export interface SubscriptionToken {
address: string;
symbol: string;
@ -33,27 +35,6 @@ export async function getSubscriptionToken(
}
}
export function getPricePerSec(
pricePerMonth: number,
tokenDecimals: number,
): BigNumber {
const pricePerMonthInt = floatToBigNumber(pricePerMonth, tokenDecimals)
return pricePerMonthInt.div(SECONDS_IN_MONTH)
}
export function getPricePerMonth(
pricePerSec: BigNumber,
): BigNumber {
return roundBigNumber(pricePerSec.mul(SECONDS_IN_MONTH), 4)
}
export function formatAmount(
value: BigNumber,
tokenDecimals: number,
): FixedNumber {
return FixedNumber.fromValue(value, tokenDecimals)
}
export async function getSubscriptionAuthorization(
authToken: string,
pricePerSec: BigNumber,

View file

@ -2,22 +2,40 @@ import { BigNumber } from "@ethersproject/bignumber"
import { BACKEND_URL } from "@/constants"
import { http } from "./common"
import { registerSubscriptionOption } from "./subscriptions-common"
import {
formatAmount,
getPricePerMonth as _getPricePerMonth,
getPricePerSec as _getPricePerSec,
} from "./subscriptions-ethereum"
registerSubscriptionOption,
} from "./subscriptions-common"
import { Profile, User } from "./users"
const MONERO_DECIMALS = 12
export function formatXmrAmount(value: number | BigNumber): number {
if (typeof value === "number") {
value = BigNumber.from(value)
}
return formatAmount(value, MONERO_DECIMALS).toUnsafeFloat()
}
export function getPricePerSec(pricePerMonth: number): number {
return _getPricePerSec(pricePerMonth, 12).toNumber()
return _getPricePerSec(pricePerMonth, MONERO_DECIMALS).toNumber()
}
export function getPricePerMonth(pricePerSec: number): number {
const pricePerSecInt = BigNumber.from(pricePerSec)
const pricePerMonthInt = _getPricePerMonth(pricePerSecInt)
return formatAmount(pricePerMonthInt, 12).toUnsafeFloat()
return formatXmrAmount(pricePerMonthInt)
}
export function getPaymentAmount(
pricePerSec: number,
durationMonths: number,
): number {
const pricePerSecInt = BigNumber.from(pricePerSec)
const pricePerMonthInt = _getPricePerMonth(pricePerSecInt)
return Math.round(pricePerMonthInt.toNumber() * durationMonths)
}
export async function registerMoneroSubscriptionOption(

View file

@ -63,7 +63,7 @@
</div>
<div class="payment-amount">
<label>Amount</label>
<div>{{ getPaymentAmount() }} XMR</div>
<div>{{ formatXmrAmount(paymentAmount) }} XMR</div>
</div>
<button
type="submit"
@ -78,7 +78,7 @@
</button>
</form>
<div class="invoice" v-if="invoice">
<div>Please send {{ getPaymentAmount() }} XMR to this address:</div>
<div>Please send {{ formatXmrAmount(paymentAmount) }} XMR to this address:</div>
<div class="payment-address">{{ invoice.payment_address }}</div>
<div class="invoice-status">
<template v-if="invoice.status === 'open'">Waiting for payment</template>
@ -92,14 +92,16 @@
<script setup lang="ts">
import { onMounted } from "vue"
import { $, $ref } from "vue/macros"
import { $, $computed, $ref } from "vue/macros"
import { DateTime } from "luxon"
import { searchProfilesByAcct } from "@/api/search"
import { findSubscription, SubscriptionDetails } from "@/api/subscriptions-common"
import {
createInvoice,
formatXmrAmount,
getInvoice,
getPaymentAmount,
getPricePerMonth,
Invoice,
} from "@/api/subscriptions-monero"
@ -120,7 +122,6 @@ const senderAcct = $ref("")
let senderError = $ref<string | null>(null)
let sender = $ref<ProfileWrapper>(new ProfileWrapper(currentUser || guest()))
let subscriptionOption = $ref<ProfilePaymentOption | null>(null)
let subscriptionPrice = $ref<number | null>(null)
let subscriptionDetails = $ref<SubscriptionDetails | null>(null)
const paymentDuration = $ref<number>(1)
let invoice = $ref<Invoice | null>(null)
@ -131,14 +132,19 @@ onMounted(async () => {
subscriptionOption = recipient.payment_options.find((option) => {
return option.type === "monero-subscription" && option.price !== undefined
}) || null
if (subscriptionOption?.price) {
subscriptionPrice = getPricePerMonth(subscriptionOption.price)
if (sender.id !== "") {
subscriptionDetails = await findSubscription(sender.id, recipient.id)
}
if (subscriptionOption && sender.id !== "") {
subscriptionDetails = await findSubscription(sender.id, recipient.id)
}
})
// Human-readable subscription price
const subscriptionPrice = $computed<number | null>(() => {
if (!subscriptionOption?.price) {
return null
}
return getPricePerMonth(subscriptionOption.price)
})
async function identifySender() {
if (!senderAcct) {
return
@ -176,16 +182,15 @@ function canSubscribe(): boolean {
)
}
function getPaymentAmount(): number {
if (!subscriptionPrice) {
return 0
const paymentAmount = $computed<number | null>(() => {
if (!subscriptionOption?.price) {
return null
}
const amount = subscriptionPrice * paymentDuration
return amount
}
return getPaymentAmount(subscriptionOption.price, paymentDuration)
})
function canPay(): boolean {
return getPaymentAmount() > 0
return paymentAmount !== null
}
async function onCreateInvoice() {

View file

@ -79,6 +79,7 @@ import { DateTime } from "luxon"
import { ProfileWrapper } from "@/api/users"
import {
getPricePerSec,
getSubscriptionOptions,
getReceivedSubscriptions,
Subscription,
@ -86,7 +87,6 @@ import {
} from "@/api/subscriptions-common"
import {
configureSubscriptions,
getPricePerSec,
getSubscriptionAuthorization,
getSubscriptionConfig,
getSubscriptionState,

View file

@ -117,6 +117,7 @@ function getSubscriptionPageUrl(): string {
function isFormValid(): boolean {
return (
// Price must be greater than 0 when expressed in piconeros
getPricePerSec(subscriptionPrice) > 0 &&
subscriptionPayoutAddress.length > 0
)