11<template >
22 <ModrinthServersPurchaseModal
3- v-if =" customer"
3+ v-if =" customer && regionsData "
44 ref =" purchaseModal"
55 :publishable-key =" config.public.stripePublishableKey"
66 :initiate-payment =" async (body) => await initiatePayment(body)"
1111 :currency =" selectedCurrency"
1212 :return-url =" `${config.public.siteUrl}/servers/manage`"
1313 :pings =" regionPings"
14- :regions =" regions "
14+ :regions =" regionsData "
1515 :refresh-payment-methods =" fetchPaymentData"
1616 :fetch-stock =" fetchStock"
1717 :plan-stage =" true"
2727</template >
2828
2929<script setup lang="ts">
30- import { injectNotificationManager , ModrinthServersPurchaseModal } from ' @modrinth/ui'
31- import type { ServerPlan } from ' @modrinth/ui/src/utils/billing'
32- import type { UserSubscription } from ' @modrinth/utils'
33- import { computed , onMounted , ref } from ' vue'
30+ import type { Archon , Labrinth } from ' @modrinth/api-client'
31+ import {
32+ injectModrinthClient ,
33+ injectNotificationManager ,
34+ ModrinthServersPurchaseModal ,
35+ } from ' @modrinth/ui'
36+ import { useMutation , useQuery } from ' @tanstack/vue-query'
37+ import { computed , ref , watch } from ' vue'
3438
35- import { useServersFetch } from ' ~/composables/servers/servers-fetch.ts'
3639import { products } from ' ~/generated/state.json'
3740
3841const { addNotification } = injectNotificationManager ()
42+ const { labrinth, archon } = injectModrinthClient ()
3943
4044const config = useRuntimeConfig ()
4145const purchaseModal = ref <InstanceType <typeof ModrinthServersPurchaseModal > | null >(null )
4246const customer = ref <any >(null )
4347const paymentMethods = ref <any []>([])
4448const selectedCurrency = ref <string >(' USD' )
45- const regions = ref <any []>([])
4649const regionPings = ref <any []>([])
4750
48- const pyroProducts = (products as any [])
51+ const pyroProducts = (products as Labrinth . Billing . Internal . Product [])
4952 .filter ((p ) => p ?.metadata ?.type === ' pyro' )
50- .sort ((a , b ) => (a ?.metadata ?.ram ?? 0 ) - (b ?.metadata ?.ram ?? 0 ))
53+ .sort ((a , b ) => {
54+ const aRam = a ?.metadata ?.type === ' pyro' ? a .metadata .ram : 0
55+ const bRam = b ?.metadata ?.type === ' pyro' ? b .metadata .ram : 0
56+ return aRam - bRam
57+ })
5158
5259function handleError(err : any ) {
5360 console .error (' Purchase modal error:' , err )
5461}
5562
56- async function fetchPaymentData() {
57- try {
58- const [customerData, paymentMethodsData] = await Promise .all ([
59- useBaseFetch (' billing/customer' , { internal: true }),
60- useBaseFetch (' billing/payment_methods' , { internal: true }),
61- ])
62- customer .value = customerData as any
63- paymentMethods .value = paymentMethodsData as any []
64- } catch (error ) {
65- console .error (' Error fetching payment data:' , error )
66- }
67- }
63+ const { data : customerData } = useQuery ({
64+ queryKey: [' billing' , ' customer' ],
65+ queryFn : () => labrinth .billing_internal .getCustomer (),
66+ })
6867
69- function fetchStock(region : any , request : any ) {
70- return useServersFetch (` stock?region=${region .shortcode } ` , {
71- method: ' POST' ,
72- body: {
73- ... request ,
74- },
75- bypassAuth: true ,
76- }).then ((res : any ) => res .available as number )
77- }
68+ const { data : paymentMethodsData, refetch : refetchPaymentMethods } = useQuery ({
69+ queryKey: [' billing' , ' payment-methods' ],
70+ queryFn : () => labrinth .billing_internal .getPaymentMethods (),
71+ })
72+
73+ const { data : regionsData } = useQuery ({
74+ queryKey: [' servers' , ' regions' ],
75+ queryFn : () => archon .servers_v1 .getRegions (),
76+ })
77+
78+ watch (customerData , (newCustomer ) => {
79+ if (newCustomer ) customer .value = newCustomer
80+ })
81+
82+ watch (paymentMethodsData , (newMethods ) => {
83+ if (newMethods ) paymentMethods .value = newMethods
84+ })
7885
79- function pingRegions() {
80- useServersFetch (' regions' , {
81- method: ' GET' ,
82- version: 1 ,
83- bypassAuth: true ,
84- }).then ((res : any ) => {
85- regions .value = res as any []
86- ;(regions .value as any []).forEach ((region : any ) => {
86+ watch (regionsData , (newRegions ) => {
87+ if (newRegions ) {
88+ newRegions .forEach ((region ) => {
8789 runPingTest (region )
8890 })
89- })
91+ }
92+ })
93+
94+ async function fetchPaymentData() {
95+ await refetchPaymentMethods ()
96+ }
97+
98+ async function fetchStock(
99+ region : Archon .Servers .v1 .Region ,
100+ request : Archon .Servers .v0 .StockRequest ,
101+ ): Promise <number > {
102+ const result = await archon .servers_v0 .checkStock (region .shortcode , request )
103+ return result .available
90104}
91105
92106const PING_COUNT = 20
@@ -141,24 +155,22 @@ function runPingTest(region: any, index = 1) {
141155 }
142156}
143157
144- const subscription = ref <UserSubscription | null >(null )
158+ const subscription = ref <Labrinth . Billing . Internal . UserSubscription | null >(null )
145159// Dry run state
146160const dryRunResponse = ref <{
147161 requires_payment: boolean
148162 required_payment_is_proration: boolean
149163} | null >(null )
150- const pendingDowngradeBody = ref <any | null >(null )
151- const currentPlanFromSubscription = computed <ServerPlan | undefined >(() => {
164+ const pendingDowngradeBody = ref <Labrinth . Billing . Internal . EditSubscriptionRequest | null >(null )
165+ const currentPlanFromSubscription = computed <Labrinth . Billing . Internal . Product | undefined >(() => {
152166 return subscription .value
153- ? (pyroProducts .find (
154- (p ) =>
155- p .prices .filter ((price : { id: string }) => price .id === subscription .value ?.price_id )
156- .length > 0 ,
167+ ? (pyroProducts .find ((p ) =>
168+ p .prices .some ((price ) => price .id === subscription .value ?.price_id ),
157169 ) ?? undefined )
158170 : undefined
159171})
160172
161- const currentInterval = computed (() => {
173+ const currentInterval = computed < ' monthly ' | ' quarterly ' > (() => {
162174 const interval = subscription .value ?.interval
163175
164176 if (interval === ' monthly' || interval === ' quarterly' ) {
@@ -167,26 +179,45 @@ const currentInterval = computed(() => {
167179 return ' monthly'
168180})
169181
170- async function initiatePayment(body : any ): Promise <any > {
182+ const editSubscriptionMutation = useMutation ({
183+ mutationFn : async ({
184+ id ,
185+ body ,
186+ dry ,
187+ }: {
188+ id: string
189+ body: Labrinth .Billing .Internal .EditSubscriptionRequest
190+ dry: boolean
191+ }) => {
192+ return await labrinth .billing_internal .editSubscription (id , body , dry )
193+ },
194+ })
195+
196+ async function initiatePayment(
197+ body : Labrinth .Billing .Internal .InitiatePaymentRequest ,
198+ ): Promise <Labrinth .Billing .Internal .EditSubscriptionResponse | null > {
171199 if (subscription .value ) {
172- const transformedBody = {
173- interval: body .charge ?. interval ,
200+ const transformedBody: Labrinth . Billing . Internal . EditSubscriptionRequest = {
201+ interval: body .charge . type === ' new ' ? body . charge . interval : undefined ,
174202 payment_method: body .type === ' confirmation_token' ? body .token : body .id ,
175- product: body .charge ?. product_id ,
203+ product: body .charge . type === ' new ' ? body . charge . product_id : undefined ,
176204 region: body .metadata ?.server_region ,
177205 }
178206
179207 try {
180- const dry = await useBaseFetch (` billing/subscription/${subscription .value .id }?dry=true ` , {
181- internal: true ,
182- method: ' PATCH' ,
208+ const dry = await editSubscriptionMutation .mutateAsync ({
209+ id: subscription .value .id ,
183210 body: transformedBody ,
211+ dry: true ,
184212 })
185213
186- if (dry && typeof dry === ' object' && ' requires_payment' in dry ) {
187- dryRunResponse .value = dry as any
214+ if (dry && typeof dry === ' object' && ' payment_intent_id' in dry ) {
215+ dryRunResponse .value = {
216+ requires_payment: !! dry .payment_intent_id ,
217+ required_payment_is_proration: true ,
218+ }
188219 pendingDowngradeBody .value = transformedBody
189- if (dry .requires_payment ) {
220+ if (dry .payment_intent_id ) {
190221 return await finalizeImmediate (transformedBody )
191222 } else {
192223 return null
@@ -209,14 +240,16 @@ async function initiatePayment(body: any): Promise<any> {
209240 }
210241}
211242
212- async function finalizeImmediate(body : any ) {
213- const result = await useBaseFetch (` billing/subscription/${subscription .value ?.id } ` , {
214- internal: true ,
215- method: ' PATCH' ,
243+ async function finalizeImmediate(body : Labrinth .Billing .Internal .EditSubscriptionRequest ) {
244+ if (! subscription .value ) return null
245+
246+ const result = await editSubscriptionMutation .mutateAsync ({
247+ id: subscription .value .id ,
216248 body ,
249+ dry: false ,
217250 })
218251
219- return result
252+ return result ?? null
220253}
221254
222255async function finalizeDowngrade() {
@@ -243,11 +276,9 @@ async function finalizeDowngrade() {
243276
244277async function open(id ? : string ) {
245278 if (id ) {
246- const subscriptions = (await useBaseFetch (` billing/subscriptions ` , {
247- internal: true ,
248- })) as any []
279+ const subscriptions = await labrinth .billing_internal .getSubscriptions ()
249280 for (const sub of subscriptions ) {
250- if (sub ?.metadata ?.id === id ) {
281+ if (sub ?.metadata ?.type === ' pyro ' && sub . metadata . id === id ) {
251282 subscription .value = sub
252283 break
253284 }
@@ -262,9 +293,4 @@ async function open(id?: string) {
262293defineExpose ({
263294 open ,
264295})
265-
266- onMounted (() => {
267- fetchPaymentData ()
268- pingRegions ()
269- })
270296 </script >
0 commit comments