get_charset_collate();
$t_products = $wpdb->prefix . 'ppg_products';
$t_wallet = $wpdb->prefix . 'ppg_wallet';
$t_trans = $wpdb->prefix . 'ppg_transactions';
$t_withdraw = $wpdb->prefix . 'ppg_withdrawals';
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
$sql = "
CREATE TABLE $t_products (
id BIGINT(20) NOT NULL AUTO_INCREMENT,
name VARCHAR(191) NOT NULL,
reward INT NOT NULL DEFAULT 0,
time_allowed INT NOT NULL DEFAULT 60,
affiliate_url TEXT,
PRIMARY KEY (id)
) $charset_collate;
CREATE TABLE $t_wallet (
user_id BIGINT(20) NOT NULL,
balance BIGINT(20) NOT NULL DEFAULT 0,
PRIMARY KEY (user_id)
) $charset_collate;
CREATE TABLE $t_trans (
id BIGINT(20) NOT NULL AUTO_INCREMENT,
user_id BIGINT(20) NOT NULL,
amount INT NOT NULL,
type VARCHAR(50) NOT NULL,
note TEXT,
created_at DATETIME NOT NULL,
PRIMARY KEY (id)
) $charset_collate;
CREATE TABLE $t_withdraw (
id BIGINT(20) NOT NULL AUTO_INCREMENT,
user_id BIGINT(20) NOT NULL,
upi VARCHAR(191),
amount INT NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'pending',
created_at DATETIME NOT NULL,
updated_at DATETIME NULL,
PRIMARY KEY (id)
) $charset_collate;
";
dbDelta($sql);
// default options: affiliate tag + levels
if(get_option('ppg_affiliate_tag') === false) update_option('ppg_affiliate_tag','yourtag-21');
if(get_option('ppg_levels') === false){
$default = [
["MOBILE",10,60],
["LAPTOP",12,70],
["SHOES",8,45],
["WATCH",9,50],
["TABLET",10,60],
["HEADPHONE",9,55],
["CAMERA",11,65]
];
update_option('ppg_levels',$default);
}
}
/* --------------------------
Admin menu and pages (+ wallets view)
----------------------------*/
add_action('admin_menu','ppg_admin_menu');
function ppg_admin_menu(){
add_menu_page('Product Puzzle','Product Puzzle','manage_options','ppg-admin','ppg_admin_page','dashicons-games',6);
add_submenu_page('ppg-admin','Withdrawals','Withdrawals','manage_options','ppg-withdrawals','ppg_withdrawals_page');
add_submenu_page('ppg-admin','Wallets','Wallets','manage_options','ppg-wallets','ppg_wallets_page');
}
function ppg_admin_page(){
if(!current_user_can('manage_options')) return;
// Save settings (levels etc)
if(isset($_POST['ppg_save'])){
check_admin_referer('ppg_save_settings','ppg_nonce');
update_option('ppg_affiliate_tag', sanitize_text_field($_POST['affiliate_tag']));
$levels = [];
if(!empty($_POST['level_name']) && is_array($_POST['level_name'])){
foreach($_POST['level_name'] as $i=>$n){
$name = sanitize_text_field($n);
$reward = intval($_POST['level_reward'][$i]);
$time_allowed = intval($_POST['level_time'][$i]);
if($name) $levels[] = [$name,$reward,$time_allowed];
}
}
update_option('ppg_levels',$levels);
echo '';
}
$affiliate = esc_attr(get_option('ppg_affiliate_tag',''));
$levels = get_option('ppg_levels', []);
?>
prefix . 'ppg_wallet';
$t_trans = $wpdb->prefix . 'ppg_transactions';
$wallets = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$t_wallet} ORDER BY balance DESC LIMIT %d", 500));
$recent_trans = $wpdb->get_results("SELECT t.*, u.user_login, u.user_email FROM $t_trans t LEFT JOIN {$wpdb->users} u ON u.ID = t.user_id ORDER BY t.created_at DESC LIMIT 200");
?>
prefix . 'ppg_wallet';
// ensure wallet row
$bal = $wpdb->get_var($wpdb->prepare("SELECT balance FROM {$t_wallet} WHERE user_id=%d",$user_id));
if($bal === null){
$res = $wpdb->insert($t_wallet,['user_id'=>$user_id,'balance'=>0]);
if($res === false) wp_send_json_error('db_error: '.$wpdb->last_error);
$bal = 0;
}
$bal = intval($bal);
$correct = intval(get_user_meta($user_id,'ppg_correct',true) ?: 0);
$help = intval(get_user_meta($user_id,'ppg_help',true) ?: 0);
$aff = intval(get_user_meta($user_id,'ppg_aff',true) ?: 0);
$level = intval(get_user_meta($user_id,'ppg_level',true) ?: 0);
$playSeconds = intval(get_user_meta($user_id,'ppg_play_seconds',true) ?: 0);
wp_send_json_success([
'balance'=>$bal,
'correct'=>$correct,
'help'=>$help,
'aff'=>$aff,
'level'=>$level,
'playSeconds'=>$playSeconds
]);
}
/* FIXED award: server-only */
add_action('wp_ajax_ppg_award','ppg_award');
function ppg_award(){
check_ajax_referer('ppg_nonce','nonce');
if(!is_user_logged_in()){
wp_send_json_error('login_required');
}
global $wpdb;
$user_id = get_current_user_id();
$amount = intval($_POST['amount'] ?? 0);
$note = sanitize_text_field($_POST['note'] ?? 'level_reward');
if($amount <= 0){
wp_send_json_error('invalid_amount');
}
$t_wallet = $wpdb->prefix . 'ppg_wallet';
$t_trans = $wpdb->prefix . 'ppg_transactions';
// ensure wallet
$bal = $wpdb->get_var($wpdb->prepare("SELECT balance FROM {$t_wallet} WHERE user_id=%d",$user_id));
if($bal === null){
$ins = $wpdb->insert($t_wallet, ['user_id'=>$user_id,'balance'=>0]);
if($ins === false) wp_send_json_error('db_error: '.$wpdb->last_error);
$bal = 0;
}
$bal = intval($bal);
// compute new balance and update
$newbal = $bal + $amount;
$updated = $wpdb->update($t_wallet, ['balance'=>$newbal], ['user_id'=>$user_id], ['%d'], ['%d']);
if($updated === false){
wp_send_json_error('db_error: '.$wpdb->last_error);
}
// insert transaction log
$ins = $wpdb->insert($t_trans, [
'user_id'=>$user_id,
'amount'=>$amount,
'type'=>'credit',
'note'=>$note,
'created_at'=>current_time('mysql')
]);
if($ins === false){
wp_send_json_error('db_error_trans: '.$wpdb->last_error);
}
// update counters
$correct = intval(get_user_meta($user_id,'ppg_correct',true) ?: 0) + 1;
update_user_meta($user_id,'ppg_correct',$correct);
$levels = get_option('ppg_levels', []);
$current_level = intval(get_user_meta($user_id,'ppg_level',true) ?: 0);
$max_level = max(0, count($levels)-1);
$new_level = ($current_level < $max_level) ? ($current_level + 1) : $max_level;
update_user_meta($user_id,'ppg_level',$new_level);
$final = $wpdb->get_var($wpdb->prepare("SELECT balance FROM {$t_wallet} WHERE user_id=%d",$user_id));
if($final === null) wp_send_json_error('db_error_final: '.$wpdb->last_error);
wp_send_json_success(['balance'=>intval($final),'level'=>$new_level,'correct'=>$correct]);
}
/* affiliate click */
add_action('wp_ajax_ppg_aff_click','ppg_aff_click');
add_action('wp_ajax_nopriv_ppg_aff_click','ppg_aff_click');
function ppg_aff_click(){
check_ajax_referer('ppg_nonce','nonce');
global $wpdb;
$t_trans = $wpdb->prefix . 'ppg_transactions';
$user_id = get_current_user_id() ?: 0;
$product = sanitize_text_field($_POST['product'] ?? '');
$wpdb->insert($t_trans,[
'user_id'=>$user_id,
'amount'=>0,
'type'=>'affiliate_click',
'note'=>$product,
'created_at'=>current_time('mysql')
]);
if($user_id){
$aff = intval(get_user_meta($user_id,'ppg_aff',true) ?: 0) + 1;
update_user_meta($user_id,'ppg_aff',$aff);
}
wp_send_json_success();
}
/* withdraw */
add_action('wp_ajax_ppg_withdraw','ppg_withdraw');
add_action('wp_ajax_nopriv_ppg_withdraw','ppg_withdraw');
function ppg_withdraw(){
check_ajax_referer('ppg_nonce','nonce');
if(!is_user_logged_in()){
wp_send_json_error('login_required');
}
global $wpdb;
$user_id = get_current_user_id();
$upi = sanitize_text_field($_POST['upi'] ?? '');
$amount = intval($_POST['amount'] ?? 0);
if(!$upi || $amount <= 0){
wp_send_json_error('invalid_input');
}
if($amount < 50){
wp_send_json_error('min_withdraw_50');
}
$t_withdraw = $wpdb->prefix . 'ppg_withdrawals';
$t_wallet = $wpdb->prefix . 'ppg_wallet';
$t_trans = $wpdb->prefix . 'ppg_transactions';
// ensure wallet exists
$bal_db = $wpdb->get_var($wpdb->prepare("SELECT balance FROM {$t_wallet} WHERE user_id=%d",$user_id));
if($bal_db === null){
$wpdb->insert($t_wallet, ['user_id'=>$user_id,'balance'=>0]);
if($wpdb->last_error) wp_send_json_error('db_error: '.$wpdb->last_error);
$bal_db = 0;
}
$bal = intval($bal_db);
if($bal < $amount){
wp_send_json_error('insufficient');
}
// create withdraw request (pending)
$inserted = $wpdb->insert($t_withdraw,[
'user_id'=>$user_id,
'upi'=>$upi,
'amount'=>$amount,
'status'=>'pending',
'created_at'=>current_time('mysql')
]);
if(!$inserted) wp_send_json_error('db_error: '.$wpdb->last_error);
// log transaction (type withdraw_request)
$wpdb->insert($t_trans,[
'user_id'=>$user_id,
'amount'=>0,
'type'=>'withdraw_request',
'note'=>"Withdraw request to $upi",
'created_at'=>current_time('mysql')
]);
// notify admin
$admin_email = get_option('admin_email');
if($admin_email){
$user_info = get_userdata($user_id);
$user_name = $user_info ? $user_info->display_name : "User #{$user_id}";
$subject = "New Withdrawal Request — Product Puzzle (ID: {$wpdb->insert_id})";
$message = "Hello Admin,\n\nA new withdrawal request was created:\n\n";
$message .= "Request ID: {$wpdb->insert_id}\nUser ID: {$user_id}\n";
if($user_info && $user_info->user_email) $message .= "User Email: {$user_info->user_email}\n";
$message .= "User Name: {$user_name}\nAmount: {$amount}\nUPI: {$upi}\nCreated: ".current_time('mysql')."\n\nApprove in WP Admin > Product Puzzle > Withdrawals.";
wp_mail($admin_email, $subject, $message);
}
wp_send_json_success(['message'=>'request_created','id'=>$wpdb->insert_id]);
}
/* get wallet */
add_action('wp_ajax_ppg_get_wallet','ppg_get_wallet');
add_action('wp_ajax_nopriv_ppg_get_wallet','ppg_get_wallet');
function ppg_get_wallet(){
check_ajax_referer('ppg_nonce','nonce');
if(!is_user_logged_in()){
wp_send_json_error('login_required');
}
global $wpdb;
$user_id = get_current_user_id();
$t_wallet = $wpdb->prefix . 'ppg_wallet';
$bal = $wpdb->get_var($wpdb->prepare("SELECT balance FROM {$t_wallet} WHERE user_id=%d",$user_id));
if($bal === null){
$wpdb->insert($t_wallet,['user_id'=>$user_id,'balance'=>0]);
if($wpdb->last_error) wp_send_json_error('db_error: '.$wpdb->last_error);
$bal = 0;
}
wp_send_json_success(['balance'=>intval($bal)]);
}
/* --------------------------
Admin withdrawals page (approve/reject sends user email)
----------------------------*/
function ppg_withdrawals_page(){
if(!current_user_can('manage_options')) return;
global $wpdb;
$t_withdraw = $wpdb->prefix . 'ppg_withdrawals';
$t_wallet = $wpdb->prefix . 'ppg_wallet';
$t_trans = $wpdb->prefix . 'ppg_transactions';
// process actions: approve/reject
if(isset($_GET['action']) && isset($_GET['id'])){
if(check_admin_referer('ppg_withdraw_action','ppg_withdraw_nonce')){
$id = intval($_GET['id']);
$action = sanitize_text_field($_GET['action']);
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t_withdraw} WHERE id=%d",$id));
if($row){
$user_id = intval($row->user_id);
$amount = intval($row->amount);
$user_info = get_userdata($user_id);
$user_email = $user_info ? $user_info->user_email : '';
if($action === 'approve' && $row->status === 'pending'){
$bal = $wpdb->get_var($wpdb->prepare("SELECT balance FROM {$t_wallet} WHERE user_id=%d",$user_id));
$bal = ($bal === null) ? 0 : intval($bal);
if($bal >= $amount){
$wpdb->update($t_wallet, ['balance' => $bal - $amount], ['user_id' => $user_id], ['%d'], ['%d']);
$wpdb->insert($t_trans,[
'user_id'=>$user_id,
'amount'=>-$amount,
'type'=>'withdraw_approved',
'note'=>"Withdraw approved (id {$id})",
'created_at'=>current_time('mysql')
]);
$wpdb->update($t_withdraw,['status'=>'approved','updated_at'=>current_time('mysql')],['id'=>$id]);
if($user_email){
$subject = "Your withdrawal request #{$id} approved";
$msg = "Hello,\n\nYour withdrawal request (ID: {$id}) for amount {$amount} has been approved by admin.\nUPI: {$row->upi}\n\nRegards,\nProduct Puzzle";
wp_mail($user_email, $subject, $msg);
}
} else {
$wpdb->update($t_withdraw,['status'=>'rejected','updated_at'=>current_time('mysql')],['id'=>$id]);
if($user_email){
$subject = "Your withdrawal request #{$id} rejected";
$msg = "Hello,\n\nYour withdrawal request (ID: {$id}) was rejected due to insufficient balance. Please contact admin.\n\nRegards,\nProduct Puzzle";
wp_mail($user_email, $subject, $msg);
}
}
} elseif($action === 'reject' && $row->status === 'pending'){
$wpdb->update($t_withdraw,['status'=>'rejected','updated_at'=>current_time('mysql')],['id'=>$id]);
if($user_email){
$subject = "Your withdrawal request #{$id} rejected";
$msg = "Hello,\n\nYour withdrawal request (ID: {$id}) has been rejected by admin.\n\nRegards,\nProduct Puzzle";
wp_mail($user_email, $subject, $msg);
}
}
}
}
}
$rows = $wpdb->get_results("SELECT w.*, u.user_login, u.user_email FROM {$t_withdraw} w LEFT JOIN {$wpdb->users} u ON u.ID = w.user_id ORDER BY w.created_at DESC");
?>
$l[0],'reward'=>intval($l[1]),'time'=>intval($l[2])];
}
$ajax_url = admin_url('admin-ajax.php');
$nonce = wp_create_nonce('ppg_nonce');
ob_start();
?>
Settings saved.
Product Puzzle — Settings
PPG Wallets & Transactions
Wallets (top 500)
| User ID | WP User | Balance |
|---|---|---|
| user_id); ?> | balance); ?> |
Recent Transactions (200)
| ID | User | Amount | Type | Note | Date | |
|---|---|---|---|---|---|---|
| id); ?> | user_id).' / '.esc_html($t->user_login); ?> | user_email); ?> | amount); ?> | type); ?> | note); ?> | created_at); ?> |
Withdrawals
| ID | User | UPI | Amount | Status | Created | Action | |
|---|---|---|---|---|---|---|---|
| id; ?> | user_id).' / '.esc_html($r->user_login); ?> | user_email); ?> | upi); ?> | amount); ?> | status); ?> | created_at); ?> | status === 'pending'): ?> Approve | Reject |
🧩 Product Puzzle
💰 0
Level 1
✔️ Correct: 0
❓ Help used: 0
🔗 Affiliate clicks: 0
⏱️ Play time today: 0s
⛽ Withdraw Petrol
Balance: 0 coins