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: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101:
102: class Mage_Sales_Model_Recurring_Profile extends Mage_Payment_Model_Recurring_Profile
103: {
104: 105: 106: 107: 108:
109: const STATE_UNKNOWN = 'unknown';
110: const STATE_PENDING = 'pending';
111: const STATE_ACTIVE = 'active';
112: const STATE_SUSPENDED = 'suspended';
113: const STATE_CANCELED = 'canceled';
114: const STATE_EXPIRED = 'expired';
115:
116: 117: 118: 119: 120:
121: const PAYMENT_TYPE_REGULAR = 'regular';
122: const PAYMENT_TYPE_TRIAL = 'trial';
123: const PAYMENT_TYPE_INITIAL = 'initial';
124:
125: 126: 127: 128: 129:
130: protected $_workflow = null;
131:
132: 133: 134: 135: 136: 137:
138: public function loadByInternalReferenceId($internalReferenceId)
139: {
140: return $this->load($internalReferenceId, 'internal_reference_id');
141: }
142:
143: 144: 145: 146:
147: public function submit()
148: {
149: $this->_getResource()->beginTransaction();
150: try {
151: $this->setInternalReferenceId(Mage::helper('core')->uniqHash('temporary-'));
152: $this->save();
153: $this->setInternalReferenceId(Mage::helper('core')->uniqHash($this->getId() . '-'));
154: $this->getMethodInstance()->submitRecurringProfile($this, $this->getQuote()->getPayment());
155: $this->save();
156: $this->_getResource()->commit();
157: } catch (Exception $e) {
158: $this->_getResource()->rollBack();
159: throw $e;
160: }
161: }
162:
163: 164: 165:
166: public function activate()
167: {
168: $this->_checkWorkflow(self::STATE_ACTIVE, false);
169: $this->setNewState(self::STATE_ACTIVE);
170: $this->getMethodInstance()->updateRecurringProfileStatus($this);
171: $this->setState(self::STATE_ACTIVE)
172: ->save();
173: }
174:
175: 176: 177: 178: 179:
180: public function canActivate()
181: {
182: return $this->_checkWorkflow(self::STATE_ACTIVE);
183: }
184:
185: 186: 187:
188: public function suspend()
189: {
190: $this->_checkWorkflow(self::STATE_SUSPENDED, false);
191: $this->setNewState(self::STATE_SUSPENDED);
192: $this->getMethodInstance()->updateRecurringProfileStatus($this);
193: $this->setState(self::STATE_SUSPENDED)
194: ->save();
195: }
196:
197: 198: 199: 200: 201:
202: public function canSuspend()
203: {
204: return $this->_checkWorkflow(self::STATE_SUSPENDED);
205: }
206:
207: 208: 209:
210: public function cancel()
211: {
212: $this->_checkWorkflow(self::STATE_CANCELED, false);
213: $this->setNewState(self::STATE_CANCELED);
214: $this->getMethodInstance()->updateRecurringProfileStatus($this);
215: $this->setState(self::STATE_CANCELED)
216: ->save();
217: }
218:
219: 220: 221: 222: 223:
224: public function canCancel()
225: {
226: return $this->_checkWorkflow(self::STATE_CANCELED);
227: }
228:
229: public function fetchUpdate()
230: {
231: $result = new Varien_Object();
232: $this->getMethodInstance()->getRecurringProfileDetails($this->getReferenceId(), $result);
233:
234: if ($result->getIsProfileActive()) {
235: $this->setState(self::STATE_ACTIVE);
236: } elseif ($result->getIsProfilePending()) {
237: $this->setState(self::STATE_PENDING);
238: } elseif ($result->getIsProfileCanceled()) {
239: $this->setState(self::STATE_CANCELED);
240: } elseif ($result->getIsProfileSuspended()) {
241: $this->setState(self::STATE_SUSPENDED);
242: } elseif ($result->getIsProfileExpired()) {
243: $this->setState(self::STATE_EXPIRED);
244: }
245: }
246:
247: public function canFetchUpdate()
248: {
249: return $this->getMethodInstance()->canGetRecurringProfileDetails();
250: }
251:
252:
253:
254:
255:
256:
257:
258:
259:
260:
261:
262:
263:
264:
265:
266:
267:
268:
269:
270:
271:
272:
273:
274:
275:
276:
277:
278:
279:
280:
281:
282: 283: 284: 285: 286: 287: 288:
289: public function createOrder()
290: {
291: $items = array();
292: $itemInfoObjects = func_get_args();
293:
294: $billingAmount = 0;
295: $shippingAmount = 0;
296: $taxAmount = 0;
297: $isVirtual = 1;
298: $weight = 0;
299: foreach ($itemInfoObjects as $itemInfo) {
300: $item = $this->_getItem($itemInfo);
301: $billingAmount += $item->getPrice();
302: $shippingAmount += $item->getShippingAmount();
303: $taxAmount += $item->getTaxAmount();
304: $weight += $item->getWeight();
305: if (!$item->getIsVirtual()) {
306: $isVirtual = 0;
307: }
308: $items[] = $item;
309: }
310: $grandTotal = $billingAmount + $shippingAmount + $taxAmount;
311:
312: $order = Mage::getModel('sales/order');
313:
314: $billingAddress = Mage::getModel('sales/order_address')
315: ->setData($this->getBillingAddressInfo())
316: ->setId(null);
317:
318: $shippingInfo = $this->getShippingAddressInfo();
319: $shippingAddress = Mage::getModel('sales/order_address')
320: ->setData($shippingInfo)
321: ->setId(null);
322:
323: $payment = Mage::getModel('sales/order_payment')
324: ->setMethod($this->getMethodCode());
325:
326: $transferDataKays = array(
327: 'store_id', 'store_name', 'customer_id', 'customer_email',
328: 'customer_firstname', 'customer_lastname', 'customer_middlename', 'customer_prefix',
329: 'customer_suffix', 'customer_taxvat', 'customer_gender', 'customer_is_guest',
330: 'customer_note_notify', 'customer_group_id', 'customer_note', 'shipping_method',
331: 'shipping_description', 'base_currency_code', 'global_currency_code', 'order_currency_code',
332: 'store_currency_code', 'base_to_global_rate', 'base_to_order_rate', 'store_to_base_rate',
333: 'store_to_order_rate'
334: );
335:
336: $orderInfo = $this->getOrderInfo();
337: foreach ($transferDataKays as $key) {
338: if (isset($orderInfo[$key])) {
339: $order->setData($key, $orderInfo[$key]);
340: } elseif (isset($shippingInfo[$key])) {
341: $order->setData($key, $shippingInfo[$key]);
342: }
343: }
344:
345: $order->setStoreId($this->getStoreId())
346: ->setState(Mage_Sales_Model_Order::STATE_NEW)
347: ->setBaseToOrderRate($this->getInfoValue('order_info', 'base_to_quote_rate'))
348: ->setStoreToOrderRate($this->getInfoValue('order_info', 'store_to_quote_rate'))
349: ->setOrderCurrencyCode($this->getInfoValue('order_info', 'quote_currency_code'))
350: ->setBaseSubtotal($billingAmount)
351: ->setSubtotal($billingAmount)
352: ->setBaseShippingAmount($shippingAmount)
353: ->setShippingAmount($shippingAmount)
354: ->setBaseTaxAmount($taxAmount)
355: ->setTaxAmount($taxAmount)
356: ->setBaseGrandTotal($grandTotal)
357: ->setGrandTotal($grandTotal)
358: ->setIsVirtual($isVirtual)
359: ->setWeight($weight)
360: ->setTotalQtyOrdered($this->getInfoValue('order_info', 'items_qty'))
361: ->setBillingAddress($billingAddress)
362: ->setShippingAddress($shippingAddress)
363: ->setPayment($payment);
364:
365: foreach ($items as $item) {
366: $order->addItem($item);
367: }
368:
369: return $order;
370: }
371:
372: 373: 374: 375: 376:
377: public function isValid()
378: {
379: parent::isValid();
380:
381:
382: if (!in_array($this->getState(), $this->getAllStates(false), true)) {
383: $this->_errors['state'][] = Mage::helper('sales')->__('Wrong state: "%s".', $this->getState());
384: }
385:
386: return empty($this->_errors);
387: }
388:
389: 390: 391: 392: 393: 394:
395: public function importQuote(Mage_Sales_Model_Quote $quote)
396: {
397: $this->setQuote($quote);
398:
399: if ($quote->getPayment() && $quote->getPayment()->getMethod()) {
400: $this->setMethodInstance($quote->getPayment()->getMethodInstance());
401: }
402:
403: $orderInfo = $quote->getData();
404: $this->_cleanupArray($orderInfo);
405: $this->setOrderInfo($orderInfo);
406:
407: $addressInfo = $quote->getBillingAddress()->getData();
408: $this->_cleanupArray($addressInfo);
409: $this->setBillingAddressInfo($addressInfo);
410: if (!$quote->isVirtual()) {
411: $addressInfo = $quote->getShippingAddress()->getData();
412: $this->_cleanupArray($addressInfo);
413: $this->setShippingAddressInfo($addressInfo);
414: }
415:
416: $this->setCurrencyCode($quote->getBaseCurrencyCode());
417: $this->setCustomerId($quote->getCustomerId());
418: $this->setStoreId($quote->getStoreId());
419:
420: return $this;
421: }
422:
423: 424: 425: 426: 427: 428:
429: public function importQuoteItem(Mage_Sales_Model_Quote_Item_Abstract $item)
430: {
431: $this->setQuoteItemInfo($item);
432:
433:
434: $this->setBillingAmount($item->getBaseRowTotal())
435: ->setTaxAmount($item->getBaseTaxAmount())
436: ->setShippingAmount($item->getBaseShippingAmount())
437: ;
438: if (!$this->getScheduleDescription()) {
439: $this->setScheduleDescription($item->getName());
440: }
441:
442: $orderItemInfo = $item->getData();
443: $this->_cleanupArray($orderItemInfo);
444:
445: $customOptions = $item->getOptionsByCode();
446: if ($customOptions['info_buyRequest']) {
447: $orderItemInfo['info_buyRequest'] = $customOptions['info_buyRequest']->getValue();
448: }
449:
450: $this->setOrderItemInfo($orderItemInfo);
451:
452: return $this->_filterValues();
453: }
454:
455: 456: 457: 458: 459: 460:
461: public function getFieldLabel($field)
462: {
463: switch ($field) {
464: case 'order_item_id':
465: return Mage::helper('sales')->__('Purchased Item');
466: case 'state':
467: return Mage::helper('sales')->__('Profile State');
468: case 'created_at':
469: return Mage::helper('adminhtml')->__('Created At');
470: case 'updated_at':
471: return Mage::helper('adminhtml')->__('Updated At');
472: default:
473: return parent::getFieldLabel($field);
474: }
475: }
476:
477: 478: 479: 480: 481: 482:
483: public function ($field)
484: {
485: switch ($field) {
486: case 'order_item_id':
487: return Mage::helper('sales')->__('Original order item that recurring payment profile correspondss to.');
488: default:
489: return parent::getFieldComment($field);
490: }
491: }
492:
493: 494: 495: 496: 497: 498:
499: public function getAllStates($withLabels = true)
500: {
501: $states = array(self::STATE_UNKNOWN, self::STATE_PENDING, self::STATE_ACTIVE,
502: self::STATE_SUSPENDED, self::STATE_CANCELED, self::STATE_EXPIRED,
503: );
504: if ($withLabels) {
505: $result = array();
506: foreach ($states as $state) {
507: $result[$state] = $this->getStateLabel($state);
508: }
509: return $result;
510: }
511: return $states;
512: }
513:
514: 515: 516: 517: 518: 519:
520: public function getStateLabel($state)
521: {
522: switch ($state) {
523: case self::STATE_UNKNOWN: return Mage::helper('sales')->__('Not Initialized');
524: case self::STATE_PENDING: return Mage::helper('sales')->__('Pending');
525: case self::STATE_ACTIVE: return Mage::helper('sales')->__('Active');
526: case self::STATE_SUSPENDED: return Mage::helper('sales')->__('Suspended');
527: case self::STATE_CANCELED: return Mage::helper('sales')->__('Canceled');
528: case self::STATE_EXPIRED: return Mage::helper('sales')->__('Expired');
529: default: return $state;
530: }
531: }
532:
533: 534: 535: 536: 537: 538:
539: public function renderData($key)
540: {
541: $value = $this->_getData($key);
542: switch ($key) {
543: case 'state':
544: return $this->getStateLabel($value);
545: }
546: return parent::renderData($key);
547: }
548:
549: 550: 551: 552: 553: 554: 555: 556:
557: public function getInfoValue($infoKey, $infoValueKey)
558: {
559: $info = $this->getData($infoKey);
560: if (!$info) {
561: return;
562: }
563: if (!is_object($info)) {
564: if (is_array($info) && isset($info[$infoValueKey])) {
565: return $info[$infoValueKey];
566: }
567: } else {
568: if ($info instanceof Varien_Object) {
569: return $info->getDataUsingMethod($infoValueKey);
570: } elseif (isset($info->$infoValueKey)) {
571: return $info->$infoValueKey;
572: }
573: }
574: }
575:
576: 577: 578:
579: protected function _construct()
580: {
581: $this->_init('sales/recurring_profile');
582: }
583:
584: 585: 586: 587: 588:
589: protected function _filterValues()
590: {
591: $result = parent::_filterValues();
592:
593: if (!$this->getState()) {
594: $this->setState(self::STATE_UNKNOWN);
595: }
596:
597: return $result;
598: }
599:
600: 601: 602:
603: protected function _initWorkflow()
604: {
605: if (null === $this->_workflow) {
606: $this->_workflow = array(
607: 'unknown' => array('pending', 'active', 'suspended', 'canceled'),
608: 'pending' => array('active', 'canceled'),
609: 'active' => array('suspended', 'canceled'),
610: 'suspended' => array('active', 'canceled'),
611: 'canceled' => array(),
612: 'expired' => array(),
613: );
614: }
615: }
616:
617: 618: 619: 620: 621: 622: 623: 624:
625: protected function _checkWorkflow($againstState, $soft = true)
626: {
627: $this->_initWorkflow();
628: $state = $this->getState();
629: $result = (!empty($this->_workflow[$state])) && in_array($againstState, $this->_workflow[$state]);
630: if (!$soft && !$result) {
631: Mage::throwException(
632: Mage::helper('sales')->__('This profile state cannot be changed to "%s".', $againstState)
633: );
634: }
635: return $result;
636: }
637:
638: 639: 640: 641: 642:
643: public function getChildOrderIds()
644: {
645: $ids = $this->_getResource()->getChildOrderIds($this);
646: if (empty($ids)){
647: $ids[] = '-1';
648: }
649: return $ids;
650: }
651:
652: 653: 654: 655: 656: 657:
658: public function addOrderRelation($orderId)
659: {
660: $this->getResource()->addOrderRelation($this->getId(), $orderId);
661: return $this;
662: }
663:
664: 665: 666: 667: 668: 669:
670: protected function _getItem($itemInfo)
671: {
672: $paymentType = $itemInfo->getPaymentType();
673: if (!$paymentType) {
674: throw new Exception("Recurring profile payment type is not specified.");
675: }
676:
677: switch ($paymentType) {
678: case self::PAYMENT_TYPE_REGULAR: return $this->_getRegularItem($itemInfo);
679: case self::PAYMENT_TYPE_TRIAL: return $this->_getTrialItem($itemInfo);
680: case self::PAYMENT_TYPE_INITIAL: return $this->_getInitialItem($itemInfo);
681: default: new Exception("Invalid recurring profile payment type '{$paymentType}'.");
682: }
683: }
684:
685: 686: 687: 688: 689: 690: 691:
692: protected function _getRegularItem($itemInfo)
693: {
694: $price = $itemInfo->getPrice() ? $itemInfo->getPrice() : $this->getBillingAmount();
695: $shippingAmount = $itemInfo->getShippingAmount() ? $itemInfo->getShippingAmount() : $this->getShippingAmount();
696: $taxAmount = $itemInfo->getTaxAmount() ? $itemInfo->getTaxAmount() : $this->getTaxAmount();
697:
698: $item = Mage::getModel('sales/order_item')
699: ->setData($this->getOrderItemInfo())
700: ->setQtyOrdered($this->getInfoValue('order_item_info', 'qty'))
701: ->setBaseOriginalPrice($this->getInfoValue('order_item_info', 'price'))
702: ->setPrice($price)
703: ->setBasePrice($price)
704: ->setRowTotal($price)
705: ->setBaseRowTotal($price)
706: ->setTaxAmount($taxAmount)
707: ->setShippingAmount($shippingAmount)
708: ->setId(null);
709: return $item;
710: }
711:
712: 713: 714: 715: 716: 717: 718:
719: protected function _getTrialItem($itemInfo)
720: {
721: $item = $this->_getRegularItem($itemInfo);
722:
723: $item->setName(
724: Mage::helper('sales')->__('Trial ') . $item->getName()
725: );
726:
727: $option = array(
728: 'label' => Mage::helper('sales')->__('Payment type'),
729: 'value' => Mage::helper('sales')->__('Trial period payment')
730: );
731:
732: $this->_addAdditionalOptionToItem($item, $option);
733:
734: return $item;
735: }
736:
737: 738: 739: 740: 741: 742: 743:
744: protected function _getInitialItem($itemInfo)
745: {
746: $price = $itemInfo->getPrice() ? $itemInfo->getPrice() : $this->getInitAmount();
747: $shippingAmount = $itemInfo->getShippingAmount() ? $itemInfo->getShippingAmount() : 0;
748: $taxAmount = $itemInfo->getTaxAmount() ? $itemInfo->getTaxAmount() : 0;
749: $item = Mage::getModel('sales/order_item')
750: ->setStoreId($this->getStoreId())
751: ->setProductType(Mage_Catalog_Model_Product_Type::TYPE_VIRTUAL)
752: ->setIsVirtual(1)
753: ->setSku('initial_fee')
754: ->setName(Mage::helper('sales')->__('Recurring Profile Initial Fee'))
755: ->setDescription('')
756: ->setWeight(0)
757: ->setQtyOrdered(1)
758: ->setPrice($price)
759: ->setOriginalPrice($price)
760: ->setBasePrice($price)
761: ->setBaseOriginalPrice($price)
762: ->setRowTotal($price)
763: ->setBaseRowTotal($price)
764: ->setTaxAmount($taxAmount)
765: ->setShippingAmount($shippingAmount);
766:
767: $option = array(
768: 'label' => Mage::helper('sales')->__('Payment type'),
769: 'value' => Mage::helper('sales')->__('Initial period payment')
770: );
771:
772: $this->_addAdditionalOptionToItem($item, $option);
773: return $item;
774: }
775:
776: 777: 778: 779: 780: 781:
782: protected function _addAdditionalOptionToItem($item, $option)
783: {
784: $options = $item->getProductOptions();
785: $additionalOptions = $item->getProductOptionByCode('additional_options');
786: if (is_array($additionalOptions)) {
787: $additionalOptions[] = $option;
788: } else {
789: $additionalOptions = array($option);
790: }
791: $options['additional_options'] = $additionalOptions;
792: $item->setProductOptions($options);
793: }
794:
795: 796: 797: 798: 799:
800: private function _cleanupArray(&$array)
801: {
802: if (!$array) {
803: return;
804: }
805: foreach ($array as $key => $value) {
806: if (is_object($value)) {
807: unset($array[$key]);
808: } elseif (is_array($value)) {
809: $this->_cleanupArray($array[$key]);
810: }
811: }
812: }
813: }
814: