1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
26:
27: 28: 29: 30:
31: class Mage_Paypal_Model_Cart
32: {
33: 34: 35: 36: 37:
38: const TOTAL_SUBTOTAL = 'subtotal';
39: const TOTAL_DISCOUNT = 'discount';
40: const TOTAL_TAX = 'tax';
41: const TOTAL_SHIPPING = 'shipping';
42:
43: 44: 45: 46: 47: 48:
49: protected $_salesEntity = null;
50:
51: 52: 53: 54: 55: 56:
57: protected $_items = array();
58:
59: 60: 61: 62: 63: 64:
65: protected $_totals = array();
66:
67: 68: 69: 70: 71: 72:
73: protected $_totalLineItemDescriptions = array();
74:
75: 76: 77: 78: 79:
80: protected $_shouldRender = true;
81:
82: 83: 84: 85: 86:
87: protected $_areItemsValid = false;
88:
89: 90: 91: 92: 93:
94: protected $_areTotalsValid = false;
95:
96: 97: 98: 99: 100: 101:
102: protected $_isDiscountAsItem = false;
103:
104: 105: 106: 107: 108: 109:
110: protected $_isShippingAsItem = false;
111:
112: 113: 114: 115: 116:
117: public function __construct($params = array())
118: {
119: $salesEntity = array_shift($params);
120: if (is_object($salesEntity)
121: && (($salesEntity instanceof Mage_Sales_Model_Order) || ($salesEntity instanceof Mage_Sales_Model_Quote))) {
122: $this->_salesEntity = $salesEntity;
123: } else {
124: throw new Exception('Invalid sales entity provided.');
125: }
126: }
127:
128: 129: 130: 131: 132: 133:
134: public function getSalesEntity()
135: {
136: return $this->_salesEntity;
137: }
138:
139: 140: 141: 142: 143: 144: 145:
146: public function getItems($bypassValidation = false)
147: {
148: $this->_render();
149: if (!$bypassValidation && !$this->_areItemsValid) {
150: return false;
151: }
152: return $this->_items;
153: }
154:
155: 156: 157: 158: 159: 160: 161: 162:
163: public function getTotals($mergeDiscount = false)
164: {
165: $this->_render();
166:
167:
168: if (!$this->_areTotalsValid) {
169: $totals = array(self::TOTAL_SUBTOTAL =>
170: $this->_totals[self::TOTAL_SUBTOTAL] + $this->_totals[self::TOTAL_TAX]
171: );
172: if (!$this->_isShippingAsItem) {
173: $totals[self::TOTAL_SUBTOTAL] += $this->_totals[self::TOTAL_SHIPPING];
174: }
175: if (!$this->_isDiscountAsItem) {
176: $totals[self::TOTAL_SUBTOTAL] -= $this->_totals[self::TOTAL_DISCOUNT];
177: }
178: return $totals;
179: } elseif ($mergeDiscount) {
180: $totals = $this->_totals;
181: unset($totals[self::TOTAL_DISCOUNT]);
182: if (!$this->_isDiscountAsItem) {
183: $totals[self::TOTAL_SUBTOTAL] -= $this->_totals[self::TOTAL_DISCOUNT];
184: }
185: return $totals;
186: }
187: return $this->_totals;
188: }
189:
190: 191: 192: 193: 194: 195: 196: 197: 198:
199: public function addItem($name, $qty, $amount, $identifier = null)
200: {
201: $this->_shouldRender = true;
202: $item = new Varien_Object(array(
203: 'name' => $name,
204: 'qty' => $qty,
205: 'amount' => (float)$amount,
206: ));
207: if ($identifier) {
208: $item->setData('id', $identifier);
209: }
210: $this->_items[] = $item;
211: return $item;
212: }
213:
214: 215: 216: 217: 218: 219:
220: public function removeItem($identifier)
221: {
222: foreach ($this->_items as $key => $item) {
223: if ($item->getId() == $identifier) {
224: unset($this->_items[$key]);
225: return true;
226: }
227: }
228: return false;
229: }
230:
231: 232: 233: 234: 235: 236: 237: 238:
239: public function updateTotal($code, $amount, $lineItemDescription = null)
240: {
241: $this->_shouldRender = true;
242: if (isset($this->_totals[$code])) {
243: $this->_totals[$code] += $amount;
244: if ($lineItemDescription) {
245: $this->_totalLineItemDescriptions[$code][] = $lineItemDescription;
246: }
247: }
248: return $this;
249: }
250:
251: 252: 253: 254: 255: 256:
257: public function isDiscountAsItem($setValue = null)
258: {
259: return $this->_totalAsItem('_isDiscountAsItem', $setValue);
260: }
261:
262: 263: 264: 265: 266: 267:
268: public function isShippingAsItem($setValue = null)
269: {
270: return $this->_totalAsItem('_isShippingAsItem', $setValue);
271: }
272:
273: 274: 275:
276: protected function _render()
277: {
278: if (!$this->_shouldRender) {
279: return;
280: }
281:
282:
283: $this->_items = array();
284: foreach ($this->_salesEntity->getAllItems() as $item) {
285: if (!$item->getParentItem()) {
286: $this->_addRegularItem($item);
287: }
288: }
289: end($this->_items);
290: $lastRegularItemKey = key($this->_items);
291:
292:
293: $shippingDescription = '';
294: if ($this->_salesEntity instanceof Mage_Sales_Model_Order) {
295: $shippingDescription = $this->_salesEntity->getShippingDescription();
296: $this->_totals = array(
297: self::TOTAL_SUBTOTAL => $this->_salesEntity->getBaseSubtotal(),
298: self::TOTAL_TAX => $this->_salesEntity->getBaseTaxAmount(),
299: self::TOTAL_SHIPPING => $this->_salesEntity->getBaseShippingAmount(),
300: self::TOTAL_DISCOUNT => abs($this->_salesEntity->getBaseDiscountAmount()),
301: );
302: $this->_applyHiddenTaxWorkaround($this->_salesEntity);
303: } else {
304: $address = $this->_salesEntity->getIsVirtual() ?
305: $this->_salesEntity->getBillingAddress() : $this->_salesEntity->getShippingAddress();
306: $shippingDescription = $address->getShippingDescription();
307: $this->_totals = array (
308: self::TOTAL_SUBTOTAL => $this->_salesEntity->getBaseSubtotal(),
309: self::TOTAL_TAX => $address->getBaseTaxAmount(),
310: self::TOTAL_SHIPPING => $address->getBaseShippingAmount(),
311: self::TOTAL_DISCOUNT => abs($address->getBaseDiscountAmount()),
312: );
313: $this->_applyHiddenTaxWorkaround($address);
314: }
315: $originalDiscount = $this->_totals[self::TOTAL_DISCOUNT];
316:
317:
318: Mage::dispatchEvent('paypal_prepare_line_items', array('paypal_cart' => $this));
319:
320:
321: if ($originalDiscount > 0.0001 && isset($this->_totalLineItemDescriptions[self::TOTAL_DISCOUNT])) {
322: $this->_totalLineItemDescriptions[self::TOTAL_DISCOUNT][] = Mage::helper('sales')->__('Discount (%s)', Mage::app()->getStore()->convertPrice($originalDiscount, true, false));
323: }
324:
325:
326: if ($this->_isDiscountAsItem && $this->_totals[self::TOTAL_DISCOUNT]) {
327: $this->addItem(Mage::helper('paypal')->__('Discount'), 1, -1.00 * $this->_totals[self::TOTAL_DISCOUNT],
328: $this->_renderTotalLineItemDescriptions(self::TOTAL_DISCOUNT)
329: );
330: }
331: $shippingItemId = $this->_renderTotalLineItemDescriptions(self::TOTAL_SHIPPING, $shippingDescription);
332: if ($this->_isShippingAsItem && (float)$this->_totals[self::TOTAL_SHIPPING]) {
333: $this->addItem(Mage::helper('paypal')->__('Shipping'), 1, (float)$this->_totals[self::TOTAL_SHIPPING],
334: $shippingItemId
335: );
336: }
337:
338:
339: foreach ($this->_items as $key => $item) {
340: if ($key > $lastRegularItemKey && $item->getAmount() != 0) {
341: $this->_totals[self::TOTAL_SUBTOTAL] += $item->getAmount();
342: }
343: }
344:
345: $this->_validate();
346:
347: if (!$this->_areItemsValid) {
348: $this->removeItem($shippingItemId);
349: }
350:
351: $this->_shouldRender = false;
352: }
353:
354: 355: 356: 357: 358: 359: 360: 361: 362:
363: protected function _renderTotalLineItemDescriptions($code, $prepend = '', $append = '', $glue = '; ')
364: {
365: $result = array();
366: if ($prepend) {
367: $result[] = $prepend;
368: }
369: if (isset($this->_totalLineItemDescriptions[$code])) {
370: $result = array_merge($this->_totalLineItemDescriptions[$code]);
371: }
372: if ($append) {
373: $result[] = $append;
374: }
375: return implode($glue, $result);
376: }
377:
378: 379: 380:
381: protected function _validate()
382: {
383: $this->_areItemsValid = false;
384: $this->_areTotalsValid = false;
385:
386: $referenceAmount = $this->_salesEntity->getBaseGrandTotal();
387:
388: $itemsSubtotal = 0;
389: foreach ($this->_items as $i) {
390: $itemsSubtotal = $itemsSubtotal + $i['qty'] * $i['amount'];
391: }
392: $sum = $itemsSubtotal + $this->_totals[self::TOTAL_TAX];
393: if (!$this->_isShippingAsItem) {
394: $sum += $this->_totals[self::TOTAL_SHIPPING];
395: }
396: if (!$this->_isDiscountAsItem) {
397: $sum -= $this->_totals[self::TOTAL_DISCOUNT];
398: }
399: 400: 401: 402:
403:
404: if (sprintf('%.4F', $sum) == sprintf('%.4F', $referenceAmount)) {
405: $this->_areItemsValid = true;
406: }
407:
408:
409: if (!$this->_isDiscountAsItem) {
410: $this->_areTotalsValid = round($this->_totals[self::TOTAL_DISCOUNT], 4) < round($itemsSubtotal, 4);
411: } else {
412: $this->_areTotalsValid = $itemsSubtotal > 0.00001;
413: }
414:
415: $this->_areItemsValid = $this->_areItemsValid && $this->_areTotalsValid;
416: }
417:
418: 419: 420: 421: 422: 423:
424: protected function _addRegularItem(Varien_Object $salesItem)
425: {
426: if ($this->_salesEntity instanceof Mage_Sales_Model_Order) {
427: $qty = (int) $salesItem->getQtyOrdered();
428: $amount = (float) $salesItem->getBasePrice();
429:
430: } else {
431: $qty = (int) $salesItem->getTotalQty();
432: $amount = $salesItem->isNominal() ? 0 : (float) $salesItem->getBaseCalculationPrice();
433: }
434:
435: $subAggregatedLabel = '';
436: if ($amount - round($amount, 2)) {
437: $amount = $amount * $qty;
438: $subAggregatedLabel = ' x' . $qty;
439: $qty = 1;
440: }
441:
442:
443: if (($amount * $qty) != $salesItem->getBaseRowTotal()) {
444: $amount = (float) $salesItem->getBaseRowTotal();
445: $subAggregatedLabel = ' x' . $qty;
446: $qty = 1;
447: }
448:
449: return $this->addItem($salesItem->getName() . $subAggregatedLabel, $qty, $amount, $salesItem->getSku());
450: }
451:
452: 453: 454: 455: 456: 457: 458: 459:
460: private function _totalAsItem($var, $setValue = null)
461: {
462: if (null !== $setValue) {
463: if ($setValue != $this->$var) {
464: $this->_shouldRender = true;
465: }
466: $this->$var = $setValue;
467: return $this;
468: }
469: return $this->$var;
470: }
471:
472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492:
493: private function _applyHiddenTaxWorkaround($salesEntity)
494: {
495: $this->_totals[self::TOTAL_TAX] += (float)$salesEntity->getBaseHiddenTaxAmount();
496: $this->_totals[self::TOTAL_TAX] += (float)$salesEntity->getBaseShippingHiddenTaxAmount();
497: }
498: }
499: