' : print_r($address_arr, true)));
// Do we require a "confirmed" shipping address ?
if (zen_config('MODULE_PAYMENT_PAYPALWPP_CONFIRMED_ADDRESS') === 'Yes') {
$options['REQCONFIRMSHIPPING'] = 1;
}
}
// if we know customer's email address, supply it, so as to pre-fill the signup box at PayPal (useful for new PayPal accounts only)
if (!empty($_SESSION['customer_first_name']) && !empty($_SESSION['customer_id'])) {
$sql = "SELECT * FROM " . TABLE_CUSTOMERS . " WHERE customers_id = :custID ";
$sql = $db->bindVars($sql, ':custID', $_SESSION['customer_id'], 'integer');
$zc_getemail = $db->Execute($sql);
if ($zc_getemail->RecordCount() > 0 && $zc_getemail->fields['customers_email_address'] != '') {
$options['EMAIL'] = $zc_getemail->fields['customers_email_address'];
}
if ($zc_getemail->RecordCount() > 0 && $zc_getemail->fields['customers_telephone'] != '' && (!isset($options['ADDROVERRIDE']) || isset($options['ADDROVERRIDE']) && $options['ADDROVERRIDE'] != 1)) {
$options['PAYMENTREQUEST_0_SHIPTOPHONENUM'] = $zc_getemail->fields['customers_telephone'];
}
}
if (!isset($options['PAYMENTREQUEST_0_AMT'])) $options['PAYMENTREQUEST_0_AMT'] = round($order_amount, 2);
$this->zcLog('ec_step1 - 2 -submit', print_r(array_merge($options, array('RETURNURL' => $return_url, 'CANCELURL' => $cancel_url)), true));
$this->notify('NOTIFY_PAYMENT_PAYPALEC_BEFORE_SETEC', array(), $options, $order, $order_totals);
/**
* Ask PayPal for the token with which to initiate communications
*/
$response = $doPayPal->SetExpressCheckout($return_url, $cancel_url, $options);
$this->notify('NOTIFY_PAYMENT_PAYPALEC_TOKEN', $response, $options);
$submissionCheckOne = TRUE;
$submissionCheckTwo = TRUE;
if ($submissionCheckOne) {
// If there's an error on line-item details, remove tax values and resubmit, since the most common cause of 10413 is tax mismatches
if (isset($response['L_ERRORCODE0']) && $response['L_ERRORCODE0'] == '10413') {
$this->zcLog('ec_step1 - 3 - removing tax portion', 'Tax Subtotal does not match sum of taxes for line-items. Tax details removed from line-item submission data.' . "\n" . print_r($options, true));
//echo '1st submission REJECTED. {'.$response['L_ERRORCODE0'].'}'.print_r($options, true) . urldecode(print_r($response, true));
$tsubtotal = 0;
foreach ($options as $key=>$value) {
if (substr($key, -6) == 'TAXAMT') {
$tsubtotal += preg_replace('/[^0-9.\-]/', '', $value);
unset($options[$key]);
}
}
$options['PAYMENTREQUEST_0_TAXAMT'] = $tsubtotal;
$amt = preg_replace('/[^0-9.%]/', '', $options['PAYMENTREQUEST_0_AMT']);
// echo 'oldAMT:'.$amt;
// echo ' newTAXAMT:'.$tsubtotal;
$taxamt = preg_replace('/[^0-9.%]/', '', $options['PAYMENTREQUEST_0_TAXAMT']);
$shipamt = preg_replace('/[^0-9.%]/', '', $options['PAYMENTREQUEST_0_SHIPPINGAMT']);
$itemamt = preg_replace('/[^0-9.%]/', '', $options['PAYMENTREQUEST_0_ITEMAMT']);
$calculatedAmount = $itemamt + $taxamt + $shipamt;
if ($amt != $calculatedAmount) $amt = $calculatedAmount;
// echo ' newAMT:'.$amt;
$options['PAYMENTREQUEST_0_AMT'] = $amt;
$response = $doPayPal->SetExpressCheckout($return_url, $cancel_url, $options);
//echo '
2nd submission. {'.$response['L_ERRORCODE0'].'}'.print_r($options, true);
}
if ($submissionCheckTwo) {
if (isset($response['L_ERRORCODE0']) && $response['L_ERRORCODE0'] == '10413') {
$this->zcLog('ec_step1 - 4 - removing line-item details', 'PayPal designed their own mathematics rules. Dumbing it down for them.' . "\n" . print_r($options, true));
//echo '2nd submission REJECTED. {'.$response['L_ERRORCODE0'].'}'.print_r($options, true) . urldecode(print_r($response, true));
foreach ($options as $key=>$value) {
if (substr($key, 0, 2) == 'L_') {
unset($options[$key]);
}
}
$amt = preg_replace('/[^0-9.%]/', '', $options['PAYMENTREQUEST_0_AMT']);
$taxamt = preg_replace('/[^0-9.%]/', '', $options['PAYMENTREQUEST_0_TAXAMT']);
$shipamt = preg_replace('/[^0-9.%]/', '', $options['PAYMENTREQUEST_0_SHIPPINGAMT']);
$itemamt = preg_replace('/[^0-9.%]/', '', $options['PAYMENTREQUEST_0_ITEMAMT']);
$calculatedAmount = $itemamt + $taxamt + $shipamt;
if ($amt != $calculatedAmount) $amt = $calculatedAmount;
$options['PAYMENTREQUEST_0_AMT'] = $amt;
$response = $doPayPal->SetExpressCheckout($return_url, $cancel_url, $options);
//echo '
3rd submission. {'.$response['L_ERRORCODE0'].'}'.print_r($options, true);
}
}
}
/**
* Determine result of request for token -- if error occurred, the errorHandler will redirect accordingly
*/
$error = $this->_errorHandler($response, 'SetExpressCheckout');
// Success, so read the EC token
$_SESSION['paypal_ec_token'] = preg_replace('/[^0-9.A-Z\-]/', '', urldecode($response['TOKEN']));
// prepare to redirect to PayPal so the customer can log in and make their selections
$paypal_url = $this->getPayPalLoginServer();
// incontext checkout URL override:
if ($this->use_incontext_checkout) {
$paypal_url = str_replace('cgi-bin/webscr', 'checkoutnow/', $paypal_url);
}
// Set the name of the displayed "continue" button on the PayPal site.
// 'commit' = "Pay Now" || 'continue' = "Review Payment"
$orderReview = true;
if (isset($_SESSION['paypal_ec_markflow']) && $_SESSION['paypal_ec_markflow'] == 1) {
$orderReview = false;
}
$userActionKey = "&useraction=" . ((int)$orderReview == false ? 'commit' : 'continue');
$this->ec_redirect_url = $paypal_url . "?cmd=_express-checkout&token=" . $_SESSION['paypal_ec_token'] . $userActionKey;
// This is where we actually redirect the customer's browser to PayPal. Upon return from PayPal, they go to ec_step2
header("HTTP/1.1 302 Object Moved");
zen_redirect($this->ec_redirect_url);
// this should never be reached:
return $error;
}
/**
* This method is for step 2 of the express checkout option. This
* retrieves from PayPal the data set by step one and sets the Zen Cart
* data accordingly depending on admin settings.
* @since ZC v1.3.7
*/
function ec_step2() {
// Visitor just came back from PayPal and so we collect all
// the info returned, create an account if necessary, then log
// them in, and then send them to the appropriate page.
if (empty($_SESSION['paypal_ec_token'])) {
// see if the token is set -- if not, we cannot continue -- ideally the token should match the session token
if (isset($_GET['token'])) {
// we have a token, so we will proceed
$_SESSION['paypal_ec_token'] = $_GET['token'];
// sanitize this
$_SESSION['paypal_ec_token'] = preg_replace('/[^0-9.A-Z\-]/', '', $_GET['token']);
} else {
// no token -- not ready for this step -- send them back to checkout page with error
$this->terminateEC(MODULE_PAYMENT_PAYPALWPP_INVALID_RESPONSE, true);
}
}
// debug
//$this->zcLog('PayPal test Log - ec_step2 $_REQUEST data', "In function: ec_step2()\r\nData in \$_REQUEST = \r\n" . print_r($_REQUEST, true));
// Initialize the paypal caller object.
global $doPayPal;
$doPayPal = $this->paypal_init();
// with the token we retrieve the data about this user
$response = $doPayPal->GetExpressCheckoutDetails($_SESSION['paypal_ec_token']);
$this->notify('NOTIFY_PAYPALEC_PARSE_GETEC_RESULT', [], $response);
//$this->zcLog('ec_step2 - GetExpressCheckout response', print_r($response, true));
/**
* Determine result of request for data -- if error occurred, the errorHandler will redirect accordingly
* @since ZC v2.2.0
*/
$error = $this->_errorHandler($response, 'GetExpressCheckoutDetails');
// Fill in possibly blank return values, prevents PHP notices in follow-on checking clause.
$response_vars = [
$this->requestPrefix . 'SHIPTONAME',
$this->requestPrefix . 'SHIPTOSTREET',
$this->requestPrefix . 'SHIPTOSTREET2',
$this->requestPrefix . 'SHIPTOCITY',
$this->requestPrefix . 'SHIPTOSTATE',
$this->requestPrefix . 'SHIPTOZIP',
$this->requestPrefix . 'SHIPTOCOUNTRYCODE',
];
$address_received = '';
foreach ($response_vars as $response_var) {
if (!isset($response[$response_var])) {
$response[$response_var] = '';
} else {
$address_received .= $response[$response_var];
}
}
// Check for blank address -- if address received from PayPal is blank, ask the customer to register in the store first and then resume checkout
if ($_SESSION['cart']->get_content_type() != 'virtual' && $address_received == '') {
$this->terminateEC(MODULES_PAYMENT_PAYPALWPP_TEXT_BLANK_ADDRESS, true, FILENAME_CREATE_ACCOUNT);
}
// will we be creating an account for this customer? We must if the cart contents are virtual, so can login to download etc.
if ($_SESSION['cart']->get_content_type('true') > 0 || in_array($_SESSION['cart']->get_content_type(), ['mixed', 'virtual'])) {
$this->new_acct_notify = 'Yes';
}
// More optional response elements; initialize them to prevent follow-on PHP notices.
$response_optional = [
'PAYMENTREQUEST_0_SHIPTOPHONENUM',
'PAYERID',
'EMAIL',
'PHONENUM',
'FIRSTNAME',
'LASTNAME',
'BUSINESS',
'PAYERSTATUS',
];
foreach ($response_optional as $optional) {
if (!isset($response[$optional])) {
$response[$optional] = '';
}
}
// get the payer_id from the customer's info as returned from PayPal
$_SESSION['paypal_ec_payer_id'] = ($response['PAYERID'] ?? '--none--');
$this->notify('NOTIFY_PAYPAL_EXPRESS_CHECKOUT_PAYERID_DETERMINED', $response['PAYERID']);
// prepare the information to pass to the ec_step2_finish() function, which does the account creation, address build, etc
$step2_payerinfo = [
'payer_id' => $response['PAYERID'],
'payer_email' => urldecode($response['EMAIL']),
'payer_salutation'=> '',
'payer_gender' => '',
'payer_firstname' => urldecode($response['FIRSTNAME']),
'payer_lastname' => urldecode($response['LASTNAME']),
'payer_business' => urldecode($response['BUSINESS']),
'payer_status' => $response['PAYERSTATUS'],
'ship_country_code' => urldecode($response[$this->requestPrefix . 'SHIPTOCOUNTRYCODE']),
'ship_address_status' => (empty($response[$this->requestPrefix . 'ADDRESSSTATUS'])) ? 'NONE' : urldecode($response[$this->requestPrefix . 'ADDRESSSTATUS']),
'ship_phone' => urldecode($response[$this->requestPrefix . 'SHIPTOPHONENUM'] != '' ? $response[$this->requestPrefix . 'SHIPTOPHONENUM'] : $response['PHONENUM']),
'order_comment' => (isset($response['NOTE'], $response[$this->requestPrefix . 'NOTETEXT']) ? urldecode($response['NOTE']) . ' ' . urldecode($response[$this->requestPrefix . 'NOTETEXT']) : ''),
];
// if (strtoupper($response['ADDRESSSTATUS']) == 'NONE' || !isset($response['SHIPTOSTREET']) || $response['SHIPTOSTREET'] == '') {
// $step2_shipto = array();
// } else {
// accomodate PayPal bug which repeats 1st line of address for 2nd line if 2nd line is empty.
if ($response[$this->requestPrefix . 'SHIPTOSTREET2'] == $response[$this->requestPrefix . 'SHIPTOSTREET']) {
$response[$this->requestPrefix . 'SHIPTOSTREET2'] = '';
}
// accomodate PayPal bug which incorrectly treats 'Yukon Territory' as YK instead of ISO standard of YT.
if ($response[$this->requestPrefix . 'SHIPTOSTATE'] === 'YK') {
$response[$this->requestPrefix . 'SHIPTOSTATE'] = 'YT';
}
// same with Newfoundland
if ($response[$this->requestPrefix . 'SHIPTOSTATE'] === 'NF') {
$response[$this->requestPrefix . 'SHIPTOSTATE'] = 'NL';
}
// process address details supplied
$step2_shipto = [
'ship_name' => urldecode($response[$this->requestPrefix . 'SHIPTONAME']),
'ship_street_1' => urldecode($response[$this->requestPrefix . 'SHIPTOSTREET']),
'ship_street_2' => urldecode($response[$this->requestPrefix . 'SHIPTOSTREET2']),
'ship_city' => urldecode($response[$this->requestPrefix . 'SHIPTOCITY']),
'ship_state' => (($response[$this->requestPrefix . 'SHIPTOSTATE'] ?? '') !== '') ? urldecode($response[$this->requestPrefix . 'SHIPTOSTATE']) : urldecode($response[$this->requestPrefix . 'SHIPTOCITY']),
'ship_postal_code' => urldecode($response[$this->requestPrefix . 'SHIPTOZIP']),
'ship_country_code' => urldecode($response[$this->requestPrefix . 'SHIPTOCOUNTRYCODE']),
'ship_country_name' => urldecode($response['SHIPTOCOUNTRY'] ?? $response['SHIPTOCOUNTRYNAME'] ?? ''),
];
// reset all previously-selected shipping choices, because cart contents may have been changed
if ((isset($response['SHIPPINGCALCULATIONMODE']) && $response['SHIPPINGCALCULATIONMODE'] !== 'Callback') && (!(isset($_SESSION['paypal_ec_markflow']) && $_SESSION['paypal_ec_markflow'] == 1))) {
unset($_SESSION['shipping']);
}
// set total temporarily based on amount returned from PayPal, so validations continue to work properly
global $order, $order_totals;
if (!isset($order) || !isset($order->info) || !is_array($order->info) || !zen_not_null($order)) {
$this->zcLog('ec_step2 ', 'Re-instantiating $order object.');
// -----
// Register the payer_info in the session; it'll be used during the observer portion
// of the payment module.
//
$_SESSION['paypal_ec_payer_info'] = array_merge($step2_payerinfo, $step2_shipto);
// -----
// Watch for the order's address-creation notification. That event will be used
// to populate the customer/billing/delivery addresses with the information that
// PayPal pushed via IPN.
//
$this->attach($this, ['NOTIFY_ORDER_CART_ADDRESS_OVERRIDES']);
// init new order object
if (!class_exists('order')) {
require DIR_WS_CLASSES . 'order.php';
}
$order = new order();
// load the selected shipping module so that shipping taxes can be assessed
if (isset($_SESSION['shipping'])) {
if (!class_exists('shipping')) {
require DIR_WS_CLASSES . 'shipping.php';
}
$shipping_modules = new shipping($_SESSION['shipping']);
}
// load OT modules so that discounts and taxes can be assessed
if (!class_exists('order_total')) {
require DIR_WS_CLASSES . 'order_total.php';
}
$order_total_modules = new order_total();
$order_totals = $order_total_modules->pre_confirmation_check();
$order_totals = $order_total_modules->process();
$this->zcLog('ec_step2 ', 'Instantiated $order object contents: ' . print_r($order, true));
}
if ($order->info['total'] < 0.01 && urldecode($response[$this->requestPrefix . 'AMT']) > 0) {
$order->info['total'] = urldecode($response[$this->requestPrefix . 'AMT']);
}
//$this->zcLog('ec_step2 - processed info', print_r(array_merge($step2_payerinfo, $step2_shipto), true));
// send data off to build account, log in, set addresses, place order
$this->ec_step2_finish();
}
// -----
// This method is invoked via notification by the order-class. The payment module takes the
// address-related information returned by PayPal and instructs the order-class to use
// that information in the order's customer/delivery/billing addresses.
//
// Note: For Zen Cart versions up to 2.1.0, this functionality was present at the beginning
// of the ec_step2_finish method.
//
/**
* @since v2.2.0
*/
public function notify_order_cart_address_overrides(&$order, string $e, $unused, array &$customer_address_override, array &$delivery_address_override, array &$billing_address_override): void
{
global $db;
$paypal_ec_payer_info = $_SESSION['paypal_ec_payer_info'];
$this->zcLog('ec_step2 - overrides - 1', 'START: paypal_ec_payer_info= ' . print_r($paypal_ec_payer_info, true));
/**
* Building customer zone/address from returned data
*/
// set some defaults, which will be updated later:
$country_id = 223;
$address_format_id = 2;
$state_id = 0;
$country_code3 = '???';
// Get the customer's country ID based on name or ISO code
$sql =
"SELECT countries_id, address_format_id, countries_iso_code_2, countries_iso_code_3
FROM " . TABLE_COUNTRIES . "
WHERE countries_iso_code_2 = :countryId
OR countries_name = :countryId
LIMIT 1";
$sql1 = $db->bindVars($sql, ':countryId', $paypal_ec_payer_info['ship_country_name'], 'string');
$country1 = $db->Execute($sql1);
$sql2 = $db->bindVars($sql, ':countryId', $paypal_ec_payer_info['ship_country_code'], 'string');
$country2 = $db->Execute($sql2);
// see if we found a record, if yes, then use it instead of default American format
if (!$country1->EOF) {
$country_id = (int)$country1->fields['countries_id'];
if (!isset($paypal_ec_payer_info['ship_country_code']) || $paypal_ec_payer_info['ship_country_code'] == '') {
$paypal_ec_payer_info['ship_country_code'] = $country1->fields['countries_iso_code_2'];
}
$country_code3 = $country1->fields['countries_iso_code_3'];
$address_format_id = (int)$country1->fields['address_format_id'];
} elseif (!$country2->EOF) {
// if didn't find it based on name, check using ISO code (ie: in case of no-shipping-address required/supplied)
$country_id = (int)$country2->fields['countries_id'];
$country_code3 = $country2->fields['countries_iso_code_3'];
$address_format_id = (int)$country2->fields['address_format_id'];
} else {
// if defaulting to US, make sure US is valid
$sql = "SELECT countries_id FROM " . TABLE_COUNTRIES . " WHERE countries_id = :countryId: LIMIT 1";
$sql = $db->bindVars($sql, ':countryId:', $country_id, 'integer');
$result = $db->Execute($sql);
if ($result->EOF) {
$this->notify('NOTIFY_PAYPAL_CUSTOMER_ATTEMPT_TO_USE_INVALID_COUNTRY_CODE');
$this->zcLog('ec-step2 - override - 1b', 'Cannot use address due to country lookup/match failure.');
$this->terminateEC(MODULE_PAYMENT_PAYPALWPP_TEXT_INVALID_ZONE_ERROR, true, FILENAME_SHOPPING_CART);
}
}
// Need to determine zone, based on zone name first, and then zone code if name fails check. Otherwise uses 0.
$sql =
"SELECT zone_id
FROM " . TABLE_ZONES . "
WHERE zone_country_id = :zCountry
AND zone_code = :zoneCode
OR zone_name = :zoneCode
LIMIT 1";
$sql = $db->bindVars($sql, ':zCountry', $country_id, 'integer');
$sql = $db->bindVars($sql, ':zoneCode', $paypal_ec_payer_info['ship_state'], 'string');
$states = $db->Execute($sql);
if (!$states->EOF) {
$state_id = (int)$states->fields['zone_id'];
}
/**
* Using the supplied data from PayPal, set the data into the order record
*/
// customer
$customer_address_override['name'] = $paypal_ec_payer_info['payer_firstname'] . ' ' . $paypal_ec_payer_info['payer_lastname'];
$customer_address_override['company'] = $paypal_ec_payer_info['payer_business'];
$customer_address_override['street_address'] = $paypal_ec_payer_info['ship_street_1'];
$customer_address_override['suburb'] = $paypal_ec_payer_info['ship_street_2'];
$customer_address_override['city'] = $paypal_ec_payer_info['ship_city'];
$customer_address_override['postcode'] = $paypal_ec_payer_info['ship_postal_code'];
$customer_address_override['state'] = $paypal_ec_payer_info['ship_state'];
$customer_address_override['country'] = [
'id' => $country_id,
'title' => $paypal_ec_payer_info['ship_country_name'],
'iso_code_2' => $paypal_ec_payer_info['ship_country_code'],
'iso_code_3' => $country_code3,
];
$customer_address_override['format_id'] = $address_format_id;
$customer_address_override['email_address'] = $paypal_ec_payer_info['payer_email'];
$customer_address_override['telephone'] = $paypal_ec_payer_info['ship_phone'];
$customer_address_override['zone_id'] = $state_id;
// billing
$billing_address_override['name'] = $paypal_ec_payer_info['payer_firstname'] . ' ' . $paypal_ec_payer_info['payer_lastname'];
$billing_address_override['company'] = $paypal_ec_payer_info['payer_business'];
$billing_address_override['street_address'] = $paypal_ec_payer_info['ship_street_1'];
$billing_address_override['suburb'] = $paypal_ec_payer_info['ship_street_2'];
$billing_address_override['city'] = $paypal_ec_payer_info['ship_city'];
$billing_address_override['postcode'] = $paypal_ec_payer_info['ship_postal_code'];
$billing_address_override['state'] = $paypal_ec_payer_info['ship_state'];
$billing_address_override['country'] = [
'id' => $country_id,
'title' => $paypal_ec_payer_info['ship_country_name'],
'iso_code_2' => $paypal_ec_payer_info['ship_country_code'],
'iso_code_3' => $country_code3,
];
$billing_address_override['format_id'] = $address_format_id;
$billing_address_override['zone_id'] = $state_id;
// delivery
if (strtoupper($_SESSION['paypal_ec_payer_info']['ship_address_status']) !== 'NONE') {
$delivery_address_override['name'] = $paypal_ec_payer_info['ship_name'];
$delivery_address_override['company'] = trim($paypal_ec_payer_info['ship_name'] . ' ' . $paypal_ec_payer_info['payer_business']);
$delivery_address_override['street_address'] = $paypal_ec_payer_info['ship_street_1'];
$delivery_address_override['suburb'] = $paypal_ec_payer_info['ship_street_2'];
$delivery_address_override['city'] = $paypal_ec_payer_info['ship_city'];
$delivery_address_override['postcode'] = $paypal_ec_payer_info['ship_postal_code'];
$delivery_address_override['state'] = $paypal_ec_payer_info['ship_state'];
$delivery_address_override['country'] = [
'id' => $country_id,
'title' => $paypal_ec_payer_info['ship_country_name'],
'iso_code_2' => $paypal_ec_payer_info['ship_country_code'],
'iso_code_3' => $country_code3,
];
$delivery_address_override['country_id'] = $country_id;
$delivery_address_override['format_id'] = $address_format_id;
$delivery_address_override['zone_id'] = $state_id;
}
// process submitted customer notes
if (!empty($paypal_ec_payer_info['order_comment'])) {
$_SESSION['comments'] = (!empty($_SESSION['comments']) ? $_SESSION['comments'] . "\n" : '') . $paypal_ec_payer_info['order_comment'];
$order->info['comments'] = $_SESSION['comments'];
}
// -----
// Save calculated country_id/state_id for use by ec2_step2_finish
//
$this->country_state = [
'country_id' => $country_id,
'state_id' => $state_id,
];
// debug
$this->zcLog(
'ec_step2 - override - 2',
'country_id = ' . $country_id . ' ' . $paypal_ec_payer_info['ship_country_name'] . ' ' . $paypal_ec_payer_info['ship_country_code'] . "\n" .
'address_format_id = ' . $address_format_id . "\n" .
'state_id = ' . $state_id . ' (original state tested: ' . $paypal_ec_payer_info['ship_state'] . ')' . "\n" .
"country1->fields['countries_id'] = " . ($country1->fields['countries_id'] ?? 'no result') . "\n" .
"country2->fields['countries_id'] = " . ($country2->fields['countries_id'] ?? 'no result') . "\n" .
'customer_address_override = ' . print_r($customer_address_override, true)
);
}
/**
* Complete the step2 phase by creating accounts if needed, linking data, placing order, etc.
* @since ZC v1.3.7
*/
protected function ec_step2_finish(): void
{
global $db, $order;
$acct_exists = false;
// Capture the session-based payer information into a local variable.
$paypal_ec_payer_info = $_SESSION['paypal_ec_payer_info'];
// store default address id for later use/reference
$original_default_address_id = $_SESSION['customer_default_address_id'] ?? 'Not set';
// -----
// Retrieve the address' country/state IDs as determined by the ec_step 2
// observer (see above).
//
$country_id = $this->country_state['country_id'] ?? $order->customer['country']['id'];
$state_id = $this->country_state['state_id'] ?? $order->customer['zone_id'];
// check to see whether PayPal should still be offered to this customer, based on the zone of their address:
$this->update_status();
if (!$this->enabled) {
$this->terminateEC(MODULE_PAYMENT_PAYPALWPP_TEXT_INVALID_ZONE_ERROR, true, FILENAME_SHOPPING_CART);
}
// see if the user is logged in
if (!empty($_SESSION['customer_first_name']) && zen_is_logged_in()) {
// They're logged in, so forward them straight to checkout stages, depending on address needs etc
$order->customer['id'] = $_SESSION['customer_id'];
// set the session value for express checkout temp
$_SESSION['paypal_ec_temp'] = false;
// -----
// Allow an observer to override the default address-creation processing.
//
$bypass_address_creation = false;
$this->notify('NOTIFY_PAYPALEXPRESS_BYPASS_ADDRESS_CREATION', $paypal_ec_payer_info, $bypass_address_creation);
if ($bypass_address_creation) {
$this->zcLog('ec_step2_finish - 2a', 'address-creation bypassed based on observer setting.');
}
// if no address required for shipping (or overridden by above), leave shipping portion alone
$address_book_id = $original_default_address_id;
if (!$bypass_address_creation && strtoupper($_SESSION['paypal_ec_payer_info']['ship_address_status']) != 'NONE' && $_SESSION['paypal_ec_payer_info']['ship_street_1'] != '') {
// set the session info for the sendto
$_SESSION['sendto'] = $_SESSION['customer_default_address_id'];
// This is the address matching section
// try to match it first
// note: this is by no means 100%
$address_book_id = $this->findMatchingAddressBookEntry($_SESSION['customer_id'], (isset($order->delivery) ? $order->delivery : $order->billing));
// no match, so add the record
if (!$address_book_id) {
$address_book_id = $this->addAddressBookEntry($_SESSION['customer_id'], (isset($order->delivery) ? $order->delivery : $order->billing), false);
}
// if couldn't add the record, perhaps due to removed country, or some other error, abort with message
if (!$address_book_id) {
$this->terminateEC(MODULE_PAYMENT_PAYPALWPP_TEXT_INVALID_ZONE_ERROR, true, FILENAME_SHOPPING_CART);
}
// set the address for use
$_SESSION['sendto'] = $address_book_id;
}
// set the users billto information (default address)
if (!isset($_SESSION['billto'])) {
$_SESSION['billto'] = $_SESSION['customer_default_address_id'];
}
// debug
$this->zcLog('ec_step2_finish - 3', 'Exiting ec_step2_finish logged-in mode.' . "\n" . 'Selected address: ' . $address_book_id . "\nOriginal was: " . $original_default_address_id);
// select a shipping method, based on cheapest available option
if (zen_config('MODULE_PAYMENT_PAYPALWPP_AUTOSELECT_CHEAPEST_SHIPPING') === 'Yes') $this->setShippingMethod();
// send the user on
if (!empty($_SESSION['paypal_ec_markflow']) && $_SESSION['paypal_ec_markflow'] == 1) {
$this->terminateEC('', false, FILENAME_CHECKOUT_PROCESS);
} else {
$this->terminateEC('', false, FILENAME_CHECKOUT_CONFIRMATION);
}
} else {
// They're not logged in. Create an account if necessary, and then log them in.
// First, see if they're an existing customer, and log them in automatically
// If Paypal didn't supply us an email address, something went wrong
if (trim($paypal_ec_payer_info['payer_email']) == '') $this->terminateEC(MODULE_PAYMENT_PAYPALWPP_INVALID_RESPONSE, true);
// attempt to obtain the user information using the payer_email from the info returned from PayPal, via email address
$sql = "SELECT customers_id, customers_firstname, customers_lastname, customers_paypal_payerid, customers_paypal_ec
FROM " . TABLE_CUSTOMERS . "
WHERE customers_email_address = :emailAddress ";
$sql = $db->bindVars($sql, ':emailAddress', $paypal_ec_payer_info['payer_email'], 'string');
$check_customer = $db->Execute($sql);
// debug
$this->zcLog('ec_step2_finish - 4', 'Not logged in. Looking for account.' . "\n" . (int)$check_customer->RecordCount() . ' matching customer records found.');
if (!$check_customer->EOF) {
$acct_exists = true;
// see if this was only a temp account -- if so, remove it
if ($check_customer->fields['customers_paypal_ec'] == '1') {
// Delete the existing temporary account
$this->ec_delete_user($check_customer->fields['customers_id']);
$acct_exists = false;
// debug
$this->zcLog('ec_step2_finish - 5', 'Found temporary account - deleting it.');
}
}
// Create an account, if the account does not exist
if (!$acct_exists) {
// debug
$this->zcLog('ec_step2_finish - 6', 'No ZC account found for this customer. Creating new account.' . "\n" . '$this->new_acct_notify =' . $this->new_acct_notify);
// Generate a random 8-char password
$password = zen_create_random_value(8);
$sql_data_array = array();
// set the customer information in the array for the table insertion
$sql_data_array = array(
'customers_firstname' => $paypal_ec_payer_info['payer_firstname'],
'customers_lastname' => $paypal_ec_payer_info['payer_lastname'],
'customers_email_address' => $paypal_ec_payer_info['payer_email'],
'customers_email_format' => (zen_config('ACCOUNT_EMAIL_PREFERENCE') === '1' ? 'HTML' : 'TEXT'),
'customers_telephone' => $paypal_ec_payer_info['ship_phone'],
'customers_fax' => '',
'customers_gender' => $paypal_ec_payer_info['payer_gender'],
'customers_newsletter' => '0',
'customers_password' => zen_encrypt_password($password),
'customers_paypal_payerid' => $_SESSION['paypal_ec_payer_id']);
// insert the data
$result = zen_db_perform(TABLE_CUSTOMERS, $sql_data_array);
// grab the customer_id (last insert id)
$customer_id = $db->insert_ID();
// set the Guest customer ID -- for PWA purposes
$_SESSION['customer_guest_id'] = $customer_id;
$this->notify('NOTIFY_PAYPALEXPRESS_CREATE_ACCOUNT_ADDED_CUSTOMER_RECORD', $customer_id, $sql_data_array);
// set the customer address information in the array for the table insertion
$sql_data_array = array(
'customers_id' => $customer_id,
'entry_gender' => $paypal_ec_payer_info['payer_gender'],
'entry_firstname' => $paypal_ec_payer_info['payer_firstname'],
'entry_lastname' => $paypal_ec_payer_info['payer_lastname'],
'entry_street_address' => $paypal_ec_payer_info['ship_street_1'],
'entry_suburb' => $paypal_ec_payer_info['ship_street_2'],
'entry_city' => $paypal_ec_payer_info['ship_city'],
'entry_zone_id' => $state_id,
'entry_postcode' => $paypal_ec_payer_info['ship_postal_code'],
'entry_country_id' => $country_id);
if (isset($paypal_ec_payer_info['ship_name']) && $paypal_ec_payer_info['ship_name'] != '' && $paypal_ec_payer_info['ship_name'] != $paypal_ec_payer_info['payer_firstname'] . ' ' . $paypal_ec_payer_info['payer_lastname']) {
$sql_data_array['entry_company'] = $paypal_ec_payer_info['ship_name'];
}
if ($state_id > 0) {
$sql_data_array['entry_zone_id'] = $state_id;
$sql_data_array['entry_state'] = '';
} else {
$sql_data_array['entry_zone_id'] = 0;
$sql_data_array['entry_state'] = $paypal_ec_payer_info['ship_state'];
}
// insert the data
zen_db_perform(TABLE_ADDRESS_BOOK, $sql_data_array);
// grab the address_id (last insert id)
$address_id = $db->insert_ID();
$this->notify('NOTIFY_PAYPALEXPRESS_CREATE_ACCOUNT_ADDED_ADDRESS_BOOK_RECORD', array(), $address_id, $sql_data_array);
// set the address id lookup for the customer
$sql = "UPDATE " . TABLE_CUSTOMERS . "
SET customers_default_address_id = :addrID
WHERE customers_id = :custID";
$sql = $db->bindVars($sql, ':addrID', $address_id, 'integer');
$sql = $db->bindVars($sql, ':custID', $customer_id, 'integer');
$db->Execute($sql);
// insert the new customer_id into the customers info table for consistency
$sql = "INSERT INTO " . TABLE_CUSTOMERS_INFO . "
(customers_info_id, customers_info_number_of_logons, customers_info_date_account_created, customers_info_date_of_last_logon)
VALUES (:custID, 1, now(), now())";
$sql = $db->bindVars($sql, ':custID', $customer_id, 'integer');
$db->Execute($sql);
// send Welcome Email if appropriate
if ($this->new_acct_notify == 'Yes') {
// require the language file
zen_include_language_file('create_account_send_email.php', '/modules/', 'inline');
// set the mail text
$email_text = sprintf(EMAIL_GREET_NONE, $paypal_ec_payer_info['payer_firstname']) . EMAIL_WELCOME . "\n\n" . EMAIL_TEXT;
$email_text .= "\n\n" . EMAIL_EC_ACCOUNT_INFORMATION . "\nUsername: " . $paypal_ec_payer_info['payer_email'] . "\nPassword: " . $password . "\n\n";
$email_text .= EMAIL_CONTACT;
// include create-account-specific disclaimer
$email_text .= "\n\n" . sprintf(EMAIL_DISCLAIMER_NEW_CUSTOMER, zen_config('STORE_OWNER_EMAIL_ADDRESS')). "\n\n";
$email_html = array();
$email_html['EMAIL_GREETING'] = sprintf(EMAIL_GREET_NONE, $paypal_ec_payer_info['payer_firstname']) ;
$email_html['EMAIL_WELCOME'] = EMAIL_WELCOME;
$email_html['EMAIL_MESSAGE_HTML'] = nl2br(EMAIL_TEXT . "\n\n" . EMAIL_EC_ACCOUNT_INFORMATION . "\nUsername: " . $paypal_ec_payer_info['payer_email'] . "\nPassword: " . $password . "\n\n");
$email_html['EMAIL_CONTACT_OWNER'] = EMAIL_CONTACT;
$email_html['EMAIL_CLOSURE'] = nl2br(EMAIL_GV_CLOSURE);
$email_html['EMAIL_DISCLAIMER'] = sprintf(EMAIL_DISCLAIMER_NEW_CUSTOMER, ''. zen_config('STORE_OWNER_EMAIL_ADDRESS') .' ');
// send the mail
if (trim(EMAIL_SUBJECT) != 'n/a') zen_mail($paypal_ec_payer_info['payer_firstname'] . " " . $paypal_ec_payer_info['payer_lastname'], $paypal_ec_payer_info['payer_email'], EMAIL_SUBJECT, $email_text, zen_config('STORE_OWNER'), zen_config('STORE_OWNER_EMAIL_ADDRESS'), $email_html, 'welcome');
// set the express checkout temp -- false means the account is no longer "only" for EC ... it'll be permanent
$_SESSION['paypal_ec_temp'] = false;
} else {
// Make it a temporary account that'll be deleted once they've checked out
$sql = "UPDATE " . TABLE_CUSTOMERS . "
SET customers_paypal_ec = 1
WHERE customers_id = :custID ";
$sql = $db->bindVars($sql, ':custID', $customer_id, 'integer');
$db->Execute($sql);
// set the boolean ec temp value since we created account strictly for EC purposes
$_SESSION['paypal_ec_temp'] = true;
}
// hook notifier class vis a vis account-creation
$this->notify('NOTIFY_LOGIN_SUCCESS_VIA_CREATE_ACCOUNT', 'paypal express checkout');
} else {
// set the boolean ec temp value for the account to false, since we didn't have to create one
$_SESSION['paypal_ec_temp'] = false;
}
// log the user in with the email sent back from paypal response
$this->user_login($_SESSION['paypal_ec_payer_info']['payer_email'], false);
// debug
$this->zcLog('ec_step2_finish - 7', 'Auto-Logged customer in. (' . $_SESSION['paypal_ec_payer_info']['payer_email'] . ') (' . $_SESSION['customer_id'] . ')' . "\n" . '$_SESSION[paypal_ec_temp]=' . $_SESSION['paypal_ec_temp']);
// This is the address matching section
// try to match it first
// note: this is by no means 100%
$address_book_id = $this->findMatchingAddressBookEntry($_SESSION['customer_id'], (isset($order->delivery) ? $order->delivery : $order->billing));
// no match add the record
if (!$address_book_id) {
$address_book_id = $this->addAddressBookEntry($_SESSION['customer_id'], (isset($order->delivery) ? $order->delivery : $order->billing), false);
if (!$address_book_id) {
$address_book_id = $_SESSION['customer_default_address_id'];
}
}
// if couldn't add the record, perhaps due to removed country, or some other error, abort with message
if ($address_book_id == FALSE) {
$this->terminateEC(MODULE_PAYMENT_PAYPALWPP_TEXT_INVALID_ZONE_ERROR, true, FILENAME_SHOPPING_CART);
}
// set the sendto to the address
$_SESSION['sendto'] = $address_book_id;
// set billto in the session
$_SESSION['billto'] = $_SESSION['customer_default_address_id'];
// select a shipping method, based on cheapest available option
if (zen_config('MODULE_PAYMENT_PAYPALWPP_AUTOSELECT_CHEAPEST_SHIPPING') === 'Yes') $this->setShippingMethod();
// debug
$this->zcLog('ec_step2_finish - 8', 'Exiting via terminateEC (from originally-not-logged-in mode).' . "\n" . 'Selected address: ' . $address_book_id . "\nOriginal was: " . (int)$original_default_address_id . "\nprepared data: " . print_r($order->customer, true));
$this->notify('NOTIFY_PAYPALEC_END_ECSTEP2', $order);
// send the user on
if (isset($_SESSION['paypal_ec_markflow']) && $_SESSION['paypal_ec_markflow'] == 1) {
$this->terminateEC('', false, FILENAME_CHECKOUT_PROCESS);
} else {
$this->terminateEC('', false, FILENAME_CHECKOUT_CONFIRMATION);
}
}
}
/**
* Determine the appropriate shipping method if applicable
* By default, selects the lowest-cost quote
* @since ZC v1.3.7
*/
function setShippingMethod() {
global $total_count, $total_weight;
// ensure that cart contents is calculated properly for weight and value
if (!isset($total_weight)) $total_weight = $_SESSION['cart']->show_weight();
if (!isset($total_count)) $total_count = $_SESSION['cart']->count_contents();
// set the shipping method if one is not already set
// defaults to the cheapest shipping method
if ((!isset($_SESSION['shipping']) || (!isset($_SESSION['shipping']['id']) || $_SESSION['shipping']['id'] == '') && zen_count_shipping_modules() >= 1)) {
require_once(DIR_WS_CLASSES . 'shipping.php');
$shipping_Obj = new shipping;
// generate the quotes
$shipping_Obj->quote();
// set the cheapest one
$_SESSION['shipping'] = $shipping_Obj->cheapest();
}
}
/**
* Get Override Address (uses sendto if set, otherwise uses customer's primary address)
* @since ZC v1.3.7
*/
function getOverrideAddress() {
global $db;
// Only proceed IF *in* markflow mode AND logged-in (have to be logged in to get to markflow mode anyway)
if (!empty($_GET['markflow']) && zen_is_logged_in()) {
// From now on for this user we will edit addresses in Zen Cart, not by going to PayPal.
$_SESSION['paypal_ec_markflow'] = 1;
// debug
$this->zcLog('getOverrideAddress - 1', 'Now in markflow mode.' . "\n" . 'SESSION[sendto] = ' . (int)$_SESSION['sendto']);
// find the users default address id
if (!empty($_SESSION['sendto'])) {
$address_id = $_SESSION['sendto'];
} else {
$sql = "SELECT customers_default_address_id
FROM " . TABLE_CUSTOMERS . "
WHERE customers_id = :customerId";
$sql = $db->bindVars($sql, ':customerId', $_SESSION['customer_id'], 'integer');
$default_address_id_arr = $db->Execute($sql);
if (!$default_address_id_arr->EOF) {
$address_id = $default_address_id_arr->fields['customers_default_address_id'];
} else {
// couldn't find an address.
return false;
}
}
// -----
// Give a watching observer the opportunity to bypass this address-override. An observer
// can disable the address-override processing by setting the $disable_address_override
// parameter to specifically (bool)true.
//
$disable_address_override = false;
$this->notify('NOTIFY_PAYPALWPP_DISABLE_GET_OVERRIDE_ADDRESS', $address_id, $disable_address_override);
if ($disable_address_override === true) {
$this->zcLog('getOverrideAddress - 1a', "Override disabled by observer request.\n");
return false;
}
// now grab the address from the database and set it as the overridden address
$sql = "SELECT entry_firstname, entry_lastname, entry_company,
entry_street_address, entry_suburb, entry_city, entry_postcode,
entry_country_id, entry_zone_id, entry_state
FROM " . TABLE_ADDRESS_BOOK . "
WHERE address_book_id = :addressId
AND customers_id = :customerId
LIMIT 1";
$sql = $db->bindVars($sql, ':addressId', $address_id, 'integer');
$sql = $db->bindVars($sql, ':customerId', $_SESSION['customer_id'], 'integer');
$address_arr = $db->Execute($sql);
// see if we found a record, if not then we have nothing to override with
if (!$address_arr->EOF) {
// get the state/prov code
$sql = "SELECT zone_code
FROM " . TABLE_ZONES . "
WHERE zone_id = :zoneId";
$sql = $db->bindVars($sql, ':zoneId', $address_arr->fields['entry_zone_id'], 'integer');
$state_code_arr = $db->Execute($sql);
if ($state_code_arr->EOF) {
$state_code_arr->fields['zone_code'] = '';
}
if ($state_code_arr->fields['zone_code'] == '' && $address_arr->fields['entry_state'] != '') {
$state_code_arr->fields['zone_code'] = $address_arr->fields['entry_state'];
}
$address_arr->fields['zone_code'] = $state_code_arr->fields['zone_code'];
// get the country code
// ISO 3166 standard country code
$sql = "SELECT countries_iso_code_2
FROM " . TABLE_COUNTRIES . "
WHERE countries_id = :countryId";
$sql = $db->bindVars($sql, ':countryId', $address_arr->fields['entry_country_id'], 'integer');
$country_code_arr = $db->Execute($sql);
if ($country_code_arr->EOF) {
// default to US if not found
$country_code_arr->fields['countries_iso_code_2'] = 'US';
}
$address_arr->fields['countries_iso_code_2'] = $country_code_arr->fields['countries_iso_code_2'];
// debug
$this->zcLog('getOverrideAddress - 2', '$address_arr->fields = ' . print_r($address_arr->fields, true));
// return address data.
return $address_arr->fields;
}
// debug
$this->zcLog('getOverrideAddress - 3', 'no override record found');
}
// debug
$this->zcLog('getOverrideAddress - 4', 'not logged in and not in markflow mode - nothing to override');
return false;
}
/**
* This method attempts to match items in an address book, to avoid
* duplicate entries to the address book. On a successful match it
* returns the address_book_id(int) - on failure it returns false.
*
* @param int $customer_id
* @param array $address_question_arr
* @return int|boolean
* @since ZC v1.3.7
*/
function findMatchingAddressBookEntry($customer_id, $address_question_arr) {
global $db;
// if address is blank, don't do any matching
if ($address_question_arr['street_address'] == '') return false;
// default
$country_id = '223';
$address_format_id = 2; //2 is the American format
// first get the zone id's from the 2 digit iso codes
// country first
$sql = "SELECT countries_id, address_format_id
FROM " . TABLE_COUNTRIES . "
WHERE countries_iso_code_2 = :countryCode:
OR countries_name = :countryName:
OR countries_iso_code_2 = :countryName:
OR countries_name = :countryCode:
LIMIT 1";
$sql = $db->bindVars($sql, ':countryCode:', $address_question_arr['country']['iso_code_2'], 'string');
$sql = $db->bindVars($sql, ':countryName:', $address_question_arr['country']['title'], 'string');
$country = $db->Execute($sql);
// see if we found a record, if not default to American format
if (!$country->EOF) {
$country_id = $country->fields['countries_id'];
$address_format_id = $country->fields['address_format_id'];
}
// see if the country code has a state
$sql = "SELECT zone_id
FROM " . TABLE_ZONES . "
WHERE zone_country_id = :zoneId
LIMIT 1";
$sql = $db->bindVars($sql, ':zoneId', $country_id, 'integer');
$country_zone_check = $db->Execute($sql);
$check_zone = $country_zone_check->RecordCount();
$zone_id = 0;
$logMsg = array('zone_id' => '-not found-');
// now try and find the zone_id (state/province code)
// use the country id above
if ($check_zone) {
$sql = "SELECT zone_id
FROM " . TABLE_ZONES . "
WHERE zone_country_id = :zoneId
AND (zone_code = :zoneCode
OR zone_name = :zoneCode )
LIMIT 1";
$sql = $db->bindVars($sql, ':zoneId', $country_id, 'integer');
$sql = $db->bindVars($sql, ':zoneCode', $address_question_arr['state'], 'string');
$zone = $db->Execute($sql);
if (!$zone->EOF) {
// grab the id
$zone_id = $zone->fields['zone_id'];
$logMsg = $zone->fields;
} else {
$check_zone = false;
}
}
// debug
$this->zcLog('findMatchingAddressBookEntry - 1-stats', 'lookups:' . "\n" . print_r(array_merge($country->fields, array('zone_country_id' => $country_zone_check->fields), $logMsg), true) . "\n" . 'check_zone: ' . $check_zone . "\n" . 'zone:' . $zone_id . "\nSubmittedAddress:".print_r($address_question_arr, TRUE));
// do a match on address, street, street2, city
$sql = "SELECT address_book_id, entry_street_address, entry_suburb, entry_city, entry_company, entry_firstname, entry_lastname
FROM " . TABLE_ADDRESS_BOOK . "
WHERE customers_id = :customerId
AND entry_country_id = :countryId";
if ($check_zone) {
$sql .= " AND entry_zone_id = :zoneId";
}
$sql = $db->bindVars($sql, ':zoneId', $zone_id, 'integer');
$sql = $db->bindVars($sql, ':countryId', $country_id, 'integer');
$sql = $db->bindVars($sql, ':customerId', $customer_id, 'integer');
$answers_arr = $db->Execute($sql);
// debug
$this->zcLog('findMatchingAddressBookEntry - 2-read for match', "\nLookup RecordCount = " . $answers_arr->RecordCount());
if (!$answers_arr->EOF) {
// build a base string to compare street+suburb+city content
//$matchQuestion = str_replace("\n", '', $address_question_arr['company']);
//$matchQuestion = str_replace("\n", '', $address_question_arr['name']);
$matchQuestion = str_replace("\n", '', $address_question_arr['street_address']);
$matchQuestion = trim($matchQuestion);
$matchQuestion = $matchQuestion . str_replace("\n", '', $address_question_arr['suburb']);
$matchQuestion = $matchQuestion . str_replace("\n", '', $address_question_arr['city']);
$matchQuestion = str_replace("\t", '', $matchQuestion);
$matchQuestion = trim($matchQuestion);
$matchQuestion = strtolower($matchQuestion);
$matchQuestion = str_replace(' ', '', $matchQuestion);
// go through the data
while (!$answers_arr->EOF) {
// now the matching logic
// first from the db
$fromDb = '';
// $fromDb = str_replace("\n", '', $answers_arr->fields['entry_company']);
/// $fromDb = str_replace("\n", '', $answers_arr->fields['entry_firstname'].$answers_arr->fields['entry_lastname']);
$fromDb = str_replace("\n", '', $answers_arr->fields['entry_street_address']);
$fromDb = trim($fromDb);
if (!empty($answers_arr->fields['entry_suburb'])) {
$fromDb = $fromDb . str_replace("\n", '', $answers_arr->fields['entry_suburb']);
}
$fromDb = $fromDb . str_replace("\n", '', $answers_arr->fields['entry_city']);
$fromDb = str_replace("\t", '', $fromDb);
$fromDb = trim($fromDb);
$fromDb = strtolower($fromDb);
$fromDb = str_replace(' ', '', $fromDb);
// debug
$this->zcLog('findMatchingAddressBookEntry - 3a', "From PayPal:\r\n" . $matchQuestion . "\r\n\r\nFrom DB:\r\n" . $fromDb . "\r\n". print_r($answers_arr->fields, true));
// check the strings
if (strlen($fromDb) == strlen($matchQuestion)) {
if ($fromDb == $matchQuestion) {
// exact match return the id
// debug
$this->zcLog('findMatchingAddressBookEntry - 3b', "Exact match:\n" . print_r($answers_arr->fields, true));
return $answers_arr->fields['address_book_id'];
}
} elseif (strlen($fromDb) > strlen($matchQuestion)) {
if (substr($fromDb, 0, strlen($matchQuestion)) == $matchQuestion) {
// we have a match return it (PP)
// debug
$this->zcLog('findMatchingAddressBookEntry - 3b', "partial match (PP):\n" . print_r($answers_arr->fields, true));
return $answers_arr->fields['address_book_id'];
}
} else {
if ($fromDb == substr($matchQuestion, 0, strlen($fromDb))) {
// we have a match return it (DB)
// debug
$this->zcLog('findMatchingAddressBookEntry - 3b', "partial match (DB):\n" . print_r($answers_arr->fields, true));
return $answers_arr->fields['address_book_id'];
}
}
$answers_arr->MoveNext();
}
}
// debug
$this->zcLog('findMatchingAddressBookEntry - 4', "no match");
// no matches found
return false;
}
/**
* This method adds an address book entry to the database, this allows us to add addresses
* that we get back from PayPal that are not in Zen Cart
*
* @param int $customer_id
* @param array $address_question_arr
* @return int
* @since ZC v1.3.7
*/
function addAddressBookEntry($customer_id, $address_question_arr, $make_default = false) {
global $db;
// debug
$this->zcLog('addAddressBookEntry - 1', 'address to add: ' . "\n" . print_r($address_question_arr, true));
// if address is blank, don't do any matching
if ($address_question_arr['street_address'] == '') return false;
// set some defaults
$country_id = '223';
$address_format_id = 2; //2 is the American format
// first get the zone id's from the 2 digit iso codes
// country first
$sql = "SELECT countries_id, address_format_id
FROM " . TABLE_COUNTRIES . "
WHERE countries_iso_code_2 = :countryCode:
OR countries_name = :countryName:
OR countries_iso_code_2 = :countryName:
OR countries_name = :countryCode:
LIMIT 1";
$sql = $db->bindVars($sql, ':countryCode:', $address_question_arr['country']['iso_code_2'], 'string');
$sql = $db->bindVars($sql, ':countryName:', $address_question_arr['country']['title'], 'string');
$country = $db->Execute($sql);
// see if we found a record, if not default to American format
if (!$country->EOF) {
$country_id = $country->fields['countries_id'];
$address_format_id = (int)$country->fields['address_format_id'];
} else {
// if defaulting to US, make sure US is valid
$sql = "SELECT countries_id FROM " . TABLE_COUNTRIES . " WHERE countries_id = :countryId LIMIT 1";
$sql = $db->bindVars($sql, ':countryId', $country_id, 'integer');
$result = $db->Execute($sql);
if ($result->EOF) {
$this->notify('NOTIFY_HEADER_ADDRESS_BOOK_ADD_ENTRY_INVALID_ATTEMPT', $customer_id, $country_id, $address_format_id, $address_question_arr);
$this->zcLog('addAddressBookEntry - 3', 'Failed to add address due to country restrictions');
return FALSE;
}
}
// see if the country code has a state
$sql = "SELECT zone_id
FROM " . TABLE_ZONES . "
WHERE zone_country_id = :zoneId
LIMIT 1";
$sql = $db->bindVars($sql, ':zoneId', $country_id, 'integer');
$country_zone_check = $db->Execute($sql);
$check_zone = $country_zone_check->RecordCount();
// now try and find the zone_id (state/province code)
// use the country id above
$zone_id = 0;
if ($check_zone) {
$sql = "SELECT zone_id
FROM " . TABLE_ZONES . "
WHERE zone_country_id = :zoneId
AND (zone_code = :zoneCode
OR zone_name = :zoneCode )
LIMIT 1";
$sql = $db->bindVars($sql, ':zoneId', $country_id, 'integer');
$sql = $db->bindVars($sql, ':zoneCode', $address_question_arr['state'], 'string');
$zone = $db->Execute($sql);
if (!$zone->EOF) {
// grab the id
$zone_id = $zone->fields['zone_id'];
}
}
// truncate long data
$address_question_arr['company'] = substr($address_question_arr['company'], 0, zen_field_length(TABLE_ADDRESS_BOOK, 'entry_company'));
$address_question_arr['street_address'] = substr($address_question_arr['street_address'], 0, zen_field_length(TABLE_ADDRESS_BOOK, 'entry_street_address'));
$address_question_arr['suburb'] = substr($address_question_arr['suburb'], 0, zen_field_length(TABLE_ADDRESS_BOOK, 'entry_suburb'));
$address_question_arr['city'] = substr($address_question_arr['city'], 0, zen_field_length(TABLE_ADDRESS_BOOK, 'entry_city'));
$address_question_arr['state'] = substr($address_question_arr['state'], 0, zen_field_length(TABLE_ADDRESS_BOOK, 'entry_state'));
$address_question_arr['postcode'] = substr($address_question_arr['postcode'], 0, zen_field_length(TABLE_ADDRESS_BOOK, 'entry_postcode'));
// now run the insert
// this isn't the best way to get fname/lname but it will get the majority of cases
[$fname, $lname] = explode(' ', $address_question_arr['name']);
$sql_data_array= array(array('fieldName'=>'entry_firstname', 'value'=>$fname, 'type'=>'string'),
array('fieldName'=>'entry_lastname', 'value'=>$lname, 'type'=>'string'),
array('fieldName'=>'entry_street_address', 'value'=>$address_question_arr['street_address'], 'type'=>'string'),
array('fieldName'=>'entry_postcode', 'value'=>$address_question_arr['postcode'], 'type'=>'string'),
array('fieldName'=>'entry_city', 'value'=>$address_question_arr['city'], 'type'=>'string'),
array('fieldName'=>'entry_country_id', 'value'=>$country_id, 'type'=>'integer'));
if ($address_question_arr['company'] != '' && $address_question_arr['company'] != $address_question_arr['name']) array('fieldName'=>'entry_company', 'value'=>$address_question_arr['company'], 'type'=>'string');
if (!empty($address_question_arr['payer_gender'])) {
$sql_data_array[] = array('fieldName'=>'entry_gender', 'value'=>$address_question_arr['payer_gender'], 'type'=>'enum:m|f');
}
$sql_data_array[] = array('fieldName'=>'entry_suburb', 'value'=>$address_question_arr['suburb'], 'type'=>'string');
if ($zone_id > 0) {
$sql_data_array[] = array('fieldName'=>'entry_zone_id', 'value'=>$zone_id, 'type'=>'integer');
$sql_data_array[] = array('fieldName'=>'entry_state', 'value'=>'', 'type'=>'string');
} else {
$sql_data_array[] = array('fieldName'=>'entry_zone_id', 'value'=>'0', 'type'=>'integer');
$sql_data_array[] = array('fieldName'=>'entry_state', 'value'=>$address_question_arr['state'], 'type'=>'string');
}
$sql_data_array[] = array('fieldName'=>'customers_id', 'value'=>$customer_id, 'type'=>'integer');
$db->perform(TABLE_ADDRESS_BOOK, $sql_data_array);
$new_address_book_id = $db->insert_ID();
$this->notify('NOTIFY_HEADER_ADDRESS_BOOK_ADD_ENTRY_DONE', 'paypal express checkout', $new_address_book_id, $sql_data_array, $make_default);
// make default if set, update
if ($make_default) {
$sql_data_array = array();
$sql_data_array[] = array('fieldName'=>'customers_default_address_id', 'value'=>$new_address_book_id, 'type'=>'integer');
$where_clause = "customers_id = :customersID";
$where_clause = $db->bindVars($where_clause, ':customersID', $customer_id, 'integer');
$db->perform(TABLE_CUSTOMERS, $sql_data_array, 'update', $where_clause);
$_SESSION['customer_default_address_id'] = $new_address_book_id;
}
// set the sendto
$_SESSION['sendto'] = $new_address_book_id;
// debug
$this->zcLog('addAddressBookEntry - 2', 'added address #' . $new_address_book_id. "\n" . 'SESSION[sendto] is now set to ' . $_SESSION['sendto']);
// return the address_id
return $new_address_book_id;
}
/**
* If we created an account for the customer, this logs them in and notes that the record was created for PayPal EC purposes
* @since ZC v1.3.7
*/
function user_login($email_address, $redirect = true) {
global $db, $order, $messageStack;
global $session_started;
if ($session_started == false) {
zen_redirect(zen_href_link(FILENAME_COOKIE_USAGE));
}
$sql = "SELECT * FROM " . TABLE_CUSTOMERS . "
WHERE customers_email_address = :custEmail ";
$sql = $db->bindVars($sql, ':custEmail', $email_address, 'string');
$check_customer = $db->Execute($sql);
if ($check_customer->EOF) {
$this->terminateEC(MODULE_PAYMENT_PAYPALWPP_TEXT_BAD_LOGIN, true);
}
if (zen_config('SESSION_RECREATE') === 'True') {
zen_session_recreate();
}
$sql = "SELECT entry_country_id, entry_zone_id
FROM " . TABLE_ADDRESS_BOOK . "
WHERE customers_id = :custID
AND address_book_id = :addrID ";
$sql = $db->bindVars($sql, ':custID', $check_customer->fields['customers_id'], 'integer');
$sql = $db->bindVars($sql, ':addrID', $check_customer->fields['customers_default_address_id'], 'integer');
$check_country = $db->Execute($sql);
$_SESSION['customer_id'] = (int)$check_customer->fields['customers_id'];
$_SESSION['customer_default_address_id'] = $check_customer->fields['customers_default_address_id'];
$_SESSION['customer_first_name'] = $check_customer->fields['customers_firstname'];
$_SESSION['customer_last_name'] = $check_customer->fields['customers_lastname'];
$_SESSION['customer_country_id'] = $check_country->fields['entry_country_id'];
$_SESSION['customer_zone_id'] = $check_country->fields['entry_zone_id'];
$order->customer['id'] = $_SESSION['customer_id'];
$sql = "UPDATE " . TABLE_CUSTOMERS_INFO . "
SET customers_info_date_of_last_logon = now(),
customers_info_number_of_logons = customers_info_number_of_logons+1
WHERE customers_info_id = :custID ";
$sql = $db->bindVars($sql, ':custID', $_SESSION['customer_id'], 'integer');
$db->Execute($sql);
// -----
// Check any **previous** customer's cart contents for potential merge, bypassing
// this check if a temporary account has been created for an order placed via
// click of the PPEC button.
//
if ($_SESSION['paypal_ec_temp'] === false) {
// -----
// Determine the sum of products' quantities (a floating-point value!) currently in the cart,
// restore the customer's saved cart contents and determine the sum of products' quantities
// now present in the cart.
//
$zc_check_basket_before = $_SESSION['cart']->count_contents();
$_SESSION['cart']->restore_contents();
$zc_check_basket_after = $_SESSION['cart']->count_contents();
// -----
// See if a message to the customer (possibly with a redirect back to the shopping-cart page)
// is required if the cart's contents have changed.
//
if (zen_config('SHOW_SHOPPING_CART_COMBINED') !== '0' && $zc_check_basket_before > 0) {
if ($zc_check_basket_before != $zc_check_basket_after && $zc_check_basket_after > 0) {
if (zen_config('SHOW_SHOPPING_CART_COMBINED') === '2') {
// warning only do not send to cart ('2')
$messageStack->add_session('header', WARNING_SHOPPING_CART_COMBINED, 'caution');
} else {
// show warning and send to shopping cart for review ('1')
$messageStack->add_session('shopping_cart', WARNING_SHOPPING_CART_COMBINED, 'caution');
zen_redirect(zen_href_link(FILENAME_SHOPPING_CART, '', 'NONSSL'));
}
}
}
// -----
// If the cart contents are unchanged, i.e. no saved products were added via the
// cart's restore_contents method, save the cart's cartID value (a random number
// that's been changed by that call to restore_contents) into the session
// so that an unchanged cart doesn't result in an unwanted redirect back to the
// checkout_shipping phase.
//
if ($zc_check_basket_before == $zc_check_basket_after) {
$_SESSION['cartID'] = $_SESSION['cart']->cartID;
}
}
// eof: contents merge notice
if ($redirect) {
$this->terminateEC();
}
return true;
}
/**
* If the account was created only for temporary purposes to place the PayPal order, delete it.
* @since ZC v1.3.7
*/
function ec_delete_user($cid) {
global $db;
unset($_SESSION['customer_id']);
unset($_SESSION['customer_default_address_id']);
unset($_SESSION['customer_first_name']);
unset($_SESSION['customer_country_id']);
unset($_SESSION['customer_zone_id']);
unset($_SESSION['comments']);
unset($_SESSION['customer_guest_id']);
$cid = (int)$cid;
$sql = "DELETE FROM " . TABLE_ADDRESS_BOOK . " WHERE customers_id = " . $cid;
$db->Execute($sql);
$sql = "DELETE FROM " . TABLE_CUSTOMERS . " WHERE customers_id = " . $cid;
$db->Execute($sql);
$sql = "DELETE FROM " . TABLE_CUSTOMERS_INFO . " WHERE customers_info_id = " . $cid;
$db->Execute($sql);
$sql = "DELETE FROM " . TABLE_CUSTOMERS_BASKET . " WHERE customers_id = " . $cid;
$db->Execute($sql);
$sql = "DELETE FROM " . TABLE_CUSTOMERS_BASKET_ATTRIBUTES . " WHERE customers_id = " . $cid;
$db->Execute($sql);
$sql = "DELETE FROM " . TABLE_WHOS_ONLINE . " WHERE customer_id = " . $cid;
$db->Execute($sql);
}
/**
* If the EC flow has to be interrupted for any reason, this does the appropriate cleanup and displays status/error messages.
* @since ZC v1.3.7
*/
function terminateEC($error_msg = '', $kill_sess_vars = false, $goto_page = '') {
global $messageStack, $order, $order_total_modules;
$error_msg = trim($error_msg);
if (substr($error_msg, -1) == '-') $error_msg = trim(substr($error_msg, 0, strlen($error_msg) - 1));
$stackAlert = 'header';
// debug
$this->_doDebug('PayPal test Log - terminateEC-A', "goto page: " . $goto_page . "\nerror_msg: " . $error_msg . "\n\nSession data: " . print_r($_SESSION, true));
if ($kill_sess_vars) {
if (!empty($_SESSION['paypal_ec_temp'])) {
$this->ec_delete_user($_SESSION['customer_id']);
}
// Unregister the paypal session variables, making the user start over.
unset($_SESSION['paypal_ec_temp']);
unset($_SESSION['paypal_ec_token']);
unset($_SESSION['paypal_ec_payer_id']);
unset($_SESSION['paypal_ec_payer_info']);
unset($_SESSION['paypal_ec_final']);
unset($_SESSION['paypal_ec_markflow']);
// debug
$this->zcLog('termEC-1', 'Killed the session vars as requested');
}
$this->zcLog('termEC-2', 'BEFORE: $this->showPaymentPage = ' . (int)$this->showPaymentPage . "\nToken Data:" . (!isset($_SESSION['paypal_ec_token']) ? 'not set' : $_SESSION['paypal_ec_token'])) ;
// force display of payment page if GV/DC active for this customer
if (zen_config('MODULE_ORDER_TOTAL_INSTALLED') && $this->showPaymentPage !== true && isset($_SESSION['paypal_ec_token']) ) {
require_once(DIR_WS_CLASSES . 'order.php');
$order = new order;
require_once(DIR_WS_CLASSES . 'order_total.php');
$order_total_modules = new order_total;
$order_totals = $order_total_modules->process();
$selection = $order_total_modules->credit_selection();
if (sizeof($selection)>0) $this->showPaymentPage = true;
}
// if came from Payment page, don't go back to it
if (isset($_SESSION['paypal_ec_markflow']) && $_SESSION['paypal_ec_markflow'] == 1) {
$this->showPaymentPage = false;
}
// if in DP mode, don't go to payment page ... we've already been there to get here
if ($goto_page == FILENAME_CHECKOUT_PROCESS) $this->showPaymentPage = false;
// debug
$this->zcLog('termEC-3', 'AFTER: $this->showPaymentPage = ' . (int)$this->showPaymentPage);
if (!empty($_SESSION['customer_first_name']) && !empty($_SESSION['customer_id'])) {
if ($this->showPaymentPage === true || $goto_page == FILENAME_CHECKOUT_PAYMENT) {
// debug
$this->zcLog('termEC-4', 'We ARE logged in, and $this->showPaymentPage === true');
// if no shipping selected or if shipping cost is < 0 goto shipping page
if (!isset($_SESSION['shipping']) || !isset($_SESSION['shipping']['cost']) || $_SESSION['shipping']['cost'] < 0) {
// debug
$this->zcLog('termEC-5', 'Have no shipping method selected, or shipping < 0 so set FILENAME_CHECKOUT_SHIPPING');
$redirect_path = FILENAME_CHECKOUT_SHIPPING;
$stackAlert = 'checkout_shipping';
} else {
// debug
$this->zcLog('termEC-6', 'We DO have a shipping method selected, so goto PAYMENT');
$redirect_path = FILENAME_CHECKOUT_PAYMENT;
$stackAlert = 'checkout_payment';
}
} elseif ($goto_page) {
// debug
$this->zcLog('termEC-7', '$this->showPaymentPage NOT true, and have custom page parameter: ' . $goto_page);
$redirect_path = $goto_page;
$stackAlert = 'header';
if ($goto_page == FILENAME_SHOPPING_CART) $stackAlert = 'shopping_cart';
} else {
// debug
$this->zcLog('termEC-8', '$this->showPaymentPage NOT true, and NO custom page selected ... using SHIPPING as default');
$redirect_path = FILENAME_CHECKOUT_SHIPPING;
$stackAlert = 'checkout_shipping';
}
} else {
// debug
$this->zcLog('termEC-9', 'We are NOT logged in, so set snapshot to Shipping page, and redirect to login');
$_SESSION['navigation']->set_snapshot(FILENAME_CHECKOUT_SHIPPING);
$redirect_path = FILENAME_LOGIN;
$stackAlert = 'login';
}
if ($error_msg) {
$messageStack->add_session($stackAlert, $error_msg, 'error');
}
// debug
$this->zcLog('termEC-10', 'Redirecting to ' . $redirect_path . ' - Stack: ' . $stackAlert . "\n" . 'Message: ' . $error_msg . "\nSession Data: " . print_r($_SESSION, true));
zen_redirect(zen_href_link($redirect_path, '', 'SSL', true, false));
}
/**
* Error / exception handling
* @since ZC v1.3.7
*/
function _errorHandler($response, $operation = '', $ignore_codes = '') {
global $messageStack, $doPayPal;
$gateway_mode = (!empty($response['PNREF']));
$basicError = (!$response || (isset($response['RESULT']) && $response['RESULT'] != 0) || (isset($response['ACK']) && !strstr($response['ACK'], 'Success')) || (!isset($response['RESULT']) && !isset($response['ACK'])));
$ignoreList = explode(',', str_replace(' ', '', $ignore_codes));
if (!empty($response['L_ERRORCODE0'])) {
foreach($ignoreList as $key=>$value) {
if ($value != '' && $response['L_ERRORCODE0'] == $value) {
$basicError = false;
}
}
}
/* Handle unilateral **/
if (!empty($response['RESULT']) && $response['RESULT'] == 'Unauthorized: Unilateral') {
$errorText = $response['RESULT'] . MODULE_PAYMENT_PAYPALWPP_TEXT_UNILATERAL;
$messageStack->add_session($errorText, 'error');
}
/* Handle FMF Scenarios **/
$response['L_ERRORCODE0'] = empty($response['L_ERRORCODE0']) ? 0 : $response['L_ERRORCODE0'];
$response['L_LONGMESSAGE2'] = empty($response['L_LONGMESSAGE2']) ? '' : $response['L_LONGMESSAGE2'];
if (in_array($operation, array('DoExpressCheckoutPayment', 'DoDirectPayment'))
&& ((isset($response['PAYMENTINFO_0_PAYMENTSTATUS']) && $response['PAYMENTINFO_0_PAYMENTSTATUS'] == 'Pending')
|| (isset($response['PAYMENTSTATUS']) && $response['PAYMENTSTATUS'] == 'Pending')
)
&& $response['L_ERRORCODE0'] == 11610) {
$this->fmfResponse = urldecode($response['L_SHORTMESSAGE0']);
$this->fmfErrors = array();
if ($response['ACK'] == 'SuccessWithWarning' && isset($response['L_PAYMENTINFO_0_FMFPENDINGID0'])) {
for ($i=0; $i<20; $i++) {
if (!isset($response["L_PAYMENTINFO_0_FMFPENDINGID$i"])) {
break;
}
$pendingid = $response["L_PAYMENTINFO_0_FMFPENDINGID$i"];
$this->fmfErrors[] = array('key' => $pendingid, 'status' => $pendingid, 'desc' => $response["L_PAYMENTINFO_0_FMFPENDINGDESCRIPTION$i"]);
}
}
return (sizeof($this->fmfErrors)>0) ? $this->fmfErrors : FALSE;
}
if (!isset($response['L_SHORTMESSAGE0']) && isset($response['RESPMSG']) && $response['RESPMSG'] != '') $response['L_SHORTMESSAGE0'] = $response['RESPMSG'];
//echo '
basicError='.$basicError.'
' . urldecode(print_r($response,true)); die('halted');
$errorInfo = '';
if (IS_ADMIN_FLAG === false) {
if (zen_is_logged_in()) {
if (zen_in_guest_checkout()) {
$customer_info = 'Guest checkout';
} else {
$customer_info = $_SESSION['customer_id'] . ' ' . $_SESSION['customer_first_name'] . ' ' . $_SESSION['customer_last_name'];
}
} else {
$customer_info = 'Not logged in';
}
$errorInfo = 'Problem occurred while customer ' . zen_output_string_protected($customer_info) . ' was attempting checkout with PayPal Express Checkout.' . "\n\n";
}
$this->notify('NOTIFY_PAYPALWPP_ERROR_HANDLER', $response, $operation, $basicError, $ignoreList, $errorInfo);
switch($operation) {
case 'SetExpressCheckout':
if ($basicError) {
// if error, display error message. If debug options enabled, email dump to store owner
if ($this->enableDebugging) {
$this->_doDebug('PayPal Error Log - ec_step1()', "In function: ec_step1()\r\n\r\nValue List:\r\n" . str_replace('&',"\r\n", $doPayPal->_sanitizeLog($doPayPal->_parseNameValueList($doPayPal->lastParamList))) . "\r\n\r\nResponse:\r\n" . print_r($response, true));
}
$errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_GEN_ERROR;
$errorNum = urldecode($response['L_ERRORCODE0'] . ($response['RESULT'] ?? ''));
if (isset($response['RESULT']) && $response['RESULT'] == 25) $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_NOT_WPP_ACCOUNT_ERROR;
if ($response['L_ERRORCODE0'] == 10002) $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_SANDBOX_VS_LIVE_ERROR;
if ($response['L_ERRORCODE0'] == 10565) {
$errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_WPP_BAD_COUNTRY_ERROR;
$_SESSION['payment'] = '';
}
if ($response['L_ERRORCODE0'] == 10736) $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_ADDR_ERROR;
if ($response['L_ERRORCODE0'] == 10752) $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_DECLINED;
// More optional response elements; initialize them to prevent follow-on PHP notices.
$response_optional = [
'L_ERRORCODE1',
'L_SHORTMESSAGE1',
'L_LONGMESSAGE1',
'L_ERRORCODE2',
'L_SHORTMESSAGE2',
'L_LONGMESSAGE2',
];
foreach ($response_optional as $optional) {
$response[$optional] ??= '';
}
$detailedMessage = ($errorText == MODULE_PAYMENT_PAYPALWPP_TEXT_GEN_ERROR || (int)trim($errorNum) > 0 || $this->enableDebugging || $response['CURL_ERRORS'] != '' || $this->emailAlerts) ? $errorNum . ' ' . urldecode(' ' . $response['L_SHORTMESSAGE0'] . ' - ' . $response['L_LONGMESSAGE0'] . (isset($response['RESPMSG']) ? ' ' . $response['RESPMSG'] : '') . ' ' . $response['CURL_ERRORS']) : '';
$detailedEmailMessage = ($detailedMessage == '') ? '' : $errorInfo . MODULE_PAYMENT_PAYPALWPP_TEXT_EMAIL_ERROR_MESSAGE . urldecode($response['L_ERRORCODE0'] . "\n" . $response['L_SHORTMESSAGE0'] . "\n" . $response['L_LONGMESSAGE0'] . $response['L_ERRORCODE1'] . "\n" . $response['L_SHORTMESSAGE1'] . "\n" . $response['L_LONGMESSAGE1'] . $response['L_ERRORCODE2'] . "\n" . $response['L_SHORTMESSAGE2'] . "\n" . $response['L_LONGMESSAGE2'] . ($response['CURL_ERRORS'] != '' ? "\n" . $response['CURL_ERRORS'] : '') . "\n\n" . 'Zen Cart message: ' . $errorText);
if (!isset($response['L_ERRORCODE0']) && isset($response['RESULT'])) $detailedEmailMessage .= "\n\n" . print_r($response, TRUE);
if ($detailedEmailMessage != '') zen_mail(zen_config('STORE_NAME'), zen_config('STORE_OWNER_EMAIL_ADDRESS'), MODULE_PAYMENT_PAYPALWPP_TEXT_EMAIL_ERROR_SUBJECT . ' (' . $this->uncomment($errorNum) . ')', $this->uncomment($detailedEmailMessage), zen_config('STORE_OWNER'), zen_config('STORE_OWNER_EMAIL_ADDRESS'), array('EMAIL_MESSAGE_HTML'=>$this->uncomment($detailedMessage)), 'paymentalert');
$this->terminateEC($errorText . ' (' . $errorNum . ') ' . $detailedMessage, true);
return true;
}
break;
case 'GetExpressCheckoutDetails':
if ($basicError || $_SESSION['paypal_ec_token'] != urldecode($response['TOKEN'])) {
// if response indicates an error, send the customer back to checkout and display the error. Debug to store owner if active.
if ($this->enableDebugging) {
$this->_doDebug('PayPal Error Log - ec_step2()', "In function: ec_step2()\r\n\r\nValue List:\r\n" . str_replace('&',"\r\n", urldecode($doPayPal->_sanitizeLog($doPayPal->_parseNameValueList($doPayPal->lastParamList)))) . "\r\n\r\nResponse:\r\n" . urldecode(print_r($response, true)));
}
$errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_GEN_ERROR;
if ($response['L_ERRORCODE0'] == 13113) {
$errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_PAYPAL_DECLINED;
$_SESSION['payment'] = '';
}
$this->terminateEC($errorText . ' (' . $response['L_ERRORCODE0'] . ' ' . urldecode($response['L_SHORTMESSAGE0'] . $response['RESULT']) . ')', true);
return true;
}
break;
case 'DoExpressCheckoutPayment':
if ($basicError || $_SESSION['paypal_ec_token'] != urldecode($response['TOKEN'])) {
// there's an error, so alert customer, and if debug is on, notify storeowner
if ($this->enableDebugging) {
$this->_doDebug('PayPal Error Log - before_process() - EC', "In function: before_process() - Express Checkout\r\n\r\nValue List:\r\n" . str_replace('&',"\r\n", $doPayPal->_sanitizeLog($doPayPal->_parseNameValueList($doPayPal->lastParamList))) . "\r\n\r\nResponse:\r\n" . print_r($response, true));
}
// if funding source problem occurred, must send back to re-select alternate funding source
if ($response['L_ERRORCODE0'] == 10422 || $response['L_ERRORCODE0'] == 10486) {
header("HTTP/1.1 302 Object Moved");
zen_redirect($this->ec_redirect_url);
die(MODULE_PAYMENT_PAYPALWPP_FUNDING_ERROR . " (Error " . zen_output_string_protected($response['L_ERRORCODE0']) . ")");
}
// some other error condition
$errorText = MODULE_PAYMENT_PAYPALWPP_INVALID_RESPONSE;
$errorNum = urldecode($response['L_ERRORCODE0'] . ($response['RESULT'] ?? ''));
if ($response['L_ERRORCODE0'] == 10415) $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_ORDER_ALREADY_PLACED_ERROR;
if ($response['L_ERRORCODE0'] == 10417) $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_INSUFFICIENT_FUNDS_ERROR;
if ($response['L_ERRORCODE0'] == 10474) $errorText .= urldecode($response['L_LONGMESSAGE0']);
$detailedMessage = ($errorText == MODULE_PAYMENT_PAYPALWPP_INVALID_RESPONSE || (int)trim($errorNum) > 0 || $this->enableDebugging || $response['CURL_ERRORS'] != '' || $this->emailAlerts) ? $errorNum . ' ' . urldecode(' ' . $response['L_SHORTMESSAGE0'] . ' - ' . $response['L_LONGMESSAGE0'] . ($response['RESULT'] ?? '') . ' ' . $response['CURL_ERRORS']) : '';
$detailedEmailMessage = ($detailedMessage == '') ? '' : $errorInfo . MODULE_PAYMENT_PAYPALWPP_TEXT_EMAIL_ERROR_MESSAGE . urldecode($response['L_ERRORCODE0'] . "\n" . $response['L_SHORTMESSAGE0'] . "\n" . $response['L_LONGMESSAGE0'] . ($response['L_ERRORCODE1'] ?? '') . "\n" . ($response['L_SHORTMESSAGE1'] ?? '') . "\n" . ($response['L_LONGMESSAGE1'] ?? '') . ($response['L_ERRORCODE2'] ?? '') . "\n" . ($response['L_SHORTMESSAGE2'] ?? '') . "\n" . ($response['L_LONGMESSAGE2'] ?? '') . ($response['CURL_ERRORS'] != '' ? "\n" . $response['CURL_ERRORS'] : '') . "\n\n" . 'Zen Cart message: ' . $errorText);
if (!isset($response['L_ERRORCODE0']) && isset($response['RESULT'])) $detailedEmailMessage .= "\n\n" . print_r($response, TRUE);
if ($detailedEmailMessage != '') zen_mail(zen_config('STORE_NAME'), zen_config('STORE_OWNER_EMAIL_ADDRESS'), MODULE_PAYMENT_PAYPALWPP_TEXT_EMAIL_ERROR_SUBJECT . ' (' . $this->uncomment($errorNum) . ')', $this->uncomment($detailedEmailMessage), zen_config('STORE_OWNER'), zen_config('STORE_OWNER_EMAIL_ADDRESS'), array('EMAIL_MESSAGE_HTML'=>$this->uncomment($detailedMessage)), 'paymentalert');
$this->terminateEC(($detailedEmailMessage == '' ? $errorText . ' (' . urldecode($response['L_SHORTMESSAGE0'] . $response['RESULT']) . ') ' : $detailedMessage), true);
return true;
}
break;
case 'DoRefund':
if ($basicError || (!isset($response['RESPMSG']) && !isset($response['REFUNDTRANSACTIONID']))) {
// if error, display error message. If debug options enabled, email dump to store owner
if ($this->enableDebugging) {
$this->_doDebug('PayPal Error Log - ' . $operation, "Value List:\r\n" . str_replace('&',"\r\n", $doPayPal->_sanitizeLog($doPayPal->_parseNameValueList($doPayPal->lastParamList))) . "\r\n\r\nResponse:\r\n" . print_r($response, true));
}
$errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_REFUND_ERROR;
if ($response['L_ERRORCODE0'] == 10009) $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_REFUNDFULL_ERROR;
if ((!empty($response['RESULT']) && $response['RESULT'] == 105) || isset($response['RESPMSG'])) $response['L_SHORTMESSAGE0'] = ($response['RESULT'] ?? '') . ' ' . $response['RESPMSG'];
if (urldecode($response['L_LONGMESSAGE0']) == 'This transaction has already been fully refunded') $response['L_SHORTMESSAGE0'] = urldecode($response['L_LONGMESSAGE0']);
if (urldecode($response['L_LONGMESSAGE0']) == 'Can not do a full refund after a partial refund') $response['L_SHORTMESSAGE0'] = urldecode($response['L_LONGMESSAGE0']);
if (urldecode($response['L_LONGMESSAGE0']) == 'The partial refund amount must be less than or equal to the remaining amount') $response['L_SHORTMESSAGE0'] = urldecode($response['L_LONGMESSAGE0']);
if (urldecode($response['L_LONGMESSAGE0']) == 'You can not refund this type of transaction') $response['L_SHORTMESSAGE0'] = urldecode($response['L_LONGMESSAGE0']);
$errorText .= ' (' . urldecode($response['L_SHORTMESSAGE0']) . ') ' . $response['L_ERRORCODE0'];
$messageStack->add_session($errorText, 'error');
return true;
}
break;
case 'DoAuthorization':
case 'DoReauthorization':
if ($basicError) {
// if error, display error message. If debug options enabled, email dump to store owner
if ($this->enableDebugging) {
$this->_doDebug('PayPal Error Log - ' . $operation, "Value List:\r\n" . str_replace('&',"\r\n", $doPayPal->_sanitizeLog($doPayPal->_parseNameValueList($doPayPal->lastParamList))) . "\r\n\r\nResponse:\r\n" . print_r($response, true));
}
$errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_AUTH_ERROR;
$errorText .= ' (' . urldecode($response['L_SHORTMESSAGE0']) . ') ' . $response['L_ERRORCODE0'];
$messageStack->add_session($errorText, 'error');
return true;
}
break;
case 'DoCapture':
if ($basicError) {
// if error, display error message. If debug options enabled, email dump to store owner
if ($this->enableDebugging) {
$this->_doDebug('PayPal Error Log - ' . $operation, "Value List:\r\n" . str_replace('&',"\r\n", $doPayPal->_sanitizeLog($doPayPal->_parseNameValueList($doPayPal->lastParamList))) . "\r\n\r\nResponse:\r\n" . print_r($response, true));
}
$errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_CAPT_ERROR;
if ($response['RESULT'] == 111) $response['L_SHORTMESSAGE0'] = $response['RESULT'] . ' ' . $response['RESPMSG'];
$errorText .= ' (' . urldecode($response['L_SHORTMESSAGE0']) . ') ' . $response['L_ERRORCODE0'];
$messageStack->add_session($errorText, 'error');
return true;
}
break;
case 'DoVoid':
if ($basicError) {
// if error, display error message. If debug options enabled, email dump to store owner
if ($this->enableDebugging) {
$this->_doDebug('PayPal Error Log - ' . $operation, "Value List:\r\n" . str_replace('&',"\r\n", $doPayPal->_sanitizeLog($doPayPal->_parseNameValueList($doPayPal->lastParamList))) . "\r\n\r\nResponse:\r\n" . print_r($response, true));
}
$errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_VOID_ERROR;
if (empty($response['RESULT'])) {
$response['RESULT'] = '';
}
if ($response['RESULT'] == 12) $response['L_SHORTMESSAGE0'] = $response['RESULT'] . ' ' . $response['RESPMSG'];
if ($response['RESULT'] == 108) $response['L_SHORTMESSAGE0'] = $response['RESULT'] . ' ' . $response['RESPMSG'];
$errorText .= ' (' . urldecode($response['L_SHORTMESSAGE0']) . ') ' . $response['L_ERRORCODE0'];
$messageStack->add_session($errorText, 'error');
return true;
}
break;
case 'GetTransactionDetails':
if ($basicError) {
// if error, display error message. If debug options enabled, email dump to store owner
if ($this->enableDebugging) {
$this->_doDebug('PayPal Error Log - ' . $operation, "Value List:\r\n" . str_replace('&',"\r\n", $doPayPal->_sanitizeLog($doPayPal->_parseNameValueList($doPayPal->lastParamList))) . "\r\n\r\nResponse:\r\n" . print_r($response, true));
}
$errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_GETDETAILS_ERROR;
$errorText .= ' (' . urldecode($response['L_SHORTMESSAGE0']) . ') ' . $response['L_ERRORCODE0'];
$messageStack->add_session($errorText, 'error');
return true;
}
break;
case 'TransactionSearch':
if ($basicError) {
// if error, display error message. If debug options enabled, email dump to store owner
if ($this->enableDebugging) {
$this->_doDebug('PayPal Error Log - ' . $operation, "Value List:\r\n" . str_replace('&',"\r\n", $doPayPal->_sanitizeLog($doPayPal->_parseNameValueList($doPayPal->lastParamList))) . "\r\n\r\nResponse:\r\n" . print_r($response, true));
}
$errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_TRANSSEARCH_ERROR;
$errorText .= ' (' . urldecode($response['L_SHORTMESSAGE0']) . ') ' . $response['L_ERRORCODE0'];
$messageStack->add_session($errorText, 'error');
return true;
}
break;
default:
if ($basicError) {
// if error, display error message. If debug options enabled, email dump to store owner
if ($this->enableDebugging) {
$this->_doDebug('PayPal Error Log - ' . $operation, "Value List:\r\n" . str_replace('&',"\r\n", $doPayPal->_sanitizeLog($doPayPal->_parseNameValueList($doPayPal->lastParamList))) . "\r\n\r\nResponse:\r\n" . print_r($response, true));
}
$errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_GEN_API_ERROR;
$errorNum = $errorNum ?? '';
$errorNum .= ' (' . urldecode($response['L_SHORTMESSAGE0'] . ' ) ' . $response['L_ERRORCODE0'];
$detailedMessage = ($errorText == MODULE_PAYMENT_PAYPALWPP_TEXT_GEN_API_ERROR || $errorText == MODULE_PAYMENT_PAYPALWPP_TEXT_DECLINED || (int)trim($errorNum) > 0 || $this->enableDebugging || $response['CURL_ERRORS'] != '' || $this->emailAlerts) ? urldecode(' ' . $response['L_SHORTMESSAGE0'] . ' - ' . $response['L_LONGMESSAGE0'] . ' ' . $response['CURL_ERRORS']) : '';
$detailedEmailMessage = ($detailedMessage == '') ? '' : MODULE_PAYMENT_PAYPALWPP_TEXT_EMAIL_ERROR_MESSAGE . ' ' . $response['RESPMSG'] . urldecode($response['L_ERRORCODE0'] . "\n" . $response['L_SHORTMESSAGE0'] . "\n" . $response['L_LONGMESSAGE0'] . $response['L_ERRORCODE1'] . "\n" . $response['L_SHORTMESSAGE1'] . "\n" . $response['L_LONGMESSAGE1'] . $response['L_ERRORCODE2'] . "\n" . $response['L_SHORTMESSAGE2'] . "\n" . $response['L_LONGMESSAGE2'] . ($response['CURL_ERRORS'] != '' ? "\n" . $response['CURL_ERRORS'] : '') . "\n\n" . 'Zen Cart message: ' . $detailedMessage . "\n\n" . 'Transaction Response Details: ' . print_r($response, true) . "\n\n" . 'Transaction Submission: ' . urldecode($doPayPal->_sanitizeLog($doPayPal->_parseNameValueList($doPayPal->lastParamList), true)));
if ($detailedEmailMessage != '') zen_mail(zen_config('STORE_NAME'), zen_config('STORE_OWNER_EMAIL_ADDRESS'), MODULE_PAYMENT_PAYPALWPP_TEXT_EMAIL_ERROR_SUBJECT . ' (' . $this->uncomment($errorNum) . ')', $this->uncomment($detailedMessage), zen_config('STORE_OWNER'), zen_config('STORE_OWNER_EMAIL_ADDRESS'), array('EMAIL_MESSAGE_HTML'=>nl2br($this->uncomment($detailedEmailMessage))), 'paymentalert');
$messageStack->add_session($errorText . $errorNum . $detailedMessage, 'error');
return true;
}
break;
}
}
/**
* @since v1.3.7.1
*/
function tableCheckup() {
global $db, $sniffer;
$fieldOkay1 = (method_exists($sniffer, 'field_type')) ? $sniffer->field_type(TABLE_PAYPAL, 'txn_id', 'varchar(20)', true) : -1;
$fieldOkay2 = ($sniffer->field_exists(TABLE_PAYPAL, 'module_name')) ? true : -1;
$fieldOkay3 = ($sniffer->field_exists(TABLE_PAYPAL, 'order_id')) ? true : -1;
if ($fieldOkay1 == -1) {
$sql = "SHOW fields FROM " . TABLE_PAYPAL;
$result = $db->Execute($sql);
while (!$result->EOF) {
if ($result->fields['Field'] == 'txn_id') {
if ($result->fields['Type'] == 'varchar(20)') {
$fieldOkay1 = true; // exists and matches required type, so skip to other checkup
} else {
$fieldOkay1 = $result->fields['Type']; // doesn't match, so return what it "is"
break;
}
}
$result->MoveNext();
}
}
if ($fieldOkay1 !== true) {
// temporary fix to table structure for v1.3.7.x -- may remove in later release
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE payment_type payment_type varchar(40) NOT NULL default ''");
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE txn_type txn_type varchar(40) NOT NULL default ''");
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE payment_status payment_status varchar(32) NOT NULL default ''");
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE reason_code reason_code varchar(40) default NULL");
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE pending_reason pending_reason varchar(32) default NULL");
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE invoice invoice varchar(128) default NULL");
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE payer_business_name payer_business_name varchar(128) default NULL");
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE address_name address_name varchar(64) default NULL");
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE address_street address_street varchar(254) default NULL");
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE address_city address_city varchar(120) default NULL");
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE address_state address_state varchar(120) default NULL");
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE payer_email payer_email varchar(128) NOT NULL default ''");
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE business business varchar(128) NOT NULL default ''");
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE receiver_email receiver_email varchar(128) NOT NULL default ''");
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE txn_id txn_id varchar(20) NOT NULL default ''");
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE parent_txn_id parent_txn_id varchar(20) default NULL");
}
if ($fieldOkay2 !== true) {
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " ADD COLUMN module_name varchar(40) NOT NULL default '' after txn_type");
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " ADD COLUMN module_mode varchar(40) NOT NULL default '' after module_name");
}
if ($fieldOkay3 !== true) {
$db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE zen_order_id order_id int(11) NOT NULL default '0'");
}
global $current_page;
if ($current_page === (FILENAME_MODULES . '.php')) {
$db->Execute(
"UPDATE " . TABLE_CONFIGURATION . "
SET configuration_description = 'Do you want to enable this payment module? Use the Retired setting if you are planning to remove this payment module but still have administrative actions to perform against orders placed with this module.',
set_function = 'zen_cfg_select_option(array(\'True\', \'False\', \'Retired\'), '
WHERE configuration_key = 'MODULE_PAYMENT_PAYPALWPP_STATUS'
LIMIT 1"
);
}
}
/**
* Convert HTML comments to readable text
* @param string $string
* @return string
* @since ZC v1.5.5
*/
function uncomment($string) {
return str_replace(array(''), array('[', ']'), $string);
}
}