PageRenderTime 43ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 1ms

/includes/modules/payment/paypalwpp.php

https://bitbucket.org/gcooksey3/zencart
PHP | 2949 lines | 2060 code | 262 blank | 627 comment | 808 complexity | 2dd11783943721b5963187ec25c48d8a MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. /**
  3. * paypalwpp.php payment module class for PayPal Express Checkout payment method
  4. *
  5. * @package paymentMethod
  6. * @copyright Copyright 2003-2012 Zen Cart Development Team
  7. * @copyright Portions Copyright 2003 osCommerce
  8. * @license http://www.zen-cart.com/license/2_0.txt GNU Public License V2.0
  9. * @version GIT: $Id: Author: DrByte Tue Aug 28 14:21:34 2012 -0400 Modified in v1.5.1 $
  10. */
  11. /**
  12. * load the communications layer code
  13. */
  14. require_once(DIR_FS_CATALOG . DIR_WS_MODULES . 'payment/paypal/paypal_curl.php');
  15. /**
  16. * the PayPal payment module with Express Checkout
  17. */
  18. class paypalwpp extends base {
  19. /**
  20. * name of this module
  21. *
  22. * @var string
  23. */
  24. var $code;
  25. /**
  26. * displayed module title
  27. *
  28. * @var string
  29. */
  30. var $title;
  31. /**
  32. * displayed module description
  33. *
  34. * @var string
  35. */
  36. var $description;
  37. /**
  38. * module status - set based on various config and zone criteria
  39. *
  40. * @var string
  41. */
  42. var $enabled;
  43. /**
  44. * the zone to which this module is restricted for use
  45. *
  46. * @var string
  47. */
  48. var $zone;
  49. /**
  50. * debugging flag
  51. *
  52. * @var boolean
  53. */
  54. var $enableDebugging = false;
  55. /**
  56. * Determines whether payment page is displayed or not
  57. *
  58. * @var boolean
  59. */
  60. var $showPaymentPage = false;
  61. var $flagDisablePaymentAddressChange = false;
  62. /**
  63. * sort order of display
  64. *
  65. * @var int
  66. */
  67. var $sort_order = 0;
  68. /**
  69. * Button Source / BN code -- enables the module to work for Zen Cart
  70. *
  71. * @var string
  72. */
  73. var $buttonSourceEC = 'ZenCart-EC_us';
  74. /**
  75. * order status setting for pending orders
  76. *
  77. * @var int
  78. */
  79. var $order_pending_status = 1;
  80. /**
  81. * order status setting for completed orders
  82. *
  83. * @var int
  84. */
  85. var $order_status = DEFAULT_ORDERS_STATUS_ID;
  86. /**
  87. * Debug tools
  88. */
  89. var $_logDir = 'includes/modules/payment/paypal/logs/';
  90. var $_logLevel = 0;
  91. /**
  92. * FMF
  93. */
  94. var $fmfResponse = '';
  95. var $fmfErrors = array();
  96. /**
  97. * class constructor
  98. */
  99. function paypalwpp() {
  100. include_once(zen_get_file_directory(DIR_FS_CATALOG . DIR_WS_LANGUAGES . $_SESSION['language'] . '/modules/payment/', 'paypalwpp.php', 'false'));
  101. global $order;
  102. $this->code = 'paypalwpp';
  103. $this->codeTitle = MODULE_PAYMENT_PAYPALWPP_TEXT_ADMIN_TITLE_EC;
  104. $this->codeVersion = '1.5.1';
  105. $this->enableDirectPayment = FALSE;
  106. $this->enabled = (MODULE_PAYMENT_PAYPALWPP_STATUS == 'True');
  107. // Set the title & description text based on the mode we're in ... EC vs US/UK vs admin
  108. if (IS_ADMIN_FLAG === true) {
  109. $this->description = sprintf(MODULE_PAYMENT_PAYPALWPP_TEXT_ADMIN_DESCRIPTION, ' (rev' . $this->codeVersion . ')');
  110. switch (MODULE_PAYMENT_PAYPALWPP_MODULE_MODE) {
  111. case ('PayPal'):
  112. $this->title = MODULE_PAYMENT_PAYPALWPP_TEXT_ADMIN_TITLE_EC;
  113. break;
  114. case ('Payflow-UK'):
  115. $this->title = MODULE_PAYMENT_PAYPALWPP_TEXT_ADMIN_TITLE_PRO20;
  116. break;
  117. case ('Payflow-US'):
  118. if (defined('MODULE_PAYMENT_PAYPALWPP_PAYFLOW_EC') && MODULE_PAYMENT_PAYPALWPP_PAYFLOW_EC == 'Yes') {
  119. $this->title = MODULE_PAYMENT_PAYPALWPP_TEXT_ADMIN_TITLE_PF_EC;
  120. } else {
  121. $this->title = MODULE_PAYMENT_PAYPALWPP_TEXT_ADMIN_TITLE_PF_GATEWAY;
  122. }
  123. break;
  124. default:
  125. $this->title = MODULE_PAYMENT_PAYPALWPP_TEXT_ADMIN_TITLE_EC;
  126. }
  127. if ($this->enabled) {
  128. if ( (MODULE_PAYMENT_PAYPALWPP_MODULE_MODE == 'PayPal' && (MODULE_PAYMENT_PAYPALWPP_APISIGNATURE == '' || MODULE_PAYMENT_PAYPALWPP_APIUSERNAME == '' || MODULE_PAYMENT_PAYPALWPP_APIPASSWORD == ''))
  129. || (substr(MODULE_PAYMENT_PAYPALWPP_MODULE_MODE,0,7) == 'Payflow' && (MODULE_PAYMENT_PAYPALWPP_PFPARTNER == '' || MODULE_PAYMENT_PAYPALWPP_PFVENDOR == '' || MODULE_PAYMENT_PAYPALWPP_PFUSER == '' || MODULE_PAYMENT_PAYPALWPP_PFPASSWORD == ''))
  130. ) $this->title .= '<span class="alert"><strong> NOT CONFIGURED YET</strong></span>';
  131. if (MODULE_PAYMENT_PAYPALWPP_SERVER =='sandbox') $this->title .= '<strong><span class="alert"> (sandbox active)</span></strong>';
  132. if (MODULE_PAYMENT_PAYPALWPP_DEBUGGING =='Log File' || MODULE_PAYMENT_PAYPALWPP_DEBUGGING =='Log and Email') $this->title .= '<strong> (Debug)</strong>';
  133. if (!function_exists('curl_init')) $this->title .= '<strong><span class="alert"> CURL NOT FOUND. Cannot Use.</span></strong>';
  134. }
  135. } else {
  136. $this->description = MODULE_PAYMENT_PAYPALWPP_TEXT_DESCRIPTION;
  137. $this->title = MODULE_PAYMENT_PAYPALWPP_EC_TEXT_TITLE; //pp
  138. }
  139. if ((!defined('PAYPAL_OVERRIDE_CURL_WARNING') || (defined('PAYPAL_OVERRIDE_CURL_WARNING') && PAYPAL_OVERRIDE_CURL_WARNING != 'True')) && !function_exists('curl_init')) $this->enabled = false;
  140. $this->enableDebugging = (MODULE_PAYMENT_PAYPALWPP_DEBUGGING == 'Log File' || MODULE_PAYMENT_PAYPALWPP_DEBUGGING =='Log and Email');
  141. $this->emailAlerts = (MODULE_PAYMENT_PAYPALWPP_DEBUGGING == 'Log File' || MODULE_PAYMENT_PAYPALWPP_DEBUGGING =='Log and Email' || MODULE_PAYMENT_PAYPALWPP_DEBUGGING == 'Alerts Only');
  142. $this->doDPonly = (MODULE_PAYMENT_PAYPALWPP_MODULE_MODE =='Payflow-US' && !(defined('MODULE_PAYMENT_PAYPALWPP_PAYFLOW_EC') && MODULE_PAYMENT_PAYPALWPP_PAYFLOW_EC == 'Yes'));
  143. $this->showPaymentPage = (MODULE_PAYMENT_PAYPALWPP_SKIP_PAYMENT_PAGE == 'No') ? true : false;
  144. $this->sort_order = MODULE_PAYMENT_PAYPALWPP_SORT_ORDER;
  145. $this->buttonSourceEC = 'ZenCart-EC_us';
  146. $this->buttonSourceDP = 'ZenCart-DP_us';
  147. if (MODULE_PAYMENT_PAYPALWPP_MODULE_MODE == 'Payflow-UK') {
  148. $this->buttonSourceEC = 'ZenCart-EC_uk';
  149. $this->buttonSourceDP = 'ZenCart-DP_uk';
  150. }
  151. if (MODULE_PAYMENT_PAYPALWPP_MODULE_MODE == 'Payflow-US') {
  152. $this->buttonSourceEC = 'ZenCart-ECGW_us';
  153. $this->buttonSourceDP = 'ZenCart-GW_us';
  154. }
  155. $this->order_pending_status = MODULE_PAYMENT_PAYPALWPP_ORDER_PENDING_STATUS_ID;
  156. if ((int)MODULE_PAYMENT_PAYPALWPP_ORDER_STATUS_ID > 0) {
  157. $this->order_status = MODULE_PAYMENT_PAYPALWPP_ORDER_STATUS_ID;
  158. }
  159. $this->new_acct_notify = MODULE_PAYMENT_PAYPALWPP_NEW_ACCT_NOTIFY;
  160. $this->zone = (int)MODULE_PAYMENT_PAYPALWPP_ZONE;
  161. if (is_object($order)) $this->update_status();
  162. if (PROJECT_VERSION_MAJOR != '1' && substr(PROJECT_VERSION_MINOR, 0, 3) != '5.0') $this->enabled = false;
  163. $this->cards = array();
  164. // if operating in markflow mode, start EC process when submitting order
  165. if (!$this->in_special_checkout()) {
  166. $this->form_action_url = zen_href_link('ipn_main_handler.php', 'type=ec&markflow=1&clearSess=1&stage=final', 'SSL', true, true, true);
  167. }
  168. // debug setup
  169. if (!@is_writable($this->_logDir)) $this->_logDir = DIR_FS_CATALOG . $this->_logDir;
  170. if (!@is_writable($this->_logDir)) $this->_logDir = DIR_FS_SQL_CACHE;
  171. // Regular mode:
  172. if ($this->enableDebugging) $this->_logLevel = 2;
  173. // DEV MODE:
  174. if (defined('PAYPAL_DEV_MODE') && PAYPAL_DEV_MODE == 'true') $this->_logLevel = 3;
  175. if (IS_ADMIN_FLAG === true) $this->tableCheckup();
  176. }
  177. /**
  178. * Sets payment module status based on zone restrictions etc
  179. */
  180. function update_status() {
  181. global $order, $db;
  182. // $this->zcLog('update_status', 'Checking whether module should be enabled or not.');
  183. if ($this->enabled && (int)$this->zone > 0) {
  184. $check_flag = false;
  185. $sql = "SELECT zone_id
  186. FROM " . TABLE_ZONES_TO_GEO_ZONES . "
  187. WHERE geo_zone_id = :zoneId
  188. AND zone_country_id = :countryId
  189. ORDER BY zone_id";
  190. $sql = $db->bindVars($sql, ':zoneId', $this->zone, 'integer');
  191. $sql = $db->bindVars($sql, ':countryId', $order->billing['country']['id'], 'integer');
  192. $check = $db->Execute($sql);
  193. while (!$check->EOF) {
  194. if ($check->fields['zone_id'] < 1) {
  195. $check_flag = true;
  196. break;
  197. } elseif ($check->fields['zone_id'] == $order->billing['zone_id']) {
  198. $check_flag = true;
  199. break;
  200. }
  201. $check->MoveNext();
  202. }
  203. if (!$check_flag) {
  204. $this->enabled = false;
  205. $this->zcLog('update_status', 'Module disabled due to zone restriction. Billing address is not within the Payment Zone selected in the module settings.');
  206. }
  207. // module cannot be used for purchase > $10,000 USD
  208. $order_amount = $this->calc_order_amount($order->info['total'], 'USD');
  209. if ($order_amount > 10000) {
  210. $this->enabled = false;
  211. $this->zcLog('update_status', 'Module disabled because purchase price (' . $order_amount . ') exceeds PayPal-imposed maximum limit of 10,000 USD.');
  212. }
  213. if ($order->info['total'] == 0) {
  214. $this->enabled = false;
  215. $this->zcLog('update_status', 'Module disabled because purchase amount is set to 0.00.' . "\n" . print_r($order, true));
  216. }
  217. }
  218. }
  219. /**
  220. * Validate the credit card information via javascript (Number, Owner, and CVV Lengths)
  221. */
  222. function javascript_validation() {
  223. return false;
  224. }
  225. /**
  226. * Display Credit Card Information Submission Fields on the Checkout Payment Page
  227. */
  228. function selection() {
  229. $this->cc_type_check = '';
  230. /**
  231. * since we are NOT processing via the gateway, we will only display MarkFlow payment option, and no CC fields
  232. */
  233. return array('id' => $this->code,
  234. 'module' => '<img src="' . MODULE_PAYMENT_PAYPALEC_MARK_BUTTON_IMG . '" alt="' . MODULE_PAYMENT_PAYPALWPP_TEXT_BUTTON_ALTTEXT . '" /><span style="font-size:11px; font-family: Arial, Verdana;"> ' . MODULE_PAYMENT_PAYPALWPP_MARK_BUTTON_TXT . '</span>');
  235. }
  236. function pre_confirmation_check() {
  237. // Since this is an EC checkout, do nothing.
  238. return false;
  239. }
  240. /**
  241. * Display Payment Information for review on the Checkout Confirmation Page
  242. */
  243. function confirmation() {
  244. $confirmation = array('title' => '', 'fields' => array());
  245. return $confirmation;
  246. }
  247. /**
  248. * Prepare the hidden fields comprising the parameters for the Submit button on the checkout confirmation page
  249. */
  250. function process_button() {
  251. $_SESSION['paypal_ec_markflow'] = 1;
  252. return '';
  253. }
  254. /**
  255. * Prepare and submit the final authorization to PayPal via the appropriate means as configured
  256. */
  257. function before_process() {
  258. global $order, $doPayPal, $messageStack;
  259. $options = array();
  260. $optionsShip = array();
  261. $optionsNVP = array();
  262. $options = $this->getLineItemDetails($this->selectCurrency($order->info['currency']));
  263. if (defined('MODULE_PAYMENT_PAYPALEC_ALLOWEDPAYMENT') && MODULE_PAYMENT_PAYPALEC_ALLOWEDPAYMENT == 'Instant Only') $options['ALLOWEDPAYMENTMETHOD'] = 'InstantPaymentOnly';
  264. //$this->zcLog('before_process - 1', 'Have line-item details:' . "\n" . print_r($options, true));
  265. // Initializing DESC field: using for comments related to tax-included pricing, populated by getLineItemDetails()
  266. $options['DESC'] = '';
  267. $doPayPal = $this->paypal_init();
  268. $this->zcLog('before_process - EC-1', 'Beginning EC mode');
  269. /****************************************
  270. * Do EC checkout
  271. ****************************************/
  272. // do not allow blank address to be sent to PayPal
  273. if ($_SESSION['paypal_ec_payer_info']['ship_street_1'] != '' && strtoupper($_SESSION['paypal_ec_payer_info']['ship_address_status']) != 'NONE') {
  274. $options = array_merge($options,
  275. array('SHIPTONAME' => $_SESSION['paypal_ec_payer_info']['ship_name'],
  276. 'SHIPTOSTREET' => $_SESSION['paypal_ec_payer_info']['ship_street_1'],
  277. 'SHIPTOSTREET2'=> $_SESSION['paypal_ec_payer_info']['ship_street_2'],
  278. 'SHIPTOCITY' => $_SESSION['paypal_ec_payer_info']['ship_city'],
  279. 'SHIPTOSTATE' => $_SESSION['paypal_ec_payer_info']['ship_state'],
  280. 'SHIPTOZIP' => $_SESSION['paypal_ec_payer_info']['ship_postal_code'],
  281. 'SHIPTOCOUNTRYCODE'=> $_SESSION['paypal_ec_payer_info']['ship_country_code'],
  282. ));
  283. $this->zcLog('before_process - EC-2', 'address overrides added:' . "\n" . print_r($options, true));
  284. }
  285. $this->zcLog('before_process - EC-3', 'address info added:' . "\n" . print_r($options, true));
  286. $options['BUTTONSOURCE'] = $this->buttonSourceEC;
  287. // If the customer has changed their shipping address,
  288. // override the shipping address in PayPal with the shipping
  289. // address that is selected in Zen Cart.
  290. if ($order->delivery['street_address'] != '' && $order->delivery['street_address'] != $_SESSION['paypal_ec_payer_info']['ship_street_1'] && $_SESSION['paypal_ec_payer_info']['ship_street_1'] != '') {
  291. $_GET['markflow'] = 2;
  292. if (($address_arr = $this->getOverrideAddress()) !== false) {
  293. // set the override var
  294. $options['ADDROVERRIDE'] = 1;
  295. // set the address info
  296. $options['SHIPTONAME'] = $address_arr['entry_firstname'] . ' ' . $address_arr['entry_lastname'];
  297. $options['SHIPTOSTREET'] = $address_arr['entry_street_address'];
  298. if ($address_arr['entry_suburb'] != '') $options['SHIPTOSTREET2'] = $address_arr['entry_suburb'];
  299. $options['SHIPTOCITY'] = $address_arr['entry_city'];
  300. $options['SHIPTOZIP'] = $address_arr['entry_postcode'];
  301. $options['SHIPTOSTATE'] = $address_arr['zone_code'];
  302. $options['SHIPTOCOUNTRYCODE'] = $address_arr['countries_iso_code_2'];
  303. }
  304. }
  305. // if these optional parameters are blank, remove them from transaction
  306. if (isset($options['SHIPTOSTREET2']) && trim($options['SHIPTOSTREET2']) == '') unset($options['SHIPTOSTREET2']);
  307. if (isset($options['SHIPTOPHONENUM']) && trim($options['SHIPTOPHONENUM']) == '') unset($options['SHIPTOPHONENUM']);
  308. // if State is not supplied, repeat the city so that it's not blank, otherwise PayPal croaks
  309. if ((!isset($options['SHIPTOSTATE']) || trim($options['SHIPTOSTATE']) == '') && $options['SHIPTOCITY'] != '') $options['SHIPTOSTATE'] = $options['SHIPTOCITY'];
  310. // FMF support
  311. $options['RETURNFMFDETAILS'] = (MODULE_PAYMENT_PAYPALWPP_EC_RETURN_FMF_DETAILS == 'Yes') ? 1 : 0;
  312. // Add note to track that this was an EC transaction (used in properly handling update IPNs related to EC transactions):
  313. $options['CUSTOM'] = 'EC-' . (int)$_SESSION['customer_id'] . '-' . time();
  314. // send the store name as transaction identifier, to help distinguish payments between multiple stores:
  315. $options['INVNUM'] = (int)$_SESSION['customer_id'] . '-' . time() . '-[' . substr(preg_replace('/[^a-zA-Z0-9_]/', '', STORE_NAME), 0, 30) . ']'; // (cannot send actual invoice number because it's not assigned until after payment is completed)
  316. $options['CURRENCY'] = $this->selectCurrency($order->info['currency']);
  317. // $order->info['total'] = zen_round($order->info['total'], 2);
  318. $order_amount = $this->calc_order_amount($order->info['total'], $options['CURRENCY'], FALSE);
  319. // debug output
  320. $this->zcLog('before_process - EC-4', 'info being submitted:' . "\n" . $_SESSION['paypal_ec_token'] . ' ' . $_SESSION['paypal_ec_payer_id'] . ' ' . number_format($order_amount, 2) . "\n" . print_r($options, true));
  321. if (isset($options['DESC']) && $options['DESC'] == '') unset($options['DESC']);
  322. if (!isset($options['AMT'])) $options['AMT'] = number_format($order_amount, 2, '.', '');
  323. $response = $doPayPal->DoExpressCheckoutPayment($_SESSION['paypal_ec_token'],
  324. $_SESSION['paypal_ec_payer_id'],
  325. $options);
  326. $this->zcLog('before_process - EC-5', 'resultset:' . "\n" . urldecode(print_r($response, true)));
  327. // CHECK RESPONSE -- if error, actions are taken in the errorHandler
  328. $errorHandlerMessage = $this->_errorHandler($response, 'DoExpressCheckoutPayment');
  329. if ($this->fmfResponse != '') {
  330. $this->order_status = $this->order_pending_status;
  331. }
  332. // SUCCESS
  333. $this->payment_type = MODULE_PAYMENT_PAYPALWPP_EC_TEXT_TYPE;
  334. $this->responsedata = $response;
  335. if ($response['PAYMENTTYPE'] != '') $this->payment_type .= ' (' . urldecode($response['PAYMENTTYPE']) . ')';
  336. $this->transaction_id = trim($response['PNREF'] . ' ' . $response['TRANSACTIONID']);
  337. if (empty($response['PENDINGREASON']) ||
  338. $response['PENDINGREASON'] == 'none' ||
  339. $response['PENDINGREASON'] == 'completed' ||
  340. $response['PAYMENTSTATUS'] == 'Completed') {
  341. $this->payment_status = 'Completed';
  342. if ($this->order_status > 0) $order->info['order_status'] = $this->order_status;
  343. } else {
  344. if ($response['PAYMENTSTATUS'] == 'Pending')
  345. {
  346. if ($response['L_ERRORCODE0'] == 11610 && $response['PENDINGREASON'] == '') $response['PENDINGREASON'] = 'Pending FMF Review by Storeowner';
  347. }
  348. $this->payment_status = 'Pending (' . $response['PENDINGREASON'] . ')';
  349. $order->info['order_status'] = $this->order_pending_status;
  350. }
  351. $this->avs = 'N/A';
  352. $this->cvv2 = 'N/A';
  353. $this->correlationid = $response['CORRELATIONID'];
  354. $this->transactiontype = $response['TRANSACTIONTYPE'];
  355. $this->payment_time = urldecode($response['ORDERTIME']);
  356. $this->feeamt = urldecode($response['FEEAMT']);
  357. $this->taxamt = urldecode($response['TAXAMT']);
  358. $this->pendingreason = $response['PENDINGREASON'];
  359. $this->reasoncode = $response['REASONCODE'];
  360. $this->numitems = sizeof($order->products);
  361. $this->amt = urldecode($response['AMT'] . ' ' . $response['CURRENCYCODE']);
  362. $this->auth_code = (isset($this->response['AUTHCODE'])) ? $this->response['AUTHCODE'] : $this->response['TOKEN'];
  363. }
  364. /**
  365. * When the order returns from the processor, this stores the results in order-status-history and logs data for subsequent use
  366. */
  367. function after_process() {
  368. global $insert_id, $db, $order;
  369. // FMF
  370. if ($this->fmfResponse != '') {
  371. $detailedMessage = $insert_id . "\n" . $this->fmfResponse . "\n" . MODULES_PAYMENT_PAYPALDP_TEXT_EMAIL_FMF_INTRO . "\n" . print_r($this->fmfErrors, TRUE);
  372. zen_mail(STORE_NAME, STORE_OWNER_EMAIL_ADDRESS, MODULES_PAYMENT_PAYPALDP_TEXT_EMAIL_FMF_SUBJECT . ' (' . $insert_id . ')', $detailedMessage, STORE_OWNER, STORE_OWNER_EMAIL_ADDRESS, array('EMAIL_MESSAGE_HTML'=>nl2br($detailedMessage)), 'paymentalert');
  373. }
  374. // add a new OSH record for this order's PP details
  375. $commentString = "Transaction ID: :transID: " .
  376. (isset($this->responsedata['PPREF']) ? "\nPPRef: " . $this->responsedata['PPREF'] : "") .
  377. (isset($this->responsedata['AUTHCODE'])? "\nAuthCode: " . $this->responsedata['AUTHCODE'] : "") .
  378. "\nPayment Type: :pmtType: " .
  379. ($this->payment_time != '' ? "\nTimestamp: :pmtTime: " : "") .
  380. "\nPayment Status: :pmtStatus: " .
  381. (isset($this->responsedata['auth_exp']) ? "\nAuth-Exp: " . $this->responsedata['auth_exp'] : "") .
  382. ($this->avs != 'N/A' ? "\nAVS Code: ".$this->avs."\nCVV2 Code: ".$this->cvv2 : '') .
  383. (trim($this->amt) != '' ? "\nAmount: :orderAmt: " : "");
  384. $commentString = $db->bindVars($commentString, ':transID:', $this->transaction_id, 'noquotestring');
  385. $commentString = $db->bindVars($commentString, ':pmtType:', $this->payment_type, 'noquotestring');
  386. $commentString = $db->bindVars($commentString, ':pmtTime:', $this->payment_time, 'noquotestring');
  387. $commentString = $db->bindVars($commentString, ':pmtStatus:', $this->payment_status, 'noquotestring');
  388. $commentString = $db->bindVars($commentString, ':orderAmt:', $this->amt, 'noquotestring');
  389. $sql_data_array= array(array('fieldName'=>'orders_id', 'value'=>$insert_id, 'type'=>'integer'),
  390. array('fieldName'=>'orders_status_id', 'value'=>$order->info['order_status'], 'type'=>'integer'),
  391. array('fieldName'=>'date_added', 'value'=>'now()', 'type'=>'noquotestring'),
  392. array('fieldName'=>'customer_notified', 'value'=>0, 'type'=>'integer'),
  393. array('fieldName'=>'comments', 'value'=>$commentString, 'type'=>'string'));
  394. $db->perform(TABLE_ORDERS_STATUS_HISTORY, $sql_data_array);
  395. // store the PayPal order meta data -- used for later matching and back-end processing activities
  396. $paypal_order = array('order_id' => $insert_id,
  397. 'txn_type' => $this->transactiontype,
  398. 'module_name' => $this->code,
  399. 'module_mode' => MODULE_PAYMENT_PAYPALWPP_MODULE_MODE,
  400. 'reason_code' => $this->reasoncode,
  401. 'payment_type' => $this->payment_type,
  402. 'payment_status' => $this->payment_status,
  403. 'pending_reason' => $this->pendingreason,
  404. 'invoice' => urldecode($_SESSION['paypal_ec_token'] . $this->responsedata['PPREF']),
  405. 'first_name' => $_SESSION['paypal_ec_payer_info']['payer_firstname'],
  406. 'last_name' => $_SESSION['paypal_ec_payer_info']['payer_lastname'],
  407. 'payer_business_name' => $_SESSION['paypal_ec_payer_info']['payer_business'],
  408. 'address_name' => $_SESSION['paypal_ec_payer_info']['ship_name'],
  409. 'address_street' => $_SESSION['paypal_ec_payer_info']['ship_street_1'],
  410. 'address_city' => $_SESSION['paypal_ec_payer_info']['ship_city'],
  411. 'address_state' => $_SESSION['paypal_ec_payer_info']['ship_state'],
  412. 'address_zip' => $_SESSION['paypal_ec_payer_info']['ship_postal_code'],
  413. 'address_country' => $_SESSION['paypal_ec_payer_info']['ship_country'],
  414. 'address_status' => $_SESSION['paypal_ec_payer_info']['ship_address_status'],
  415. 'payer_email' => $_SESSION['paypal_ec_payer_info']['payer_email'],
  416. 'payer_id' => $_SESSION['paypal_ec_payer_id'],
  417. 'payer_status' => $_SESSION['paypal_ec_payer_info']['payer_status'],
  418. 'payment_date' => trim(preg_replace('/[^0-9-:]/', ' ', $this->payment_time)),
  419. 'business' => '',
  420. 'receiver_email' => (substr(MODULE_PAYMENT_PAYPALWPP_MODULE_MODE,0,7) == 'Payflow' ? MODULE_PAYMENT_PAYPALWPP_PFVENDOR : str_replace('_api1', '', MODULE_PAYMENT_PAYPALWPP_APIUSERNAME)),
  421. 'receiver_id' => '',
  422. 'txn_id' => $this->transaction_id,
  423. 'parent_txn_id' => '',
  424. 'num_cart_items' => (float)$this->numitems,
  425. 'mc_gross' => (float)$this->amt,
  426. 'mc_fee' => (float)urldecode($this->feeamt),
  427. 'mc_currency' => $this->responsedata['CURRENCYCODE'],
  428. 'settle_amount' => (float)urldecode($this->responsedata['SETTLEAMT']),
  429. 'settle_currency' => $this->responsedata['CURRENCYCODE'],
  430. 'exchange_rate' => (urldecode($this->responsedata['EXCHANGERATE']) > 0 ? urldecode($this->responsedata['EXCHANGERATE']) : 1.0),
  431. 'notify_version' => '0',
  432. 'verify_sign' =>'',
  433. 'date_added' => 'now()',
  434. 'memo' => (sizeof($this->fmfErrors) > 0 ? 'FMF Details ' . print_r($this->fmfErrors, TRUE) : '{Record generated by payment module}'),
  435. );
  436. zen_db_perform(TABLE_PAYPAL, $paypal_order);
  437. // Unregister the paypal session variables, making it necessary to start again for another purchase
  438. unset($_SESSION['paypal_ec_temp']);
  439. unset($_SESSION['paypal_ec_token']);
  440. unset($_SESSION['paypal_ec_payer_id']);
  441. unset($_SESSION['paypal_ec_payer_info']);
  442. unset($_SESSION['paypal_ec_final']);
  443. unset($_SESSION['paypal_ec_markflow']);
  444. }
  445. /**
  446. * Build admin-page components
  447. *
  448. * @param int $zf_order_id
  449. * @return string
  450. */
  451. function admin_notification($zf_order_id) {
  452. if (!defined('MODULE_PAYMENT_PAYPALWPP_STATUS')) return '';
  453. global $db;
  454. $module = $this->code;
  455. $output = '';
  456. $response = $this->_GetTransactionDetails($zf_order_id);
  457. //$response = $this->_TransactionSearch('2006-12-01T00:00:00Z', $zf_order_id);
  458. $sql = "SELECT * from " . TABLE_PAYPAL . " WHERE order_id = :orderID
  459. AND parent_txn_id = '' AND order_id > 0
  460. ORDER BY paypal_ipn_id DESC LIMIT 1";
  461. $sql = $db->bindVars($sql, ':orderID', $zf_order_id, 'integer');
  462. $ipn = $db->Execute($sql);
  463. if ($ipn->RecordCount() == 0) $ipn->fields = array();
  464. if (file_exists(DIR_FS_CATALOG . DIR_WS_MODULES . 'payment/paypal/paypalwpp_admin_notification.php')) require(DIR_FS_CATALOG . DIR_WS_MODULES . 'payment/paypal/paypalwpp_admin_notification.php');
  465. return $output;
  466. }
  467. /**
  468. * Used to read details of an existing transaction. FOR FUTURE USE.
  469. */
  470. function _GetTransactionDetails($oID) {
  471. if ($oID == '' || $oID < 1) return FALSE;
  472. global $db, $messageStack, $doPayPal;
  473. $doPayPal = $this->paypal_init();
  474. // look up history on this order from PayPal table
  475. $sql = "select * from " . TABLE_PAYPAL . " where order_id = :orderID order by last_modified DESC, date_added DESC, parent_txn_id DESC, paypal_ipn_id DESC ";
  476. $sql = $db->bindVars($sql, ':orderID', $oID, 'integer');
  477. $zc_ppHist = $db->Execute($sql);
  478. if ($zc_ppHist->RecordCount() == 0) return false;
  479. $txnID = $zc_ppHist->fields['txn_id'];
  480. if ($txnID == '' || $txnID === 0) return FALSE;
  481. /**
  482. * Read data from PayPal
  483. */
  484. $response = $doPayPal->GetTransactionDetails($txnID);
  485. $error = $this->_errorHandler($response, 'GetTransactionDetails', 10007);
  486. if ($error === false) {
  487. return false;
  488. } else {
  489. return $response;
  490. }
  491. }
  492. /**
  493. * Used to read details of existing transactions. FOR FUTURE USE.
  494. */
  495. function _TransactionSearch($startDate = '', $oID = '', $criteria = '') {
  496. global $db, $messageStack, $doPayPal;
  497. $doPayPal = $this->paypal_init();
  498. // look up history on this order from PayPal table
  499. $sql = "select * from " . TABLE_PAYPAL . " where order_id = :orderID AND parent_txn_id = '' ";
  500. $sql = $db->bindVars($sql, ':orderID', $oID, 'integer');
  501. $zc_ppHist = $db->Execute($sql);
  502. if ($zc_ppHist->RecordCount() == 0) return false;
  503. $txnID = $zc_ppHist->fields['txn_id'];
  504. $startDate = $zc_ppHist->fields['payment_date'];
  505. $timeval = time();
  506. if ($startDate == '') $startDate = date('Y-m-d', $timeval) . 'T' . date('h:i:s', $timeval) . 'Z';
  507. /**
  508. * Read data from PayPal
  509. */
  510. $response = $doPayPal->TransactionSearch($startDate, $txnID, $email, $criteria);
  511. $error = $this->_errorHandler($response, 'TransactionSearch');
  512. if ($error === false) {
  513. return false;
  514. } else {
  515. return $response;
  516. }
  517. }
  518. /**
  519. * Evaluate installation status of this module. Returns true if the status key is found.
  520. */
  521. function check() {
  522. global $db;
  523. if (!isset($this->_check)) {
  524. $check_query = $db->Execute("select configuration_value from " . TABLE_CONFIGURATION . " where configuration_key = 'MODULE_PAYMENT_PAYPALWPP_STATUS'");
  525. $this->_check = !$check_query->EOF;
  526. }
  527. return $this->_check;
  528. }
  529. /**
  530. * Installs all the configuration keys for this module
  531. */
  532. function install() {
  533. global $db, $messageStack;
  534. if (defined('MODULE_PAYMENT_PAYPALWPP_STATUS')) {
  535. $messageStack->add_session('Express Checkout module already installed.', 'error');
  536. zen_redirect(zen_href_link(FILENAME_MODULES, 'set=payment&module=paypalwpp', 'NONSSL'));
  537. return 'failed';
  538. }
  539. if (defined('MODULE_PAYMENT_PAYPAL_STATUS')) {
  540. $messageStack->add_session('NOTE: You already have the PayPal Website Payments Standard module installed. You should REMOVE it if you want to install Express Checkout.', 'error');
  541. zen_redirect(zen_href_link(FILENAME_MODULES, 'set=payment&module=paypal', 'NONSSL'));
  542. return 'failed';
  543. }
  544. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Enable this Payment Module', 'MODULE_PAYMENT_PAYPALWPP_STATUS', 'True', 'Do you want to enable this payment module?', '6', '25', 'zen_cfg_select_option(array(\'True\', \'False\'), ', now())");
  545. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Live or Sandbox', 'MODULE_PAYMENT_PAYPALWPP_SERVER', 'live', '<strong>Live: </strong> Used to process Live transactions<br><strong>Sandbox: </strong>For developers and testing', '6', '25', 'zen_cfg_select_option(array(\'live\', \'sandbox\'), ', now())");
  546. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Express Checkout Shortcut Button', 'MODULE_PAYMENT_PAYPALWPP_ECS_BUTTON', 'On', 'The Express Checkout Shortcut button shows up on your shopping cart page to invite your customers to pay using PayPal without having to give all their address details on your site first before selecting shipping options.<br />It has been shown to increase sales and conversions when enabled.<br />Default: On ', '6', '25', 'zen_cfg_select_option(array(\'On\', \'Off\'), ', now())");
  547. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Express Checkout: Require Confirmed Address', 'MODULE_PAYMENT_PAYPALWPP_CONFIRMED_ADDRESS', 'No', 'Do you want to require that your (not-logged-in) customers use a *confirmed* address when choosing their shipping address in PayPal?<br />(this is ignored for logged-in customers)', '6', '25', 'zen_cfg_select_option(array(\'Yes\', \'No\'), ', now())");
  548. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Express Checkout: Select Cheapest Shipping Automatically', 'MODULE_PAYMENT_PAYPALWPP_AUTOSELECT_CHEAPEST_SHIPPING', 'Yes', 'When customer returns from PayPal, do we want to automatically select the Cheapest shipping method and skip the shipping page? (making it more *express*)<br />Note: enabling this means the customer does *not* have easy access to select an alternate shipping method (without going back to the Checkout-Step-1 page)', '6', '25', 'zen_cfg_select_option(array(\'Yes\', \'No\'), ', now())");
  549. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Express Checkout: Skip Payment Page', 'MODULE_PAYMENT_PAYPALWPP_SKIP_PAYMENT_PAGE', 'Yes', 'If the customer is checking out with Express Checkout, do you want to skip the checkout payment page, making things more *express*? <br /><strong>(NOTE: The Payment Page will auto-display regardless of this setting if you have Coupons or Gift Certificates enabled in your store.)</strong>', '6', '25', 'zen_cfg_select_option(array(\'Yes\', \'No\'), ', now())");
  550. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Express Checkout: Automatic Account Creation', 'MODULE_PAYMENT_PAYPALWPP_NEW_ACCT_NOTIFY', 'Yes', 'If a visitor is not an existing customer, a Zen Cart account is created for them. Would you like make it a permanent account and send them an email containing their login information?<br />NOTE: Permanent accounts are auto-created if the customer purchases downloads or gift certificates, regardless of this setting.', '6', '25', 'zen_cfg_select_option(array(\'Yes\', \'No\'), ', now())");
  551. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('Sort order of display.', 'MODULE_PAYMENT_PAYPALWPP_SORT_ORDER', '0', 'Sort order of display. Lowest is displayed first.', '6', '25', now())");
  552. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, use_function, set_function, date_added) values ('Payment Zone', 'MODULE_PAYMENT_PAYPALWPP_ZONE', '0', 'If a zone is selected, only enable this payment method for that zone.', '6', '25', 'zen_get_zone_class_title', 'zen_cfg_pull_down_zone_classes(', now())");
  553. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, use_function, date_added) values ('Set Order Status', 'MODULE_PAYMENT_PAYPALWPP_ORDER_STATUS_ID', '2', 'Set the status of orders paid with this payment module to this value. <br /><strong>Recommended: Processing[2]</strong>', '6', '25', 'zen_cfg_pull_down_order_statuses(', 'zen_get_order_status_name', now())");
  554. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, use_function, date_added) values ('Set Unpaid Order Status', 'MODULE_PAYMENT_PAYPALWPP_ORDER_PENDING_STATUS_ID', '1', 'Set the status of unpaid orders made with this payment module to this value. <br /><strong>Recommended: Pending[1]</strong>', '6', '25', 'zen_cfg_pull_down_order_statuses(', 'zen_get_order_status_name', now())");
  555. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, use_function, date_added) values ('Set Refund Order Status', 'MODULE_PAYMENT_PAYPALWPP_REFUNDED_STATUS_ID', '1', 'Set the status of refunded orders to this value. <br /><strong>Recommended: Pending[1]</strong>', '6', '25', 'zen_cfg_pull_down_order_statuses(', 'zen_get_order_status_name', now())");
  556. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('PayPal Page Style', 'MODULE_PAYMENT_PAYPALWPP_PAGE_STYLE', 'Primary', 'The page-layout style you want customers to see when they visit the PayPal site. You can configure your <strong>Custom Page Styles</strong> in your PayPal Profile settings. This value is case-sensitive.', '6', '25', now())");
  557. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('Store (Brand) Name at PayPal', 'MODULE_PAYMENT_PAYPALWPP_BRANDNAME', '', 'The name of your store as it should appear on the PayPal login page. If blank, your store name will be used.', '6', '25', now())");
  558. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Payment Action', 'MODULE_PAYMENT_PAYPALWPP_TRANSACTION_MODE', 'Final Sale', 'How do you want to obtain payment?<br /><strong>Default: Final Sale</strong>', '6', '25', 'zen_cfg_select_option(array(\'Auth Only\', \'Final Sale\'), ', now())");
  559. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Transaction Currency', 'MODULE_PAYMENT_PAYPALWPP_CURRENCY', 'Selected Currency', 'Which currency should the order be sent to PayPal as? <br />NOTE: if an unsupported currency is sent to PayPal, it will be auto-converted to USD (or GBP if using UK account)<br /><strong>Default: Selected Currency</strong>', '6', '25', 'zen_cfg_select_option(array(\'Selected Currency\', \'Only USD\', \'Only AUD\', \'Only CAD\', \'Only EUR\', \'Only GBP\', \'Only CHF\', \'Only CZK\', \'Only DKK\', \'Only HKD\', \'Only HUF\', \'Only JPY\', \'Only NOK\', \'Only NZD\', \'Only PLN\', \'Only SEK\', \'Only SGD\', \'Only THB\', \'Only MXN\', \'Only ILS\', \'Only PHP\', \'Only TWD\', \'Only BRL\', \'Only MYR\', \'Only TKD\'), ', now())");
  560. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Allow eCheck?', 'MODULE_PAYMENT_PAYPALEC_ALLOWEDPAYMENT', 'Any', 'Do you want to allow non-instant payments like eCheck/EFT/ELV?', '6', '25', 'zen_cfg_select_option(array(\'Any\', \'Instant Only\'), ', now())");
  561. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Fraud Mgmt Filters - FMF', 'MODULE_PAYMENT_PAYPALWPP_EC_RETURN_FMF_DETAILS', 'No', 'If you have enabled FMF support in your PayPal account and wish to utilize it in your transactions, set this to yes. Otherwise, leave it at No.', '6', '25','zen_cfg_select_option(array(\'No\', \'Yes\'), ', now())");
  562. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('API Signature -- Username', 'MODULE_PAYMENT_PAYPALWPP_APIUSERNAME', '', 'The API Username from your PayPal API Signature settings under *API Access*. This value typically looks like an email address and is case-sensitive.', '6', '25', now())");
  563. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added, set_function, use_function) values ('API Signature -- Password', 'MODULE_PAYMENT_PAYPALWPP_APIPASSWORD', '', 'The API Password from your PayPal API Signature settings under *API Access*. This value is a 16-character code and is case-sensitive.', '6', '25', now(), 'zen_cfg_password_input(', 'zen_cfg_password_display')");
  564. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added, set_function, use_function) values ('API Signature -- Signature Code', 'MODULE_PAYMENT_PAYPALWPP_APISIGNATURE', '', 'The API Signature from your PayPal API Signature settings under *API Access*. This value is a 56-character code, and is case-sensitive.', '6', '25', now(), '', 'zen_cfg_password_display')");
  565. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('PAYFLOW: User', 'MODULE_PAYMENT_PAYPALWPP_PFUSER', '', 'If you set up one or more additional users on the account, this value is the ID of the user authorized to process transactions. Otherwise it should be the same value as VENDOR. This value is case-sensitive.', '6', '25', now())");
  566. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('PAYFLOW: Partner', 'MODULE_PAYMENT_PAYPALWPP_PFPARTNER', 'ZenCart', 'Your Payflow Partner name linked to your Payflow account. This value is case-sensitive.<br />Typical values: <strong>PayPal</strong> or <strong>ZenCart</strong>', '6', '25', now())");
  567. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('PAYFLOW: Vendor', 'MODULE_PAYMENT_PAYPALWPP_PFVENDOR', '', 'Your merchant login ID that you created when you registered for the Payflow Pro account. This value is case-sensitive.', '6', '25', now())");
  568. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added, set_function, use_function) values ('PAYFLOW: Password', 'MODULE_PAYMENT_PAYPALWPP_PFPASSWORD', '', 'The 6- to 32-character password that you defined while registering for the account. This value is case-sensitive.', '6', '25', now(), 'zen_cfg_password_input(', 'zen_cfg_password_display')");
  569. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('PayPal Mode', 'MODULE_PAYMENT_PAYPALWPP_MODULE_MODE', 'PayPal', 'Which PayPal API system should be used for processing? <br /><u>Choices:</u><br /><font color=green>For choice #1, you need to supply <strong>API Settings</strong> above.</font><br /><strong>1. PayPal</strong> = Express Checkout with a regular PayPal account<br />or<br /><font color=green>for choices 2 &amp; 3 you need to supply <strong>PAYFLOW settings</strong>, below (and have a Payflow account)</font><br /><strong>2. Payflow-UK</strong> = Website Payments Pro UK Payflow Edition<br /><strong>3. Payflow-US</strong> = Payflow Pro Gateway only<!--<br /><strong>4. PayflowUS+EC</strong> = Payflow Pro with Express Checkout-->', '6', '25', 'zen_cfg_select_option(array(\'PayPal\', \'Payflow-UK\', \'Payflow-US\'), ', now())");
  570. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Debug Mode', 'MODULE_PAYMENT_PAYPALWPP_DEBUGGING', 'Off', 'Would you like to enable debug mode? A complete detailed log of failed transactions will be emailed to the store owner.', '6', '25', 'zen_cfg_select_option(array(\'Off\', \'Alerts Only\', \'Log File\', \'Log and Email\'), ', now())");
  571. $this->notify('NOTIFY_PAYMENT_PAYPALWPP_INSTALLED');
  572. }
  573. function keys() {
  574. if (defined('MODULE_PAYMENT_PAYPALWPP_STATUS')) {
  575. global $db;
  576. if (!defined('MODULE_PAYMENT_PAYPALWPP_ECS_BUTTON')) {
  577. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Express Checkout Shortcut Button', 'MODULE_PAYMENT_PAYPALWPP_ECS_BUTTON', 'On', 'The Express Checkout Shortcut button shows up on your shopping cart page to invite your customers to pay using PayPal without having to give all their address details on your site first before selecting shipping options.<br />It has been shown to increase sales and conversions when enabled.<br />Default: On ', '6', '25', 'zen_cfg_select_option(array(\'On\', \'Off\'), ', now())");
  578. }
  579. if (!defined('MODULE_PAYMENT_PAYPALWPP_BRANDNAME')) {
  580. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('Store Brand Name at PayPal', 'MODULE_PAYMENT_PAYPALWPP_BRANDNAME', '', 'The name of your store as it should appear on the PayPal login page. If blank, your store name will be used.', '6', '25', now())");
  581. }
  582. if (!defined('MODULE_PAYMENT_PAYPALEC_ALLOWEDPAYMENT')) {
  583. $db->Execute("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Allow eCheck?', 'MODULE_PAYMENT_PAYPALEC_ALLOWEDPAYMENT', 'Any', 'Do you want to allow non-instant payments like eCheck/EFT/ELV?', '6', '25', 'zen_cfg_select_option(array(\'Any\', \'Instant Only\'), ', now())");
  584. }
  585. }
  586. $keys_list = array('MODULE_PAYMENT_PAYPALWPP_STATUS', 'MODULE_PAYMENT_PAYPALWPP_SORT_ORDER', 'MODULE_PAYMENT_PAYPALWPP_ZONE', 'MODULE_PAYMENT_PAYPALWPP_ECS_BUTTON', 'MODULE_PAYMENT_PAYPALWPP_ORDER_STATUS_ID', 'MODULE_PAYMENT_PAYPALWPP_ORDER_PENDING_STATUS_ID', 'MODULE_PAYMENT_PAYPALWPP_REFUNDED_STATUS_ID', 'MODULE_PAYMENT_PAYPALWPP_CONFIRMED_ADDRESS', 'MODULE_PAYMENT_PAYPALWPP_AUTOSELECT_CHEAPEST_SHIPPING', 'MODULE_PAYMENT_PAYPALWPP_SKIP_PAYMENT_PAGE', 'MODULE_PAYMENT_PAYPALWPP_NEW_ACCT_NOTIFY', 'MODULE_PAYMENT_PAYPALWPP_TRANSACTION_MODE', 'MODULE_PAYMENT_PAYPALWPP_CURRENCY', 'MODULE_PAYMENT_PAYPALWPP_BRANDNAME', 'MODULE_PAYMENT_PAYPALEC_ALLOWEDPAYMENT', 'MODULE_PAYMENT_PAYPALWPP_PAGE_STYLE', 'MODULE_PAYMENT_PAYPALWPP_APIUSERNAME', 'MODULE_PAYMENT_PAYPALWPP_APIPASSWORD', 'MODULE_PAYMENT_PAYPALWPP_APISIGNATURE', 'MODULE_PAYMENT_PAYPALWPP_MODULE_MODE', 'MODULE_PAYMENT_PAYPALWPP_SERVER', 'MODULE_PAYMENT_PAYPALWPP_DEBUGGING');
  587. if (IS_ADMIN_FLAG === true && (PAYPAL_DEV_MODE == 'true' || strstr(MODULE_PAYMENT_PAYPALWPP_MODULE_MODE, 'Payflow'))) {
  588. $keys_list = array_merge($keys_list, array('MODULE_PAYMENT_PAYPALWPP_PFPARTNER', 'MODULE_PAYMENT_PAYPALWPP_PFVENDOR', 'MODULE_PAYMENT_PAYPALWPP_PFUSER', 'MODULE_PAYMENT_PAYPALWPP_PFPASSWORD'));
  589. }
  590. return $keys_list;
  591. }
  592. /**
  593. * De-install this module
  594. */
  595. function remove() {
  596. global $messageStack;
  597. // cannot remove EC if DP installed:
  598. if (defined('MODULE_PAYMENT_PAYPALDP_STATUS')) {
  599. // this language text is hard-coded in english since Website Payments Pro is not yet available in any countries that speak any other language at this time.
  600. $messageStack->add_session('<strong>Sorry, you must remove PayPal Payments Pro (paypaldp) first.</strong> PayPal Payments Pro (Website Payments Pro) requires that you offer Express Checkout to your customers.<br /><a href="' . zen_href_link('modules.php?set=payment&module=paypaldp', '', 'NONSSL') . '">Click here to edit or remove your PayPal Payments Pro module.</a>' , 'error');
  601. zen_redirect(zen_href_link(FILENAME_MODULES, 'set=payment&module=paypalwpp', 'NONSSL'));
  602. return 'failed';
  603. }
  604. global $db;
  605. $db->Execute("delete from " . TABLE_CONFIGURATION . " where configuration_key LIKE 'MODULE\_PAYMENT\_PAYPALWPP\_%' or configuration_key like 'MODULE\_PAYMENT\_PAYPALEC\_%'");
  606. $this->notify('NOTIFY_PAYMENT_PAYPALWPP_UNINSTALLED');
  607. }
  608. /**
  609. * Check settings and conditions to determine whether we are in an Express Checkout phase or not
  610. */
  611. function in_special_checkout() {
  612. if ((defined('MODULE_PAYMENT_PAYPALWPP_STATUS') && MODULE_PAYMENT_PAYPALWPP_STATUS == 'True') &&
  613. !empty($_SESSION['paypal_ec_token']) &&
  614. !empty($_SESSION['paypal_ec_payer_id']) &&
  615. !empty($_SESSION['paypal_ec_payer_info'])) {
  616. $this->flagDisablePaymentAddressChange = true;
  617. return true;
  618. }
  619. }
  620. /**
  621. * Determine whether the shipping-edit button should be displayed or not
  622. */
  623. function alterShippingEditButton() {
  624. return false;
  625. if ($this->in_special_checkout() && empty($_SESSION['paypal_ec_markflow'])) {
  626. return zen_href_link('ipn_main_handler.php', 'type=ec&clearSess=1', 'SSL', true,true, true);
  627. }
  628. }
  629. /**
  630. * Debug Logging support
  631. */
  632. function zcLog($stage, $message) {
  633. static $tokenHash;
  634. if ($tokenHash == '') $tokenHash = '_' . zen_create_random_value(4);
  635. if (MODULE_PAYMENT_PAYPALWPP_DEBUGGING == 'Log and Email' || MODULE_PAYMENT_PAYPALWPP_DEBUGGING == 'Log File') {
  636. $token = (isset($_SESSION['paypal_ec_token'])) ? $_SESSION['paypal_ec_token'] : preg_replace('/[^0-9.A-Z\-]/', '', $_GET['token']);
  637. $token = ($token == '') ? date('m-d-Y-H-i') : $token; // or time()
  638. $token .= $tokenHash;
  639. $file = $this->_logDir . '/' . $this->code . '_Paypal_Action_' . $token . '.log';
  640. if (defined('PAYPAL_DEV_MODE') && PAYPAL_DEV_MODE == 'true') $file = $this->_logDir . '/' . $this->code . '_Paypal_Debug_' . $token . '.log';
  641. $fp = @fopen($file, 'a');
  642. @fwrite($fp, date('M-d-Y H:i:s') . ' (' . time() . ')' . "\n" . $stage . "\n" . $message . "\n=================================\n\n");
  643. @fclose($fp);
  644. }
  645. $this->_doDebug($stage, $message, false);
  646. }
  647. /**
  648. * Debug Emailing support
  649. */
  650. function _doDebug($subject = 'PayPal debug data', $data, $useSession = true) {
  651. if (MODULE_PAYMENT_PAYPALWPP_DEBUGGING == 'Log and Email') {
  652. $data = urldecode($data) . "\n\n";
  653. if ($useSession) $data .= "\nSession data: " . print_r($_SESSION, true);
  654. zen_mail(STORE_NAME, STORE_OWNER_EMAIL_ADDRESS, $subject, $this->code . "\n" . $data, STORE_OWNER, STORE_OWNER_EMAIL_ADDRESS, array('EMAIL_MESSAGE_HTML'=>nl2br($this->code . "\n" . $data)), 'debug');
  655. }
  656. }
  657. /**
  658. * Initialize the PayPal/PayflowPro object for communication to the processing gateways
  659. */
  660. function paypal_init() {
  661. if (!defined('MODULE_PAYMENT_PAYPALWPP_STATUS') || !defined('MODULE_PAYMENT_PAYPALWPP_SERVER')) {
  662. $doPayPal = new paypal_curl(array('mode' => 'NOTCONFIGURED'));
  663. return $doPayPal;
  664. }
  665. $ec_uses_gateway = (defined('MODULE_PAYMENT_PAYPALWPP_PRO20_EC_METHOD') && MODULE_PAYMENT_PAYPALWPP_PRO20_EC_METHOD == 'Payflow') ? true : false;
  666. if (substr(MODULE_PAYMENT_PAYPALWPP_MODULE_MODE,0,7) == 'Payflow') {
  667. $doPayPal = new paypal_curl(array('mode' => 'payflow',
  668. 'user' => trim(MODULE_PAYMENT_PAYPALWPP_PFUSER),
  669. 'vendor' => trim(MODULE_PAYMENT_PAYPALWPP_PFVENDOR),
  670. 'partner'=> trim(MODULE_PAYMENT_PAYPALWPP_PFPARTNER),
  671. 'pwd' => trim(MODULE_PAYMENT_PAYPALWPP_PFPASSWORD),
  672. 'server' => MODULE_PAYMENT_PAYPALWPP_SERVER));
  673. $doPayPal->_endpoints = array('live' => 'https://payflowpro.paypal.com/transaction',
  674. 'sandbox' => 'https://pilot-payflowpro.paypal.com/transaction');
  675. } else {
  676. $doPayPal = new paypal_curl(array('mode' => 'nvp',
  677. 'user' => trim(MODULE_PAYMENT_PAYPALWPP_APIUSERNAME),
  678. 'pwd' => trim(MODULE_PAYMENT_PAYPALWPP_APIPASSWORD),
  679. 'signature' => trim(MODULE_PAYMENT_PAYPALWPP_APISIGNATURE),
  680. 'version' => '61.0',
  681. 'server' => MODULE_PAYMENT_PAYPALWPP_SERVER));
  682. $doPayPal->_endpoints = array('live' => 'https://api-3t.paypal.com/nvp',
  683. 'sandbox' => 'https://api-3t.sandbox.paypal.com/nvp');
  684. }
  685. // set logging options
  686. $doPayPal->_logDir = $this->_logDir;
  687. $doPayPal->_logLevel = $this->_logLevel;
  688. // set proxy options if configured
  689. if (CURL_PROXY_REQUIRED == 'True' && CURL_PROXY_SERVER_DETAILS != '') {
  690. $proxy_tunnel_flag = (defined('CURL_PROXY_TUNNEL_FLAG') && strtoupper(CURL_PROXY_TUNNEL_FLAG) == 'FALSE') ? false : true;
  691. $doPayPal->setCurlOption(CURLOPT_HTTPPROXYTUNNEL, $proxy_tunnel_flag);
  692. $doPayPal->setCurlOption(CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
  693. $doPayPal->setCurlOption(CURLOPT_PROXY, CURL_PROXY_SERVER_DETAILS);
  694. }
  695. // transaction processing mode
  696. $doPayPal->_trxtype = (in_array(MODULE_PAYMENT_PAYPALWPP_TRANSACTION_MODE, array('Auth Only', 'Order'))) ? 'A' : 'S';
  697. return $doPayPal;
  698. }
  699. /**
  700. * Determine which PayPal URL to direct the customer's browser to when needed
  701. */
  702. function getPayPalLoginServer() {
  703. if (MODULE_PAYMENT_PAYPALWPP_SERVER == 'live') {
  704. // live url
  705. $paypal_url = 'https://www.paypal.com/cgi-bin/webscr';
  706. } else {
  707. // sandbox url
  708. $paypal_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
  709. }
  710. return $paypal_url;
  711. }
  712. /**
  713. * Used to submit a refund for a given transaction. FOR FUTURE USE.
  714. */
  715. function _doRefund($oID, $amount = 'Full', $note = '') {
  716. global $db, $doPayPal, $messageStack;
  717. $new_order_status = (int)MODULE_PAYMENT_PAYPALWPP_REFUNDED_STATUS_ID;
  718. $orig_order_amount = 0;
  719. $doPayPal = $this->paypal_init();
  720. $proceedToRefund = false;
  721. $refundNote = strip_tags(zen_db_input($_POST['refnote']));
  722. if (isset($_POST['fullrefund']) && $_POST['fullrefund'] == MODULE_PAYMENT_PAYPAL_ENTRY_REFUND_BUTTON_TEXT_FULL) {
  723. $refundAmt = 'Full';
  724. if (isset($_POST['reffullconfirm']) && $_POST['reffullconfirm'] == 'on') {
  725. $proceedToRefund = true;
  726. } else {
  727. $messageStack->add_session(MODULE_PAYMENT_PAYPALWPP_TEXT_REFUND_FULL_CONFIRM_ERROR, 'error');
  728. }
  729. }
  730. if (isset($_POST['partialrefund']) && $_POST['partialrefund'] == MODULE_PAYMENT_PAYPAL_ENTRY_REFUND_BUTTON_TEXT_PARTIAL) {
  731. $refundAmt = (float)$_POST['refamt'];
  732. $proceedToRefund = true;
  733. if ($refundAmt == 0) {
  734. $messageStack->add_session(MODULE_PAYMENT_PAYPALWPP_TEXT_INVALID_REFUND_AMOUNT, 'error');
  735. $proceedToRefund = false;
  736. }
  737. }
  738. // look up history on this order from PayPal table
  739. $sql = "select * from " . TABLE_PAYPAL . " where order_id = :orderID AND parent_txn_id = '' ";
  740. $sql = $db->bindVars($sql, ':orderID', $oID, 'integer');
  741. $zc_ppHist = $db->Execute($sql);
  742. if ($zc_ppHist->RecordCount() == 0) return false;
  743. $txnID = $zc_ppHist->fields['txn_id'];
  744. $curCode = $zc_ppHist->fields['mc_currency'];
  745. $PFamt = $zc_ppHist->fields['mc_gross'];
  746. if ($doPayPal->_mode == 'payflow' && $refundAmt == 'Full') $refundAmt = $PFamt;
  747. /**
  748. * Submit refund request to PayPal
  749. */
  750. if ($proceedToRefund) {
  751. $response = $doPayPal->RefundTransaction($oID, $txnID, $refundAmt, $refundNote, $curCode);
  752. $error = $this->_errorHandler($response, 'DoRefund');
  753. $new_order_status = ($new_order_status > 0 ? $new_order_status : 1);
  754. if (!$error) {
  755. if (!isset($response['GROSSREFUNDAMT'])) $response['GROSSREFUNDAMT'] = $refundAmt;
  756. // Success, so save the results
  757. $sql_data_array = array('orders_id' => $oID,
  758. 'orders_status_id' => (int)$new_order_status,
  759. 'date_added' => 'now()',
  760. 'comments' => 'REFUND INITIATED. Trans ID:' . $response['REFUNDTRANSACTIONID'] . $response['PNREF']. "\n" . /*' Net Refund Amt:' . urldecode($response['NETREFUNDAMT']) . "\n" . ' Fee Refund Amt: ' . urldecode($response['FEEREFUNDAMT']) . "\n" . */' Gross Refund Amt: ' . urldecode($response['GROSSREFUNDAMT']) . (isset($response['PPREF']) ? "\nPPRef: " . $response['PPREF'] : '') . "\n" . $refundNote,
  761. 'customer_notified' => 0
  762. );
  763. zen_db_perform(TABLE_ORDERS_STATUS_HISTORY, $sql_data_array);
  764. $db->Execute("update " . TABLE_ORDERS . "
  765. set orders_status = '" . (int)$new_order_status . "'
  766. where orders_id = '" . (int)$oID . "'");
  767. $messageStack->add_session(sprintf(MODULE_PAYMENT_PAYPALWPP_TEXT_REFUND_INITIATED, urldecode($response['GROSSREFUNDAMT']), urldecode($response['REFUNDTRANSACTIONID']). $response['PNREF']), 'success');
  768. return true;
  769. }
  770. }
  771. }
  772. /**
  773. * Used to authorize part of a given previously-initiated transaction. FOR FUTURE USE.
  774. */
  775. function _doAuth($oID, $amt, $currency = 'USD') {
  776. global $db, $doPayPal, $messageStack;
  777. $doPayPal = $this->paypal_init();
  778. $authAmt = $amt;
  779. $new_order_status = (int)MODULE_PAYMENT_PAYPALWPP_ORDER_PENDING_STATUS_ID;
  780. if (isset($_POST['orderauth']) && $_POST['orderauth'] == MODULE_PAYMENT_PAYPAL_ENTRY_AUTH_BUTTON_TEXT_PARTIAL) {
  781. $authAmt = (float)$_POST['authamt'];
  782. $new_order_status = MODULE_PAYMENT_PAYPALWPP_ORDER_STATUS_ID;
  783. if (isset($_POST['authconfirm']) && $_POST['authconfirm'] == 'on') {
  784. $proceedToAuth = true;
  785. } else {
  786. $messageStack->add_session(MODULE_PAYMENT_PAYPALWPP_TEXT_AUTH_CONFIRM_ERROR, 'error');
  787. $proceedToAuth = false;
  788. }
  789. if ($authAmt == 0) {
  790. $messageStack->add_session(MODULE_PAYMENT_PAYPALWPP_TEXT_INVALID_AUTH_AMOUNT, 'error');
  791. $proceedToAuth = false;
  792. }
  793. }
  794. // look up history on this order from PayPal table
  795. $sql = "select * from " . TABLE_PAYPAL . " where order_id = :orderID AND parent_txn_id = '' ";
  796. $sql = $db->bindVars($sql, ':orderID', $oID, 'integer');
  797. $zc_ppHist = $db->Execute($sql);
  798. if ($zc_ppHist->RecordCount() == 0) return false;
  799. $txnID = $zc_ppHist->fields['txn_id'];
  800. /**
  801. * Submit auth request to PayPal
  802. */
  803. if ($proceedToAuth) {
  804. $response = $doPayPal->DoAuthorization($txnID, $authAmt, $currency);
  805. $error = $this->_errorHandler($response, 'DoAuthorization');
  806. $new_order_status = ($new_order_status > 0 ? $new_order_status : 1);
  807. if (!$error) {
  808. // Success, so save the results
  809. $sql_data_array = array('orders_id' => (int)$oID,
  810. 'orders_status_id' => (int)$new_order_status,
  811. 'date_added' => 'now()',
  812. 'comments' => 'AUTHORIZATION ADDED. Trans ID: ' . urldecode($response['TRANSACTIONID']) . "\n" . ' Amount:' . urldecode($response['AMT']) . ' ' . $currency,
  813. 'customer_notified' => -1
  814. );
  815. zen_db_perform(TABLE_ORDERS_STATUS_HISTORY, $sql_data_array);
  816. $db->Execute("update " . TABLE_ORDERS . "
  817. set orders_status = '" . (int)$new_order_status . "'
  818. where orders_id = '" . (int)$oID . "'");
  819. $messageStack->add_session(sprintf(MODULE_PAYMENT_PAYPALWPP_TEXT_AUTH_INITIATED, urldecode($response['AMT'])), 'success');
  820. return true;
  821. }
  822. }
  823. }
  824. /**
  825. * Used to capture part or all of a given previously-authorized transaction. FOR FUTURE USE.
  826. * (alt value for $captureType = 'NotComplete')
  827. */
  828. function _doCapt($oID, $captureType = 'Complete', $amt = 0, $currency = 'USD', $note = '') {
  829. global $db, $doPayPal, $messageStack;
  830. $doPayPal = $this->paypal_init();
  831. //@TODO: Read current order status and determine best status to set this to
  832. $new_order_status = (int)MODULE_PAYMENT_PAYPALWPP_ORDER_STATUS_ID;
  833. $orig_order_amount = 0;
  834. $doPayPal = $this->paypal_init();
  835. $proceedToCapture = false;
  836. $captureNote = strip_tags(zen_db_input($_POST['captnote']));
  837. if (isset($_POST['captfullconfirm']) && $_POST['captfullconfirm'] == 'on') {
  838. $proceedToCapture = true;
  839. } else {
  840. $messageStack->add_session(MODULE_PAYMENT_PAYPALWPP_TEXT_CAPTURE_FULL_CONFIRM_ERROR, 'error');
  841. }
  842. if (isset($_POST['captfinal']) && $_POST['captfinal'] == 'on') {
  843. $captureType = 'Complete';
  844. } else {
  845. $captureType = 'NotComplete';
  846. }
  847. if (isset($_POST['btndocapture']) && $_POST['btndocapture'] == MODULE_PAYMENT_PAYPAL_ENTRY_CAPTURE_BUTTON_TEXT_FULL) {
  848. $captureAmt = (float)$_POST['captamt'];
  849. if ($captureAmt == 0) {
  850. $messageStack->add_session(MODULE_PAYMENT_PAYPALWPP_TEXT_INVALID_CAPTURE_AMOUNT, 'error');
  851. $proceedToCapture = false;
  852. }
  853. }
  854. // look up history on this order from PayPal table
  855. $sql = "select * from " . TABLE_PAYPAL . " where order_id = :orderID AND parent_txn_id = '' ";
  856. $sql = $db->bindVars($sql, ':orderID', $oID, 'integer');
  857. $zc_ppHist = $db->Execute($sql);
  858. if ($zc_ppHist->RecordCount() == 0) return false;
  859. $txnID = $zc_ppHist->fields['txn_id'];
  860. /**
  861. * Submit capture request to PayPal
  862. */
  863. if ($proceedToCapture) {
  864. $response = $doPayPal->DoCapture($txnID, $captureAmt, $currency, $captureType, '', $captureNote);
  865. $error = $this->_errorHandler($response, 'DoCapture');
  866. $new_order_status = ($new_order_status > 0 ? $new_order_status : 1);
  867. if (!$error) {
  868. if (isset($response['PNREF'])) {
  869. if (!isset($response['AMT'])) $response['AMT'] = $captureAmt;
  870. if (!isset($response['ORDERTIME'])) $response['ORDERTIME'] = date("M-d-Y h:i:s");
  871. }
  872. // Success, so save the results
  873. $sql_data_array = array('orders_id' => (int)$oID,
  874. 'orders_status_id' => (int)$new_order_status,
  875. 'date_added' => 'now()',
  876. 'comments' => 'FUNDS COLLECTED. Trans ID: ' . urldecode($response['TRANSACTIONID']) . $response['PNREF']. "\n" . ' Amount: ' . urldecode($response['AMT']) . ' ' . $currency . "\n" . 'Time: ' . urldecode($response['ORDERTIME']) . "\n" . (isset($response['RECEIPTID']) ? 'Receipt ID: ' . urldecode($response['RECEIPTID']) : 'Auth Code: ' . (isset($response['AUTHCODE']) && $response['AUTHCODE'] != '' ? $response['AUTHCODE'] : $response['CORRELATIONID'])) . (isset($response['PPREF']) ? "\nPPRef: " . $response['PPREF'] : '') . "\n" . $captureNote,
  877. 'customer_notified' => 0
  878. );
  879. zen_db_perform(TABLE_ORDERS_STATUS_HISTORY, $sql_data_array);
  880. $db->Execute("update " . TABLE_ORDERS . "
  881. set orders_status = '" . (int)$new_order_status . "'
  882. where orders_id = '" . (int)$oID . "'");
  883. $messageStack->add_session(sprintf(MODULE_PAYMENT_PAYPALWPP_TEXT_CAPT_INITIATED, urldecode($response['AMT']), urldecode($response['RECEIPTID'] . (isset($response['AUTHCODE']) && $response['AUTHCODE'] != '' ? $response['AUTHCODE'] : $response['CORRELATIONID']) ). $response['PNREF']), 'success');
  884. return true;
  885. }
  886. }
  887. }
  888. /**
  889. * Used to void a given previously-authorized transaction. FOR FUTURE USE.
  890. */
  891. function _doVoid($oID, $note = '') {
  892. global $db, $doPayPal, $messageStack;
  893. $new_order_status = (int)MODULE_PAYMENT_PAYPALWPP_REFUNDED_STATUS_ID;
  894. $doPayPal = $this->paypal_init();
  895. $voidNote = strip_tags(zen_db_input($_POST['voidnote']));
  896. $voidAuthID = trim(strip_tags(zen_db_input($_POST['voidauthid'])));
  897. if (isset($_POST['ordervoid']) && $_POST['ordervoid'] == MODULE_PAYMENT_PAYPAL_ENTRY_VOID_BUTTON_TEXT_FULL) {
  898. if (isset($_POST['voidconfirm']) && $_POST['voidconfirm'] == 'on') {
  899. $proceedToVoid = true;
  900. } else {
  901. $messageStack->add_session(MODULE_PAYMENT_PAYPALWPP_TEXT_VOID_CONFIRM_ERROR, 'error');
  902. }
  903. }
  904. // look up history on this order from PayPal table
  905. $sql = "select * from " . TABLE_PAYPAL . " where order_id = :orderID AND parent_txn_id = '' ";
  906. $sql = $db->bindVars($sql, ':orderID', $oID, 'integer');
  907. $sql = $db->bindVars($sql, ':transID', $voidAuthID, 'string');
  908. $zc_ppHist = $db->Execute($sql);
  909. if ($zc_ppHist->RecordCount() == 0) return false;
  910. $txnID = $zc_ppHist->fields['txn_id'];
  911. /**
  912. * Submit void request to PayPal
  913. */
  914. if ($proceedToVoid) {
  915. $response = $doPayPal->DoVoid($voidAuthID, $voidNote);
  916. $error = $this->_errorHandler($response, 'DoVoid');
  917. $new_order_status = ($new_order_status > 0 ? $new_order_status : 1);
  918. if (!$error) {
  919. // Success, so save the results
  920. $sql_data_array = array('orders_id' => (int)$oID,
  921. 'orders_status_id' => (int)$new_order_status,
  922. 'date_added' => 'now()',
  923. 'comments' => 'VOIDED. Trans ID: ' . urldecode($response['AUTHORIZATIONID']). $response['PNREF'] . (isset($response['PPREF']) ? "\nPPRef: " . $response['PPREF'] : '') . "\n" . $voidNote,
  924. 'customer_notified' => 0
  925. );
  926. zen_db_perform(TABLE_ORDERS_STATUS_HISTORY, $sql_data_array);
  927. $db->Execute("update " . TABLE_ORDERS . "
  928. set orders_status = '" . (int)$new_order_status . "'
  929. where orders_id = '" . (int)$oID . "'");
  930. $messageStack->add_session(sprintf(MODULE_PAYMENT_PAYPALWPP_TEXT_VOID_INITIATED, urldecode($response['AUTHORIZATIONID']) . $response['PNREF']), 'success');
  931. return true;
  932. }
  933. }
  934. }
  935. /**
  936. * Determine the language to use when visiting the PayPal site
  937. */
  938. function getLanguageCode() {
  939. global $order;
  940. $lang_code = '';
  941. $orderISO = zen_get_countries($order->customer['country']['id'], true);
  942. $storeISO = zen_get_countries(STORE_COUNTRY, true);
  943. if (in_array(strtoupper($orderISO['countries_iso_code_2']), array('US', 'AU', 'DE', 'FR', 'IT', 'GB', 'ES', 'AT', 'BE', 'CA', 'CH', 'CN', 'NL', 'PL'))) {
  944. $lang_code = strtoupper($orderISO['countries_iso_code_2']);
  945. } elseif (in_array(strtoupper($storeISO['countries_iso_code_2']), array('US', 'AU', 'DE', 'FR', 'IT', 'GB', 'ES', 'AT', 'BE', 'CA', 'CH', 'CN', 'NL', 'PL'))) {
  946. $lang_code = strtoupper($storeISO['countries_iso_code_2']);
  947. }
  948. else
  949. if (in_array(strtoupper($_SESSION['languages_code']), array('EN', 'US', 'AU', 'DE', 'FR', 'IT', 'GB', 'ES', 'AT', 'BE', 'CA', 'CH', 'CN', 'NL', 'PL'))) {
  950. $lang_code = $_SESSION['languages_code'];
  951. }
  952. if (strtoupper($lang_code) == 'EN') $lang_code = 'US';
  953. return strtoupper($lang_code);
  954. }
  955. /**
  956. * Set the currency code -- use defaults if active currency is not a currency accepted by PayPal
  957. */
  958. function selectCurrency($val = '', $subset = 'EC') {
  959. $ec_currencies = array('CAD', 'EUR', 'GBP', 'JPY', 'USD', 'AUD', 'CHF', 'CZK', 'DKK', 'HKD', 'HUF', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'THB', 'MXN', 'ILS', 'PHP', 'TWD', 'BRL', 'MYR', 'TKD');
  960. $dp_currencies = array('CAD', 'EUR', 'GBP', 'JPY', 'USD', 'AUD');
  961. $paypalSupportedCurrencies = ($subset == 'EC') ? $ec_currencies : $dp_currencies;
  962. // if using Pro 2.0 (UK), only the 6 currencies are supported.
  963. $paypalSupportedCurrencies = (MODULE_PAYMENT_PAYPALWPP_MODULE_MODE == 'Payflow-UK') ? $dp_currencies : $paypalSupportedCurrencies;
  964. $my_currency = substr(MODULE_PAYMENT_PAYPALWPP_CURRENCY, 5);
  965. if (MODULE_PAYMENT_PAYPALWPP_CURRENCY == 'Selected Currency') {
  966. $my_currency = ($val == '') ? $_SESSION['currency'] : $val;
  967. }
  968. if (!in_array($my_currency, $paypalSupportedCurrencies)) {
  969. $my_currency = (MODULE_PAYMENT_PAYPALWPP_MODULE_MODE == 'Payflow-UK') ? 'GBP' : 'USD';
  970. }
  971. return $my_currency;
  972. }
  973. /**
  974. * Calculate the amount based on acceptable currencies
  975. */
  976. function calc_order_amount($amount, $paypalCurrency, $applyFormatting = false) {
  977. global $currencies;
  978. $amount = ($amount * $currencies->get_value($paypalCurrency));
  979. if ($paypalCurrency == 'JPY' || (int)$currencies->get_decimal_places($paypalCurrency) == 0) {
  980. $amount = (int)$amount;
  981. $applyFormatting = FALSE;
  982. }
  983. return ($applyFormatting ? number_format($amount, $currencies->get_decimal_places($paypalCurrency)) : $amount);
  984. }
  985. /**
  986. * Set the state field depending on what PayPal requires for that country.
  987. */
  988. function setStateAndCountry(&$info) {
  989. global $db, $messageStack;
  990. switch ($info['country']['iso_code_2']) {
  991. case 'AU':
  992. case 'US':
  993. case 'CA':
  994. // Paypal only accepts two character state/province codes for some countries.
  995. if (strlen($info['state']) > 2) {
  996. $sql = "SELECT zone_code FROM " . TABLE_ZONES . " WHERE zone_name = :zoneName";
  997. $sql = $db->bindVars($sql, ':zoneName', $info['state'], 'string');
  998. $state = $db->Execute($sql);
  999. if (!$state->EOF) {
  1000. $info['state'] = $state->fields['zone_code'];
  1001. } else {
  1002. $messageStack->add_session('header', MODULE_PAYMENT_PAYPALWPP_TEXT_STATE_ERROR, 'error');
  1003. $this->terminateEC(MODULE_PAYMENT_PAYPALWPP_TEXT_STATE_ERROR);
  1004. }
  1005. }
  1006. break;
  1007. case 'AT':
  1008. case 'BE':
  1009. case 'FR':
  1010. case 'DE':
  1011. case 'CH':
  1012. $info['state'] = '';
  1013. break;
  1014. case 'GB':
  1015. break;
  1016. default:
  1017. $info['state'] = '';
  1018. }
  1019. }
  1020. /**
  1021. * Prepare subtotal and line-item detail content to send to PayPal
  1022. */
  1023. function getLineItemDetails($restrictedCurrency) {
  1024. global $order, $currencies, $order_totals, $order_total_modules;
  1025. // if not default currency, do not send subtotals or line-item details
  1026. if (DEFAULT_CURRENCY != $order->info['currency'] || $restrictedCurrency != DEFAULT_CURRENCY) {
  1027. $this->zcLog('getLineItemDetails 1', 'Not using default currency. Thus, no line-item details can be submitted.');
  1028. return array();
  1029. }
  1030. if ($currencies->currencies[$_SESSION['currency']]['value'] != 1 || $currencies->currencies[$order->info['currency']]['value'] != 1) {
  1031. $this->zcLog('getLineItemDetails 2', 'currency val not equal to 1.0000 - cannot proceed without coping with currency conversions. Aborting line-item details.');
  1032. return array();
  1033. }
  1034. $optionsST = array();
  1035. $optionsLI = array();
  1036. $optionsNB = array();
  1037. $numberOfLineItemsProcessed = 0;
  1038. $creditsApplied = 0;
  1039. $surcharges = 0;
  1040. $sumOfLineItems = 0;
  1041. $sumOfLineTax = 0;
  1042. $optionsST['AMT'] = 0;
  1043. $optionsST['ITEMAMT'] = 0;
  1044. $optionsST['TAXAMT'] = 0;
  1045. $optionsST['SHIPPINGAMT'] = 0;
  1046. $optionsST['SHIPDISCAMT'] = 0;
  1047. $optionsST['HANDLINGAMT'] = 0;
  1048. $optionsST['INSURANCEAMT'] = 0;
  1049. $flagSubtotalsUnknownYet = true;
  1050. $subTotalLI = 0;
  1051. $subTotalTax = 0;
  1052. $subTotalShipping = 0;
  1053. $subtotalPRE = array('no data');
  1054. $discountProblemsFlag = FALSE;
  1055. $flag_treat_as_partial = FALSE;
  1056. if (sizeof($order_totals)) {
  1057. // prepare subtotals
  1058. for ($i=0, $n=sizeof($order_totals); $i<$n; $i++) {
  1059. if ($order_totals[$i]['code'] == '') continue;
  1060. if (in_array($order_totals[$i]['code'], array('ot_total','ot_subtotal','ot_tax','ot_shipping')) || strstr($order_totals[$i]['code'], 'insurance')) {
  1061. if ($order_totals[$i]['code'] == 'ot_shipping') $optionsST['SHIPPINGAMT'] = round($order_totals[$i]['value'],2);
  1062. if ($order_totals[$i]['code'] == 'ot_total') $optionsST['AMT'] = round($order_totals[$i]['value'],2);
  1063. if ($order_totals[$i]['code'] == 'ot_tax') $optionsST['TAXAMT'] += round($order_totals[$i]['value'],2);
  1064. if ($order_totals[$i]['code'] == 'ot_subtotal') $optionsST['ITEMAMT'] = round($order_totals[$i]['value'],2);
  1065. if (strstr($order_totals[$i]['code'], 'insurance')) $optionsST['INSURANCEAMT'] += round($order_totals[$i]['value'],2);
  1066. //$optionsST['SHIPDISCAMT'] = ''; // Not applicable
  1067. } else {
  1068. // handle other order totals:
  1069. global $$order_totals[$i]['code'];
  1070. if ((substr($order_totals[$i]['text'], 0, 1) == '-') || (isset($$order_totals[$i]['code']->credit_class) && $$order_totals[$i]['code']->credit_class == true)) {
  1071. // handle credits
  1072. $creditsApplied += round($order_totals[$i]['value'], 2);
  1073. } else {
  1074. // treat all other OT's as if they're related to handling fees or other extra charges to be added/included
  1075. $surcharges += $order_totals[$i]['value'];
  1076. }
  1077. }
  1078. }
  1079. if ($creditsApplied > 0) $optionsST['ITEMAMT'] -= $creditsApplied;
  1080. if ($surcharges > 0) $optionsST['ITEMAMT'] += $surcharges;
  1081. // Handle tax-included scenario
  1082. if (DISPLAY_PRICE_WITH_TAX == 'true') $optionsST['TAXAMT'] = 0;
  1083. $subtotalPRE = $optionsST;
  1084. // Move shipping tax amount from Tax subtotal into Shipping subtotal for submission to PayPal, since PayPal applies tax to each line-item individually
  1085. $module = substr($_SESSION['shipping']['id'], 0, strpos($_SESSION['shipping']['id'], '_'));
  1086. if (zen_not_null($order->info['shipping_method']) && DISPLAY_PRICE_WITH_TAX != 'true') {
  1087. if ($GLOBALS[$module]->tax_class > 0) {
  1088. $shipping_tax_basis = (!isset($GLOBALS[$module]->tax_basis)) ? STORE_SHIPPING_TAX_BASIS : $GLOBALS[$module]->tax_basis;
  1089. $shippingOnBilling = zen_get_tax_rate($GLOBALS[$module]->tax_class, $order->billing['country']['id'], $order->billing['zone_id']);
  1090. $shippingOnDelivery = zen_get_tax_rate($GLOBALS[$module]->tax_class, $order->delivery['country']['id'], $order->delivery['zone_id']);
  1091. if ($shipping_tax_basis == 'Billing') {
  1092. $shipping_tax = $shippingOnBilling;
  1093. } elseif ($shipping_tax_basis == 'Shipping') {
  1094. $shipping_tax = $shippingOnDelivery;
  1095. } else {
  1096. if (STORE_ZONE == $order->billing['zone_id']) {
  1097. $shipping_tax = $shippingOnBilling;
  1098. } elseif (STORE_ZONE == $order->delivery['zone_id']) {
  1099. $shipping_tax = $shippingOnDelivery;
  1100. } else {
  1101. $shipping_tax = 0;
  1102. }
  1103. }
  1104. $taxAdjustmentForShipping = zen_round(zen_calculate_tax($order->info['shipping_cost'], $shipping_tax), $currencies->currencies[$_SESSION['currency']]['decimal_places']);
  1105. $optionsST['SHIPPINGAMT'] += $taxAdjustmentForShipping;
  1106. $optionsST['TAXAMT'] -= $taxAdjustmentForShipping;
  1107. }
  1108. }
  1109. $flagSubtotalsUnknownYet = (($optionsST['SHIPPINGAMT'] + $optionsST['SHIPDISCAMT'] + $optionsST['AMT'] + $optionsST['TAXAMT'] + $optionsST['ITEMAMT'] + $optionsST['INSURANCEAMT']) == 0);
  1110. } else {
  1111. // if we get here, we don't have any order-total information yet because the customer has clicked Express before starting normal checkout flow
  1112. // thus, we must make a note to manually calculate subtotals, rather than relying on the more robust order-total infrastructure
  1113. $flagSubtotalsUnknownYet = TRUE;
  1114. }
  1115. $decimals = $currencies->get_decimal_places($_SESSION['currency']);
  1116. // loop thru all products to prepare details of quantity and price.
  1117. for ($i=0, $n=sizeof($order->products), $k=-1; $i<$n; $i++) {
  1118. // PayPal is inconsistent in how it handles zero-value line-items, so skip this entry if price is zero
  1119. if ($order->products[$i]['final_price'] == 0) {
  1120. continue;
  1121. } else {
  1122. $k++;
  1123. }
  1124. $optionsLI["L_NUMBER$k"] = $order->products[$i]['model'];
  1125. $optionsLI["L_NAME$k"] = $order->products[$i]['name'] . ' [' . (int)$order->products[$i]['id'] . ']';
  1126. // Append *** if out-of-stock.
  1127. $optionsLI["L_NAME$k"] .= ((zen_get_products_stock($order->products[$i]['id']) - $order->products[$i]['qty']) < 0 ? STOCK_MARK_PRODUCT_OUT_OF_STOCK : '');
  1128. // if there are attributes, loop thru them and add to description
  1129. if (isset($order->products[$i]['attributes']) && sizeof($order->products[$i]['attributes']) > 0 ) {
  1130. for ($j=0, $n2=sizeof($order->products[$i]['attributes']); $j<$n2; $j++) {
  1131. $optionsLI["L_NAME$k"] .= "\n " . $order->products[$i]['attributes'][$j]['option'] .
  1132. ': ' . $order->products[$i]['attributes'][$j]['value'];
  1133. } // end loop
  1134. } // endif attribute-info
  1135. // PayPal can't handle fractional-quantity values, so convert it to qty 1 here
  1136. if ($order->products[$i]['qty'] > 1 && ($order->products[$i]['qty'] != (int)$order->products[$i]['qty'] || $flag_treat_as_partial)) {
  1137. $optionsLI["L_NAME$k"] = '('.$order->products[$i]['qty'].' x ) ' . $optionsLI["L_NAME$k"];
  1138. // zen_add_tax already handles whether DISPLAY_PRICES_WITH_TAX is set
  1139. $optionsLI["L_AMT$k"] = zen_round(zen_round(zen_add_tax($order->products[$i]['final_price'], $order->products[$i]['tax']), $decimals) * $order->products[$i]['qty'], $decimals);
  1140. $optionsLI["L_QTY$k"] = 1;
  1141. // no line-item tax component
  1142. } else {
  1143. $optionsLI["L_QTY$k"] = $order->products[$i]['qty'];
  1144. $optionsLI["L_AMT$k"] = zen_round(zen_add_tax($order->products[$i]['final_price'], $order->products[$i]['tax']), $decimals);
  1145. }
  1146. $subTotalLI += ($optionsLI["L_QTY$k"] * $optionsLI["L_AMT$k"]);
  1147. // $subTotalTax += ($optionsLI["L_QTY$k"] * $optionsLI["L_TAXAMT$k"]);
  1148. // add line-item for one-time charges on this product
  1149. if ($order->products[$i]['onetime_charges'] != 0 ) {
  1150. $k++;
  1151. $optionsLI["L_NAME$k"] = MODULES_PAYMENT_PAYPALWPP_LINEITEM_TEXT_ONETIME_CHARGES_PREFIX . substr(htmlentities($order->products[$i]['name'], ENT_QUOTES, 'UTF-8'), 0, 120);
  1152. $optionsLI["L_AMT$k"] = zen_round(zen_add_tax($order->products[$i]['onetime_charges'], $order->products[$i]['tax']), $decimals);
  1153. $optionsLI["L_QTY$k"] = 1;
  1154. // $optionsLI["L_TAXAMT$k"] = zen_round(zen_calculate_tax($order->products[$i]['onetime_charges'], $order->products[$i]['tax']), $decimals);
  1155. $subTotalLI += $optionsLI["L_AMT$k"];
  1156. // $subTotalTax += $optionsLI["L_TAXAMT$k"];
  1157. }
  1158. $numberOfLineItemsProcessed = $k;
  1159. } // end for loopthru all products
  1160. // add line items for any surcharges added by order-total modules
  1161. if ($surcharges > 0) {
  1162. $numberOfLineItemsProcessed++;
  1163. $k = $numberOfLineItemsProcessed;
  1164. $optionsLI["L_NAME$k"] = MODULES_PAYMENT_PAYPALWPP_LINEITEM_TEXT_SURCHARGES_LONG;
  1165. $optionsLI["L_AMT$k"] = $surcharges;
  1166. $optionsLI["L_QTY$k"] = 1;
  1167. $subTotalLI += $surcharges;
  1168. }
  1169. // add line items for discounts such as gift certificates and coupons
  1170. if ($creditsApplied > 0) {
  1171. $numberOfLineItemsProcessed++;
  1172. $k = $numberOfLineItemsProcessed;
  1173. $optionsLI["L_NAME$k"] = MODULES_PAYMENT_PAYPALWPP_LINEITEM_TEXT_DISCOUNTS_LONG;
  1174. $optionsLI["L_AMT$k"] = (-1 * $creditsApplied);
  1175. $optionsLI["L_QTY$k"] = 1;
  1176. $subTotalLI -= $creditsApplied;
  1177. }
  1178. // Reformat properly
  1179. // Replace & and = and % with * if found.
  1180. // reformat properly according to API specs
  1181. // Remove HTML markup from name if found
  1182. for ($k=0, $n=$numberOfLineItemsProcessed+1; $k<$n; $k++) {
  1183. $optionsLI["L_NAME$k"] = str_replace(array('&','=','%'), '*', $optionsLI["L_NAME$k"]);
  1184. $optionsLI["L_NAME$k"] = zen_clean_html($optionsLI["L_NAME$k"], 'strong');
  1185. $optionsLI["L_NAME$k"] = substr($optionsLI["L_NAME$k"], 0, 127);
  1186. $optionsLI["L_AMT$k"] = round($optionsLI["L_AMT$k"], 2);
  1187. if (isset($optionsLI["L_NUMBER$k"])) {
  1188. if ($optionsLI["L_NUMBER$k"] == '') {
  1189. unset($optionsLI["L_NUMBER$k"]);
  1190. } else {
  1191. $optionsLI["L_NUMBER$k"] = str_replace(array('&','=','%'), '*', $optionsLI["L_NUMBER$k"]);
  1192. $optionsLI["L_NUMBER$k"] = substr($optionsLI["L_NUMBER$k"], 0, 127);
  1193. }
  1194. }
  1195. // if (isset($optionsLI["L_TAXAMT$k"]) && ($optionsLI["L_TAXAMT$k"] != '' || $optionsLI["L_TAXAMT$k"] > 0)) {
  1196. // $optionsLI["L_TAXAMT$k"] = round($optionsLI["L_TAXAMT$k"], 2);
  1197. // }
  1198. }
  1199. // Sanity Check of line-item subtotals
  1200. for ($j=0; $j<$k; $j++) {
  1201. $itemAMT = $optionsLI["L_AMT$j"];
  1202. $itemQTY = $optionsLI["L_QTY$j"];
  1203. $itemTAX = (isset($optionsLI["L_TAXAMT$j"]) ? $optionsLI["L_TAXAMT$j"] : 0);
  1204. $sumOfLineItems += ($itemQTY * $itemAMT);
  1205. $sumOfLineTax += ($itemQTY * $itemTAX);
  1206. }
  1207. $sumOfLineItems = round($sumOfLineItems, 2);
  1208. $sumOfLineTax = round($sumOfLineTax, 2);
  1209. if ($sumOfLineItems == 0) {
  1210. $sumOfLineTax = 0;
  1211. $optionsLI = array();
  1212. $discountProblemsFlag = TRUE;
  1213. if ($optionsST['SHIPPINGAMT'] == $optionsST['AMT']) {
  1214. $optionsST['SHIPPINGAMT'] = 0;
  1215. }
  1216. }
  1217. // // Sanity check -- if tax-included pricing is causing problems, remove the numbers and put them in a comment instead:
  1218. // $stDiffTaxOnly = (strval($sumOfLineItems - $sumOfLineTax - round($optionsST['AMT'], 2)) + 0);
  1219. // $this->zcLog('tax sanity check', 'stDiffTaxOnly: ' . $stDiffTaxOnly . "\nsumOfLineItems: " . $sumOfLineItems . "\nsumOfLineTax: " . $sumOfLineTax . ' ' . $subTotalTax . ' ' . print_r(array_merge($optionsST, $optionsLI), true));
  1220. // if (DISPLAY_PRICE_WITH_TAX == 'true' && $stDiffTaxOnly == 0 && ($optionsST['TAXAMT'] != 0 && $sumOfLineTax != 0)) {
  1221. // $optionsNB['DESC'] = 'Tax included in prices: ' . $sumOfLineTax . ' (' . $optionsST['TAXAMT'] . ') ';
  1222. // $optionsST['TAXAMT'] = 0;
  1223. // for ($k=0, $n=$numberOfLineItemsProcessed+1; $k<$n; $k++) {
  1224. // if (isset($optionsLI["L_TAXAMT$k"])) unset($optionsLI["L_TAXAMT$k"]);
  1225. // }
  1226. // }
  1227. // // Do sanity check -- if any of the line-item subtotal math doesn't add up properly, skip line-item details,
  1228. // // so that the order can go through even though PayPal isn't being flexible to handle Zen Cart's diversity
  1229. // if ((strval($subTotalTax) - strval($sumOfLineTax)) > 0.02) {
  1230. // $this->zcLog('getLineItemDetails 3', 'Tax Subtotal does not match sum of taxes for line-items. Tax details are being removed from line-item submission data.' . "\n" . $sumOfLineTax . ' ' . $subTotalTax . print_r(array_merge($optionsST, $optionsLI), true));
  1231. // for ($k=0, $n=$numberOfLineItemsProcessed+1; $k<$n; $k++) {
  1232. // if (isset($optionsLI["L_TAXAMT$k"])) unset($optionsLI["L_TAXAMT$k"]);
  1233. // }
  1234. // $subTotalTax = 0;
  1235. // $sumOfLineTax = 0;
  1236. // }
  1237. // // If coupons exist and there's a calculation problem, then it's likely that taxes are incorrect, so reset L_TAXAMTn values
  1238. // if ($creditsApplied > 0 && (strval($optionsST['TAXAMT']) != strval($sumOfLineTax))) {
  1239. // $pre = $optionsLI;
  1240. // for ($k=0, $n=$numberOfLineItemsProcessed+1; $k<$n; $k++) {
  1241. // if (isset($optionsLI["L_TAXAMT$k"])) unset($optionsLI["L_TAXAMT$k"]);
  1242. // }
  1243. // $this->zcLog('getLineItemDetails 4', 'Coupons/Discounts have affected tax calculations, so tax details are being removed from line-item submission data.' . "\n" . $sumOfLineTax . ' ' . $optionsST['TAXAMT'] . "\n" . print_r(array_merge($optionsST, $pre, $optionsNB), true) . "\nAFTER:" . print_r(array_merge($optionsST, $optionsLI, $optionsNB), TRUE));
  1244. // $subTotalTax = 0;
  1245. // $sumOfLineTax = 0;
  1246. // }
  1247. // disable line-item tax details, leaving only TAXAMT subtotal as tax indicator
  1248. for ($k=0, $n=$numberOfLineItemsProcessed+1; $k<$n; $k++) {
  1249. if (isset($optionsLI["L_TAXAMT$k"])) unset($optionsLI["L_TAXAMT$k"]);
  1250. }
  1251. // if ITEMAMT >0 and subTotalLI > 0 and they're not equal ... OR subTotalLI minus sumOfLineItems isn't 0
  1252. // check subtotals
  1253. if ((strval($optionsST['ITEMAMT']) > 0 && strval($subTotalLI) > 0 && strval($subTotalLI) != strval($optionsST['ITEMAMT'])) || strval($subTotalLI) - strval($sumOfLineItems) != 0) {
  1254. $this->zcLog('getLineItemDetails 5', 'Line-item subtotals do not add up properly. Line-item-details skipped.' . "\n" . strval($sumOfLineItems) . ' ' . strval($subTotalLI) . ' ' . print_r(array_merge($optionsST, $optionsLI), true));
  1255. $optionsLI = array();
  1256. $optionsLI["L_NAME0"] = MODULES_PAYMENT_PAYPALWPP_AGGREGATE_CART_CONTENTS;
  1257. $optionsLI["L_AMT0"] = $sumOfLineItems = $subTotalLI = $optionsST['ITEMAMT'];
  1258. }
  1259. // check whether discounts are causing a problem
  1260. if (strval($optionsST['ITEMAMT']) <= 0) {
  1261. $pre = (array_merge($optionsST, $optionsLI));
  1262. $optionsST['ITEMAMT'] = $optionsST['AMT'];
  1263. $optionsLI = array();
  1264. $optionsLI["L_NAME0"] = MODULES_PAYMENT_PAYPALWPP_AGGREGATE_CART_CONTENTS;
  1265. $optionsLI["L_AMT0"] = $sumOfLineItems = $subTotalLI = $optionsST['ITEMAMT'];
  1266. /*if ($optionsST['AMT'] < $optionsST['TAXAMT']) */ $optionsST['TAXAMT'] = 0;
  1267. /*if ($optionsST['AMT'] < $optionsST['SHIPPINGAMT']) */ $optionsST['SHIPPINGAMT'] = 0;
  1268. $discountProblemsFlag = TRUE;
  1269. $this->zcLog('getLineItemDetails 6', 'Discounts have caused the subtotal to calculate incorrectly. Line-item-details cannot be submitted.' . "\nBefore:" . print_r($pre, TRUE) . "\nAfter:" . print_r(array_merge($optionsST, $optionsLI), true));
  1270. }
  1271. // if AMT or ITEMAMT values are 0 (ie: certain OT modules disabled) or we've started express checkout without going through normal checkout flow, we have to get subtotals manually
  1272. if ((!isset($optionsST['AMT']) || $optionsST['AMT'] == 0 || $flagSubtotalsUnknownYet == TRUE || $optionsST['ITEMAMT'] == 0) && $discountProblemsFlag != TRUE) {
  1273. $optionsST['ITEMAMT'] = $sumOfLineItems;
  1274. $optionsST['TAXAMT'] = $sumOfLineTax;
  1275. if ($subTotalShipping > 0) $optionsST['SHIPPINGAMT'] = $subTotalShipping;
  1276. $optionsST['AMT'] = $sumOfLineItems + $optionsST['TAXAMT'] + $optionsST['SHIPPINGAMT'];
  1277. }
  1278. $this->zcLog('getLineItemDetails 7 - subtotal comparisons', 'BEFORE line-item calcs: ' . print_r($subtotalPRE, true) . ($flagSubtotalsUnknownYet == TRUE ? 'Subtotals Unknown Yet - ' : '') . 'AFTER doing line-item calcs: ' . print_r(array_merge($optionsST, $optionsLI, $optionsNB), true));
  1279. // if subtotals are not adding up correctly, then skip sending any line-item or subtotal details to PayPal
  1280. $stAll = round(strval($optionsST['ITEMAMT'] + $optionsST['TAXAMT'] + $optionsST['SHIPPINGAMT'] + $optionsST['SHIPDISCAMT'] + $optionsST['HANDLINGAMT'] + $optionsST['INSURANCEAMT']), 2);
  1281. $stDiff = strval($optionsST['AMT'] - $stAll);
  1282. $stDiffRounded = (strval($stAll - round($optionsST['AMT'], 2)) + 0);
  1283. // unset any subtotal values that are zero
  1284. if (isset($optionsST['ITEMAMT']) && $optionsST['ITEMAMT'] == 0) unset($optionsST['ITEMAMT']);
  1285. if (isset($optionsST['TAXAMT']) && $optionsST['TAXAMT'] == 0) unset($optionsST['TAXAMT']);
  1286. if (isset($optionsST['SHIPPINGAMT']) && $optionsST['SHIPPINGAMT'] == 0) unset($optionsST['SHIPPINGAMT']);
  1287. if (isset($optionsST['SHIPDISCAMT']) && $optionsST['SHIPDISCAMT'] == 0) unset($optionsST['SHIPDISCAMT']);
  1288. if (isset($optionsST['HANDLINGAMT']) && $optionsST['HANDLINGAMT'] == 0) unset($optionsST['HANDLINGAMT']);
  1289. if (isset($optionsST['INSURANCEAMT']) && $optionsST['INSURANCEAMT'] == 0) unset($optionsST['INSURANCEAMT']);
  1290. // tidy up all values so that they comply with proper format (number_format(xxxx,2) for PayPal US use )
  1291. if (!defined('PAYPALWPP_SKIP_LINE_ITEM_DETAIL_FORMATTING') || PAYPALWPP_SKIP_LINE_ITEM_DETAIL_FORMATTING != 'true' || in_array($order->info['currency'], array('JPY', 'NOK', 'HUF'))) {
  1292. if (is_array($optionsST)) foreach ($optionsST as $key=>$value) {
  1293. $optionsST[$key] = number_format($value, ((int)$currencies->get_decimal_places($restrictedCurrency) == 0 ? 0 : 2));
  1294. }
  1295. if (is_array($optionsLI)) foreach ($optionsLI as $key=>$value) {
  1296. if (substr($key, 0, 8) == 'L_TAXAMT' && ($optionsLI[$key] == '' || $optionsLI[$key] == 0)) {
  1297. unset($optionsLI[$key]);
  1298. } else {
  1299. if (strstr($key, 'AMT')) $optionsLI[$key] = number_format($value, ((int)$currencies->get_decimal_places($restrictedCurrency) == 0 ? 0 : 2));
  1300. }
  1301. }
  1302. }
  1303. $this->zcLog('getLineItemDetails 8', 'checking subtotals... ' . "\n" . print_r(array_merge(array('calculated total'=>number_format($stAll, ((int)$currencies->get_decimal_places($restrictedCurrency) == 0 ? 0 : 2))), $optionsST), true) . "\n-------------------\ndifference: " . ($stDiff + 0) . ' (abs+rounded: ' . ($stDiffRounded + 0) . ')');
  1304. if ( $stDiffRounded != 0) {
  1305. $this->zcLog('getLineItemDetails 9', 'Subtotals Bad. Skipping line-item/subtotal details');
  1306. return array();
  1307. }
  1308. $this->zcLog('getLineItemDetails 10', 'subtotals balance - okay');
  1309. // Send Subtotal and LineItem results back to be submitted to PayPal
  1310. return array_merge($optionsST, $optionsLI, $optionsNB);
  1311. }
  1312. /**
  1313. * This method sends the customer to PayPal's site
  1314. * There, they will log in to their PayPal account, choose a funding source and shipping method
  1315. * and then return to our store site with an EC token
  1316. */
  1317. function ec_step1() {
  1318. global $order, $order_totals, $db, $doPayPal;
  1319. // if cart is empty due to timeout on login or shopping cart page, go to timeout screen
  1320. if ($_SESSION['cart']->count_contents() == 0) {
  1321. $message = 'Logging out due to empty shopping cart. Is session started properly? ... ' . "\nSESSION Details:\n" . print_r($_SESSION, TRUE) . 'GET:' . "\n" . print_r($_GET, TRUE);
  1322. include_once(DIR_WS_MODULES . 'payment/paypal/paypal_functions.php');
  1323. ipn_debug_email($message);
  1324. zen_redirect(zen_href_link(FILENAME_TIME_OUT, '', 'SSL'));
  1325. }
  1326. // init new order object
  1327. require(DIR_WS_CLASSES . 'order.php');
  1328. $order = new order;
  1329. // load the selected shipping module so that shipping taxes can be assessed
  1330. require(DIR_WS_CLASSES . 'shipping.php');
  1331. $shipping_modules = new shipping($_SESSION['shipping']);
  1332. // load OT modules so that discounts and taxes can be assessed
  1333. require(DIR_WS_CLASSES . 'order_total.php');
  1334. $order_total_modules = new order_total;
  1335. $order_totals = $order_total_modules->pre_confirmation_check();
  1336. $order_totals = $order_total_modules->process();
  1337. $doPayPal = $this->paypal_init();
  1338. $options = array();
  1339. // build line item details
  1340. $options = $this->getLineItemDetails($this->selectCurrency());
  1341. // Set currency and amount
  1342. $options['CURRENCYCODE'] = $this->selectCurrency();
  1343. $order_amount = $this->calc_order_amount($order->info['total'], $options['CURRENCYCODE']);
  1344. // Determine the language to use when visiting the PP site
  1345. $lc_code = $this->getLanguageCode();
  1346. if ($lc_code != '') $options['LOCALECODE'] = $lc_code;
  1347. // Allow delayed payments such as eCheck?
  1348. if (defined('MODULE_PAYMENT_PAYPALEC_ALLOWEDPAYMENT') && MODULE_PAYMENT_PAYPALEC_ALLOWEDPAYMENT == 'Instant Only') $options['ALLOWEDPAYMENTMETHOD'] = 'InstantPaymentOnly';
  1349. //Gift Options
  1350. $options['GIFTMESSAGEENABLE'] = 0;
  1351. $options['GIFTRECEIPTEENABLE'] = 0;
  1352. $options['GIFTWRAPENABLE'] = 0;
  1353. $options['GIFTWRAPNAME'] = '';
  1354. $options['GIFTWRAPAMOUNT'] = 0;
  1355. $options['BUYEREMAILOPTINENABLE'] = 0;
  1356. $options['CUSTOMERSERVICENUMBER'] = (defined('STORE_TELEPHONE_CUSTSERVICE') && STORE_TELEPHONE_CUSTSERVICE != '') ? substr(STORE_TELEPHONE_CUSTSERVICE, 0, 16) : '';
  1357. // Store Name to appear on PayPal page
  1358. $options['BRANDNAME'] = (defined('MODULE_PAYMENT_PAYPALWPP_BRANDNAME') && strlen(MODULE_PAYMENT_PAYPALWPP_BRANDNAME) > 3) ? substr(MODULE_PAYMENT_PAYPALWPP_BRANDNAME, 0, 127) : substr(STORE_NAME, 0, 127);
  1359. // Payment Transaction/Authorization Mode
  1360. $options['PAYMENTACTION'] = (MODULE_PAYMENT_PAYPALWPP_TRANSACTION_MODE == 'Auth Only') ? 'Authorization' : 'Sale';
  1361. // for future:
  1362. if (MODULE_PAYMENT_PAYPALWPP_TRANSACTION_MODE == 'Order') $options['PAYMENTACTION'] = 'Order';
  1363. $options['ALLOWNOTE'] = 1; // allow customer to enter a note on the PayPal site, which will be copied to order comments upon return to store.
  1364. $options['SOLUTIONTYPE'] = 'Sole'; // Use 'Mark' for normal Express Checkout, 'Sole' for auctions or alternate flow
  1365. $options['LANDINGPAGE'] = 'Billing'; // "Billing" or "Login" selects the style of landing page on PayPal site during checkout
  1366. // Set the return URL if they click "Submit" on PayPal site
  1367. $return_url = str_replace('&amp;', '&', zen_href_link('ipn_main_handler.php', 'type=ec', 'SSL', true, true, true));
  1368. // Select the return URL if they click "cancel" on PayPal site or click to return without making payment or login
  1369. $cancel_url = str_replace('&amp;', '&', zen_href_link(($_SESSION['customer_first_name'] != '' && $_SESSION['customer_id'] != '' ? FILENAME_CHECKOUT_SHIPPING : FILENAME_SHOPPING_CART), 'ec_cancel=1', 'SSL'));
  1370. // debug
  1371. $val = $_SESSION; unset($val['navigation']);
  1372. $this->zcLog('ec_step1 - 1', 'Checking to see if we are in markflow' . "\n" . 'cart contents: ' . $_SESSION['cart']->get_content_type() . "\n\nNOTE: " . '$this->showPaymentPage = ' . (int)$this->showPaymentPage . "\nCustomer ID: " . (int)$_SESSION['customer_id'] . "\nSession Data: " . print_r($val, true));
  1373. /**
  1374. * Check whether shipping is required on this order or not.
  1375. * If not, tell PayPal to skip all shipping options
  1376. * ie: don't ask for any shipping info if cart content is strictly virtual and customer is already logged-in
  1377. * (if not logged in, we need address information only to build the customer record)
  1378. */
  1379. if ($_SESSION['cart']->get_content_type() == 'virtual' && isset($_SESSION['customer_id']) && $_SESSION['customer_id'] > 0) {
  1380. $this->zcLog('ec-step1-addr_check', "cart contents is virtual and customer is logged in ... therefore options['NOSHIPPING']=1");
  1381. $options['NOSHIPPING'] = 1;
  1382. } else {
  1383. $this->zcLog('ec-step1-addr_check', "cart content is not all virtual (or customer is not logged in) ... therefore will be submitting address details");
  1384. $options['NOSHIPPING'] = 0;
  1385. // If we are in a "mark" flow and the customer has a usable address, set the addressoverride variable to 1. This will
  1386. // override the shipping address in PayPal with the shipping address that is selected in Zen Cart.
  1387. // @TODO: consider using address-validation against Paypal's addresses via API
  1388. if (MODULE_PAYMENT_PAYPALWPP_CONFIRMED_ADDRESS != 'Yes' && ($address_arr = $this->getOverrideAddress()) !== false) {
  1389. $address_error = false;
  1390. foreach(array('entry_firstname','entry_lastname','entry_street_address','entry_city','entry_postcode','zone_code','countries_iso_code_2') as $val) {
  1391. if ($address_arr[$val] == '') $address_error = true;
  1392. if ($address_error == true) $this->zcLog('ec-step1-addr_check2', '$address_error = true because ' .$val . ' is blank.');
  1393. }
  1394. if ($address_error == false) {
  1395. // set the override var
  1396. $options['ADDROVERRIDE'] = 1;
  1397. // set the address info
  1398. $options['SHIPTONAME'] = substr($address_arr['entry_firstname'] . ' ' . $address_arr['entry_lastname'], 0, 32);
  1399. $options['SHIPTOSTREET'] = substr($address_arr['entry_street_address'], 0, 100);
  1400. if ($address_arr['entry_suburb'] != '') $options['SHIPTOSTREET2'] = substr($address_arr['entry_suburb'], 0, 100);
  1401. $options['SHIPTOCITY'] = substr($address_arr['entry_city'], 0, 40);
  1402. $options['SHIPTOZIP'] = substr($address_arr['entry_postcode'], 0, 20);
  1403. $options['SHIPTOSTATE'] = substr($address_arr['zone_code'], 0, 40);
  1404. $options['SHIPTOCOUNTRYCODE'] = substr($address_arr['countries_iso_code_2'], 0, 2);
  1405. $options['SHIPTOPHONENUM'] = substr($address_arr['entry_telephone'], 0, 20);
  1406. }
  1407. }
  1408. $this->zcLog('ec-step1-addr_check3', 'address details from override check:'.($address_arr == FALSE ? ' <NONE FOUND>' : print_r($address_arr, true)));
  1409. // Do we require a "confirmed" shipping address ?
  1410. if (MODULE_PAYMENT_PAYPALWPP_CONFIRMED_ADDRESS == 'Yes') {
  1411. $options['REQCONFIRMSHIPPING'] = 1;
  1412. }
  1413. }
  1414. // 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)
  1415. if (!empty($_SESSION['customer_first_name']) && !empty($_SESSION['customer_id'])) {
  1416. $sql = "select * from " . TABLE_CUSTOMERS . " where customers_id = :custID ";
  1417. $sql = $db->bindVars($sql, ':custID', $_SESSION['customer_id'], 'integer');
  1418. $zc_getemail = $db->Execute($sql);
  1419. if ($zc_getemail->RecordCount() > 0 && $zc_getemail->fields['customers_email_address'] != '') {
  1420. $options['EMAIL'] = $zc_getemail->fields['customers_email_address'];
  1421. }
  1422. if ($zc_getemail->RecordCount() > 0 && $zc_getemail->fields['customers_telephone'] != '' && (!isset($options['ADDROVERRIDE']) || isset($options['ADDROVERRIDE']) && $options['ADDROVERRIDE'] != 1)) {
  1423. $options['SHIPTOPHONENUM'] = $zc_getemail->fields['customers_telephone'];
  1424. }
  1425. }
  1426. if (!isset($options['AMT'])) $options['AMT'] = number_format($order_amount, 2);
  1427. $this->zcLog('ec_step1 - 2 -submit', print_r(array_merge($options, array('RETURNURL' => $return_url, 'CANCELURL' => $cancel_url)), true));
  1428. /**
  1429. * Ask PayPal for the token with which to initiate communications
  1430. */
  1431. $response = $doPayPal->SetExpressCheckout($return_url, $cancel_url, $options);
  1432. $submissionCheckOne = TRUE;
  1433. $submissionCheckTwo = TRUE;
  1434. if ($submissionCheckOne) {
  1435. // If there's an error on line-item details, remove tax values and resubmit, since the most common cause of 10413 is tax mismatches
  1436. if ($response['L_ERRORCODE0'] == '10413') {
  1437. $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));
  1438. //echo '1st submission REJECTED. {'.$response['L_ERRORCODE0'].'}<pre>'.print_r($options, true) . urldecode(print_r($response, true));
  1439. $tsubtotal = 0;
  1440. foreach ($options as $key=>$value) {
  1441. if (substr($key, 0, 8) == 'L_TAXAMT') {
  1442. $tsubtotal += preg_replace('/[^0-9.\-]/', '', $value);
  1443. unset($options[$key]);
  1444. }
  1445. }
  1446. $options['TAXAMT'] = $tsubtotal;
  1447. $amt = preg_replace('/[^0-9.%]/', '', $options['AMT']);
  1448. // echo 'oldAMT:'.$amt;
  1449. // echo ' newTAXAMT:'.$tsubtotal;
  1450. $taxamt = preg_replace('/[^0-9.%]/', '', $options['TAXAMT']);
  1451. $shipamt = preg_replace('/[^0-9.%]/', '', $options['SHIPPINGAMT']);
  1452. $itemamt = preg_replace('/[^0-9.%]/', '', $options['ITEMAMT']);
  1453. $calculatedAmount = $itemamt + $taxamt + $shipamt;
  1454. if ($amt != $calculatedAmount) $amt = $calculatedAmount;
  1455. // echo ' newAMT:'.$amt;
  1456. $options['AMT'] = $amt;
  1457. $response = $doPayPal->SetExpressCheckout($return_url, $cancel_url, $options);
  1458. //echo '<br>2nd submission. {'.$response['L_ERRORCODE0'].'}<pre>'.print_r($options, true);
  1459. }
  1460. if ($submissionCheckTwo) {
  1461. if ($response['L_ERRORCODE0'] == '10413') {
  1462. $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));
  1463. //echo '2nd submission REJECTED. {'.$response['L_ERRORCODE0'].'}<pre>'.print_r($options, true) . urldecode(print_r($response, true));
  1464. foreach ($options as $key=>$value) {
  1465. if (substr($key, 0, 2) == 'L_') {
  1466. unset($options[$key]);
  1467. }
  1468. }
  1469. $amt = preg_replace('/[^0-9.%]/', '', $options['AMT']);
  1470. $taxamt = preg_replace('/[^0-9.%]/', '', $options['TAXAMT']);
  1471. $shipamt = preg_replace('/[^0-9.%]/', '', $options['SHIPPINGAMT']);
  1472. $itemamt = preg_replace('/[^0-9.%]/', '', $options['ITEMAMT']);
  1473. $calculatedAmount = $itemamt + $taxamt + $shipamt;
  1474. if ($amt != $calculatedAmount) $amt = $calculatedAmount;
  1475. $options['AMT'] = $amt;
  1476. $response = $doPayPal->SetExpressCheckout($return_url, $cancel_url, $options);
  1477. //echo '<br>3rd submission. {'.$response['L_ERRORCODE0'].'}<pre>'.print_r($options, true);
  1478. }
  1479. }
  1480. }
  1481. /**
  1482. * Determine result of request for token -- if error occurred, the errorHandler will redirect accordingly
  1483. */
  1484. $error = $this->_errorHandler($response, 'SetExpressCheckout');
  1485. // Success, so read the EC token
  1486. $_SESSION['paypal_ec_token'] = preg_replace('/[^0-9.A-Z\-]/', '', urldecode($response['TOKEN']));
  1487. // prepare to redirect to PayPal so the customer can log in and make their selections
  1488. $paypal_url = $this->getPayPalLoginServer();
  1489. // Set the name of the displayed "continue" button on the PayPal site.
  1490. // 'commit' = "Pay Now" || 'continue' = "Review Payment"
  1491. $orderReview = true;
  1492. if ($_SESSION['paypal_ec_markflow'] == 1) $orderReview = false;
  1493. $userActionKey = "&useraction=" . ((int)$orderReview == false ? 'commit' : 'continue');
  1494. // This is where we actually redirect the customer's browser to PayPal. Upon return from PayPal, they go to ec_step2
  1495. header("HTTP/1.1 302 Object Moved");
  1496. zen_redirect($paypal_url . "?cmd=_express-checkout&token=" . $_SESSION['paypal_ec_token'] . $userActionKey);
  1497. // this should never be reached:
  1498. return $error;
  1499. }
  1500. /**
  1501. * This method is for step 2 of the express checkout option. This
  1502. * retrieves from PayPal the data set by step one and sets the Zen Cart
  1503. * data accordingly depending on admin settings.
  1504. */
  1505. function ec_step2() {
  1506. // Visitor just came back from PayPal and so we collect all
  1507. // the info returned, create an account if necessary, then log
  1508. // them in, and then send them to the appropriate page.
  1509. if (empty($_SESSION['paypal_ec_token'])) {
  1510. // see if the token is set -- if not, we cannot continue -- ideally the token should match the session token
  1511. if (isset($_GET['token'])) {
  1512. // we have a token, so we will proceed
  1513. $_SESSION['paypal_ec_token'] = $_GET['token'];
  1514. // sanitize this
  1515. $_SESSION['paypal_ec_token'] = preg_replace('/[^0-9.A-Z\-]/', '', $_GET['token']);
  1516. } else {
  1517. // no token -- not ready for this step -- send them back to checkout page with error
  1518. $this->terminateEC(MODULE_PAYMENT_PAYPALWPP_INVALID_RESPONSE, true);
  1519. }
  1520. }
  1521. // debug
  1522. //$this->zcLog('PayPal test Log - ec_step2 $_REQUEST data', "In function: ec_step2()\r\nData in \$_REQUEST = \r\n" . print_r($_REQUEST, true));
  1523. // Initialize the paypal caller object.
  1524. global $doPayPal;
  1525. $doPayPal = $this->paypal_init();
  1526. // with the token we retrieve the data about this user
  1527. $response = $doPayPal->GetExpressCheckoutDetails($_SESSION['paypal_ec_token']);
  1528. //$this->zcLog('ec_step2 - GetExpressCheckout response', print_r($response, true));
  1529. /**
  1530. * Determine result of request for data -- if error occurred, the errorHandler will redirect accordingly
  1531. */
  1532. $error = $this->_errorHandler($response, 'GetExpressCheckoutDetails');
  1533. // Check for blank address -- if address received from PayPal is blank, ask the customer to register in the store first and then resume checkout
  1534. if ($_SESSION['cart']->get_content_type() != 'virtual')
  1535. if ($response['SHIPTONAME'] . $response['SHIPTOSTREET'] . $response['SHIPTOSTREET2'] . $response['SHIPTOCITY'] . $response['SHIPTOSTATE'] . $response['SHIPTOZIP'] . $response['SHIPTOCOUNTRYCODE'] == '') {
  1536. $this->terminateEC(MODULES_PAYMENT_PAYPALWPP_TEXT_BLANK_ADDRESS, true, FILENAME_CREATE_ACCOUNT);
  1537. }
  1538. // will we be creating an account for this customer? We must if the cart contents are virtual, so can login to download etc.
  1539. if ($_SESSION['cart']->get_content_type('true') > 0 || in_array($_SESSION['cart']->content_type, array('mixed', 'virtual'))) $this->new_acct_notify = 'Yes';
  1540. // get the payer_id from the customer's info as returned from PayPal
  1541. $_SESSION['paypal_ec_payer_id'] = $response['PAYERID'];
  1542. $this->notify('NOTIFY_PAYPAL_EXPRESS_CHECKOUT_PAYERID_DETERMINED', $response['PAYERID']);
  1543. $gender = '';
  1544. if (urldecode($response['SALUTATION'] == 'Mr')) $gender = 'm';
  1545. if (in_array(urldecode($response['SALUTATION']), array('Ms', 'Mrs'))) $gender = 'f';
  1546. // prepare the information to pass to the ec_step2_finish() function, which does the account creation, address build, etc
  1547. $step2_payerinfo = array('payer_id' => $response['PAYERID'],
  1548. 'payer_email' => urldecode($response['EMAIL']),
  1549. 'payer_salutation'=> urldecode($response['SALUTATION']),
  1550. 'payer_gender' => $gender,
  1551. 'payer_firstname' => urldecode($response['FIRSTNAME']),
  1552. 'payer_lastname' => urldecode($response['LASTNAME']),
  1553. 'payer_business' => urldecode($response['BUSINESS']),
  1554. 'payer_status' => $response['PAYERSTATUS'],
  1555. 'ship_country_code' => urldecode($response['COUNTRYCODE']),
  1556. 'ship_address_status' => urldecode($response['ADDRESSSTATUS']),
  1557. 'ship_phone' => urldecode($response['SHIPTOPHONENUM'] != '' ? $response['SHIPTOPHONENUM'] : $response['PHONENUM']),
  1558. 'order_comment' => urldecode($response['NOTETEXT']),
  1559. );
  1560. // if (strtoupper($response['ADDRESSSTATUS']) == 'NONE' || !isset($response['SHIPTOSTREET']) || $response['SHIPTOSTREET'] == '') {
  1561. // $step2_shipto = array();
  1562. // } else {
  1563. // accomodate PayPal bug which repeats 1st line of address for 2nd line if 2nd line is empty.
  1564. if ($response['SHIPTOSTREET2'] == $response['SHIPTOSTREET1']) $response['SHIPTOSTREET2'] = '';
  1565. // accomodate PayPal bug which incorrectly treats 'Yukon Territory' as YK instead of ISO standard of YT.
  1566. if ($response['SHIPTOSTATE'] == 'YK') $response['SHIPTOSTATE'] = 'YT';
  1567. // same with Newfoundland
  1568. if ($response['SHIPTOSTATE'] == 'NF') $response['SHIPTOSTATE'] = 'NL';
  1569. // process address details supplied
  1570. $step2_shipto = array('ship_name' => urldecode($response['SHIPTONAME']),
  1571. 'ship_street_1' => urldecode($response['SHIPTOSTREET']),
  1572. 'ship_street_2' => urldecode($response['SHIPTOSTREET2']),
  1573. 'ship_city' => urldecode($response['SHIPTOCITY']),
  1574. 'ship_state' => (isset($response['SHIPTOSTATE']) && $response['SHIPTOSTATE'] !='' ? urldecode($response['SHIPTOSTATE']) : urldecode($response['SHIPTOCITY'])),
  1575. 'ship_postal_code' => urldecode($response['SHIPTOZIP']),
  1576. 'ship_country_code' => urldecode($response['SHIPTOCOUNTRYCODE']),
  1577. 'ship_country_name' => (isset($response['SHIPTOCOUNTRY']) ? urldecode($response['SHIPTOCOUNTRY']) : urldecode($response['SHIPTOCOUNTRYNAME'])));
  1578. // }
  1579. // reset all previously-selected shipping choices, because cart contents may have been changed
  1580. if (!(isset($_SESSION['paypal_ec_markflow']) && $_SESSION['paypal_ec_markflow'] == 1)) unset($_SESSION['shipping']);
  1581. // set total temporarily based on amount returned from PayPal, so validations continue to work properly
  1582. global $order, $order_totals;
  1583. if (!isset($order) || !isset($order->info) || !is_array($order->info) || !zen_not_null($order)) {
  1584. $this->zcLog('ec_step2 ', 'Re-instantiating $order object.');
  1585. // init new order object
  1586. if (!class_exists('order')) require(DIR_WS_CLASSES . 'order.php');
  1587. $order = new order;
  1588. // load the selected shipping module so that shipping taxes can be assessed
  1589. if (!class_exists('shipping')) require(DIR_WS_CLASSES . 'shipping.php');
  1590. $shipping_modules = new shipping($_SESSION['shipping']);
  1591. // load OT modules so that discounts and taxes can be assessed
  1592. if (!class_exists('order_total')) require(DIR_WS_CLASSES . 'order_total.php');
  1593. $order_total_modules = new order_total;
  1594. $order_totals = $order_total_modules->pre_confirmation_check();
  1595. $order_totals = $order_total_modules->process();
  1596. $this->zcLog('ec_step2 ', 'Instantiated $order object contents: ' . print_r($order, true));
  1597. }
  1598. $order->info['total'] = urldecode($response['AMT']);
  1599. //$this->zcLog('ec_step2 - processed info', print_r(array_merge($step2_payerinfo, $step2_shipto), true));
  1600. // send data off to build account, log in, set addresses, place order
  1601. $this->ec_step2_finish(array_merge($step2_payerinfo, $step2_shipto), $this->new_acct_notify);
  1602. }
  1603. /**
  1604. * Complete the step2 phase by creating accounts if needed, linking data, placing order, etc.
  1605. */
  1606. function ec_step2_finish($paypal_ec_payer_info, $new_acct_notify) {
  1607. global $db, $order;
  1608. // register the payer_info in the session
  1609. $_SESSION['paypal_ec_payer_info'] = $paypal_ec_payer_info;
  1610. // debug
  1611. $this->zcLog('ec_step2_finish - 1', 'START: paypal_ec_payer_info= ' . print_r($_SESSION['paypal_ec_payer_info'], true));
  1612. /**
  1613. * Building customer zone/address from returned data
  1614. */
  1615. // set some defaults, which will be updated later:
  1616. $country_id = '223';
  1617. $address_format_id = 2;
  1618. $state_id = 0;
  1619. $acct_exists = false;
  1620. // store default address id for later use/reference
  1621. $original_default_address_id = $_SESSION['customer_default_address_id'];
  1622. // Get the customer's country ID based on name or ISO code
  1623. $sql = "SELECT countries_id, address_format_id, countries_iso_code_2, countries_iso_code_3
  1624. FROM " . TABLE_COUNTRIES . "
  1625. WHERE countries_iso_code_2 = :countryId
  1626. OR countries_name = :countryId
  1627. LIMIT 1";
  1628. $sql1 = $db->bindVars($sql, ':countryId', $paypal_ec_payer_info['ship_country_name'], 'string');
  1629. $country1 = $db->Execute($sql1);
  1630. $sql2 = $db->bindVars($sql, ':countryId', $paypal_ec_payer_info['ship_country_code'], 'string');
  1631. $country2 = $db->Execute($sql2);
  1632. // see if we found a record, if yes, then use it instead of default American format
  1633. if ($country1->RecordCount() > 0) {
  1634. $country_id = $country1->fields['countries_id'];
  1635. 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'];
  1636. $country_code3 = $country1->fields['countries_iso_code_3'];
  1637. $address_format_id = (int)$country1->fields['address_format_id'];
  1638. } elseif ($country2->RecordCount() > 0) {
  1639. // if didn't find it based on name, check using ISO code (ie: in case of no-shipping-address required/supplied)
  1640. $country_id = $country2->fields['countries_id'];
  1641. $country_code3 = $country2->fields['countries_iso_code_3'];
  1642. $address_format_id = (int)$country2->fields['address_format_id'];
  1643. } else {
  1644. // if defaulting to US, make sure US is valid
  1645. $sql = "SELECT countries_id FROM " . TABLE_COUNTRIES . " WHERE countries_id = :countryId: LIMIT 1";
  1646. $sql = $db->bindVars($sql, ':countryId:', $country_id, 'integer');
  1647. $result = $db->Execute($sql);
  1648. if ($result->EOF) {
  1649. $this->notify('NOTIFY_PAYPAL_CUSTOMER_ATTEMPT_TO_USE_INVALID_COUNTRY_CODE');
  1650. $this->zcLog('ec-step2-finish - 1b', 'Cannot use address due to country lookup/match failure.');
  1651. $this->terminateEC(MODULE_PAYMENT_PAYPALWPP_TEXT_INVALID_ZONE_ERROR, true, FILENAME_SHOPPING_CART);
  1652. }
  1653. }
  1654. // Need to determine zone, based on zone name first, and then zone code if name fails check. Otherwise uses 0.
  1655. $sql = "SELECT zone_id
  1656. FROM " . TABLE_ZONES . "
  1657. WHERE zone_country_id = :zCountry
  1658. AND zone_code = :zoneCode
  1659. OR zone_name = :zoneCode
  1660. LIMIT 1";
  1661. $sql = $db->bindVars($sql, ':zCountry', $country_id, 'integer');
  1662. $sql = $db->bindVars($sql, ':zoneCode', $paypal_ec_payer_info['ship_state'], 'string');
  1663. $states = $db->Execute($sql);
  1664. if ($states->RecordCount() > 0) {
  1665. $state_id = $states->fields['zone_id'];
  1666. }
  1667. /**
  1668. * Using the supplied data from PayPal, set the data into the order record
  1669. */
  1670. // customer
  1671. $order->customer['name'] = $paypal_ec_payer_info['payer_firstname'] . ' ' . $paypal_ec_payer_info['payer_lastname'];
  1672. $order->customer['company'] = $paypal_ec_payer_info['payer_business'];
  1673. $order->customer['street_address'] = $paypal_ec_payer_info['ship_street_1'];
  1674. $order->customer['suburb'] = $paypal_ec_payer_info['ship_street_2'];
  1675. $order->customer['city'] = $paypal_ec_payer_info['ship_city'];
  1676. $order->customer['postcode'] = $paypal_ec_payer_info['ship_postal_code'];
  1677. $order->customer['state'] = $paypal_ec_payer_info['ship_state'];
  1678. $order->customer['country'] = array('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);
  1679. $order->customer['format_id'] = $address_format_id;
  1680. $order->customer['email_address'] = $paypal_ec_payer_info['payer_email'];
  1681. $order->customer['telephone'] = $paypal_ec_payer_info['ship_phone'];
  1682. $order->customer['zone_id'] = $state_id;
  1683. // billing
  1684. $order->billing['name'] = $paypal_ec_payer_info['payer_firstname'] . ' ' . $paypal_ec_payer_info['payer_lastname'];
  1685. $order->billing['company'] = $paypal_ec_payer_info['payer_business'];
  1686. $order->billing['street_address'] = $paypal_ec_payer_info['ship_street_1'];
  1687. $order->billing['suburb'] = $paypal_ec_payer_info['ship_street_2'];
  1688. $order->billing['city'] = $paypal_ec_payer_info['ship_city'];
  1689. $order->billing['postcode'] = $paypal_ec_payer_info['ship_postal_code'];
  1690. $order->billing['state'] = $paypal_ec_payer_info['ship_state'];
  1691. $order->billing['country'] = array('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);
  1692. $order->billing['format_id'] = $address_format_id;
  1693. $order->billing['zone_id'] = $state_id;
  1694. // delivery
  1695. if (strtoupper($_SESSION['paypal_ec_payer_info']['ship_address_status']) != 'NONE') {
  1696. $order->delivery['name'] = $paypal_ec_payer_info['ship_name'];
  1697. $order->delivery['company'] = trim($paypal_ec_payer_info['ship_name'] . ' ' . $paypal_ec_payer_info['payer_business']);
  1698. $order->delivery['street_address']= $paypal_ec_payer_info['ship_street_1'];
  1699. $order->delivery['suburb'] = $paypal_ec_payer_info['ship_street_2'];
  1700. $order->delivery['city'] = $paypal_ec_payer_info['ship_city'];
  1701. $order->delivery['postcode'] = $paypal_ec_payer_info['ship_postal_code'];
  1702. $order->delivery['state'] = $paypal_ec_payer_info['ship_state'];
  1703. $order->delivery['country'] = array('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);
  1704. $order->delivery['country_id'] = $country_id;
  1705. $order->delivery['format_id'] = $address_format_id;
  1706. $order->delivery['zone_id'] = $state_id;
  1707. }
  1708. // process submitted customer notes
  1709. if (isset($paypal_ec_payer_info['order_comment']) && $paypal_ec_payer_info['order_comment'] != '') {
  1710. $_SESSION['comments'] = (isset($_SESSION['comments']) && $_SESSION['comments'] != '' ? $_SESSION['comments'] . "\n" : '') . $paypal_ec_payer_info['order_comment'];
  1711. $order->info['comments'] = $_SESSION['comments'];
  1712. }
  1713. // debug
  1714. $this->zcLog('ec_step2_finish - 2', 'country_id = ' . $country_id . ' ' . $paypal_ec_payer_info['ship_country_name'] . ' ' . $paypal_ec_payer_info['ship_country_code'] ."\naddress_format_id = " . $address_format_id . "\nstate_id = " . $state_id . ' (original state tested: ' . $paypal_ec_payer_info['ship_state'] . ')' . "\ncountry1->fields['countries_id'] = " . $country1->fields['countries_id'] . "\ncountry2->fields['countries_id'] = " . $country2->fields['countries_id'] . "\n" . '$order->customer = ' . print_r($order->customer, true));
  1715. // check to see whether PayPal should still be offered to this customer, based on the zone of their address:
  1716. $this->update_status();
  1717. if (!$this->enabled) {
  1718. $this->terminateEC(MODULE_PAYMENT_PAYPALWPP_TEXT_INVALID_ZONE_ERROR, true, FILENAME_SHOPPING_CART);
  1719. }
  1720. // see if the user is logged in
  1721. if (!empty($_SESSION['customer_first_name']) && !empty($_SESSION['customer_id']) && $_SESSION['customer_id'] > 0) {
  1722. // They're logged in, so forward them straight to checkout stages, depending on address needs etc
  1723. $order->customer['id'] = $_SESSION['customer_id'];
  1724. // set the session value for express checkout temp
  1725. $_SESSION['paypal_ec_temp'] = false;
  1726. // if no address required for shipping, leave shipping portion alone
  1727. if (strtoupper($_SESSION['paypal_ec_payer_info']['ship_address_status']) != 'NONE' && $_SESSION['paypal_ec_payer_info']['ship_street_1'] != '') {
  1728. // set the session info for the sendto
  1729. $_SESSION['sendto'] = $_SESSION['customer_default_address_id'];
  1730. // This is the address matching section
  1731. // try to match it first
  1732. // note: this is by no means 100%
  1733. $address_book_id = $this->findMatchingAddressBookEntry($_SESSION['customer_id'], (isset($order->delivery) ? $order->delivery : $order->billing));
  1734. // no match, so add the record
  1735. if (!$address_book_id) {
  1736. $address_book_id = $this->addAddressBookEntry($_SESSION['customer_id'], (isset($order->delivery) ? $order->delivery : $order->billing), false);
  1737. }
  1738. // if couldn't add the record, perhaps due to removed country, or some other error, abort with message
  1739. if (!$address_book_id) {
  1740. $this->terminateEC(MODULE_PAYMENT_PAYPALWPP_TEXT_INVALID_ZONE_ERROR, true, FILENAME_SHOPPING_CART);
  1741. }
  1742. // set the address for use
  1743. $_SESSION['sendto'] = $address_book_id;
  1744. }
  1745. // set the users billto information (default address)
  1746. if (!isset($_SESSION['billto'])) {
  1747. $_SESSION['billto'] = $_SESSION['customer_default_address_id'];
  1748. }
  1749. // debug
  1750. $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);
  1751. // select a shipping method, based on cheapest available option
  1752. if (MODULE_PAYMENT_PAYPALWPP_AUTOSELECT_CHEAPEST_SHIPPING == 'Yes') $this->setShippingMethod();
  1753. // send the user on
  1754. if ($_SESSION['paypal_ec_markflow'] == 1) {
  1755. $this->terminateEC('', false, FILENAME_CHECKOUT_PROCESS);
  1756. } else {
  1757. $this->terminateEC('', false, FILENAME_CHECKOUT_CONFIRMATION);
  1758. }
  1759. } else {
  1760. // They're not logged in. Create an account if necessary, and then log them in.
  1761. // First, see if they're an existing customer, and log them in automatically
  1762. // If Paypal didn't supply us an email address, something went wrong
  1763. if (trim($paypal_ec_payer_info['payer_email']) == '') $this->terminateEC(MODULE_PAYMENT_PAYPALWPP_INVALID_RESPONSE, true);
  1764. // attempt to obtain the user information using the payer_email from the info returned from PayPal, via email address
  1765. $sql = "SELECT customers_id, customers_firstname, customers_lastname, customers_paypal_payerid, customers_paypal_ec
  1766. FROM " . TABLE_CUSTOMERS . "
  1767. WHERE customers_email_address = :emailAddress ";
  1768. $sql = $db->bindVars($sql, ':emailAddress', $paypal_ec_payer_info['payer_email'], 'string');
  1769. $check_customer = $db->Execute($sql);
  1770. // debug
  1771. $this->zcLog('ec_step2_finish - 4', 'Not logged in. Looking for account.' . "\n" . (int)$check_customer->RecordCount() . ' matching customer records found.');
  1772. if (!$check_customer->EOF) {
  1773. $acct_exists = true;
  1774. // see if this was only a temp account -- if so, remove it
  1775. if ($check_customer->fields['customers_paypal_ec'] == '1') {
  1776. // Delete the existing temporary account
  1777. $this->ec_delete_user($check_customer->fields['customers_id']);
  1778. $acct_exists = false;
  1779. // debug
  1780. $this->zcLog('ec_step2_finish - 5', 'Found temporary account - deleting it.');
  1781. }
  1782. }
  1783. // Create an account, if the account does not exist
  1784. if (!$acct_exists) {
  1785. // debug
  1786. $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);
  1787. // Generate a random 8-char password
  1788. $password = zen_create_random_value(8);
  1789. $sql_data_array = array();
  1790. // set the customer information in the array for the table insertion
  1791. $sql_data_array = array(
  1792. 'customers_firstname' => $paypal_ec_payer_info['payer_firstname'],
  1793. 'customers_lastname' => $paypal_ec_payer_info['payer_lastname'],
  1794. 'customers_email_address' => $paypal_ec_payer_info['payer_email'],
  1795. 'customers_email_format' => (ACCOUNT_EMAIL_PREFERENCE == '1' ? 'HTML' : 'TEXT'),
  1796. 'customers_telephone' => $paypal_ec_payer_info['ship_phone'],
  1797. 'customers_fax' => '',
  1798. 'customers_gender' => $paypal_ec_payer_info['payer_gender'],
  1799. 'customers_newsletter' => '0',
  1800. 'customers_password' => zen_encrypt_password($password),
  1801. 'customers_paypal_payerid' => $_SESSION['paypal_ec_payer_id']);
  1802. // insert the data
  1803. $result = zen_db_perform(TABLE_CUSTOMERS, $sql_data_array);
  1804. // grab the customer_id (last insert id)
  1805. $customer_id = $db->Insert_ID();
  1806. // set the Guest customer ID -- for PWA purposes
  1807. $_SESSION['customer_guest_id'] = $customer_id;
  1808. // set the customer address information in the array for the table insertion
  1809. $sql_data_array = array(
  1810. 'customers_id' => $customer_id,
  1811. 'entry_gender' => $paypal_ec_payer_info['payer_gender'],
  1812. 'entry_firstname' => $paypal_ec_payer_info['payer_firstname'],
  1813. 'entry_lastname' => $paypal_ec_payer_info['payer_lastname'],
  1814. 'entry_street_address' => $paypal_ec_payer_info['ship_street_1'],
  1815. 'entry_suburb' => $paypal_ec_payer_info['ship_street_2'],
  1816. 'entry_city' => $paypal_ec_payer_info['ship_city'],
  1817. 'entry_zone_id' => $state_id,
  1818. 'entry_postcode' => $paypal_ec_payer_info['ship_postal_code'],
  1819. 'entry_country_id' => $country_id);
  1820. 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']) {
  1821. $sql_data_array['entry_company'] = $paypal_ec_payer_info['ship_name'];
  1822. }
  1823. if ($state_id > 0) {
  1824. $sql_data_array['entry_zone_id'] = $state_id;
  1825. $sql_data_array['entry_state'] = '';
  1826. } else {
  1827. $sql_data_array['entry_zone_id'] = 0;
  1828. $sql_data_array['entry_state'] = $paypal_ec_payer_info['ship_state'];
  1829. }
  1830. // insert the data
  1831. zen_db_perform(TABLE_ADDRESS_BOOK, $sql_data_array);
  1832. // grab the address_id (last insert id)
  1833. $address_id = $db->Insert_ID();
  1834. // set the address id lookup for the customer
  1835. $sql = "UPDATE " . TABLE_CUSTOMERS . "
  1836. SET customers_default_address_id = :addrID
  1837. WHERE customers_id = :custID";
  1838. $sql = $db->bindVars($sql, ':addrID', $address_id, 'integer');
  1839. $sql = $db->bindVars($sql, ':custID', $customer_id, 'integer');
  1840. $db->Execute($sql);
  1841. // insert the new customer_id into the customers info table for consistency
  1842. $sql = "INSERT INTO " . TABLE_CUSTOMERS_INFO . "
  1843. (customers_info_id, customers_info_number_of_logons, customers_info_date_account_created, customers_info_date_of_last_logon)
  1844. VALUES (:custID, 1, now(), now())";
  1845. $sql = $db->bindVars($sql, ':custID', $customer_id, 'integer');
  1846. $db->Execute($sql);
  1847. // send Welcome Email if appropriate
  1848. if ($this->new_acct_notify == 'Yes') {
  1849. // require the language file
  1850. global $language_page_directory, $template_dir;
  1851. if (!isset($language_page_directory)) $language_page_directory = DIR_WS_LANGUAGES . $_SESSION['language'] . '/';
  1852. if (file_exists($language_page_directory . $template_dir . '/create_account.php')) {
  1853. $template_dir_select = $template_dir . '/';
  1854. } else {
  1855. $template_dir_select = '';
  1856. }
  1857. require($language_page_directory . $template_dir_select . '/create_account.php');
  1858. // set the mail text
  1859. $email_text = sprintf(EMAIL_GREET_NONE, $paypal_ec_payer_info['payer_firstname']) . EMAIL_WELCOME . "\n\n" . EMAIL_TEXT;
  1860. $email_text .= "\n\n" . EMAIL_EC_ACCOUNT_INFORMATION . "\nUsername: " . $paypal_ec_payer_info['payer_email'] . "\nPassword: " . $password . "\n\n";
  1861. $email_text .= EMAIL_CONTACT;
  1862. // include create-account-specific disclaimer
  1863. $email_text .= "\n\n" . sprintf(EMAIL_DISCLAIMER_NEW_CUSTOMER, STORE_OWNER_EMAIL_ADDRESS). "\n\n";
  1864. $email_html = array();
  1865. $email_html['EMAIL_GREETING'] = sprintf(EMAIL_GREET_NONE, $paypal_ec_payer_info['payer_firstname']) ;
  1866. $email_html['EMAIL_WELCOME'] = EMAIL_WELCOME;
  1867. $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");
  1868. $email_html['EMAIL_CONTACT_OWNER'] = EMAIL_CONTACT;
  1869. $email_html['EMAIL_CLOSURE'] = nl2br(EMAIL_GV_CLOSURE);
  1870. $email_html['EMAIL_DISCLAIMER'] = sprintf(EMAIL_DISCLAIMER_NEW_CUSTOMER, '<a href="mailto:' . STORE_OWNER_EMAIL_ADDRESS . '">'. STORE_OWNER_EMAIL_ADDRESS .' </a>');
  1871. // send the mail
  1872. 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, STORE_OWNER, STORE_OWNER_EMAIL_ADDRESS, $email_html, 'welcome');
  1873. // set the express checkout temp -- false means the account is no longer "only" for EC ... it'll be permanent
  1874. $_SESSION['paypal_ec_temp'] = false;
  1875. } else {
  1876. // Make it a temporary account that'll be deleted once they've checked out
  1877. $sql = "UPDATE " . TABLE_CUSTOMERS . "
  1878. SET customers_paypal_ec = 1
  1879. WHERE customers_id = :custID ";
  1880. $sql = $db->bindVars($sql, ':custID', $customer_id, 'integer');
  1881. $db->Execute($sql);
  1882. // set the boolean ec temp value since we created account strictly for EC purposes
  1883. $_SESSION['paypal_ec_temp'] = true;
  1884. }
  1885. // hook notifier class vis a vis account-creation
  1886. $this->notify('NOTIFY_LOGIN_SUCCESS_VIA_CREATE_ACCOUNT');
  1887. } else {
  1888. // set the boolean ec temp value for the account to false, since we didn't have to create one
  1889. $_SESSION['paypal_ec_temp'] = false;
  1890. }
  1891. // log the user in with the email sent back from paypal response
  1892. $this->user_login($_SESSION['paypal_ec_payer_info']['payer_email'], false);
  1893. // debug
  1894. $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']);
  1895. // This is the address matching section
  1896. // try to match it first
  1897. // note: this is by no means 100%
  1898. $address_book_id = $this->findMatchingAddressBookEntry($_SESSION['customer_id'], (isset($order->delivery) ? $order->delivery : $order->billing));
  1899. // no match add the record
  1900. if (!$address_book_id) {
  1901. $address_book_id = $this->addAddressBookEntry($_SESSION['customer_id'], (isset($order->delivery) ? $order->delivery : $order->billing), false);
  1902. if (!$address_book_id) {
  1903. $address_book_id = $_SESSION['customer_default_address_id'];
  1904. }
  1905. }
  1906. // if couldn't add the record, perhaps due to removed country, or some other error, abort with message
  1907. if ($address_book_id == FALSE) {
  1908. $this->terminateEC(MODULE_PAYMENT_PAYPALWPP_TEXT_INVALID_ZONE_ERROR, true, FILENAME_SHOPPING_CART);
  1909. }
  1910. // set the sendto to the address
  1911. $_SESSION['sendto'] = $address_book_id;
  1912. // set billto in the session
  1913. $_SESSION['billto'] = $_SESSION['customer_default_address_id'];
  1914. // select a shipping method, based on cheapest available option
  1915. if (MODULE_PAYMENT_PAYPALWPP_AUTOSELECT_CHEAPEST_SHIPPING == 'Yes') $this->setShippingMethod();
  1916. // debug
  1917. $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));
  1918. // send the user on
  1919. if ($_SESSION['paypal_ec_markflow'] == 1) {
  1920. $this->terminateEC('', false, FILENAME_CHECKOUT_PROCESS);
  1921. } else {
  1922. $this->terminateEC('', false, FILENAME_CHECKOUT_CONFIRMATION);
  1923. }
  1924. }
  1925. }
  1926. /**
  1927. * Determine the appropriate shipping method if applicable
  1928. * By default, selects the lowest-cost quote
  1929. */
  1930. function setShippingMethod() {
  1931. global $total_count, $total_weight;
  1932. // ensure that cart contents is calculated properly for weight and value
  1933. if (!isset($total_weight)) $total_weight = $_SESSION['cart']->show_weight();
  1934. if (!isset($total_count)) $total_count = $_SESSION['cart']->count_contents();
  1935. // set the shipping method if one is not already set
  1936. // defaults to the cheapest shipping method
  1937. if ( !$_SESSION['shipping'] || ( $_SESSION['shipping'] && ($_SESSION['shipping'] == false) && (zen_count_shipping_modules() > 1) ) ) {
  1938. require_once(DIR_WS_CLASSES . 'http_client.php');
  1939. require_once(DIR_WS_CLASSES . 'shipping.php');
  1940. $shipping_Obj = new shipping;
  1941. // generate the quotes
  1942. $shipping_Obj->quote();
  1943. // set the cheapest one
  1944. $_SESSION['shipping'] = $shipping_Obj->cheapest();
  1945. }
  1946. }
  1947. /**
  1948. * Get Override Address (uses sendto if set, otherwise uses customer's primary address)
  1949. */
  1950. function getOverrideAddress() {
  1951. global $db;
  1952. // Only proceed IF *in* markflow mode AND logged-in (have to be logged in to get to markflow mode anyway)
  1953. if (!empty($_GET['markflow']) && isset($_SESSION['customer_id']) && $_SESSION['customer_id']) {
  1954. // From now on for this user we will edit addresses in Zen Cart, not by going to PayPal.
  1955. $_SESSION['paypal_ec_markflow'] = 1;
  1956. // debug
  1957. $this->zcLog('getOverrideAddress - 1', 'Now in markflow mode.' . "\n" . 'SESSION[sendto] = ' . (int)$_SESSION['sendto']);
  1958. // find the users default address id
  1959. if (!empty($_SESSION['sendto'])) {
  1960. $address_id = $_SESSION['sendto'];
  1961. } else {
  1962. $sql = "SELECT customers_default_address_id
  1963. FROM " . TABLE_CUSTOMERS . "
  1964. WHERE customers_id = :customerId";
  1965. $sql = $db->bindVars($sql, ':customerId', $_SESSION['customer_id'], 'integer');
  1966. $default_address_id_arr = $db->Execute($sql);
  1967. if (!$default_address_id_arr->EOF) {
  1968. $address_id = $default_address_id_arr->fields['customers_default_address_id'];
  1969. } else {
  1970. // couldn't find an address.
  1971. return false;
  1972. }
  1973. }
  1974. // now grab the address from the database and set it as the overridden address
  1975. $sql = "SELECT entry_firstname, entry_lastname, entry_company,
  1976. entry_street_address, entry_suburb, entry_city, entry_postcode,
  1977. entry_country_id, entry_zone_id, entry_state
  1978. FROM " . TABLE_ADDRESS_BOOK . "
  1979. WHERE address_book_id = :addressId
  1980. AND customers_id = :customerId
  1981. LIMIT 1";
  1982. $sql = $db->bindVars($sql, ':addressId', $address_id, 'integer');
  1983. $sql = $db->bindVars($sql, ':customerId', $_SESSION['customer_id'], 'integer');
  1984. $address_arr = $db->Execute($sql);
  1985. // see if we found a record, if not then we have nothing to override with
  1986. if (!$address_arr->EOF) {
  1987. // get the state/prov code
  1988. $sql = "SELECT zone_code
  1989. FROM " . TABLE_ZONES . "
  1990. WHERE zone_id = :zoneId";
  1991. $sql = $db->bindVars($sql, ':zoneId', $address_arr->fields['entry_zone_id'], 'integer');
  1992. $state_code_arr = $db->Execute($sql);
  1993. if ($state_code_arr->EOF) {
  1994. $state_code_arr->fields['zone_code'] = '';
  1995. }
  1996. if ($state_code_arr->fields['zone_code'] == '' && $address_arr->fields['entry_state'] != '') {
  1997. $state_code_arr->fields['zone_code'] = $address_arr->fields['entry_state'];
  1998. }
  1999. $address_arr->fields['zone_code'] = $state_code_arr->fields['zone_code'];
  2000. // get the country code
  2001. // ISO 3166 standard country code
  2002. $sql = "SELECT countries_iso_code_2
  2003. FROM " . TABLE_COUNTRIES . "
  2004. WHERE countries_id = :countryId";
  2005. $sql = $db->bindVars($sql, ':countryId', $address_arr->fields['entry_country_id'], 'integer');
  2006. $country_code_arr = $db->Execute($sql);
  2007. if ($country_code_arr->EOF) {
  2008. // default to US if not found
  2009. $country_code_arr->fields['countries_iso_code_2'] = 'US';
  2010. }
  2011. $address_arr->fields['countries_iso_code_2'] = $country_code_arr->fields['countries_iso_code_2'];
  2012. // debug
  2013. $this->zcLog('getOverrideAddress - 2', '$address_arr->fields = ' . print_r($address_arr->fields, true));
  2014. // return address data.
  2015. return $address_arr->fields;
  2016. }
  2017. // debug
  2018. $this->zcLog('getOverrideAddress - 3', 'no override record found');
  2019. }
  2020. // debug
  2021. $this->zcLog('getOverrideAddress - 4', 'not logged in and not in markflow mode - nothing to override');
  2022. return false;
  2023. }
  2024. /**
  2025. * This method attempts to match items in an address book, to avoid
  2026. * duplicate entries to the address book. On a successful match it
  2027. * returns the address_book_id(int) - on failure it returns false.
  2028. *
  2029. * @param int $customer_id
  2030. * @param array $address_question_arr
  2031. * @return int|boolean
  2032. */
  2033. function findMatchingAddressBookEntry($customer_id, $address_question_arr) {
  2034. global $db;
  2035. // if address is blank, don't do any matching
  2036. if ($address_question_arr['street_address'] == '') return false;
  2037. // default
  2038. $country_id = '223';
  2039. $address_format_id = 2; //2 is the American format
  2040. // first get the zone id's from the 2 digit iso codes
  2041. // country first
  2042. $sql = "SELECT countries_id, address_format_id
  2043. FROM " . TABLE_COUNTRIES . "
  2044. WHERE countries_iso_code_2 = :countryCode:
  2045. OR countries_name = :countryName:
  2046. OR countries_iso_code_2 = :countryName:
  2047. OR countries_name = :countryCode:
  2048. LIMIT 1";
  2049. $sql = $db->bindVars($sql, ':countryCode:', $address_question_arr['country']['iso_code_2'], 'string');
  2050. $sql = $db->bindVars($sql, ':countryName:', $address_question_arr['country']['title'], 'string');
  2051. $country = $db->Execute($sql);
  2052. // see if we found a record, if not default to American format
  2053. if (!$country->EOF) {
  2054. $country_id = $country->fields['countries_id'];
  2055. $address_format_id = $country->fields['address_format_id'];
  2056. }
  2057. // see if the country code has a state
  2058. $sql = "SELECT zone_id
  2059. FROM " . TABLE_ZONES . "
  2060. WHERE zone_country_id = :zoneId
  2061. LIMIT 1";
  2062. $sql = $db->bindVars($sql, ':zoneId', $country_id, 'integer');
  2063. $country_zone_check = $db->Execute($sql);
  2064. $check_zone = $country_zone_check->RecordCount();
  2065. $zone_id = 0;
  2066. $logMsg = array('zone_id' => '-not found-');
  2067. // now try and find the zone_id (state/province code)
  2068. // use the country id above
  2069. if ($check_zone) {
  2070. $sql = "SELECT zone_id
  2071. FROM " . TABLE_ZONES . "
  2072. WHERE zone_country_id = :zoneId
  2073. AND (zone_code = :zoneCode
  2074. OR zone_name = :zoneCode )
  2075. LIMIT 1";
  2076. $sql = $db->bindVars($sql, ':zoneId', $country_id, 'integer');
  2077. $sql = $db->bindVars($sql, ':zoneCode', $address_question_arr['state'], 'string');
  2078. $zone = $db->Execute($sql);
  2079. if (!$zone->EOF) {
  2080. // grab the id
  2081. $zone_id = $zone->fields['zone_id'];
  2082. $logMsg = $zone->fields;
  2083. } else {
  2084. $check_zone = false;
  2085. }
  2086. }
  2087. // debug
  2088. $this->zcLog('findMatchingAddressBookEntry - 1-stats', 'lookups:' . "\n" . print_r(array_merge($country->fields, array('zone_country_id' => $country_zone_check->fields['zone_id']), $logMsg), true) . "\n" . 'check_zone: ' . $check_zone . "\n" . 'zone:' . $zone_id . "\nSubmittedAddress:".print_r($address_question_arr, TRUE));
  2089. // do a match on address, street, street2, city
  2090. $sql = "SELECT address_book_id, entry_street_address, entry_suburb, entry_city, entry_company, entry_firstname, entry_lastname
  2091. FROM " . TABLE_ADDRESS_BOOK . "
  2092. WHERE customers_id = :customerId
  2093. AND entry_country_id = :countryId";
  2094. if ($check_zone) {
  2095. $sql .= " AND entry_zone_id = :zoneId";
  2096. }
  2097. $sql = $db->bindVars($sql, ':zoneId', $zone_id, 'integer');
  2098. $sql = $db->bindVars($sql, ':countryId', $country_id, 'integer');
  2099. $sql = $db->bindVars($sql, ':customerId', $customer_id, 'integer');
  2100. $answers_arr = $db->Execute($sql);
  2101. // debug
  2102. $this->zcLog('findMatchingAddressBookEntry - 2-read for match', "\nLookup RecordCount = " . $answers_arr->RecordCount());
  2103. if (!$answers_arr->EOF) {
  2104. // build a base string to compare street+suburb+city content
  2105. //$matchQuestion = str_replace("\n", '', $address_question_arr['company']);
  2106. //$matchQuestion = str_replace("\n", '', $address_question_arr['name']);
  2107. $matchQuestion = str_replace("\n", '', $address_question_arr['street_address']);
  2108. $matchQuestion = trim($matchQuestion);
  2109. $matchQuestion = $matchQuestion . str_replace("\n", '', $address_question_arr['suburb']);
  2110. $matchQuestion = $matchQuestion . str_replace("\n", '', $address_question_arr['city']);
  2111. $matchQuestion = str_replace("\t", '', $matchQuestion);
  2112. $matchQuestion = trim($matchQuestion);
  2113. $matchQuestion = strtolower($matchQuestion);
  2114. $matchQuestion = str_replace(' ', '', $matchQuestion);
  2115. // go through the data
  2116. while (!$answers_arr->EOF) {
  2117. // now the matching logic
  2118. // first from the db
  2119. $fromDb = '';
  2120. // $fromDb = str_replace("\n", '', $answers_arr->fields['entry_company']);
  2121. /// $fromDb = str_replace("\n", '', $answers_arr->fields['entry_firstname'].$answers_arr->fields['entry_lastname']);
  2122. $fromDb = str_replace("\n", '', $answers_arr->fields['entry_street_address']);
  2123. $fromDb = trim($fromDb);
  2124. $fromDb = $fromDb . str_replace("\n", '', $answers_arr->fields['entry_suburb']);
  2125. $fromDb = $fromDb . str_replace("\n", '', $answers_arr->fields['entry_city']);
  2126. $fromDb = str_replace("\t", '', $fromDb);
  2127. $fromDb = trim($fromDb);
  2128. $fromDb = strtolower($fromDb);
  2129. $fromDb = str_replace(' ', '', $fromDb);
  2130. // debug
  2131. $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));
  2132. // check the strings
  2133. if (strlen($fromDb) == strlen($matchQuestion)) {
  2134. if ($fromDb == $matchQuestion) {
  2135. // exact match return the id
  2136. // debug
  2137. $this->zcLog('findMatchingAddressBookEntry - 3b', "Exact match:\n" . print_r($answers_arr->fields, true));
  2138. return $answers_arr->fields['address_book_id'];
  2139. }
  2140. } elseif (strlen($fromDb) > strlen($matchQuestion)) {
  2141. if (substr($fromDb, 0, strlen($matchQuestion)) == $matchQuestion) {
  2142. // we have a match return it (PP)
  2143. // debug
  2144. $this->zcLog('findMatchingAddressBookEntry - 3b', "partial match (PP):\n" . print_r($answers_arr->fields, true));
  2145. return $answers_arr->fields['address_book_id'];
  2146. }
  2147. } else {
  2148. if ($fromDb == substr($matchQuestion, 0, strlen($fromDb))) {
  2149. // we have a match return it (DB)
  2150. // debug
  2151. $this->zcLog('findMatchingAddressBookEntry - 3b', "partial match (DB):\n" . print_r($answers_arr->fields, true));
  2152. return $answers_arr->fields['address_book_id'];
  2153. }
  2154. }
  2155. $answers_arr->MoveNext();
  2156. }
  2157. }
  2158. // debug
  2159. $this->zcLog('findMatchingAddressBookEntry - 4', "no match");
  2160. // no matches found
  2161. return false;
  2162. }
  2163. /**
  2164. * This method adds an address book entry to the database, this allows us to add addresses
  2165. * that we get back from PayPal that are not in Zen Cart
  2166. *
  2167. * @param int $customer_id
  2168. * @param array $address_question_arr
  2169. * @return int
  2170. */
  2171. function addAddressBookEntry($customer_id, $address_question_arr, $make_default = false) {
  2172. global $db;
  2173. // debug
  2174. $this->zcLog('addAddressBookEntry - 1', 'address to add: ' . "\n" . print_r($address_question_arr, true));
  2175. // if address is blank, don't do any matching
  2176. if ($address_question_arr['street_address'] == '') return false;
  2177. // set some defaults
  2178. $country_id = '223';
  2179. $address_format_id = 2; //2 is the American format
  2180. // first get the zone id's from the 2 digit iso codes
  2181. // country first
  2182. $sql = "SELECT countries_id, address_format_id
  2183. FROM " . TABLE_COUNTRIES . "
  2184. WHERE countries_iso_code_2 = :countryCode:
  2185. OR countries_name = :countryName:
  2186. OR countries_iso_code_2 = :countryName:
  2187. OR countries_name = :countryCode:
  2188. LIMIT 1";
  2189. $sql = $db->bindVars($sql, ':countryCode:', $address_question_arr['country']['iso_code_2'], 'string');
  2190. $sql = $db->bindVars($sql, ':countryName:', $address_question_arr['country']['title'], 'string');
  2191. $country = $db->Execute($sql);
  2192. // see if we found a record, if not default to American format
  2193. if (!$country->EOF) {
  2194. $country_id = $country->fields['countries_id'];
  2195. $address_format_id = (int)$country->fields['address_format_id'];
  2196. } else {
  2197. // if defaulting to US, make sure US is valid
  2198. $sql = "SELECT countries_id FROM " . TABLE_COUNTRIES . " WHERE countries_id = :countryId LIMIT 1";
  2199. $sql = $db->bindVars($sql, ':countryId', $country_id, 'integer');
  2200. $result = $db->Execute($sql);
  2201. if ($result->EOF) {
  2202. $this->notify('NOTIFY_HEADER_ADDRESS_BOOK_ADD_ENTRY_INVALID_ATTEMPT');
  2203. $this->zcLog('addAddressBookEntry - 3', 'Failed to add address due to country restrictions');
  2204. return FALSE;
  2205. }
  2206. }
  2207. // see if the country code has a state
  2208. $sql = "SELECT zone_id
  2209. FROM " . TABLE_ZONES . "
  2210. WHERE zone_country_id = :zoneId
  2211. LIMIT 1";
  2212. $sql = $db->bindVars($sql, ':zoneId', $country_id, 'integer');
  2213. $country_zone_check = $db->Execute($sql);
  2214. $check_zone = $country_zone_check->RecordCount();
  2215. // now try and find the zone_id (state/province code)
  2216. // use the country id above
  2217. if ($check_zone) {
  2218. $sql = "SELECT zone_id
  2219. FROM " . TABLE_ZONES . "
  2220. WHERE zone_country_id = :zoneId
  2221. AND (zone_code = :zoneCode
  2222. OR zone_name = :zoneCode )
  2223. LIMIT 1";
  2224. $sql = $db->bindVars($sql, ':zoneId', $country_id, 'integer');
  2225. $sql = $db->bindVars($sql, ':zoneCode', $address_question_arr['state'], 'string');
  2226. $zone = $db->Execute($sql);
  2227. if (!$zone->EOF) {
  2228. // grab the id
  2229. $zone_id = $zone->fields['zone_id'];
  2230. } else {
  2231. $zone_id = 0;
  2232. }
  2233. }
  2234. // now run the insert
  2235. // this isn't the best way to get fname/lname but it will get the majority of cases
  2236. list($fname, $lname) = explode(' ', $address_question_arr['name']);
  2237. $sql_data_array= array(array('fieldName'=>'entry_firstname', 'value'=>$fname, 'type'=>'string'),
  2238. array('fieldName'=>'entry_lastname', 'value'=>$lname, 'type'=>'string'),
  2239. array('fieldName'=>'entry_street_address', 'value'=>$address_question_arr['street_address'], 'type'=>'string'),
  2240. array('fieldName'=>'entry_postcode', 'value'=>$address_question_arr['postcode'], 'type'=>'string'),
  2241. array('fieldName'=>'entry_city', 'value'=>$address_question_arr['city'], 'type'=>'string'),
  2242. array('fieldName'=>'entry_country_id', 'value'=>$country_id, 'type'=>'integer'));
  2243. if ($address_question_arr['company'] != '' && $address_question_arr['company'] != $address_question_arr['name']) array('fieldName'=>'entry_company', 'value'=>$address_question_arr['company'], 'type'=>'string');
  2244. $sql_data_array[] = array('fieldName'=>'entry_gender', 'value'=>$address_question_arr['payer_gender'], 'type'=>'enum:m|f');
  2245. $sql_data_array[] = array('fieldName'=>'entry_suburb', 'value'=>$address_question_arr['suburb'], 'type'=>'string');
  2246. if ($zone_id > 0) {
  2247. $sql_data_array[] = array('fieldName'=>'entry_zone_id', 'value'=>$zone_id, 'type'=>'integer');
  2248. $sql_data_array[] = array('fieldName'=>'entry_state', 'value'=>'', 'type'=>'string');
  2249. } else {
  2250. $sql_data_array[] = array('fieldName'=>'entry_zone_id', 'value'=>'0', 'type'=>'integer');
  2251. $sql_data_array[] = array('fieldName'=>'entry_state', 'value'=>$address_question_arr['state'], 'type'=>'string');
  2252. }
  2253. $sql_data_array[] = array('fieldName'=>'customers_id', 'value'=>$customer_id, 'type'=>'integer');
  2254. $db->perform(TABLE_ADDRESS_BOOK, $sql_data_array);
  2255. $new_address_book_id = $db->Insert_ID();
  2256. $this->notify('NOTIFY_HEADER_ADDRESS_BOOK_ADD_ENTRY_DONE');
  2257. // make default if set, update
  2258. if ($make_default) {
  2259. $sql_data_array = array();
  2260. $sql_data_array[] = array('fieldName'=>'customers_default_address_id', 'value'=>$new_address_book_id, 'type'=>'integer');
  2261. $where_clause = "customers_id = :customersID";
  2262. $where_clause = $db->bindVars($where_clause, ':customersID', $customer_id, 'integer');
  2263. $db->perform(TABLE_CUSTOMERS, $sql_data_array, 'update', $where_clause);
  2264. $_SESSION['customer_default_address_id'] = $new_address_book_id;
  2265. }
  2266. // set the sendto
  2267. $_SESSION['sendto'] = $new_address_book_id;
  2268. // debug
  2269. $this->zcLog('addAddressBookEntry - 2', 'added address #' . $new_address_book_id. "\n" . 'SESSION[sendto] is now set to ' . $_SESSION['sendto']);
  2270. // return the address_id
  2271. return $new_address_book_id;
  2272. }
  2273. /**
  2274. * If we created an account for the customer, this logs them in and notes that the record was created for PayPal EC purposes
  2275. */
  2276. function user_login($email_address, $redirect = true) {
  2277. global $db, $order, $messageStack;
  2278. global $session_started;
  2279. if ($session_started == false) {
  2280. zen_redirect(zen_href_link(FILENAME_COOKIE_USAGE));
  2281. }
  2282. $sql = "SELECT * FROM " . TABLE_CUSTOMERS . "
  2283. WHERE customers_email_address = :custEmail ";
  2284. $sql = $db->bindVars($sql, ':custEmail', $email_address, 'string');
  2285. $check_customer = $db->Execute($sql);
  2286. if ($check_customer->EOF) {
  2287. $this->terminateEC(MODULE_PAYMENT_PAYPALWPP_TEXT_BAD_LOGIN, true);
  2288. }
  2289. if (SESSION_RECREATE == 'True') {
  2290. zen_session_recreate();
  2291. }
  2292. $sql = "SELECT entry_country_id, entry_zone_id
  2293. FROM " . TABLE_ADDRESS_BOOK . "
  2294. WHERE customers_id = :custID
  2295. AND address_book_id = :addrID ";
  2296. $sql = $db->bindVars($sql, ':custID', $check_customer->fields['customers_id'], 'integer');
  2297. $sql = $db->bindVars($sql, ':addrID', $check_customer->fields['customers_default_address_id'], 'integer');
  2298. $check_country = $db->Execute($sql);
  2299. $_SESSION['customer_id'] = (int)$check_customer->fields['customers_id'];
  2300. $_SESSION['customer_default_address_id'] = $check_customer->fields['customers_default_address_id'];
  2301. $_SESSION['customer_first_name'] = $check_customer->fields['customers_firstname'];
  2302. $_SESSION['customer_country_id'] = $check_country->fields['entry_country_id'];
  2303. $_SESSION['customer_zone_id'] = $check_country->fields['entry_zone_id'];
  2304. $order->customer['id'] = $_SESSION['customer_id'];
  2305. $sql = "UPDATE " . TABLE_CUSTOMERS_INFO . "
  2306. SET customers_info_date_of_last_logon = now(),
  2307. customers_info_number_of_logons = customers_info_number_of_logons+1
  2308. WHERE customers_info_id = :custID ";
  2309. $sql = $db->bindVars($sql, ':custID', $_SESSION['customer_id'], 'integer');
  2310. $db->Execute($sql);
  2311. // bof: contents merge notice
  2312. // save current cart contents count if required
  2313. if (SHOW_SHOPPING_CART_COMBINED > 0) {
  2314. $zc_check_basket_before = $_SESSION['cart']->count_contents();
  2315. }
  2316. // bof: not require part of contents merge notice
  2317. // restore cart contents
  2318. $_SESSION['cart']->restore_contents();
  2319. // eof: not require part of contents merge notice
  2320. // check current cart contents count if required
  2321. if (SHOW_SHOPPING_CART_COMBINED > 0 && $zc_check_basket_before > 0) {
  2322. $zc_check_basket_after = $_SESSION['cart']->count_contents();
  2323. if (($zc_check_basket_before != $zc_check_basket_after) && $_SESSION['cart']->count_contents() > 0 && SHOW_SHOPPING_CART_COMBINED > 0) {
  2324. if (SHOW_SHOPPING_CART_COMBINED == 2) {
  2325. // warning only do not send to cart
  2326. $messageStack->add_session('header', WARNING_SHOPPING_CART_COMBINED, 'caution');
  2327. }
  2328. if (SHOW_SHOPPING_CART_COMBINED == 1) {
  2329. // show warning and send to shopping cart for review
  2330. $messageStack->add_session('shopping_cart', WARNING_SHOPPING_CART_COMBINED, 'caution');
  2331. zen_redirect(zen_href_link(FILENAME_SHOPPING_CART, '', 'NONSSL'));
  2332. }
  2333. }
  2334. }
  2335. // eof: contents merge notice
  2336. if ($redirect) {
  2337. $this->terminateEC();
  2338. }
  2339. return true;
  2340. }
  2341. /**
  2342. * If the account was created only for temporary purposes to place the PayPal order, delete it.
  2343. */
  2344. function ec_delete_user($cid) {
  2345. global $db;
  2346. unset($_SESSION['customer_id']);
  2347. unset($_SESSION['customer_default_address_id']);
  2348. unset($_SESSION['customer_first_name']);
  2349. unset($_SESSION['customer_country_id']);
  2350. unset($_SESSION['customer_zone_id']);
  2351. unset($_SESSION['comments']);
  2352. unset($_SESSION['customer_guest_id']);
  2353. $cid = (int)$cid;
  2354. $sql = "delete from " . TABLE_ADDRESS_BOOK . " where customers_id = " . $cid;
  2355. $db->Execute($sql);
  2356. $sql = "delete from " . TABLE_CUSTOMERS . " where customers_id = " . $cid;
  2357. $db->Execute($sql);
  2358. $sql = "delete from " . TABLE_CUSTOMERS_INFO . " where customers_info_id = " . $cid;
  2359. $db->Execute($sql);
  2360. $sql = "delete from " . TABLE_CUSTOMERS_BASKET . " where customers_id = " . $cid;
  2361. $db->Execute($sql);
  2362. $sql = "delete from " . TABLE_CUSTOMERS_BASKET_ATTRIBUTES . " where customers_id = " . $cid;
  2363. $db->Execute($sql);
  2364. $sql = "delete from " . TABLE_WHOS_ONLINE . " where customer_id = " . $cid;
  2365. $db->Execute($sql);
  2366. }
  2367. /**
  2368. * If the EC flow has to be interrupted for any reason, this does the appropriate cleanup and displays status/error messages.
  2369. */
  2370. function terminateEC($error_msg = '', $kill_sess_vars = false, $goto_page = '') {
  2371. global $messageStack, $order, $order_total_modules;
  2372. $error_msg = trim($error_msg);
  2373. if (substr($error_msg, -1) == '-') $error_msg = trim(substr($error_msg, 0, strlen($error_msg) - 1));
  2374. $stackAlert = 'header';
  2375. // debug
  2376. $this->_doDebug('PayPal test Log - terminateEC-A', "goto page: " . $goto_page . "\nerror_msg: " . $error_msg . "\n\nSession data: " . print_r($_SESSION, true));
  2377. if ($kill_sess_vars) {
  2378. if (!empty($_SESSION['paypal_ec_temp'])) {
  2379. $this->ec_delete_user($_SESSION['customer_id']);
  2380. }
  2381. // Unregister the paypal session variables, making the user start over.
  2382. unset($_SESSION['paypal_ec_temp']);
  2383. unset($_SESSION['paypal_ec_token']);
  2384. unset($_SESSION['paypal_ec_payer_id']);
  2385. unset($_SESSION['paypal_ec_payer_info']);
  2386. unset($_SESSION['paypal_ec_final']);
  2387. unset($_SESSION['paypal_ec_markflow']);
  2388. // debug
  2389. $this->zcLog('termEC-1', 'Killed the session vars as requested');
  2390. }
  2391. $this->zcLog('termEC-2', 'BEFORE: $this->showPaymentPage = ' . (int)$this->showPaymentPage . "\nToken Data:" . $_SESSION['paypal_ec_token']);
  2392. // force display of payment page if GV/DC active for this customer
  2393. if (MODULE_ORDER_TOTAL_INSTALLED && $this->showPaymentPage !== true && isset($_SESSION['paypal_ec_token']) ) {
  2394. require_once(DIR_WS_CLASSES . 'order.php');
  2395. $order = new order;
  2396. require_once(DIR_WS_CLASSES . 'order_total.php');
  2397. $order_total_modules = new order_total;
  2398. $order_totals = $order_total_modules->process();
  2399. $selection = $order_total_modules->credit_selection();
  2400. if (sizeof($selection)>0) $this->showPaymentPage = true;
  2401. }
  2402. // if came from Payment page, don't go back to it
  2403. if ($_SESSION['paypal_ec_markflow'] == 1) $this->showPaymentPage = false;
  2404. // if in DP mode, don't go to payment page ... we've already been there to get here
  2405. if ($goto_page == FILENAME_CHECKOUT_PROCESS) $this->showPaymentPage = false;
  2406. // debug
  2407. $this->zcLog('termEC-3', 'AFTER: $this->showPaymentPage = ' . (int)$this->showPaymentPage);
  2408. if (!empty($_SESSION['customer_first_name']) && !empty($_SESSION['customer_id'])) {
  2409. if ($this->showPaymentPage === true || $goto_page == FILENAME_CHECKOUT_PAYMENT) {
  2410. // debug
  2411. $this->zcLog('termEC-4', 'We ARE logged in, and $this->showPaymentPage === true');
  2412. // if no shipping selected or if shipping cost is < 0 goto shipping page
  2413. if ((!$_SESSION['shipping'] || $_SESSION['shipping'] == '') || $_SESSION['shipping']['cost'] < 0) {
  2414. // debug
  2415. $this->zcLog('termEC-5', 'Have no shipping method selected, or shipping < 0 so set FILENAME_CHECKOUT_SHIPPING');
  2416. $redirect_path = FILENAME_CHECKOUT_SHIPPING;
  2417. $stackAlert = 'checkout_shipping';
  2418. } else {
  2419. // debug
  2420. $this->zcLog('termEC-6', 'We DO have a shipping method selected, so goto PAYMENT');
  2421. $redirect_path = FILENAME_CHECKOUT_PAYMENT;
  2422. $stackAlert = 'checkout_payment';
  2423. }
  2424. } elseif ($goto_page) {
  2425. // debug
  2426. $this->zcLog('termEC-7', '$this->showPaymentPage NOT true, and have custom page parameter: ' . $goto_page);
  2427. $redirect_path = $goto_page;
  2428. $stackAlert = 'header';
  2429. if ($goto_page == FILENAME_SHOPPING_CART) $stackAlert = 'shopping_cart';
  2430. } else {
  2431. // debug
  2432. $this->zcLog('termEC-8', '$this->showPaymentPage NOT true, and NO custom page selected ... using SHIPPING as default');
  2433. $redirect_path = FILENAME_CHECKOUT_SHIPPING;
  2434. $stackAlert = 'checkout_shipping';
  2435. }
  2436. } else {
  2437. // debug
  2438. $this->zcLog('termEC-9', 'We are NOT logged in, so set snapshot to Shipping page, and redirect to login');
  2439. $_SESSION['navigation']->set_snapshot(FILENAME_CHECKOUT_SHIPPING);
  2440. $redirect_path = FILENAME_LOGIN;
  2441. $stackAlert = 'login';
  2442. }
  2443. if ($error_msg) {
  2444. $messageStack->add_session($stackAlert, $error_msg, 'error');
  2445. }
  2446. // debug
  2447. $this->zcLog('termEC-10', 'Redirecting to ' . $redirect_path . ' - Stack: ' . $stackAlert . "\n" . 'Message: ' . $error_msg . "\nSession Data: " . print_r($_SESSION, true));
  2448. zen_redirect(zen_href_link($redirect_path, '', 'SSL', true, false));
  2449. }
  2450. /**
  2451. * Error / exception handling
  2452. */
  2453. function _errorHandler($response, $operation = '', $ignore_codes = '') {
  2454. global $messageStack, $doPayPal;
  2455. $gateway_mode = (isset($response['PNREF']) && $response['PNREF'] != '');
  2456. $basicError = (!$response || (isset($response['RESULT']) && $response['RESULT'] != 0) || (isset($response['ACK']) && !strstr($response['ACK'], 'Success')) || (!isset($response['RESULT']) && !isset($response['ACK'])));
  2457. $ignoreList = explode(',', str_replace(' ', '', $ignore_codes));
  2458. foreach($ignoreList as $key=>$value) {
  2459. if ($value != '' && $response['L_ERRORCODE0'] == $value) $basicError = false;
  2460. }
  2461. /** Handle unilateral **/
  2462. if ($response['RESULT'] == 'Unauthorized: Unilateral') {
  2463. $errorText = $response['RESULT'] . MODULE_PAYMENT_PAYPALWPP_TEXT_UNILATERAL;
  2464. $messageStack->add_session($errorText, 'error');
  2465. }
  2466. /** Handle FMF Scenarios **/
  2467. if (in_array($operation, array('DoExpressCheckoutPayment', 'DoDirectPayment')) && $response['PAYMENTSTATUS'] == 'Pending' && $response['L_ERRORCODE0'] == 11610) {
  2468. $this->fmfResponse = urldecode($response['L_SHORTMESSAGE0']);
  2469. $this->fmfErrors = array();
  2470. if ($response['ACK'] == 'SuccessWithWarning' && isset($response['L_FMFPENDINGID0'])) {
  2471. for ($i=0; $i<20; $i++) {
  2472. $this->fmfErrors[] = array('key' => $response['L_FMFPENDINGID' . $i], 'status' => $response['L_FMFPENDINGID' . $i], 'desc' => $response['L_FMFPENDINGDESCRIPTION' . $i]);
  2473. }
  2474. }
  2475. return (sizeof($this->fmfErrors)>0) ? $this->fmfErrors : FALSE;
  2476. }
  2477. if (!isset($response['L_SHORTMESSAGE0']) && isset($response['RESPMSG']) && $response['RESPMSG'] != '') $response['L_SHORTMESSAGE0'] = $response['RESPMSG'];
  2478. //echo '<br />basicError='.$basicError.'<br />' . urldecode(print_r($response,true)); die('halted');
  2479. $errorInfo = "\n\nProblem occurred while customer " . $_SESSION['customer_id'] . ' ' . $_SESSION['customer_first_name'] . ' ' . $_SESSION['customer_last_name'] . ' was attempting checkout with PayPal Express Checkout.';
  2480. switch($operation) {
  2481. case 'SetExpressCheckout':
  2482. if ($basicError) {
  2483. // if error, display error message. If debug options enabled, email dump to store owner
  2484. if ($this->enableDebugging) {
  2485. $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));
  2486. }
  2487. $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_GEN_ERROR;
  2488. $errorNum = urldecode($response['L_ERRORCODE0'] . $response['RESULT']);
  2489. if ($response['RESULT'] == 25) $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_NOT_WPP_ACCOUNT_ERROR;
  2490. if ($response['L_ERRORCODE0'] == 10002) $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_SANDBOX_VS_LIVE_ERROR;
  2491. if ($response['L_ERRORCODE0'] == 10565) {
  2492. $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_WPP_BAD_COUNTRY_ERROR;
  2493. $_SESSION['payment'] = '';
  2494. }
  2495. if ($response['L_ERRORCODE0'] == 10736) $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_ADDR_ERROR;
  2496. if ($response['L_ERRORCODE0'] == 10752) $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_DECLINED;
  2497. $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']) : '';
  2498. $detailedEmailMessage = ($detailedMessage == '') ? '' : 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) . $errorInfo;
  2499. if (!isset($response['L_ERRORCODE0']) && isset($response['RESULT'])) $detailedEmailMessage .= "\n\n" . print_r($response, TRUE);
  2500. if ($detailedEmailMessage != '') zen_mail(STORE_NAME, STORE_OWNER_EMAIL_ADDRESS, MODULE_PAYMENT_PAYPALWPP_TEXT_EMAIL_ERROR_SUBJECT . ' (' . zen_uncomment($errorNum) . ')', zen_uncomment($detailedEmailMessage), STORE_OWNER, STORE_OWNER_EMAIL_ADDRESS, array('EMAIL_MESSAGE_HTML'=>zen_uncomment($detailedMessage)), 'paymentalert');
  2501. $this->terminateEC($errorText . ' (' . $errorNum . ') ' . $detailedMessage, true);
  2502. return true;
  2503. }
  2504. break;
  2505. case 'GetExpressCheckoutDetails':
  2506. if ($basicError || $_SESSION['paypal_ec_token'] != urldecode($response['TOKEN'])) {
  2507. // if response indicates an error, send the customer back to checkout and display the error. Debug to store owner if active.
  2508. if ($this->enableDebugging) {
  2509. $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)));
  2510. }
  2511. $this->terminateEC(MODULE_PAYMENT_PAYPALWPP_TEXT_GEN_ERROR . ' (' . $response['L_ERRORCODE0'] . ' ' . urldecode($response['L_SHORTMESSAGE0'] . $response['RESULT']) . ')', true);
  2512. return true;
  2513. }
  2514. break;
  2515. case 'DoExpressCheckoutPayment':
  2516. if ($basicError || $_SESSION['paypal_ec_token'] != urldecode($response['TOKEN'])) {
  2517. // there's an error, so alert customer, and if debug is on, notify storeowner
  2518. if ($this->enableDebugging) {
  2519. $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));
  2520. }
  2521. // if funding source problem occurred, must send back to re-select alternate funding source
  2522. if ($response['L_ERRORCODE0'] == 10422) {
  2523. $paypal_url = $this->getPayPalLoginServer();
  2524. zen_redirect($paypal_url . "?cmd=_express-checkout&token=" . $_SESSION['paypal_ec_token']);
  2525. die();
  2526. }
  2527. // some other error condition
  2528. $errorText = MODULE_PAYMENT_PAYPALWPP_INVALID_RESPONSE;
  2529. $errorNum = urldecode($response['L_ERRORCODE0'] . $response['RESULT']);
  2530. if ($response['L_ERRORCODE0'] == 10415) $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_ORDER_ALREADY_PLACED_ERROR;
  2531. if ($response['L_ERRORCODE0'] == 10417) $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_INSUFFICIENT_FUNDS_ERROR;
  2532. if ($response['L_ERRORCODE0'] == 10474) $errorText .= urldecode($response['L_LONGMESSAGE0']);
  2533. $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']) : '';
  2534. $detailedEmailMessage = ($detailedMessage == '') ? '' : 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) . $errorInfo;
  2535. if (!isset($response['L_ERRORCODE0']) && isset($response['RESULT'])) $detailedEmailMessage .= "\n\n" . print_r($response, TRUE);
  2536. if ($detailedEmailMessage != '') zen_mail(STORE_NAME, STORE_OWNER_EMAIL_ADDRESS, MODULE_PAYMENT_PAYPALWPP_TEXT_EMAIL_ERROR_SUBJECT . ' (' . zen_uncomment($errorNum) . ')', zen_uncomment($detailedEmailMessage), STORE_OWNER, STORE_OWNER_EMAIL_ADDRESS, array('EMAIL_MESSAGE_HTML'=>zen_uncomment($detailedMessage)), 'paymentalert');
  2537. $this->terminateEC(($detailedEmailMessage == '' ? $errorText . ' (' . urldecode($response['L_SHORTMESSAGE0'] . $response['RESULT']) . ') ' : $detailedMessage), true);
  2538. return true;
  2539. }
  2540. break;
  2541. case 'DoRefund':
  2542. if ($basicError || (!isset($response['RESPMSG']) && !isset($response['REFUNDTRANSACTIONID']))) {
  2543. // if error, display error message. If debug options enabled, email dump to store owner
  2544. if ($this->enableDebugging) {
  2545. $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));
  2546. }
  2547. $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_REFUND_ERROR;
  2548. if ($response['L_ERRORCODE0'] == 10009) $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_REFUNDFULL_ERROR;
  2549. if ($response['RESULT'] == 105 || isset($response['RESPMSG'])) $response['L_SHORTMESSAGE0'] = $response['RESULT'] . ' ' . $response['RESPMSG'];
  2550. if (urldecode($response['L_LONGMESSAGE0']) == 'This transaction has already been fully refunded') $response['L_SHORTMESSAGE0'] = urldecode($response['L_LONGMESSAGE0']);
  2551. if (urldecode($response['L_LONGMESSAGE0']) == 'Can not do a full refund after a partial refund') $response['L_SHORTMESSAGE0'] = urldecode($response['L_LONGMESSAGE0']);
  2552. 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']);
  2553. if (urldecode($response['L_LONGMESSAGE0']) == 'You can not refund this type of transaction') $response['L_SHORTMESSAGE0'] = urldecode($response['L_LONGMESSAGE0']);
  2554. $errorText .= ' (' . urldecode($response['L_SHORTMESSAGE0']) . ') ' . $response['L_ERRORCODE0'];
  2555. $messageStack->add_session($errorText, 'error');
  2556. return true;
  2557. }
  2558. break;
  2559. case 'DoAuthorization':
  2560. case 'DoReauthorization':
  2561. if ($basicError) {
  2562. // if error, display error message. If debug options enabled, email dump to store owner
  2563. if ($this->enableDebugging) {
  2564. $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));
  2565. }
  2566. $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_AUTH_ERROR;
  2567. $errorText .= ' (' . urldecode($response['L_SHORTMESSAGE0']) . ') ' . $response['L_ERRORCODE0'];
  2568. $messageStack->add_session($errorText, 'error');
  2569. return true;
  2570. }
  2571. break;
  2572. case 'DoCapture':
  2573. if ($basicError) {
  2574. // if error, display error message. If debug options enabled, email dump to store owner
  2575. if ($this->enableDebugging) {
  2576. $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));
  2577. }
  2578. $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_CAPT_ERROR;
  2579. if ($response['RESULT'] == 111) $response['L_SHORTMESSAGE0'] = $response['RESULT'] . ' ' . $response['RESPMSG'];
  2580. $errorText .= ' (' . urldecode($response['L_SHORTMESSAGE0']) . ') ' . $response['L_ERRORCODE0'];
  2581. $messageStack->add_session($errorText, 'error');
  2582. return true;
  2583. }
  2584. break;
  2585. case 'DoVoid':
  2586. if ($basicError) {
  2587. // if error, display error message. If debug options enabled, email dump to store owner
  2588. if ($this->enableDebugging) {
  2589. $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));
  2590. }
  2591. $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_VOID_ERROR;
  2592. if ($response['RESULT'] == 12) $response['L_SHORTMESSAGE0'] = $response['RESULT'] . ' ' . $response['RESPMSG'];
  2593. if ($response['RESULT'] == 108) $response['L_SHORTMESSAGE0'] = $response['RESULT'] . ' ' . $response['RESPMSG'];
  2594. $errorText .= ' (' . urldecode($response['L_SHORTMESSAGE0']) . ') ' . $response['L_ERRORCODE0'];
  2595. $messageStack->add_session($errorText, 'error');
  2596. return true;
  2597. }
  2598. break;
  2599. case 'GetTransactionDetails':
  2600. if ($basicError) {
  2601. // if error, display error message. If debug options enabled, email dump to store owner
  2602. if ($this->enableDebugging) {
  2603. $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));
  2604. }
  2605. $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_GETDETAILS_ERROR;
  2606. $errorText .= ' (' . urldecode($response['L_SHORTMESSAGE0']) . ') ' . $response['L_ERRORCODE0'];
  2607. $messageStack->add_session($errorText, 'error');
  2608. return true;
  2609. }
  2610. break;
  2611. case 'TransactionSearch':
  2612. if ($basicError) {
  2613. // if error, display error message. If debug options enabled, email dump to store owner
  2614. if ($this->enableDebugging) {
  2615. $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));
  2616. }
  2617. $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_TRANSSEARCH_ERROR;
  2618. $errorText .= ' (' . urldecode($response['L_SHORTMESSAGE0']) . ') ' . $response['L_ERRORCODE0'];
  2619. $messageStack->add_session($errorText, 'error');
  2620. return true;
  2621. }
  2622. break;
  2623. default:
  2624. if ($basicError) {
  2625. // if error, display error message. If debug options enabled, email dump to store owner
  2626. if ($this->enableDebugging) {
  2627. $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));
  2628. }
  2629. $errorText = MODULE_PAYMENT_PAYPALWPP_TEXT_GEN_API_ERROR;
  2630. $errorNum .= ' (' . urldecode($response['L_SHORTMESSAGE0'] . ' <!-- ' . $response['RESPMSG']) . ' -->) ' . $response['L_ERRORCODE0'];
  2631. $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']) : '';
  2632. $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)));
  2633. if ($detailedEmailMessage != '') zen_mail(STORE_NAME, STORE_OWNER_EMAIL_ADDRESS, MODULE_PAYMENT_PAYPALWPP_TEXT_EMAIL_ERROR_SUBJECT . ' (' . zen_uncomment($errorNum) . ')', zen_uncomment($detailedMessage), STORE_OWNER, STORE_OWNER_EMAIL_ADDRESS, array('EMAIL_MESSAGE_HTML'=>nl2br(zen_uncomment($detailedEmailMessage))), 'paymentalert');
  2634. $messageStack->add_session($errorText . $errorNum . $detailedMessage, 'error');
  2635. return true;
  2636. }
  2637. break;
  2638. }
  2639. }
  2640. function tableCheckup() {
  2641. global $db, $sniffer;
  2642. $fieldOkay1 = (method_exists($sniffer, 'field_type')) ? $sniffer->field_type(TABLE_PAYPAL, 'txn_id', 'varchar(20)', true) : -1;
  2643. $fieldOkay2 = ($sniffer->field_exists(TABLE_PAYPAL, 'module_name')) ? true : -1;
  2644. $fieldOkay3 = ($sniffer->field_exists(TABLE_PAYPAL, 'order_id')) ? true : -1;
  2645. if ($fieldOkay1 == -1) {
  2646. $sql = "show fields from " . TABLE_PAYPAL;
  2647. $result = $db->Execute($sql);
  2648. while (!$result->EOF) {
  2649. if ($result->fields['Field'] == 'txn_id') {
  2650. if ($result->fields['Type'] == 'varchar(20)') {
  2651. $fieldOkay1 = true; // exists and matches required type, so skip to other checkup
  2652. } else {
  2653. $fieldOkay1 = $result->fields['Type']; // doesn't match, so return what it "is"
  2654. break;
  2655. }
  2656. }
  2657. $result->MoveNext();
  2658. }
  2659. }
  2660. if ($fieldOkay1 !== true) {
  2661. // temporary fix to table structure for v1.3.7.x -- may remove in later release
  2662. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE payment_type payment_type varchar(40) NOT NULL default ''");
  2663. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE txn_type txn_type varchar(40) NOT NULL default ''");
  2664. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE payment_status payment_status varchar(32) NOT NULL default ''");
  2665. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE reason_code reason_code varchar(40) default NULL");
  2666. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE pending_reason pending_reason varchar(32) default NULL");
  2667. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE invoice invoice varchar(128) default NULL");
  2668. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE payer_business_name payer_business_name varchar(128) default NULL");
  2669. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE address_name address_name varchar(64) default NULL");
  2670. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE address_street address_street varchar(254) default NULL");
  2671. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE address_city address_city varchar(120) default NULL");
  2672. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE address_state address_state varchar(120) default NULL");
  2673. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE payer_email payer_email varchar(128) NOT NULL default ''");
  2674. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE business business varchar(128) NOT NULL default ''");
  2675. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE receiver_email receiver_email varchar(128) NOT NULL default ''");
  2676. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE txn_id txn_id varchar(20) NOT NULL default ''");
  2677. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE parent_txn_id parent_txn_id varchar(20) default NULL");
  2678. }
  2679. if ($fieldOkay2 !== true) {
  2680. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " ADD COLUMN module_name varchar(40) NOT NULL default '' after txn_type");
  2681. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " ADD COLUMN module_mode varchar(40) NOT NULL default '' after module_name");
  2682. }
  2683. if ($fieldOkay3 !== true) {
  2684. $db->Execute("ALTER TABLE " . TABLE_PAYPAL . " CHANGE zen_order_id order_id int(11) NOT NULL default '0'");
  2685. }
  2686. }
  2687. }