icemusike avatar

jvzoo-integration

Expert JVZoo integration specialist for building backend systems that handle JVZoo purchases, create

提供方 icemusike|开源

JVZoo Integration Specialist

Expert guidance for building production-ready JVZoo integrations that handle purchases, license management, and account creation.

Overview

JVZoo integration involves three core components:

  1. IPN (Instant Payment Notification) - Real-time webhook from JVZoo when purchases occur
  2. License Management - Generate, validate, and track software licenses
  3. Account Creation - Automatically create user accounts from purchase data

IPN Setup & Security

IPN Endpoint Requirements

Your IPN endpoint must:

  • Accept POST requests from JVZoo servers
  • Respond within 10 seconds with HTTP 200
  • Validate authenticity using secret key
  • Handle idempotent processing (duplicates possible)
  • Log all requests for debugging

IPN Verification

Always verify IPN authenticity using SHA-1 hash:

Verification Hash = SHA1(secretKey + "|" + transactionId + "|" + amount + "|" + productId)

Compare this hash with the cverify parameter sent by JVZoo. Never process unverified IPNs.

Essential IPN Parameters

Transaction Info:

  • ctransaction - Transaction ID (use as idempotency key)
  • ctransaction_type - SALE, RFND (refund), CGBK (chargeback), INSTAL (recurring), CANCEL-REBILL
  • ctransreceipt - JVZoo receipt number
  • cproditem - Product ID (your product identifier)
  • cprodtype - STANDARD, RECURRING, etc.

Customer Info:

  • ccustemail - Customer email (primary identifier)
  • ccustname - Customer full name
  • ccustcc - Customer country code
  • ccuststate - Customer state/region

Financial Info:

  • ctransamount - Sale amount
  • ctransaffiliate - Affiliate commission
  • ctransvendor - Your earnings

Verification:

  • cverify - SHA-1 hash for verification

Account Creation Workflow

Step 1: Verify IPN

function verifyIPN(ipnData, secretKey) {
  const { ctransaction, ctransamount, cproditem, cverify } = ipnData;
  const hash = crypto
    .createHash('sha1')
    .update(`${secretKey}|${ctransaction}|${ctransamount}|${cproditem}`)
    .digest('hex')
    .toUpperCase();
  
  return hash === cverify.toUpperCase();
}

Step 2: Check for Duplicate Processing

async function isDuplicateTransaction(transactionId) {
  // Query database for existing transaction
  const existing = await db.transactions.findOne({ 
    jvzoo_transaction_id: transactionId 
  });
  return !!existing;
}

Step 3: Create or Update User Account

async function processNewSale(ipnData) {
  const { ccustemail, ccustname, cproditem, ctransaction } = ipnData;
  
  // Find or create user
  let user = await db.users.findOne({ email: ccustemail });
  
  if (!user) {
    user = await db.users.create({
      email: ccustemail,
      name: ccustname,
      created_via: 'jvzoo',
      jvzoo_transaction: ctransaction
    });
  }
  
  return user;
}

Step 4: Generate License Key

function generateLicenseKey(userId, productId, transactionId) {
  const timestamp = Date.now();
  const data = `${userId}:${productId}:${transactionId}:${timestamp}`;
  const hash = crypto
    .createHash('sha256')
    .update(data + process.env.LICENSE_SECRET)
    .digest('hex');
  
  // Format: XXXX-XXXX-XXXX-XXXX
  return hash.substring(0, 16).toUpperCase().match(/.{1,4}/g).join('-');
}

Step 5: Store License & Send Access

async function createLicense(userId, productId, ipnData) {
  const licenseKey = generateLicenseKey(
    userId, 
    productId, 
    ipnData.ctransaction
  );
  
  const license = await db.licenses.create({
    user_id: userId,
    product_id: productId,
    license_key: licenseKey,
    jvzoo_transaction: ipnData.ctransaction,
    jvzoo_receipt: ipnData.ctransreceipt,
    status: 'active',
    purchase_date: new Date(),
    transaction_type: ipnData.ctransaction_type
  });
  
  // Send email with license key and login details
  await sendWelcomeEmail(userId, licenseKey);
  
  return license;
}

Transaction Type Handling

SALE (New Purchase)

case 'SALE':
  await processNewSale(ipnData);
  await createLicense(user.id, ipnData.cproditem, ipnData);
  break;

RFND (Refund)

case 'RFND':
  await db.licenses.update(
    { jvzoo_transaction: ipnData.ctransaction },
    { status: 'refunded', refunded_at: new Date() }
  );
  await revokeAccess(user.id);
  break;

CGBK (Chargeback)

case 'CGBK':
  await db.licenses.update(
    { jvzoo_transaction: ipnData.ctransaction },
    { status: 'chargeback', chargeback_at: new Date() }
  );
  await revokeAccess(user.id);
  break;

INSTAL (Recurring Payment)

case 'INSTAL':
  await db.licenses.update(
    { jvzoo_transaction: ipnData.ctransaction },
    { 
      status: 'active',
      last_payment: new Date(),
      payment_count: { $inc: 1 }
    }
  );
  break;

CANCEL-REBILL (Subscription Cancelled)

case 'CANCEL-REBILL':
  await db.licenses.update(
    { jvzoo_transaction: ipnData.ctransaction },
    { 
      status: 'cancelled',
      cancelled_at: new Date()
    }
  );
  // Grace period before revoking access
  await scheduleAccessRevocation(user.id, 30); // 30 days
  break;

License Validation API

Build an API endpoint your software calls to validate licenses:

// POST /api/validate-license
async function validateLicense(req, res) {
  const { license_key, email } = req.body;
  
  const license = await db.licenses.findOne({
    license_key: license_key,
    status: 'active'
  });
  
  if (!license) {
    return res.json({ valid: false, error: 'Invalid license' });
  }
  
  const user = await db.users.findById(license.user_id);
  
  if (user.email !== email) {
    return res.json({ valid: false, error: 'Email mismatch' });
  }
  
  // Update last validation timestamp
  await db.licenses.update(
    { _id: license._id },
    { last_validated: new Date() }
  );
  
  return res.json({
    valid: true,
    license: {
      product_id: license.product_id,
      expiry_date: license.expiry_date,
      features: license.features
    }
  });
}

Database Schema Recommendations

Users Table

CREATE TABLE users (
  id UUID PRIMARY KEY,
  email VARCHAR(255) UNIQUE NOT NULL,
  name VARCHAR(255),
  created_via VARCHAR(50),
  jvzoo_transaction VARCHAR(255),
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

Licenses Table

CREATE TABLE licenses (
  id UUID PRIMARY KEY,
  user_id UUID REFERENCES users(id),
  product_id VARCHAR(100) NOT NULL,
  license_key VARCHAR(50) UNIQUE NOT NULL,
  jvzoo_transaction VARCHAR(255) UNIQUE NOT NULL,
  jvzoo_receipt VARCHAR(255),
  status VARCHAR(50) NOT NULL, -- active, refunded, chargeback, cancelled
  purchase_date TIMESTAMP NOT NULL,
  expiry_date TIMESTAMP,
  last_validated TIMESTAMP,
  transaction_type VARCHAR(50),
  refunded_at TIMESTAMP,
  chargeback_at TIMESTAMP,
  cancelled_at TIMESTAMP,
  payment_count INTEGER DEFAULT 1,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

Transactions Table (Audit Log)

CREATE TABLE jvzoo_transactions (
  id UUID PRIMARY KEY,
  jvzoo_transaction_id VARCHAR(255) UNIQUE NOT NULL,
  transaction_type VARCHAR(50) NOT NULL,
  product_id VARCHAR(100) NOT NULL,
  customer_email VARCHAR(255) NOT NULL,
  amount DECIMAL(10,2),
  raw_ipn_data JSONB,
  processed BOOLEAN DEFAULT FALSE,
  processed_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW()
);

Product ID Mapping

Map JVZoo product IDs to your internal product identifiers:

const PRODUCT_MAPPING = {
  '123456': {
    name: 'AI Ranker Pro - Basic',
    features: ['feature1', 'feature2'],
    internal_id: 'ai-ranker-basic'
  },
  '123457': {
    name: 'AI Ranker Pro - Premium',
    features: ['feature1', 'feature2', 'feature3', 'feature4'],
    internal_id: 'ai-ranker-premium'
  },
  '123458': {
    name: 'Callfluent AI - Starter',
    features: ['100-calls', 'basic-voices'],
    internal_id: 'callfluent-starter'
  }
};

function getProductConfig(jvzooProductId) {
  return PRODUCT_MAPPING[jvzooProductId] || null;
}

Security Best Practices

Environment Variables

Never hardcode credentials:

JVZOO_SECRET_KEY=your_secret_key_here
LICENSE_SECRET=separate_key_for_license_generation
DATABASE_URL=your_database_connection
SMTP_API_KEY=email_service_key

Rate Limiting

Protect your IPN endpoint from abuse:

const rateLimit = require('express-rate-limit');

const ipnLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Max 100 requests per IP
  message: 'Too many requests from this IP'
});

app.post('/ipn', ipnLimiter, handleIPN);

Input Validation

Always validate and sanitize IPN data:

function sanitizeIPNData(data) {
  return {
    ctransaction: String(data.ctransaction || '').trim(),
    ccustemail: String(data.ccustemail || '').toLowerCase().trim(),
    ccustname: String(data.ccustname || '').trim(),
    cproditem: String(data.cproditem || '').trim(),
    ctransamount: parseFloat(data.ctransamount || 0),
    // ... sanitize all fields
  };
}

Error Handling

Always return 200 OK to prevent retries, but log errors:

app.post('/ipn', async (req, res) => {
  try {
    // Always acknowledge receipt
    res.status(200).send('OK');
    
    // Process asynchronously
    await processIPN(req.body);
  } catch (error) {
    // Log error but don't tell JVZoo about it
    console.error('IPN Processing Error:', error);
    await logError(error, req.body);
  }
});

Testing Your Integration

Test Mode

JVZoo provides test IPNs. Look for test indicators:

function isTestTransaction(ipnData) {
  return ipnData.ctransaction.toLowerCase().includes('test') ||
         ipnData.ccustemail.toLowerCase().includes('test');
}

Manual IPN Testing

Send test IPN to your endpoint:

curl -X POST https://yoursite.com/ipn \
  -d "ctransaction=TEST123456" \
  -d "ccustemail=test@example.com" \
  -d "ccustname=Test User" \
  -d "cproditem=123456" \
  -d "ctransamount=97.00" \
  -d "ctransaction_type=SALE" \
  -d "cverify=YOUR_CALCULATED_HASH"

Validation Checklist

  • IPN verification working correctly
  • Duplicate transactions prevented
  • User accounts created successfully
  • License keys generated and stored
  • Welcome emails sent
  • Refunds revoke access
  • Chargebacks logged
  • Recurring payments extend access
  • All transactions logged for audit
  • Error handling returns 200 OK

Common Implementation Frameworks

Node.js + Express

See references/nodejs-implementation.md for complete Express.js example with middleware, error handling, and database integration.

Python + FastAPI

See references/python-implementation.md for FastAPI implementation with async handling and Pydantic validation.

Next.js API Routes

See references/nextjs-implementation.md for serverless implementation using Next.js API routes with Supabase.

Troubleshooting

IPN not being received:

  • Check JVZoo IPN URL configuration in product settings
  • Verify endpoint is publicly accessible (not localhost)
  • Check firewall/server logs
  • Ensure endpoint responds within 10 seconds

Hash verification failing:

  • Verify secret key matches JVZoo dashboard
  • Check parameter order in hash calculation
  • Ensure string concatenation uses pipes (|)
  • Convert hash to uppercase for comparison

Duplicate accounts created:

  • Implement idempotency check using ctransaction
  • Use database constraints on transaction ID
  • Log all IPN requests to audit table

License validation failing:

  • Check license status in database
  • Verify email matches purchase email
  • Ensure license hasn't been refunded/charged back
  • Check for expiry dates on time-limited licenses

Quick Start Template

For fastest implementation, use scripts/setup_ipn_endpoint.js to generate boilerplate IPN handler with your framework of choice (Express, FastAPI, Next.js).

See references/deployment-guide.md for production deployment considerations including SSL, monitoring, logging, and backup strategies.

jvzoo-integration - 适用于 Claude Code 与 Cursor 的 AI 智能体 Skill | Agent Skills