mirror of
https://github.com/Amperra-Group/xcs.git
synced 2025-08-16 05:12:14 -06:00
added gumroad integration, updated starter reader
This commit is contained in:
parent
f5865928a8
commit
fc2fc63500
|
@ -30,6 +30,9 @@ ROBLOX_CLIENT_SECRET=""
|
|||
# Mail
|
||||
SENDGRID_API_KEY=
|
||||
|
||||
# Gumroad License Verification
|
||||
GUMROAD_PRODUCT_ID=
|
||||
|
||||
# Firebase Authentication and Image Storage
|
||||
FIREBASE_ADMIN_auth_provider_x509_cert_url=
|
||||
FIREBASE_ADMIN_auth_uri=
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 6.1 KiB |
|
@ -94,15 +94,15 @@ export default function Home({ allPostsData: posts }: { allPostsData: any }) {
|
|||
fontWeight={'400'}
|
||||
pb={2}
|
||||
>
|
||||
What is Restrafes XCS?
|
||||
What is Wyre Management?
|
||||
</Heading>
|
||||
<Text fontSize={'xl'}>
|
||||
Restrafes XCS is a powerful access control system designed to help you manage access points for your
|
||||
building. With Restrafes XCS, you can easily and securely control who has access to your property, including
|
||||
Wyre Management is a powerful access control system designed to help you manage access points for your
|
||||
building. With Wyre Management, you can easily and securely control who has access to your property, including
|
||||
employees and visitors. The system is highly customizable, allowing you to set access levels and permissions
|
||||
for different users, and offers a range of advanced features such as real-time monitoring, reporting, and
|
||||
reverse-compatibility with other systems. Whether you're looking to enhance the security of your
|
||||
business or residential property, Restrafes XCS provides the flexibility and reliability you need
|
||||
business or residential property, Wyre Management provides the flexibility and reliability you need
|
||||
to manage access with confidence.
|
||||
</Text>
|
||||
</Box>
|
||||
|
|
|
@ -61,7 +61,7 @@ export default function Invitation({ invite, errorMessage }: { invite: Invitatio
|
|||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Invitation - Restrafes XCS</title>
|
||||
<title>Invitation - Wyre Management</title>
|
||||
</Head>
|
||||
<Container
|
||||
maxW={'container.lg'}
|
||||
|
@ -81,7 +81,7 @@ export default function Invitation({ invite, errorMessage }: { invite: Invitatio
|
|||
>
|
||||
<Image
|
||||
src={useColorModeValue('/images/logo-black.png', '/images/logo-white.png')}
|
||||
alt={'Restrafes XCS Logo'}
|
||||
alt={'Wyre Management Logo'}
|
||||
w={'auto'}
|
||||
h={'24px'}
|
||||
objectFit={'contain'}
|
||||
|
@ -243,7 +243,7 @@ export default function Invitation({ invite, errorMessage }: { invite: Invitatio
|
|||
as={'span'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
Restrafes XCS
|
||||
Wyre Management
|
||||
</Text>{' '}
|
||||
<Text
|
||||
as={'span'}
|
||||
|
|
140
src/lib/gumroad.ts
Normal file
140
src/lib/gumroad.ts
Normal file
|
@ -0,0 +1,140 @@
|
|||
interface GumroadLicenseResponse {
|
||||
success: boolean;
|
||||
uses: number;
|
||||
purchase: {
|
||||
product_name: string;
|
||||
product_id: string;
|
||||
created_at: string;
|
||||
full_name: string;
|
||||
purchaser_id: string;
|
||||
product_permalink: string;
|
||||
id: string;
|
||||
variants: string;
|
||||
test: boolean;
|
||||
status: string;
|
||||
subscription_id?: string;
|
||||
licence_key: string;
|
||||
quantity: number;
|
||||
gumroad_fee: number;
|
||||
currency: string;
|
||||
order_number: number;
|
||||
sale_id: string;
|
||||
sale_timestamp: string;
|
||||
url_params: Record<string, any>;
|
||||
ip_country: string;
|
||||
is_gift_receiver_purchase: boolean;
|
||||
refunded: boolean;
|
||||
disputed: boolean;
|
||||
dispute_won: boolean;
|
||||
is_multiseat_license: boolean;
|
||||
subscription_ended_at?: string;
|
||||
subscription_cancelled_at?: string;
|
||||
subscription_failed_at?: string;
|
||||
is_recurring_billing: boolean;
|
||||
can_contact: boolean;
|
||||
discover_fee_charged: boolean;
|
||||
};
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export async function verifyGumroadLicense(licenseKey: string): Promise<{
|
||||
isValid: boolean;
|
||||
isActive: boolean;
|
||||
status: 'active' | 'inactive' | 'unknown';
|
||||
message?: string;
|
||||
}> {
|
||||
try {
|
||||
console.log('Verifying Gumroad license key:', licenseKey);
|
||||
|
||||
const productId = process.env.GUMROAD_PRODUCT_ID;
|
||||
|
||||
if (!productId) {
|
||||
console.error('GUMROAD_PRODUCT_ID is not set in environment variables');
|
||||
return {
|
||||
isValid: false,
|
||||
isActive: false,
|
||||
status: 'unknown',
|
||||
message: 'Server configuration error'
|
||||
};
|
||||
}
|
||||
|
||||
console.log('Using product ID:', productId);
|
||||
|
||||
const response = await fetch('https://api.gumroad.com/v2/licenses/verify', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
product_id: productId,
|
||||
license_key: licenseKey,
|
||||
increment_uses_count: 'false'
|
||||
})
|
||||
});
|
||||
|
||||
console.log('Gumroad API response status:', response.status);
|
||||
const data: GumroadLicenseResponse = await response.json();
|
||||
console.log('Gumroad API response data:', JSON.stringify(data, null, 2));
|
||||
|
||||
if (!data.success) {
|
||||
console.log('Gumroad license verification failed:', data.message);
|
||||
return {
|
||||
isValid: false,
|
||||
isActive: false,
|
||||
status: 'inactive',
|
||||
message: data.message || 'Invalid license key'
|
||||
};
|
||||
}
|
||||
|
||||
const purchase = data.purchase;
|
||||
console.log('Purchase details:', JSON.stringify(purchase, null, 2));
|
||||
|
||||
// Check if the purchase is refunded or disputed
|
||||
if (purchase.refunded || purchase.disputed) {
|
||||
console.log('License has been refunded or disputed');
|
||||
return {
|
||||
isValid: true,
|
||||
isActive: false,
|
||||
status: 'inactive',
|
||||
message: 'License has been refunded or disputed'
|
||||
};
|
||||
}
|
||||
|
||||
// Check subscription status for recurring purchases
|
||||
if (purchase.is_recurring_billing) {
|
||||
console.log('Checking subscription status for recurring purchase');
|
||||
// If subscription has ended, cancelled, or failed
|
||||
if (purchase.subscription_ended_at || purchase.subscription_cancelled_at || purchase.subscription_failed_at) {
|
||||
console.log('Subscription has ended, cancelled, or failed:', {
|
||||
ended_at: purchase.subscription_ended_at,
|
||||
cancelled_at: purchase.subscription_cancelled_at,
|
||||
failed_at: purchase.subscription_failed_at
|
||||
});
|
||||
return {
|
||||
isValid: true,
|
||||
isActive: false,
|
||||
status: 'inactive',
|
||||
message: 'Subscription has ended or been cancelled'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// License is valid and active
|
||||
console.log('License is valid and active');
|
||||
return {
|
||||
isValid: true,
|
||||
isActive: true,
|
||||
status: 'active',
|
||||
message: 'License is valid and active'
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error verifying Gumroad license:', error);
|
||||
return {
|
||||
isValid: false,
|
||||
isActive: false,
|
||||
status: 'unknown',
|
||||
message: 'Error verifying license'
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import { admin, app } from '@/pages/api/firebase';
|
||||
import { Invitation, User } from '@/types';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { verifyGumroadLicense } from '@/lib/gumroad';
|
||||
|
||||
import clientPromise from '@/lib/mongodb';
|
||||
const sgMail = require('@sendgrid/mail');
|
||||
|
@ -13,34 +14,60 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
let { activationCode } = req.query as { activationCode: string };
|
||||
activationCode = decodeURIComponent(activationCode);
|
||||
|
||||
console.log('Activation attempt with code:', activationCode);
|
||||
|
||||
// First, try to find an invitation code
|
||||
const invitation = (await invitations.findOne({
|
||||
type: 'xcs',
|
||||
code: activationCode
|
||||
})) as Invitation | null;
|
||||
|
||||
let isGumroadLicense = false;
|
||||
let gumroadVerification = null;
|
||||
|
||||
// If no invitation found, check if it's a Gumroad license key
|
||||
if (!invitation) {
|
||||
return res.status(404).json({
|
||||
valid: false,
|
||||
message: 'Invalid activation code. Please check the code and try again.'
|
||||
});
|
||||
console.log('No invitation found, checking if it\'s a Gumroad license key');
|
||||
gumroadVerification = await verifyGumroadLicense(activationCode);
|
||||
isGumroadLicense = gumroadVerification.isValid;
|
||||
|
||||
if (!isGumroadLicense) {
|
||||
console.log('Neither valid invitation nor valid Gumroad license');
|
||||
return res.status(404).json({
|
||||
valid: false,
|
||||
message: 'Invalid activation code or license key. Please check the code and try again.'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Valid Gumroad license found');
|
||||
} else {
|
||||
console.log('Valid invitation found:', invitation.code);
|
||||
}
|
||||
|
||||
if (req.method === 'GET') {
|
||||
if (invitation?.maxUses > -1 && invitation?.uses >= invitation?.maxUses) {
|
||||
// For invitation codes, check max uses
|
||||
if (invitation && invitation?.maxUses > -1 && invitation?.uses >= invitation?.maxUses) {
|
||||
return res.status(403).json({
|
||||
valid: false,
|
||||
message: `This activation code has reached its maximum uses.`
|
||||
});
|
||||
}
|
||||
|
||||
// For Gumroad licenses, check if active
|
||||
if (isGumroadLicense && gumroadVerification && !gumroadVerification.isActive) {
|
||||
return res.status(403).json({
|
||||
valid: false,
|
||||
message: 'This Gumroad license is not active. Please check your subscription status.'
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
valid: true,
|
||||
message: 'Valid activation code.'
|
||||
message: isGumroadLicense ? 'Valid Gumroad license key.' : 'Valid activation code.'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.method === 'POST') {
|
||||
} if (req.method === 'POST') {
|
||||
let { activationCode } = req.query as { activationCode: string };
|
||||
activationCode = decodeURIComponent(activationCode);
|
||||
|
||||
let { displayName, email, username, password } = req.body as {
|
||||
displayName: string;
|
||||
|
@ -49,16 +76,54 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
password: string;
|
||||
};
|
||||
|
||||
console.log('Registration attempt with:', { displayName, email, username, activationCode });
|
||||
|
||||
const mongoClient = await clientPromise;
|
||||
const db = mongoClient.db(process.env.MONGODB_DB as string);
|
||||
const users = db.collection('users');
|
||||
const invitations = db.collection('invitations');
|
||||
|
||||
if (invitation.uses > -1 && invitation.uses >= invitation.maxUses) {
|
||||
// Re-validate the activation code/license for POST request
|
||||
const postInvitation = (await invitations.findOne({
|
||||
type: 'xcs',
|
||||
code: activationCode
|
||||
})) as Invitation | null;
|
||||
|
||||
let postIsGumroadLicense = false;
|
||||
let postGumroadVerification = null;
|
||||
|
||||
// If no invitation found, check if it's a Gumroad license key
|
||||
if (!postInvitation) {
|
||||
console.log('No invitation found in POST, checking if it\'s a Gumroad license key');
|
||||
postGumroadVerification = await verifyGumroadLicense(activationCode);
|
||||
postIsGumroadLicense = postGumroadVerification.isValid;
|
||||
|
||||
if (!postIsGumroadLicense) {
|
||||
console.log('Neither valid invitation nor valid Gumroad license in POST');
|
||||
return res.status(404).json({
|
||||
message: 'Invalid activation code or license key. Please check the code and try again.'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Valid Gumroad license found in POST');
|
||||
} else {
|
||||
console.log('Valid invitation found in POST:', postInvitation.code);
|
||||
}
|
||||
|
||||
// Check invitation-specific limitations
|
||||
if (postInvitation && postInvitation.maxUses > -1 && postInvitation.uses >= postInvitation.maxUses) {
|
||||
return res.status(403).json({
|
||||
message: `This activation code has reached its maximum uses.`
|
||||
});
|
||||
}
|
||||
|
||||
// For Gumroad licenses, ensure it's still active
|
||||
if (postIsGumroadLicense && postGumroadVerification && !postGumroadVerification.isActive) {
|
||||
return res.status(403).json({
|
||||
message: 'This Gumroad license is not active. Please check your subscription status.'
|
||||
});
|
||||
}
|
||||
|
||||
// Check body for missing fields and character length
|
||||
if (
|
||||
// !firstName ||
|
||||
|
@ -194,12 +259,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
url: `${process.env.NEXT_PUBLIC_ROOT_URL}/home`
|
||||
}
|
||||
}
|
||||
],
|
||||
platform: {
|
||||
], platform: {
|
||||
staff: false,
|
||||
staffTitle: null,
|
||||
membership: 0,
|
||||
invites: invitation.startingReferrals || 0
|
||||
invites: postInvitation?.startingReferrals || 0
|
||||
},
|
||||
payment: {
|
||||
customerId: null
|
||||
|
@ -220,24 +284,33 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
referrals: 0,
|
||||
scans: 0
|
||||
},
|
||||
achievements: {},
|
||||
|
||||
sponsorId: invitation.isSponsor ? invitation.createdBy : null,
|
||||
achievements: {}, license_key: postIsGumroadLicense ? activationCode : '',
|
||||
gumroad_status: postIsGumroadLicense ? 'active' as const : 'unknown' as const,
|
||||
sponsorId: postInvitation?.isSponsor ? postInvitation.createdBy : null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
} as User)
|
||||
.then(async (result) => {
|
||||
// max uses
|
||||
if (invitation.maxUses > -1 && invitation.uses + 1 >= invitation.maxUses) {
|
||||
await invitations.deleteOne({
|
||||
code: activationCode[0]
|
||||
});
|
||||
} else {
|
||||
await invitations.updateOne({ code: activationCode[0] }, { $inc: { uses: 1 } });
|
||||
} as User) .then(async (result) => {
|
||||
// Only handle invitation-specific logic if this was an invitation code
|
||||
if (postInvitation) {
|
||||
// max uses
|
||||
if (postInvitation.maxUses > -1 && postInvitation.uses + 1 >= postInvitation.maxUses) {
|
||||
await invitations.deleteOne({
|
||||
code: activationCode
|
||||
});
|
||||
} else {
|
||||
await invitations.updateOne({ code: activationCode }, { $inc: { uses: 1 } });
|
||||
}
|
||||
// sponsors
|
||||
if (postInvitation.isSponsor) {
|
||||
await users.updateOne({ id: postInvitation.createdBy }, { $inc: { 'platform.invites': -1 } });
|
||||
}
|
||||
}
|
||||
// sponsors
|
||||
if (invitation.isSponsor) {
|
||||
await users.updateOne({ id: invitation.createdBy }, { $inc: { 'platform.invites': -1 } });
|
||||
console.log('User created successfully:', firebaseUser.uid);
|
||||
|
||||
if (postIsGumroadLicense) {
|
||||
console.log('User registered with Gumroad license:', activationCode);
|
||||
} else {
|
||||
console.log('User registered with invitation code:', activationCode);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
|
@ -277,11 +350,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
message: 'Successfully registered! You may now login.',
|
||||
success: true
|
||||
} return res.status(200).json({
|
||||
message: postIsGumroadLicense
|
||||
? 'Successfully registered with Gumroad license! You may now login.'
|
||||
: 'Successfully registered with invitation code! You may now login.',
|
||||
success: true,
|
||||
registrationType: postIsGumroadLicense ? 'gumroad' : 'invitation'
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -524,6 +524,20 @@ export default function Login() {
|
|||
variant={'outline'}
|
||||
/>
|
||||
</InputGroup>
|
||||
<Text fontSize={'sm'}>
|
||||
Don't have an activation code?{' '}
|
||||
<Text as={'span'}>
|
||||
<Link
|
||||
as={NextLink}
|
||||
href={'https://amperra.gumroad.com/l/wyre'}
|
||||
textDecor={'underline'}
|
||||
textUnderlineOffset={4}
|
||||
whiteSpace={'nowrap'}
|
||||
>
|
||||
Purchase a license here.
|
||||
</Link>
|
||||
</Text>{' '}
|
||||
</Text>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
|
|
|
@ -48,9 +48,10 @@ export interface User {
|
|||
referrals: number;
|
||||
scans: number;
|
||||
organizationInvitations?: number;
|
||||
};
|
||||
achievements?: Record<string, Achievement>;
|
||||
}; achievements?: Record<string, Achievement>;
|
||||
organizations?: Organization[];
|
||||
license_key: string;
|
||||
gumroad_status: 'active' | 'inactive' | 'unknown';
|
||||
}
|
||||
|
||||
export interface Organization {
|
||||
|
|
|
@ -74,7 +74,7 @@ To configure access points for this location, visit:
|
|||
--]]
|
||||
|
||||
local settings = {
|
||||
["url"] = "https://xcs.restrafes.co/api/v1/ingame";
|
||||
["url"] = "https://wyre.ryj.my.id/api/v1/ingame";
|
||||
|
||||
["placeId"] = "{{locationId}}";
|
||||
["apiKey"] = "{{apiKey}}";
|
||||
|
|
Loading…
Reference in a new issue