Skip to content

Commit

Permalink
Merge pull request #1321 from Coflnet/premium-subscriptions
Browse files Browse the repository at this point in the history
add premium subscriptions endpoint
  • Loading branch information
matthias-luger authored Nov 7, 2024
2 parents b9ea3f3 + e24121b commit 105516f
Show file tree
Hide file tree
Showing 13 changed files with 365 additions and 31 deletions.
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

0 comments on commit 105516f

Please sign in to comment.