mirror of
https://github.com/Amperra-Group/xcs.git
synced 2025-08-16 13:22: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
|
# Mail
|
||||||
SENDGRID_API_KEY=
|
SENDGRID_API_KEY=
|
||||||
|
|
||||||
|
# Gumroad License Verification
|
||||||
|
GUMROAD_PRODUCT_ID=
|
||||||
|
|
||||||
# Firebase Authentication and Image Storage
|
# Firebase Authentication and Image Storage
|
||||||
FIREBASE_ADMIN_auth_provider_x509_cert_url=
|
FIREBASE_ADMIN_auth_provider_x509_cert_url=
|
||||||
FIREBASE_ADMIN_auth_uri=
|
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'}
|
fontWeight={'400'}
|
||||||
pb={2}
|
pb={2}
|
||||||
>
|
>
|
||||||
What is Restrafes XCS?
|
What is Wyre Management?
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text fontSize={'xl'}>
|
<Text fontSize={'xl'}>
|
||||||
Restrafes XCS is a powerful access control system designed to help you manage access points for your
|
Wyre Management 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
|
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
|
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
|
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
|
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.
|
to manage access with confidence.
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -61,7 +61,7 @@ export default function Invitation({ invite, errorMessage }: { invite: Invitatio
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>Invitation - Restrafes XCS</title>
|
<title>Invitation - Wyre Management</title>
|
||||||
</Head>
|
</Head>
|
||||||
<Container
|
<Container
|
||||||
maxW={'container.lg'}
|
maxW={'container.lg'}
|
||||||
|
@ -81,7 +81,7 @@ export default function Invitation({ invite, errorMessage }: { invite: Invitatio
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src={useColorModeValue('/images/logo-black.png', '/images/logo-white.png')}
|
src={useColorModeValue('/images/logo-black.png', '/images/logo-white.png')}
|
||||||
alt={'Restrafes XCS Logo'}
|
alt={'Wyre Management Logo'}
|
||||||
w={'auto'}
|
w={'auto'}
|
||||||
h={'24px'}
|
h={'24px'}
|
||||||
objectFit={'contain'}
|
objectFit={'contain'}
|
||||||
|
@ -243,7 +243,7 @@ export default function Invitation({ invite, errorMessage }: { invite: Invitatio
|
||||||
as={'span'}
|
as={'span'}
|
||||||
fontWeight={'bold'}
|
fontWeight={'bold'}
|
||||||
>
|
>
|
||||||
Restrafes XCS
|
Wyre Management
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
<Text
|
<Text
|
||||||
as={'span'}
|
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 { admin, app } from '@/pages/api/firebase';
|
||||||
import { Invitation, User } from '@/types';
|
import { Invitation, User } from '@/types';
|
||||||
import { NextApiRequest, NextApiResponse } from 'next';
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { verifyGumroadLicense } from '@/lib/gumroad';
|
||||||
|
|
||||||
import clientPromise from '@/lib/mongodb';
|
import clientPromise from '@/lib/mongodb';
|
||||||
const sgMail = require('@sendgrid/mail');
|
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 };
|
let { activationCode } = req.query as { activationCode: string };
|
||||||
activationCode = decodeURIComponent(activationCode);
|
activationCode = decodeURIComponent(activationCode);
|
||||||
|
|
||||||
|
console.log('Activation attempt with code:', activationCode);
|
||||||
|
|
||||||
|
// First, try to find an invitation code
|
||||||
const invitation = (await invitations.findOne({
|
const invitation = (await invitations.findOne({
|
||||||
type: 'xcs',
|
type: 'xcs',
|
||||||
code: activationCode
|
code: activationCode
|
||||||
})) as Invitation | null;
|
})) as Invitation | null;
|
||||||
|
|
||||||
|
let isGumroadLicense = false;
|
||||||
|
let gumroadVerification = null;
|
||||||
|
|
||||||
|
// If no invitation found, check if it's a Gumroad license key
|
||||||
if (!invitation) {
|
if (!invitation) {
|
||||||
return res.status(404).json({
|
console.log('No invitation found, checking if it\'s a Gumroad license key');
|
||||||
valid: false,
|
gumroadVerification = await verifyGumroadLicense(activationCode);
|
||||||
message: 'Invalid activation code. Please check the code and try again.'
|
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 (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({
|
return res.status(403).json({
|
||||||
valid: false,
|
valid: false,
|
||||||
message: `This activation code has reached its maximum uses.`
|
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({
|
return res.status(200).json({
|
||||||
valid: true,
|
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 };
|
let { activationCode } = req.query as { activationCode: string };
|
||||||
|
activationCode = decodeURIComponent(activationCode);
|
||||||
|
|
||||||
let { displayName, email, username, password } = req.body as {
|
let { displayName, email, username, password } = req.body as {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
@ -49,16 +76,54 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
password: string;
|
password: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log('Registration attempt with:', { displayName, email, username, activationCode });
|
||||||
|
|
||||||
const mongoClient = await clientPromise;
|
const mongoClient = await clientPromise;
|
||||||
const db = mongoClient.db(process.env.MONGODB_DB as string);
|
const db = mongoClient.db(process.env.MONGODB_DB as string);
|
||||||
const users = db.collection('users');
|
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({
|
return res.status(403).json({
|
||||||
message: `This activation code has reached its maximum uses.`
|
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
|
// Check body for missing fields and character length
|
||||||
if (
|
if (
|
||||||
// !firstName ||
|
// !firstName ||
|
||||||
|
@ -194,12 +259,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
url: `${process.env.NEXT_PUBLIC_ROOT_URL}/home`
|
url: `${process.env.NEXT_PUBLIC_ROOT_URL}/home`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
], platform: {
|
||||||
platform: {
|
|
||||||
staff: false,
|
staff: false,
|
||||||
staffTitle: null,
|
staffTitle: null,
|
||||||
membership: 0,
|
membership: 0,
|
||||||
invites: invitation.startingReferrals || 0
|
invites: postInvitation?.startingReferrals || 0
|
||||||
},
|
},
|
||||||
payment: {
|
payment: {
|
||||||
customerId: null
|
customerId: null
|
||||||
|
@ -220,24 +284,33 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
referrals: 0,
|
referrals: 0,
|
||||||
scans: 0
|
scans: 0
|
||||||
},
|
},
|
||||||
achievements: {},
|
achievements: {}, license_key: postIsGumroadLicense ? activationCode : '',
|
||||||
|
gumroad_status: postIsGumroadLicense ? 'active' as const : 'unknown' as const,
|
||||||
sponsorId: invitation.isSponsor ? invitation.createdBy : null,
|
sponsorId: postInvitation?.isSponsor ? postInvitation.createdBy : null,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date()
|
updatedAt: new Date()
|
||||||
} as User)
|
} as User) .then(async (result) => {
|
||||||
.then(async (result) => {
|
// Only handle invitation-specific logic if this was an invitation code
|
||||||
// max uses
|
if (postInvitation) {
|
||||||
if (invitation.maxUses > -1 && invitation.uses + 1 >= invitation.maxUses) {
|
// max uses
|
||||||
await invitations.deleteOne({
|
if (postInvitation.maxUses > -1 && postInvitation.uses + 1 >= postInvitation.maxUses) {
|
||||||
code: activationCode[0]
|
await invitations.deleteOne({
|
||||||
});
|
code: activationCode
|
||||||
} else {
|
});
|
||||||
await invitations.updateOne({ code: activationCode[0] }, { $inc: { uses: 1 } });
|
} else {
|
||||||
|
await invitations.updateOne({ code: activationCode }, { $inc: { uses: 1 } });
|
||||||
|
}
|
||||||
|
// sponsors
|
||||||
|
if (postInvitation.isSponsor) {
|
||||||
|
await users.updateOne({ id: postInvitation.createdBy }, { $inc: { 'platform.invites': -1 } });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// sponsors
|
console.log('User created successfully:', firebaseUser.uid);
|
||||||
if (invitation.isSponsor) {
|
|
||||||
await users.updateOne({ id: invitation.createdBy }, { $inc: { 'platform.invites': -1 } });
|
if (postIsGumroadLicense) {
|
||||||
|
console.log('User registered with Gumroad license:', activationCode);
|
||||||
|
} else {
|
||||||
|
console.log('User registered with invitation code:', activationCode);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
@ -277,11 +350,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
} return res.status(200).json({
|
||||||
|
message: postIsGumroadLicense
|
||||||
return res.status(200).json({
|
? 'Successfully registered with Gumroad license! You may now login.'
|
||||||
message: 'Successfully registered! You may now login.',
|
: 'Successfully registered with invitation code! You may now login.',
|
||||||
success: true
|
success: true,
|
||||||
|
registrationType: postIsGumroadLicense ? 'gumroad' : 'invitation'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -524,6 +524,20 @@ export default function Login() {
|
||||||
variant={'outline'}
|
variant={'outline'}
|
||||||
/>
|
/>
|
||||||
</InputGroup>
|
</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>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -48,9 +48,10 @@ export interface User {
|
||||||
referrals: number;
|
referrals: number;
|
||||||
scans: number;
|
scans: number;
|
||||||
organizationInvitations?: number;
|
organizationInvitations?: number;
|
||||||
};
|
}; achievements?: Record<string, Achievement>;
|
||||||
achievements?: Record<string, Achievement>;
|
|
||||||
organizations?: Organization[];
|
organizations?: Organization[];
|
||||||
|
license_key: string;
|
||||||
|
gumroad_status: 'active' | 'inactive' | 'unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Organization {
|
export interface Organization {
|
||||||
|
|
|
@ -74,7 +74,7 @@ To configure access points for this location, visit:
|
||||||
--]]
|
--]]
|
||||||
|
|
||||||
local settings = {
|
local settings = {
|
||||||
["url"] = "https://xcs.restrafes.co/api/v1/ingame";
|
["url"] = "https://wyre.ryj.my.id/api/v1/ingame";
|
||||||
|
|
||||||
["placeId"] = "{{locationId}}";
|
["placeId"] = "{{locationId}}";
|
||||||
["apiKey"] = "{{apiKey}}";
|
["apiKey"] = "{{apiKey}}";
|
||||||
|
|
Loading…
Reference in a new issue