You've already forked AstralRinth
forked from didirus/AstralRinth
Update billing with backend changes (#2505)
This commit is contained in:
@@ -17,33 +17,24 @@
|
|||||||
<span class="font-bold text-primary">
|
<span class="font-bold text-primary">
|
||||||
<template v-if="charge.product.metadata.type === 'midas'"> Modrinth Plus </template>
|
<template v-if="charge.product.metadata.type === 'midas'"> Modrinth Plus </template>
|
||||||
<template v-else> Unknown product </template>
|
<template v-else> Unknown product </template>
|
||||||
<template v-if="charge.metadata.modrinth_subscription_interval">
|
<template v-if="charge.subscription_interval">
|
||||||
{{ charge.metadata.modrinth_subscription_interval }}
|
{{ charge.subscription_interval }}
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
⋅
|
⋅
|
||||||
<span>{{ formatPrice(charge.amount, charge.currency) }}</span>
|
<span>{{ formatPrice(charge.amount, charge.currency_code) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<Badge :color="charge.status === 'succeeded' ? 'green' : 'red'" :type="charge.status" />
|
<Badge :color="charge.status === 'succeeded' ? 'green' : 'red'" :type="charge.status" />
|
||||||
⋅
|
⋅
|
||||||
{{ $dayjs.unix(charge.created).format("YYYY-MM-DD") }}
|
{{ $dayjs(charge.due).format("YYYY-MM-DD") }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a
|
|
||||||
v-if="charge.receipt_url"
|
|
||||||
class="iconified-button raised-button"
|
|
||||||
:href="charge.receipt_url"
|
|
||||||
>
|
|
||||||
<ReceiptTextIcon />
|
|
||||||
View receipt
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ReceiptTextIcon } from "@modrinth/assets";
|
|
||||||
import { Breadcrumbs, Badge } from "@modrinth/ui";
|
import { Breadcrumbs, Badge } from "@modrinth/ui";
|
||||||
import { products } from "~/generated/state.json";
|
import { products } from "~/generated/state.json";
|
||||||
|
|
||||||
@@ -58,15 +49,17 @@ const { data: charges } = await useAsyncData(
|
|||||||
() => useBaseFetch("billing/payments", { internal: true }),
|
() => useBaseFetch("billing/payments", { internal: true }),
|
||||||
{
|
{
|
||||||
transform: (charges) => {
|
transform: (charges) => {
|
||||||
return charges.map((charge) => {
|
return charges
|
||||||
const product = products.find((product) =>
|
.filter((charge) => charge.status !== "open" && charge.status !== "cancelled")
|
||||||
product.prices.some((price) => price.id === charge.metadata.modrinth_price_id),
|
.map((charge) => {
|
||||||
);
|
const product = products.find((product) =>
|
||||||
|
product.prices.some((price) => price.id === charge.price_id),
|
||||||
|
);
|
||||||
|
|
||||||
charge.product = product;
|
charge.product = product;
|
||||||
|
|
||||||
return charge;
|
return charge;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,22 +8,20 @@
|
|||||||
:title="formatMessage(cancelModalMessages.title)"
|
:title="formatMessage(cancelModalMessages.title)"
|
||||||
:description="formatMessage(cancelModalMessages.description)"
|
:description="formatMessage(cancelModalMessages.description)"
|
||||||
:proceed-label="formatMessage(cancelModalMessages.action)"
|
:proceed-label="formatMessage(cancelModalMessages.action)"
|
||||||
@proceed="cancelSubscription(cancelSubscriptionId)"
|
@proceed="cancelSubscription(cancelSubscriptionId, true)"
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-wrap justify-between gap-4">
|
<div class="flex flex-wrap justify-between gap-4">
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<template v-if="midasSubscription">
|
<template v-if="midasCharge">
|
||||||
<span v-if="midasSubscription.status === 'active'">
|
<span v-if="midasCharge.status === 'open'"> You're currently subscribed to: </span>
|
||||||
You're currently subscribed to:
|
<span v-else-if="midasCharge.status === 'processing'" class="text-orange">
|
||||||
</span>
|
|
||||||
<span v-else-if="midasSubscription.status === 'payment-processing'" class="text-orange">
|
|
||||||
Your payment is being processed. Perks will activate once payment is complete.
|
Your payment is being processed. Perks will activate once payment is complete.
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="midasSubscription.status === 'cancelled'">
|
<span v-else-if="midasCharge.status === 'cancelled'">
|
||||||
You've cancelled your subscription. <br />
|
You've cancelled your subscription. <br />
|
||||||
You will retain your perks until the end of the current billing cycle.
|
You will retain your perks until the end of the current billing cycle.
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="midasSubscription.status === 'payment-failed'" class="text-red">
|
<span v-else-if="midasCharge.status === 'failed'" class="text-red">
|
||||||
Your subscription payment failed. Please update your payment method.
|
Your subscription payment failed. Please update your payment method.
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -49,34 +47,31 @@
|
|||||||
<div class="flex w-full flex-wrap justify-between gap-4 xl:w-auto xl:flex-col">
|
<div class="flex w-full flex-wrap justify-between gap-4 xl:w-auto xl:flex-col">
|
||||||
<div class="flex flex-col gap-1 xl:ml-auto xl:text-right">
|
<div class="flex flex-col gap-1 xl:ml-auto xl:text-right">
|
||||||
<span class="text-2xl font-bold text-dark">
|
<span class="text-2xl font-bold text-dark">
|
||||||
<template v-if="midasSubscription">
|
<template v-if="midasCharge">
|
||||||
{{
|
{{
|
||||||
formatPrice(
|
formatPrice(
|
||||||
vintl.locale,
|
vintl.locale,
|
||||||
midasSubscriptionPrice.prices.intervals[midasSubscription.interval],
|
midasSubscriptionPrice.prices.intervals[midasCharge.subscription_interval],
|
||||||
midasSubscriptionPrice.currency_code,
|
midasSubscriptionPrice.currency_code,
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/
|
/
|
||||||
{{ midasSubscription.interval }}
|
{{ midasCharge.subscription_interval }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ formatPrice(vintl.locale, price.prices.intervals.monthly, price.currency_code) }}
|
{{ formatPrice(vintl.locale, price.prices.intervals.monthly, price.currency_code) }}
|
||||||
/ month
|
/ month
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
<template v-if="midasSubscription">
|
<template v-if="midasCharge">
|
||||||
<span class="text-sm text-secondary">
|
<span class="text-sm text-secondary">
|
||||||
Since {{ $dayjs(midasSubscription.created).format("MMMM D, YYYY") }}
|
Since {{ $dayjs(midasSubscription.created).format("MMMM D, YYYY") }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="midasSubscription.status === 'active'" class="text-sm text-secondary">
|
<span v-if="midasCharge.status === 'open'" class="text-sm text-secondary">
|
||||||
Renews {{ $dayjs(midasSubscription.expires).format("MMMM D, YYYY") }}
|
Renews {{ $dayjs(midasCharge.due).format("MMMM D, YYYY") }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span v-else-if="midasCharge.status === 'cancelled'" class="text-sm text-secondary">
|
||||||
v-else-if="midasSubscription.status === 'cancelled'"
|
Expires {{ $dayjs(midasCharge.due).format("MMMM D, YYYY") }}
|
||||||
class="text-sm text-secondary"
|
|
||||||
>
|
|
||||||
Expires {{ $dayjs(midasSubscription.expires).format("MMMM D, YYYY") }}
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -90,11 +85,11 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="midasSubscription && midasSubscription.status === 'payment-failed'"
|
v-if="midasCharge && midasCharge.status === 'failed'"
|
||||||
class="ml-auto flex flex-row-reverse items-center gap-2"
|
class="ml-auto flex flex-row-reverse items-center gap-2"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="midasSubscription && midasSubscription.status === 'payment-failed'"
|
v-if="midasCharge && midasCharge.status === 'failed'"
|
||||||
class="iconified-button raised-button"
|
class="iconified-button raised-button"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
@@ -123,7 +118,7 @@
|
|||||||
</OverflowMenu>
|
</OverflowMenu>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-else-if="midasSubscription && midasSubscription.status !== 'cancelled'"
|
v-else-if="midasCharge && midasCharge.status !== 'cancelled'"
|
||||||
class="iconified-button raised-button !ml-auto"
|
class="iconified-button raised-button !ml-auto"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
@@ -134,6 +129,13 @@
|
|||||||
>
|
>
|
||||||
<XIcon /> Cancel
|
<XIcon /> Cancel
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
v-else-if="midasCharge && midasCharge.status === 'cancelled'"
|
||||||
|
class="btn btn-purple btn-large ml-auto"
|
||||||
|
@click="cancelSubscription(midasSubscription.id, false)"
|
||||||
|
>
|
||||||
|
<RightArrowIcon /> Resubscribe
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
class="btn btn-purple btn-large ml-auto"
|
class="btn btn-purple btn-large ml-auto"
|
||||||
@@ -474,12 +476,14 @@ function loadStripe() {
|
|||||||
|
|
||||||
const [
|
const [
|
||||||
{ data: paymentMethods, refresh: refreshPaymentMethods },
|
{ data: paymentMethods, refresh: refreshPaymentMethods },
|
||||||
|
{ data: charges, refresh: refreshCharges },
|
||||||
{ data: customer, refresh: refreshCustomer },
|
{ data: customer, refresh: refreshCustomer },
|
||||||
{ data: subscriptions, refresh: refreshSubscriptions },
|
{ data: subscriptions, refresh: refreshSubscriptions },
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
useAsyncData("billing/payment_methods", () =>
|
useAsyncData("billing/payment_methods", () =>
|
||||||
useBaseFetch("billing/payment_methods", { internal: true }),
|
useBaseFetch("billing/payment_methods", { internal: true }),
|
||||||
),
|
),
|
||||||
|
useAsyncData("billing/payments", () => useBaseFetch("billing/payments", { internal: true })),
|
||||||
useAsyncData("billing/customer", () => useBaseFetch("billing/customer", { internal: true })),
|
useAsyncData("billing/customer", () => useBaseFetch("billing/customer", { internal: true })),
|
||||||
useAsyncData("billing/subscriptions", () =>
|
useAsyncData("billing/subscriptions", () =>
|
||||||
useBaseFetch("billing/subscriptions", { internal: true }),
|
useBaseFetch("billing/subscriptions", { internal: true }),
|
||||||
@@ -487,18 +491,30 @@ const [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
await Promise.all([refreshPaymentMethods(), refreshCustomer(), refreshSubscriptions()]);
|
await Promise.all([
|
||||||
|
refreshPaymentMethods(),
|
||||||
|
refreshCharges(),
|
||||||
|
refreshCustomer(),
|
||||||
|
refreshSubscriptions(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const midasProduct = ref(products.find((x) => x.metadata.type === "midas"));
|
const midasProduct = ref(products.find((x) => x.metadata.type === "midas"));
|
||||||
const midasSubscription = computed(() =>
|
const midasSubscription = computed(() =>
|
||||||
subscriptions.value.find((x) => midasProduct.value.prices.find((y) => y.id === x.price_id)),
|
subscriptions.value.find(
|
||||||
|
(x) => x.status === "provisioned" && midasProduct.value.prices.find((y) => y.id === x.price_id),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
const midasSubscriptionPrice = computed(() =>
|
const midasSubscriptionPrice = computed(() =>
|
||||||
midasSubscription.value
|
midasSubscription.value
|
||||||
? midasProduct.value.prices.find((x) => x.id === midasSubscription.value.price_id)
|
? midasProduct.value.prices.find((x) => x.id === midasSubscription.value.price_id)
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
|
const midasCharge = computed(() =>
|
||||||
|
midasSubscription.value
|
||||||
|
? charges.value.find((x) => x.subscription_id === midasSubscription.value.id)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
|
||||||
const purchaseModal = ref();
|
const purchaseModal = ref();
|
||||||
const country = useUserCountry();
|
const country = useUserCountry();
|
||||||
@@ -524,10 +540,18 @@ if (route.query.priceId && route.query.plan && route.query.redirect_status) {
|
|||||||
price_id: route.query.priceId,
|
price_id: route.query.priceId,
|
||||||
interval: route.query.plan,
|
interval: route.query.plan,
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
expires: route.query.plan === "yearly" ? Date.now() + 31536000000 : Date.now() + 2629746000,
|
|
||||||
status,
|
status,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
charges.value.push({
|
||||||
|
id: "temp",
|
||||||
|
price_id: route.query.priceId,
|
||||||
|
subscription_id: "temp",
|
||||||
|
status: "open",
|
||||||
|
due: Date.now() + (route.query.plan === "yearly" ? 31536000000 : 2629746000),
|
||||||
|
subscription_interval: route.query.plan,
|
||||||
|
});
|
||||||
|
|
||||||
await router.replace({ query: {} });
|
await router.replace({ query: {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -655,12 +679,15 @@ async function removePaymentMethod(index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cancelSubscriptionId = ref();
|
const cancelSubscriptionId = ref();
|
||||||
async function cancelSubscription(id) {
|
async function cancelSubscription(id, cancelled) {
|
||||||
startLoading();
|
startLoading();
|
||||||
try {
|
try {
|
||||||
await useBaseFetch(`billing/subscription/${id}`, {
|
await useBaseFetch(`billing/subscription/${id}`, {
|
||||||
internal: true,
|
internal: true,
|
||||||
method: "DELETE",
|
method: "PATCH",
|
||||||
|
body: {
|
||||||
|
cancelled,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
await refresh();
|
await refresh();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -540,8 +540,11 @@ async function refreshPayment(confirmationId, paymentMethodId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await props.sendBillingRequest({
|
const result = await props.sendBillingRequest({
|
||||||
product_id: props.product.id,
|
charge: {
|
||||||
interval: selectedPlan.value,
|
type: 'new',
|
||||||
|
product_id: props.product.id,
|
||||||
|
interval: selectedPlan.value,
|
||||||
|
},
|
||||||
existing_payment_intent: paymentIntentId.value,
|
existing_payment_intent: paymentIntentId.value,
|
||||||
...base,
|
...base,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user