Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add premium subscriptions endpoint #1321

Merged
merged 11 commits into from
Nov 7, 2024
86 changes: 85 additions & 1 deletion api/ApiHelper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
parsePlayer,
parsePopularSearch,
parsePremiumProducts,
parsePremiumSubscription,
parsePrivacySettings,
parseProfitableCrafts,
parseRecentAuction,
Expand Down Expand Up @@ -2568,6 +2569,86 @@ export function initAPI(returnSSRResponse: boolean = false): API {
})
}

let purchasePremiumSubscription = (productSlug: string, googleToken: string): Promise<PaymentResponse> => {
return new Promise((resolve, reject) => {
httpApi.sendApiRequest({
type: RequestType.PURCHASE_PREMIUM_SUBSCRIPTION,
customRequestURL: `${getApiEndpoint()}/premium/subscription/${productSlug}`,
requestMethod: 'POST',
data: '',
requestHeader: {
GoogleToken: googleToken,
'Content-Type': 'application/json'
},
resolve: data => {
resolve(parsePaymentResponse(data))
},
reject: (error: any) => {
apiErrorHandler(RequestType.PURCHASE_PREMIUM_SUBSCRIPTION, error)
reject(error)
}
})
})
}

let getPremiumSubscriptions = (): Promise<PremiumSubscription[]> => {
return new Promise((resolve, reject) => {
let googleId = sessionStorage.getItem('googleId')
if (!googleId) {
toast.error('You need to be logged in to create a premium subscription.')
reject()
return
}

httpApi.sendApiRequest({
type: RequestType.CREATE_PREMIUM_SUBSCRIPTION,
customRequestURL: `${getApiEndpoint()}/premium/subscription`,
requestMethod: 'GET',
data: '',
requestHeader: {
GoogleToken: googleId,
'Content-Type': 'application/json'
},
resolve: (subscriptions: any = []) => {
resolve(subscriptions.map(parsePremiumSubscription))
},
reject: (error: any) => {
apiErrorHandler(RequestType.CREATE_PREMIUM_SUBSCRIPTION, error)
reject(error)
}
})
})
}

let cancelPremiumSubscription = (id: string): Promise<void> => {
return new Promise((resolve, reject) => {
let googleId = sessionStorage.getItem('googleId')
if (!googleId) {
toast.error('You need to be logged in to cancel a premium subscription.')
reject()
return
}

httpApi.sendApiRequest({
type: RequestType.DELETE_PREMIUM_SUBSCRIPTION,
customRequestURL: `${getApiEndpoint()}/premium/subscription/${id}`,
requestMethod: 'DELETE',
data: '',
requestHeader: {
GoogleToken: googleId,
'Content-Type': 'application/json'
},
resolve: () => {
resolve()
},
reject: (error: any) => {
apiErrorHandler(RequestType.DELETE_PREMIUM_SUBSCRIPTION, error)
reject(error)
}
})
})
}

return {
search,
trackSearch,
Expand Down Expand Up @@ -2660,7 +2741,10 @@ export function initAPI(returnSSRResponse: boolean = false): API {
updateConfig,
requestArchivedAuctions,
exportArchivedAuctionsData,
getLinkvertiseLink
getLinkvertiseLink,
getPremiumSubscriptions,
cancelPremiumSubscription,
purchasePremiumSubscription
}
}

Expand Down
5 changes: 4 additions & 1 deletion api/ApiTypes.d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ export enum RequestType {
UPDATE_CONFIG = 'updateConfig',
ARCHIVED_AUCTIONS = 'archivedAuctions',
EXPORT_ARCHIVED_AUCTIONS = 'exportArchivedAuctions',
GET_LINKVERTISE_LINK = 'getLinkvertiseLink'
GET_LINKVERTISE_LINK = 'getLinkvertiseLink',
CREATE_PREMIUM_SUBSCRIPTION = 'createPremiumSubscription',
DELETE_PREMIUM_SUBSCRIPTION = 'deletePremiumSubscription',
PURCHASE_PREMIUM_SUBSCRIPTION = 'purchasePremiumSubscription'
}

export enum SubscriptionType {
Expand Down
27 changes: 24 additions & 3 deletions components/AccountDetails/AccountDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ function AccountDetails() {
let [isLoading, setIsLoading] = useState(true)
let [rerenderGoogleSignIn, setRerenderGoogleSignIn] = useState(0)
let [products, setProducts] = useState<PremiumProduct[]>([])
let [premiumSubscriptions, setPremiumSubscriptions] = useState<PremiumSubscription[]>([])
let [showSendcoflcoins, setShowSendCoflcoins] = useState(false)
let coflCoins = useCoflCoins()
let { pushInstruction } = useMatomo()
Expand Down Expand Up @@ -54,7 +55,13 @@ function AccountDetails() {
return api.refreshLoadPremiumProducts(products => {
products = products.filter(product => product.expires.getTime() > new Date().getTime())
setProducts(products)
setIsLoading(false)
})
}

function loadPremiumSubscriptions(): Promise<void> {
return api.getPremiumSubscriptions().then(subscriptions => {
subscriptions = subscriptions.filter(subscription => !subscription.endsAt || subscription.endsAt.getTime() > new Date().getTime())
setPremiumSubscriptions([...subscriptions])
})
}

Expand All @@ -75,7 +82,9 @@ function AccountDetails() {
let googleId = sessionStorage.getItem('googleId')
setIsLoading(true)
if (googleId) {
loadPremiumProducts()
Promise.all([loadPremiumProducts(), loadPremiumSubscriptions()]).then(() => {
setIsLoading(false)
})
setIsLoggedIn(true)
}
}
Expand Down Expand Up @@ -116,6 +125,13 @@ function AccountDetails() {
setRerenderGoogleSignIn(rerenderGoogleSignIn + 1)
}

function onSubscriptionCancel(subscription: PremiumSubscription) {
api.cancelPremiumSubscription(subscription.externalId).then(() => {
toast.success('Subscription cancelled')
loadPremiumSubscriptions()
})
}

return (
<>
<h2 style={{ marginBottom: '30px' }}>
Expand All @@ -127,7 +143,12 @@ function AccountDetails() {
<p>
<span className={styles.label}>Account:</span> {getAccountElement()}
</p>
<PremiumStatus products={products} labelStyle={{ width: '300px', fontWeight: 'bold' }} />
<PremiumStatus
products={products}
subscriptions={premiumSubscriptions}
onSubscriptionCancel={onSubscriptionCancel}
labelStyle={{ width: '300px', fontWeight: 'bold' }}
/>
<p>
<span className={styles.label}>CoflCoins:</span> <Number number={coflCoins} />
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ function TransactionHistory() {
if (transaction.productId.startsWith('pre_api')) {
return 'Bought Pre-API'
}
if (transaction.productId.startsWith('l_premium')) {
return 'Bought Premium (Subscription)'
}
if (transaction.productId.startsWith('l_prem_plus')) {
return 'Bought Premium+ (Subscription)'
}

const parts = transaction.reference.split('.')
let suffix = ''
Expand Down
77 changes: 68 additions & 9 deletions components/Premium/BuyPremium/BuyPremium.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
'use client'
import { ChangeEvent, useState } from 'react'
import { Button, Card, Form, Modal, ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
import { Button, Card, Form, ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
import { toast } from 'react-toastify'
import api from '../../../api/ApiHelper'
import { CUSTOM_EVENTS } from '../../../api/ApiTypes.d'
import { useCoflCoins } from '../../../utils/Hooks'
import { getPremiumType, PREMIUM_TYPES } from '../../../utils/PremiumTypeUtils'
import { PREMIUM_TYPES } from '../../../utils/PremiumTypeUtils'
import { CoflCoinsDisplay } from '../../CoflCoins/CoflCoinsDisplay'
import Number from '../../Number/Number'
import styles from './BuyPremium.module.css'
import BuyPremiumConfirmationDialog from './BuyPremiumConfirmationDialog'
import BuyPremiumConfirmationDialog from '../BuyPremiumConfirmationDialog/BuyPremiumConfirmationDialog'

interface Props {
activePremiumProduct: PremiumProduct
premiumSubscriptions: PremiumSubscription[]
onNewActivePremiumProduct()
}

Expand All @@ -21,7 +22,8 @@ function BuyPremium(props: Props) {
let [purchaseSuccessfulOption, setPurchaseSuccessfulDuration] = useState<PremiumTypeOption>()
let [isPurchasing, setIsPurchasing] = useState(false)
let [purchasePremiumOption, setPurchasePremiumOption] = useState<PremiumTypeOption>(PREMIUM_TYPES[0].options[0])
let [showConfirmationDialog, setShowConfirmationDialog] = useState(false)
let [showPrepaidConfirmationDialog, setShowPrepaidConfirmationDialog] = useState(false)
let [showSubscriptionConfirmationDialog, setShowSubscriptionConfirmationDialog] = useState(false)
let coflCoins = useCoflCoins()

function onDurationChange(event: ChangeEvent<HTMLSelectElement>) {
Expand All @@ -38,7 +40,7 @@ function BuyPremium(props: Props) {
}

function onPremiumBuy(googleToken: string) {
setShowConfirmationDialog(false)
setShowPrepaidConfirmationDialog(false)
setIsPurchasing(true)

api.purchaseWithCoflcoins(purchasePremiumOption.productId, googleToken, purchasePremiumOption.value).then(() => {
Expand All @@ -54,14 +56,42 @@ function BuyPremium(props: Props) {
})
}

function onSubscriptionBuy(googleToken: string) {
let productId = ''
if (purchasePremiumType.productId === 'premium') {
productId = 'l_premium'
}
if (purchasePremiumType.productId === 'premium_plus') {
productId = 'l_prem_plus'
}

api.purchasePremiumSubscription(productId, googleToken).then(data => {
window.open(data.directLink, '_self')
})
}

function onPremiumBuyCancel() {
setShowConfirmationDialog(false)
setShowPrepaidConfirmationDialog(false)
}

function onSubscriptionBuyCancel() {
setShowSubscriptionConfirmationDialog(false)
}

function getPurchasePrice() {
return purchasePremiumOption.value * purchasePremiumOption.price
}

function getSubscriptionPrice() {
if (purchasePremiumType.productId === 'premium') {
return 8.69
}
if (purchasePremiumType.productId === 'premium_plus') {
return 29.69
}
return -1
}

function getDurationString(): string {
let durationString = purchasePremiumType.durationString
let duration = +purchasePremiumOption.value
Expand Down Expand Up @@ -151,12 +181,26 @@ function BuyPremium(props: Props) {
</div>
) : null}
<p style={{ marginTop: '20px' }}>This is a prepaid service. We won't automatically charge you after your premium time runs out!</p>
{(purchasePremiumType.productId === 'premium' || purchasePremiumType.productId === 'premium_plus') &&
(!props.premiumSubscriptions || props.premiumSubscriptions.length === 0) ? (
<p style={{ marginTop: '20px' }}>
If you want to it to automatically renew itself,{' '}
<span
onClick={() => {
setShowSubscriptionConfirmationDialog(true)
}}
style={{ color: '#007bff', cursor: 'pointer' }}
>
click here
</span>
</p>
) : null}
<hr />
<Button
style={{ marginTop: '10px' }}
variant="success"
onClick={() => {
setShowConfirmationDialog(true)
setShowPrepaidConfirmationDialog(true)
}}
disabled={getPurchasePrice() > coflCoins || isPurchasing}
>
Expand All @@ -181,15 +225,30 @@ function BuyPremium(props: Props) {
</div>
</Card>
<BuyPremiumConfirmationDialog
show={showConfirmationDialog}
type="prepaid"
show={showPrepaidConfirmationDialog}
durationString={getDurationString()}
purchasePremiumOption={purchasePremiumOption}
purchasePrice={getPurchasePrice()}
purchasePrice={
<>
<Number number={getPurchasePrice()} /> CoflCoins
</>
}
purchasePremiumType={purchasePremiumType}
onHide={onPremiumBuyCancel}
onConfirm={onPremiumBuy}
activePremiumProduct={props.activePremiumProduct}
/>
<BuyPremiumConfirmationDialog
type="subscription"
show={showSubscriptionConfirmationDialog}
purchasePremiumOption={purchasePremiumOption}
purchasePrice={<>{getSubscriptionPrice()} per month</>}
purchasePremiumType={purchasePremiumType}
onHide={onSubscriptionBuyCancel}
onConfirm={onSubscriptionBuy}
activePremiumProduct={props.activePremiumProduct}
/>
</>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.label {
width: 200px;
float: left;
margin: 0;
}
Loading
Loading