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_Payment_Model_Recurring_Profile extends Mage_Core_Model_Abstract
32: {
33: 34: 35: 36: 37:
38: const BUY_REQUEST_START_DATETIME = 'recurring_profile_start_datetime';
39: const PRODUCT_OPTIONS_KEY = 'recurring_profile_options';
40:
41: 42: 43: 44: 45:
46: const PERIOD_UNIT_DAY = 'day';
47: const PERIOD_UNIT_WEEK = 'week';
48: const PERIOD_UNIT_SEMI_MONTH = 'semi_month';
49: const PERIOD_UNIT_MONTH = 'month';
50: const PERIOD_UNIT_YEAR = 'year';
51:
52: 53: 54: 55: 56:
57: protected $_errors = array();
58:
59: 60: 61: 62:
63: protected $_methodInstance = null;
64:
65: 66: 67: 68: 69:
70: protected $_locale = null;
71:
72: 73: 74: 75: 76:
77: protected $_store = null;
78:
79: 80: 81: 82: 83:
84: protected $_paymentMethods = array();
85:
86: 87: 88: 89: 90: 91:
92: public function isValid()
93: {
94: $this->_filterValues();
95: $this->_errors = array();
96:
97:
98: if (!$this->getStartDatetime()) {
99: $this->_errors['start_datetime'][] = Mage::helper('payment')->__('Start date is undefined.');
100: } elseif (!Zend_Date::isDate($this->getStartDatetime(), Varien_Date::DATETIME_INTERNAL_FORMAT)) {
101: $this->_errors['start_datetime'][] = Mage::helper('payment')->__('Start date has invalid format.');
102: }
103: if (!$this->getScheduleDescription()) {
104: $this->_errors['schedule_description'][] = Mage::helper('payment')->__('Schedule description must be not empty.');
105: }
106:
107:
108: if (!$this->getPeriodUnit() || !in_array($this->getPeriodUnit(), $this->getAllPeriodUnits(false), true)) {
109: $this->_errors['period_unit'][] = Mage::helper('payment')->__('Billing period unit is not defined or wrong.');
110: }
111: if ($this->getPeriodFrequency() && !$this->_validatePeriodFrequency('period_unit', 'period_frequency')) {
112: $this->_errors['period_frequency'][] = Mage::helper('payment')->__('Period frequency is wrong.');;
113: }
114:
115:
116: if ($this->getTrialPeriodUnit()) {
117: if (!in_array($this->getTrialPeriodUnit(), $this->getAllPeriodUnits(false), true)) {
118: $this->_errors['trial_period_unit'][] = Mage::helper('payment')->__('Trial billing period unit is wrong.');
119: }
120: if (!$this->getTrialPeriodFrequency() || !$this->_validatePeriodFrequency('trial_period_unit', 'trial_period_frequency')) {
121: $this->_errors['trial_period_frequency'][] = Mage::helper('payment')->__('Trial period frequency is wrong.');
122: }
123: if (!$this->getTrialPeriodMaxCycles()) {
124: $this->_errors['trial_period_max_cycles'][] = Mage::helper('payment')->__('Trial period max cycles is wrong.');
125: }
126: if (!$this->getTrialBillingAmount()) {
127: $this->_errors['trial_billing_amount'][] = Mage::helper('payment')->__('Trial billing amount is wrong.');
128: }
129: }
130:
131:
132: if (!$this->getBillingAmount() || 0 >= $this->getBillingAmount()) {
133: $this->_errors['billing_amount'][] = Mage::helper('payment')->__('Wrong or empty billing amount specified.');
134: }
135: foreach (array('trial_billing_abount', 'shipping_amount', 'tax_amount', 'init_amount') as $key) {
136: if ($this->hasData($key) && 0 >= $this->getData($key)) {
137: $this->_errors[$key][] = Mage::helper('payment')->__('Wrong %s specified.', $this->getFieldLabel($key));
138: }
139: }
140:
141:
142: if (!$this->getCurrencyCode()) {
143: $this->_errors['currency_code'][] = Mage::helper('payment')->__('Currency code is undefined.');
144: }
145:
146:
147: if (!$this->_methodInstance || !$this->getMethodCode()) {
148: $this->_errors['method_code'][] = Mage::helper('payment')->__('Payment method code is undefined.');
149: }
150: if ($this->_methodInstance) {
151: try {
152: $this->_methodInstance->validateRecurringProfile($this);
153: } catch (Mage_Core_Exception $e) {
154: $this->_errors['payment_method'][] = $e->getMessage();
155: }
156: }
157:
158: return empty($this->_errors);
159: }
160:
161: 162: 163: 164: 165: 166:
167: public function getValidationErrors($isGrouped = true, $asMessage = false)
168: {
169: if ($isGrouped && $this->_errors) {
170: $result = array();
171: foreach ($this->_errors as $row) {
172: $result[] = implode(' ', $row);
173: }
174: if ($asMessage) {
175: return Mage::throwException(
176: Mage::helper('payment')->__("Payment profile is invalid:\n%s", implode("\n", $result))
177: );
178: }
179: return $result;
180: }
181: return $this->_errors;
182: }
183:
184: 185: 186: 187: 188: 189: 190:
191: public function setMethodInstance(Mage_Payment_Model_Method_Abstract $object)
192: {
193: if ($object instanceof Mage_Payment_Model_Recurring_Profile_MethodInterface) {
194: $this->_methodInstance = $object;
195: } else {
196: throw new Exception('Invalid payment method instance for use in recurring profile.');
197: }
198: return $this;
199: }
200:
201: 202: 203: 204: 205: 206: 207: 208:
209: public function importBuyRequest(Varien_Object $buyRequest)
210: {
211: $startDate = $buyRequest->getData(self::BUY_REQUEST_START_DATETIME);
212: if ($startDate) {
213: $this->_ensureLocaleAndStore();
214: $dateFormat = $this->_locale->getDateTimeFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT);
215: $localeCode = $this->_locale->getLocaleCode();
216: if (!Zend_Date::isDate($startDate, $dateFormat, $localeCode)) {
217: Mage::throwException(Mage::helper('payment')->__('Recurring profile start date has invalid format.'));
218: }
219: $utcTime = $this->_locale->utcDate($this->_store, $startDate, true, $dateFormat)
220: ->toString(Varien_Date::DATETIME_INTERNAL_FORMAT);
221: $this->setStartDatetime($utcTime)->setImportedStartDatetime($startDate);
222: }
223: return $this->_filterValues();
224: }
225:
226: 227: 228: 229: 230: 231: 232:
233: public function importProduct(Mage_Catalog_Model_Product $product)
234: {
235: if ($product->isRecurring() && is_array($product->getRecurringProfile())) {
236:
237: $this->addData($product->getRecurringProfile());
238:
239:
240: if (!$this->hasScheduleDescription()) {
241: $this->setScheduleDescription($product->getName());
242: }
243:
244:
245: $options = $product->getCustomOption(self::PRODUCT_OPTIONS_KEY);
246: if ($options) {
247: $options = unserialize($options->getValue());
248: if (is_array($options)) {
249: if (isset($options['start_datetime'])) {
250: $startDatetime = new Zend_Date($options['start_datetime'], Varien_Date::DATETIME_INTERNAL_FORMAT);
251: $this->setNearestStartDatetime($startDatetime);
252: }
253: }
254: }
255:
256: return $this->_filterValues();
257: }
258: return false;
259: }
260:
261: 262: 263: 264: 265:
266: public function exportScheduleInfo()
267: {
268: $result = array(
269: new Varien_Object(array(
270: 'title' => Mage::helper('payment')->__('Billing Period'),
271: 'schedule' => $this->_renderSchedule('period_unit', 'period_frequency', 'period_max_cycles'),
272: ))
273: );
274: $trial = $this->_renderSchedule('trial_period_unit', 'trial_period_frequency', 'trial_period_max_cycles');
275: if ($trial) {
276: $result[] = new Varien_Object(array(
277: 'title' => Mage::helper('payment')->__('Trial Period'),
278: 'schedule' => $trial,
279: ));
280: }
281: return $result;
282: }
283:
284: 285: 286: 287: 288: 289:
290: public function setNearestStartDatetime(Zend_Date $minAllowed = null)
291: {
292:
293: $date = $minAllowed;
294: if (!$date || $date->getTimestamp() < time()) {
295: $date = new Zend_Date(time());
296: }
297: $this->setStartDatetime($date->toString(Varien_Date::DATETIME_INTERNAL_FORMAT));
298: return $this;
299: }
300:
301: 302: 303: 304: 305: 306:
307: public function exportStartDatetime($asString = true)
308: {
309: $datetime = $this->getStartDatetime();
310: if (!$datetime || !$this->_locale || !$this->_store) {
311: return;
312: }
313: $date = $this->_locale->storeDate($this->_store, strtotime($datetime), true);
314: if ($asString) {
315: return $date->toString($this->_locale->getDateTimeFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT));
316: }
317: return $date;
318: }
319:
320: 321: 322: 323: 324: 325:
326: public function setLocale(Mage_Core_Model_Locale $locale)
327: {
328: $this->_locale = $locale;
329: return $this;
330: }
331:
332: 333: 334: 335: 336: 337:
338: public function setStore(Mage_Core_Model_Store $store)
339: {
340: $this->_store = $store;
341: return $this;
342: }
343:
344: 345: 346: 347: 348: 349:
350: public function getAllPeriodUnits($withLabels = true)
351: {
352: $units = array(
353: self::PERIOD_UNIT_DAY,
354: self::PERIOD_UNIT_WEEK,
355: self::PERIOD_UNIT_SEMI_MONTH,
356: self::PERIOD_UNIT_MONTH,
357: self::PERIOD_UNIT_YEAR
358: );
359:
360: if ($withLabels) {
361: $result = array();
362: foreach ($units as $unit) {
363: $result[$unit] = $this->getPeriodUnitLabel($unit);
364: }
365: return $result;
366: }
367: return $units;
368: }
369:
370: 371: 372: 373: 374:
375: public function getPeriodUnitLabel($unit)
376: {
377: switch ($unit) {
378: case self::PERIOD_UNIT_DAY: return Mage::helper('payment')->__('Day');
379: case self::PERIOD_UNIT_WEEK: return Mage::helper('payment')->__('Week');
380: case self::PERIOD_UNIT_SEMI_MONTH: return Mage::helper('payment')->__('Two Weeks');
381: case self::PERIOD_UNIT_MONTH: return Mage::helper('payment')->__('Month');
382: case self::PERIOD_UNIT_YEAR: return Mage::helper('payment')->__('Year');
383: }
384: return $unit;
385: }
386:
387: 388: 389: 390: 391: 392:
393: public function getFieldLabel($field)
394: {
395: switch ($field) {
396: case 'subscriber_name':
397: return Mage::helper('payment')->__('Subscriber Name');
398: case 'start_datetime':
399: return Mage::helper('payment')->__('Start Date');
400: case 'internal_reference_id':
401: return Mage::helper('payment')->__('Internal Reference ID');
402: case 'schedule_description':
403: return Mage::helper('payment')->__('Schedule Description');
404: case 'suspension_threshold':
405: return Mage::helper('payment')->__('Maximum Payment Failures');
406: case 'bill_failed_later':
407: return Mage::helper('payment')->__('Auto Bill on Next Cycle');
408: case 'period_unit':
409: return Mage::helper('payment')->__('Billing Period Unit');
410: case 'period_frequency':
411: return Mage::helper('payment')->__('Billing Frequency');
412: case 'period_max_cycles':
413: return Mage::helper('payment')->__('Maximum Billing Cycles');
414: case 'billing_amount':
415: return Mage::helper('payment')->__('Billing Amount');
416: case 'trial_period_unit':
417: return Mage::helper('payment')->__('Trial Billing Period Unit');
418: case 'trial_period_frequency':
419: return Mage::helper('payment')->__('Trial Billing Frequency');
420: case 'trial_period_max_cycles':
421: return Mage::helper('payment')->__('Maximum Trial Billing Cycles');
422: case 'trial_billing_amount':
423: return Mage::helper('payment')->__('Trial Billing Amount');
424: case 'currency_code':
425: return Mage::helper('payment')->__('Currency');
426: case 'shipping_amount':
427: return Mage::helper('payment')->__('Shipping Amount');
428: case 'tax_amount':
429: return Mage::helper('payment')->__('Tax Amount');
430: case 'init_amount':
431: return Mage::helper('payment')->__('Initial Fee');
432: case 'init_may_fail':
433: return Mage::helper('payment')->__('Allow Initial Fee Failure');
434: case 'method_code':
435: return Mage::helper('payment')->__('Payment Method');
436: case 'reference_id':
437: return Mage::helper('payment')->__('Payment Reference ID');
438: }
439: }
440:
441: 442: 443: 444: 445: 446:
447: public function ($field)
448: {
449: switch ($field) {
450: case 'subscriber_name':
451: return Mage::helper('payment')->__('Full name of the person receiving the product or service paid for by the recurring payment.');
452: case 'start_datetime':
453: return Mage::helper('payment')->__('The date when billing for the profile begins.');
454: case 'schedule_description':
455: return Mage::helper('payment')->__('Short description of the recurring payment. By default equals to the product name.');
456: case 'suspension_threshold':
457: return Mage::helper('payment')->__('The number of scheduled payments that can fail before the profile is automatically suspended.');
458: case 'bill_failed_later':
459: return Mage::helper('payment')->__('Automatically bill the outstanding balance amount in the next billing cycle (if there were failed payments).');
460: case 'period_unit':
461: return Mage::helper('payment')->__('Unit for billing during the subscription period.');
462: case 'period_frequency':
463: return Mage::helper('payment')->__('Number of billing periods that make up one billing cycle.');
464: case 'period_max_cycles':
465: return Mage::helper('payment')->__('The number of billing cycles for payment period.');
466: case 'init_amount':
467: return Mage::helper('payment')->__('Initial non-recurring payment amount due immediately upon profile creation.');
468: case 'init_may_fail':
469: return Mage::helper('payment')->__('Whether to suspend the payment profile if the initial fee fails or add it to the outstanding balance.');
470: }
471: }
472:
473: 474: 475: 476: 477: 478:
479: public function renderData($key)
480: {
481: $value = $this->_getData($key);
482: switch ($key) {
483: case 'period_unit':
484: return $this->getPeriodUnitLabel($value);
485: case 'method_code':
486: if (!$this->_paymentMethods) {
487: $this->_paymentMethods = Mage::helper('payment')->getPaymentMethodList(false);
488: }
489: if (isset($this->_paymentMethods[$value])) {
490: return $this->_paymentMethods[$value];
491: }
492: break;
493: case 'start_datetime':
494: return $this->exportStartDatetime(true);
495: }
496: return $value;
497: }
498:
499: 500: 501: 502: 503:
504: protected function _filterValues()
505: {
506:
507: if ($this->_methodInstance) {
508: $this->setMethodCode($this->_methodInstance->getCode());
509: }
510: elseif ($this->getMethodCode()) {
511: $this->getMethodInstance();
512: }
513:
514:
515: foreach (array('schedule_description',
516: 'suspension_threshold', 'bill_failed_later', 'period_frequency', 'period_max_cycles', 'reference_id',
517: 'trial_period_unit', 'trial_period_frequency', 'trial_period_max_cycles', 'init_may_fail') as $key) {
518: if ($this->hasData($key) && (!$this->getData($key) || '0' == $this->getData($key))) {
519: $this->unsetData($key);
520: }
521: }
522:
523:
524: foreach (array(
525: 'billing_amount', 'trial_billing_amount', 'shipping_amount', 'tax_amount', 'init_amount') as $key) {
526: if ($this->hasData($key)) {
527: if (!$this->getData($key) || 0 == $this->getData($key)) {
528: $this->unsetData($key);
529: } else {
530: $this->setData($key, sprintf('%.4F', $this->getData($key)));
531: }
532: }
533: }
534:
535:
536: if ($this->getStartDatetime()) {
537: $date = new Zend_Date($this->getStartDatetime(), Varien_Date::DATETIME_INTERNAL_FORMAT);
538: $this->setNearestStartDatetime($date);
539: } else {
540: $this->setNearestStartDatetime();
541: }
542:
543: return $this;
544: }
545:
546: 547: 548: 549: 550:
551: protected function _ensureLocaleAndStore()
552: {
553: if (!$this->_locale || !$this->_store) {
554: throw new Exception('Locale and store instances must be set for this operation.');
555: }
556: }
557:
558: 559: 560: 561: 562:
563: protected function getMethodInstance()
564: {
565: if (!$this->_methodInstance) {
566: $this->setMethodInstance(Mage::helper('payment')->getMethodInstance($this->getMethodCode()));
567: }
568: $this->_methodInstance->setStore($this->getStoreId());
569: return $this->_methodInstance;
570: }
571:
572: 573: 574: 575: 576: 577: 578:
579: protected function _validatePeriodFrequency($unitKey, $frequencyKey)
580: {
581: if ($this->getData($unitKey) == self::PERIOD_UNIT_SEMI_MONTH && $this->getData($frequencyKey) != 1) {
582: return false;
583: }
584: return true;
585: }
586:
587: 588: 589: 590: 591:
592: protected function _validateBeforeSave()
593: {
594: if (!$this->isValid()) {
595: Mage::throwException($this->getValidationErrors(true, true));
596: }
597: if (!$this->getInternalReferenceId()) {
598: Mage::throwException(
599: Mage::helper('payment')->__('An internal reference ID is required to save the payment profile.')
600: );
601: }
602: }
603:
604: 605: 606: 607: 608:
609: protected function _beforeSave()
610: {
611: $this->_validateBeforeSave();
612: return parent::_beforeSave();
613: }
614:
615: 616: 617: 618: 619: 620: 621: 622: 623: 624:
625: protected function _renderSchedule($periodKey, $frequencyKey, $cyclesKey)
626: {
627: $result = array();
628:
629: $period = $this->_getData($periodKey);
630: $frequency = (int)$this->_getData($frequencyKey);
631: if (!$period || !$frequency) {
632: return $result;
633: }
634: if (self::PERIOD_UNIT_SEMI_MONTH == $period) {
635: $frequency = '';
636: }
637: $result[] = Mage::helper('payment')->__('%s %s cycle.', $frequency, $this->getPeriodUnitLabel($period));
638:
639: $cycles = (int)$this->_getData($cyclesKey);
640: if ($cycles) {
641: $result[] = Mage::helper('payment')->__('Repeats %s time(s).', $cycles);
642: } else {
643: $result[] = Mage::helper('payment')->__('Repeats until suspended or canceled.');
644: }
645: return $result;
646: }
647: }
648: