$info) { if (!array_key_exists($name, $to_skip)) { $bitmap |= $info['bit']; } } return $bitmap; } function process_ipn() { if (!$_POST['receiver_email']) { return IPN_error_no_data; } $sql = 'INSERT INTO raw_ipn_msg (from_ip, timestamp, from_script) ' . 'VALUES ("' . mysql_escape_string($_SERVER["REMOTE_ADDR"]) . '", NOW(), "' . mysql_escape_string($_SERVER["SCRIPT_FILENAME"]) . '")'; $result = mysql_query($sql); if (!$result) { error_log("Error in " . $sql . ", " . mysql_error()); return IPN_error_database; } $msg_id = mysql_insert_id(); // read the post from PayPal system and add 'cmd' $req = 'cmd=_notify-validate'; $sql = ''; $field_num = 0; foreach ($_POST as $key => $value) { if ($field_num == 0) { $sql = 'INSERT INTO raw_ipn_field VALUES '; } else { $sql .= ', '; } $sql .= '(' . $msg_id . ', ' . $field_num . ', "' . mysql_escape_string($key) . '", "' . mysql_escape_string($value) . '")'; $field_num++; $value = urlencode($value); $req .= "&$key=$value"; } $result = mysql_query($sql); if (!$result) { error_log("Error in " . $sql . ", " . mysql_error()); return IPN_error_database; } // post back to PayPal system to validate $header = "POST /cgi-bin/webscr HTTP/1.1\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; $header .= "Host: " . IPN_server . "\r\n"; $header .= 'Content-Length: ' . strlen($req) . "\r\n"; $header .= "Connection: close\r\n\r\n"; $fp = fsockopen ('ssl://' . IPN_server, 443, $errno, $errstr, 40); if (!$fp) { set_error_reason($msg_id, "Automatic verification failed. Please verify manually."); //set_error_reason($msg_id, "Error in fopen: $errstr ($errno)"); //return IPN_error_no_verification_response; } else { fputs ($fp, $header . $req); while (!feof($fp)) { $res = fgets ($fp, 1024); if (strcmp ($res, "VERIFIED") == 0) { break; } // check the payment_status is Completed // check that txn_id has not been previously processed // check that receiver_email is an email address in your PayPal account // process payment } if (strcmp ($res, "INVALID") == 0) { set_error_reason($msg_id, "Rejected by paypal"); return IPN_error_verification_rejected; } fclose ($fp); } if ($_POST['receiver_email'] != our_paypal_id) { set_error_reason($msg_id, "Wrong receiver_email!"); return IPN_error_verification_rejected; } switch ($_POST['txn_type']) { case 'subscr_signup' : $turn_on_oddsmaker = false; $skip_exchanges = array(); switch ($_POST['item_name']) { case 'Standard service, no NASDAQ A': $required_fields = array("amount1" => "", "amount2" => "", "amount3" => "99.00", "period1" => "", "period2" => "", "period3" => "1 M"); $trial_period_days = 31; $trader_status = "pro"; // Not paying for NASDAQ. Might ask for NYSE or AMEX. We have to // turn them on manually. $skip_exchanges = array('NYSE', 'ARCA', 'AMEX', 'NASD'); break; case 'Advantage player service, no NASDAQ A': $required_fields = array("amount1" => "236.00", "amount2" => "", "amount3" => "43.00", "period1" => "1 M", "period2" => "", "period3" => "1 M"); $trial_period_days = 31; $trader_status = "pro"; $skip_exchanges = array('NYSE', 'ARCA', 'AMEX', 'NASD'); break; case 'Standard service, non-pro A': $required_fields = array("amount1" => "", "amount2" => "", "amount3" => "99.00", "period1" => "", "period2" => "", "period3" => "1 M"); $trial_period_days = 31; $trader_status = "non-pro"; // All exchanges are included in the price break; case 'Advantage player service, non-pro A': $required_fields = array("amount1" => "236.00", "amount2" => "", "amount3" => "43.00", "period1" => "1 M", "period2" => "", "period3" => "1 M"); $trial_period_days = 31; $trader_status = "non-pro"; break; case 'Standard service, pro A': $required_fields = array("amount1" => "", "amount2" => "", "amount3" => "95.00", "period1" => "", "period2" => "", "period3" => "1 M"); $trial_period_days = 31; $trader_status = "pro"; // Paying for NASDAQ. Might ask for NYSE or AMEX. We have to turn // them on manually. $skip_exchanges = array('NYSE', 'ARCA', 'AMEX'); break; case 'Advantage player service, pro A': $required_fields = array("amount1" => "256.00", "amount2" => "", "amount3" => "78.00", "period1" => "1 M", "period2" => "", "period3" => "1 M"); $trial_period_days = 31; $trader_status = "pro"; $skip_exchanges = array('NYSE', 'ARCA', 'AMEX'); break; case 'Manual Override Monthly': set_error_reason($msg_id, "Manual override required."); return IPN_error_verification_rejected; case 'Special Monthly Rate': $sql = "SELECT manual_subscription.id, oddsmaker " . "FROM users, manual_subscription " . "WHERE users.id=user_id AND username='" . mysql_escape_string($_POST['item_number']) . "' AND first_month='" . mysql_escape_string($_POST["amount1"]) . "' AND each_month='" . mysql_escape_string($_POST["amount3"]) . "' AND subscr_id='' AND valid_until > CURDATE() " . "LIMIT 1"; $result = mysql_query($sql); if (!$result) { error_log("Error in " . $sql . ", " . mysql_error()); return IPN_error_database; } $row = mysql_fetch_assoc($result); if (!$row) { set_error_reason($msg_id, "No matching request."); return IPN_error_verification_rejected; } $sql = "UPDATE manual_subscription " . "SET subscr_id='" . mysql_escape_string($_POST["subscr_id"]) . "' " . "WHERE id=" . $row['id']; $result = mysql_query($sql); if (!$result) { error_log("Error in " . $sql . ", " . mysql_error()); return IPN_error_database; } $turn_on_oddsmaker = $row['oddsmaker'] == "Y"; $required_fields = array("amount2" => "", "period1" => $_POST["amount1"]?"1 M":"", "period2" => "", "period3" => "1 M"); $trial_period_days = 31; $trader_status = "non-pro"; break; default : set_error_reason($msg_id, "unknown item name: " . $_POST['item_name']); return IPN_error_verification_rejected; } foreach ($required_fields as $field_name => $expected_value) { if ($_POST[$field_name] != $expected_value) { set_error_reason($msg_id, "Invalid value. Field $field_name was was " . $_POST[$field_name] . " but should have been $expected_value"); return IPN_error_verification_rejected; } } $sql = 'SELECT id FROM users WHERE username="' . mysql_escape_string($_POST['item_number']) . '"'; $result = mysql_query($sql); if (!$result) { error_log("Error in " . $sql . ", " . mysql_error()); return IPN_error_database; } $row = mysql_fetch_row($result); if (!$row) { error_log("(IPN subscr_signup) Unknown account name: " . $_POST['item_number']); $sql = 'INSERT IGNORE INTO valid_paypal_subscriptions ' . '(subscr_id, username) VALUES ("' . mysql_escape_string($_POST['subscr_id']) . '", "' . mysql_escape_string($_POST['item_number']) . '")'; } else { $trial_period_days += 9 + 6; // 9 days covers the automatic retries // for credit card problems. But an echeck from canada can take // even longer. $date = "FROM_UNIXTIME(" . strtotime($_POST['subscr_date']) . ") + INTERVAL $trial_period_days DAY"; $new_expr = array('authorization_expires' => $date); if ($turn_on_oddsmaker) $new_expr['oddsmaker_free'] = 'NULL'; $result = update_user_info($row[0], array('authorization_type' => 'paypal', 'authorization_code' => $_POST['subscr_id'], 'status' => $trader_status, 'paid_exchanges' => exchange_bitmap($skip_exchanges)), $new_expr, false, "PayPal"); if (!$result) { return IPN_error_database; } $sql = 'INSERT IGNORE INTO valid_paypal_subscriptions ' . '(subscr_id, username, user_id) VALUES ("' . mysql_escape_string($_POST['subscr_id']) . '", "' . mysql_escape_string($_POST['item_number']) . '", ' . $row[0] . ')'; } $result = mysql_query($sql); if (!$result) { error_log("Error in " . $sql . ", " . mysql_error()); return IPN_error_database; } break; case 'subscr_eot' : $sql = 'SELECT user_id FROM valid_paypal_subscriptions ' . 'WHERE subscr_id = "' . mysql_escape_string($_POST['subscr_id']) . '"'; $result = mysql_query($sql); if (!$result) { error_log("Error in " . $sql . ", " . mysql_error()); return IPN_error_database; } $row = mysql_fetch_array($result); if (!$row) { error_log("(IPN EOT) Unknown subscription id: " . $_POST['subscr_id']); } else { $id = $row[0]; if (!$id) { error_log("(IPN EOT) No user id for subscription id: " . $_POST['subscr_id']); } else { $result = update_user_info($id, array('authorization_type' => 'paypal', 'authorization_expires' => '0000-00-00 00:00:00', 'status' => 'undeclared'), array(), "authorization_type IN ('paypal_us', 'paypal', 'paypal_once')", # avoid cancelling a user who switched from paypal to infusionsoft "PayPal"); if (!$result) { return IPN_error_database; } } } break; case 'subscr_payment' : if ($_POST['payment_status'] == 'Completed') { $sql = 'SELECT user_id FROM valid_paypal_subscriptions ' . 'WHERE subscr_id = "' . mysql_escape_string($_POST['subscr_id']) . '"'; $result = mysql_query($sql); if (!$result) { error_log("Error in " . $sql . ", " . mysql_error()); return IPN_error_database; } $row = mysql_fetch_array($result); if (!$row) { error_log("(IPN subscr_payment) Unknown subscription id: " . $_POST['subscr_id']); } else { $id = $row[0]; if (!$id) { error_log("(IPN subscr_payment) No user id for subscription id: " . $_POST['subscr_id']); } else { switch ($_POST['item_name']) { case 'Trade-Ideas Yearly service': // 12 months. $renew_period_days = 366; break; case 'Trade-Ideas yearly service' : if ($_POST['mc_gross'] == '45.00') { $renew_period_days = 31; } else { // 13 months $renew_period_days = 397; } break; case 'Trade-Ideas year service' : $renew_period_days = 366; break; default : // Trade-Ideas basic service $renew_period_days = 31; break; } $renew_period_days += 9 + 6; // See previous note. $date = "FROM_UNIXTIME(" . strtotime($_POST['payment_date']) . ") + INTERVAL $renew_period_days DAY"; $result = update_user_info( $id, array('authorization_type' => 'paypal'), array('authorization_expires' => $date), false, "PayPal"); if (!$result) { return IPN_error_database; } } } } break; case 'recurring_payment': $date = strtotime($_POST['payment_date']); if ($date <= 0) { set_error_reason($msg_id, 'Unable to parse date: "' . $_POST['payment_date'] . '"'); // We could make up a new error code. This seems close enough. return IPN_error_unknown_user; } // Assume for simplicity that all recurring payments are on a monthly // cycle. I don't understand the way retries are done. I don't want to // extend account for too long, because we don't get an EOT message like // we do for subscriptions. We never go backwards. I don't know if this // is the best plan. It helps us in case we get a very old message. $sql = 'SELECT user_id FROM valid_recurring_payment WHERE recurring_id="' . mysql_escape_string($_POST['recurring_payment_id']) . '"'; $result = mysql_query($sql); if (!$result) { error_log("Error in " . $sql . ", " . mysql_error()); return IPN_error_database; } while ($row = mysql_fetch_array($result)) { $success = update_user_info ($row[0]+0, array('authorization_type' => 'paypal_us', 'authorization_code' => $_POST['recurring_payment_id']), array('authorization_expires' => 'GREATEST(authorization_expires, FROM_UNIXTIME(' . $date . ') + INTERVAL 40 DAY)'), false, "PayPal"); if (!$success) { return IPN_error_database; } } break; case 'web_accept': $action = 'standard_service'; switch ($_POST['item_name']) { case 'OddsMaker': $price = "1100.00"; $action = 'odds_maker'; break; case '3 months service, no NASDAQ A': $time = "3 MONTH"; $price = "225.00"; $trader_status = "pro"; // Not paying for NASDAQ. Might ask for NYSE or AMEX. We have to // turn them on manually. $skip_exchanges = array('NYSE', 'ARCA', 'AMEX', 'NASD'); break; case '1 month service, non-pro A': $time = "1 MONTH"; $price = "80.00"; $trader_status = "non-pro"; // All exchanges are included in the price $skip_exchanges = array(); break; case '3 months service, non-pro A': $time = "3 MONTH"; $price = "225.00"; $trader_status = "non-pro"; // All exchanges are included in the price $skip_exchanges = array(); break; case '3 months service, non-pro B': $time = "3 MONTH"; $price = "60.00"; $trader_status = "non-pro"; // All exchanges are included in the price $skip_exchanges = array(); break; case '3 months service, pro A': $time = "3 MONTH"; $price = "285.00"; $trader_status = "pro"; // Paying for NASDAQ. Might ask for NYSE or AMEX. We have to turn // them on manually. $skip_exchanges = array('NYSE', 'ARCA', 'AMEX'); break; case '12 months service, no NASDAQ A': $time = "1 YEAR"; $price = "628.00"; $trader_status = "pro"; // Not paying for NASDAQ. Might ask for NYSE or AMEX. We have to // turn them on manually. $skip_exchanges = array('NYSE', 'ARCA', 'AMEX', 'NASD'); break; case '12 months service, non-pro A': $time = "1 YEAR"; $price = "628.00"; $trader_status = "non-pro"; // All exchanges are included in the price $skip_exchanges = array(); break; case '12 months service, pro A': $time = "1 YEAR"; $price = "868.00"; $trader_status = "pro"; // Paying for NASDAQ. Might ask for NYSE or AMEX. We have to turn // them on manually. $skip_exchanges = array('NYSE', 'ARCA', 'AMEX'); break; default : set_error_reason($msg_id, "unknown item name: " . $_POST['item_name']); return IPN_error_verification_rejected; } if ($_POST['mc_gross'] != $price) { set_error_reason($msg_id, "Invalid value. Field mc_gross was was " . $_POST['mc_gross'] . " but should have been $price"); return IPN_error_verification_rejected; } $sql = 'SELECT id, IF((authorization_type="paypal_once") AND (authorization_expires>NOW()), "authorization_expires", "NOW()") FROM users WHERE username="' . mysql_escape_string($_POST['item_number']) . '"'; $result = mysql_query($sql); if (!$result) { error_log("Error in " . $sql . ", " . mysql_error()); return IPN_error_database; } $row = mysql_fetch_row($result); if (!$row) { set_error_reason($msg_id, "Unknown account name: " . $_POST['item_number']); return IPN_error_unknown_user; } $user_id = $row[0]; if ($action == 'standard_service') { $start_from = $row[1]; $starting_date = $row; $sql = 'INSERT INTO one_time_payments(txn_id, msg_id, timestamp)' . ' VALUES("' . mysql_escape_string($_POST['txn_id']) . '", ' . $msg_id . ', now())'; $result = mysql_query($sql); if (!$result) { if (mysql_errno() == 1062) { set_error_reason($msg_id, "Duplicate."); return; } else { error_log("Error in " . $sql . ", " . mysql_error()); return IPN_error_database; } } if ($_POST['receipt_id']) { $auth_code = $_POST['txn_id'] . ', ' . $_POST['receipt_id']; } else { $auth_code = $_POST['txn_id']; } update_user_info($user_id, array('authorization_type' => 'paypal_once', 'authorization_code' => $auth_code, 'status' => $trader_status, 'paid_exchanges' => exchange_bitmap($skip_exchanges)), array('authorization_expires' => $start_from . ' + INTERVAL ' . $time), false, "PayPal"); } else if ($action=="odds_maker") { update_user_info($user_id, array(), array('oddsmaker_free' => 'NULL'), false, "PayPal"); } break; default : break; } $sql = 'UPDATE raw_ipn_msg SET success="Y" WHERE id="' . mysql_escape_string($msg_id) . '"'; $result = mysql_query($sql); if (!$result) { error_log("Error reporting success in " . $sql . ", " . mysql_error()); return IPN_error_database; } return $msg_id; }