Skip to content

Commit

Permalink
Add copyright, update readmy, implemented validate_purchase on purchases
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivan Kerin committed Sep 23, 2013
1 parent a1d7eb2 commit 738e0aa
Show file tree
Hide file tree
Showing 19 changed files with 224 additions and 138 deletions.
18 changes: 18 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
language: php

php:
- 5.4

script:
- phpunit --coverage-clover build/logs/clover.xml

before_script:
- mkdir -p build/logs
- composer require --dev satooshi/php-coveralls:dev-master
- composer install
- mysql -e 'create database `test-promotions`;'
- mysql --default-character-set=utf8 test-promotions < tests/test_data/structure.sql

after_script:
- php vendor/bin/coveralls -v
95 changes: 32 additions & 63 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,86 +1,55 @@
# Promotions Module

The promotions module gives the ability to attach promotions to specific purchase (depends on the purchases kohana module)
[![Build Status](https://travis-ci.org/OpenBuildings/promotions.png?branch=master)](https://travis-ci.org/OpenBuildings/promotions)
[![Coverage Status](https://coveralls.io/repos/OpenBuildings/promotions/badge.png?branch=master)](https://coveralls.io/r/OpenBuildings/promotions?branch=master)
[![Latest Stable Version](https://poser.pugx.org/openbuildings/promotions/v/stable.png)](https://packagist.org/packages/openbuildings/promotions)

It has support both for attaching promo codes and promotions to a purchase
This module gives the ability to define promotions with a set of requirements that add themselves to purchases, based on these rules. Each promotion can have a static or dynamic/configurable amount.

## Promotions
Promotions can also have ontime or multiple time uses for promo_codes

Each promotion is defined by the following fields:
- __name__ - the name of the promotion
_ __description__ - short description explaining the promotion
- __type__ - the type of the promotion e.g free_shipping, discount. __Must__ be underscored word in order the callbacks to work
- __requirement__ - requirement for the purchase so the promotion is valid
- __value__ - percent or absolute value of the promotion. Depends from the __type__ field and your own implementation
- __currency__ - optional
- __requires_promo_code__ - specify whether the promotion needs an attached promo code or not
- __created_at__ - creation date
- __expires_at__ - date of expiry of the promotion
## Usage


### Adding a custom promotion

By default promotions module comes with 2 promotion types:
- promotion for orders above certain purchase price (min_purchase_price)
- promotion with promo code attached to the purchase (discount)

In order to add your own promotions, one must implement 2 specific methods in the Promotion model:

- applies_to_{{promotion_type}} - the method is called when $purchase->update_promotions() is called. Must return boolean.
- price_{{promotion_type}} - the method calculates the price of the specified promotion

Example:
Add a behavior to the purchase and store_purchase models:

```php
class Model_Store_Purchase extends Kohana_Model_Store_Purchase {

class Model_Test_Promotion extends Model_Promotion {

const TYPE_FREE_SHIPPING = 'free_shipping';

public function price_free_shipping(Model_Purchase_Item $purchase_item)
public static function initialize(Jam_Meta $meta)
{
// perform your custom price calculations for this promotion or return a fixed promotion value
return $this->value;
}
parent::initialize($meta);

/**
* Apply free shipping promotion for all purchases from Europe
* above certain price (defined in the requirement field)
*/
public function applies_to_free_shipping(Model_Purchase_Item $purchase_item)
{
if ($this->type == Model_Test_Promotion::TYPE_FREE_SHIPPING)
{
$purchase = $purchase_item->purchase_insist();
$country = $purchase->country;
$currency = ($purchase->currency === 'EUR') ? 'EUR' : 'GBP';

return ($country === 'Europe' AND $purchase->total_price('product') >= (float) $this->requirement AND $currency == $this->currency);
}

return FALSE;
$meta
->behaviors(array(
'promotable_store_purchase' => Jam::behavior('promotable_store_purchase'),
));
}
}
// ...
class Model_Purchase extends Kohana_Model_Purchase {

```
public static function initialize(Jam_Meta $meta)
{
parent::initialize($meta);

## Attaching promo codes
$meta
->behaviors(array(
'promotable_purchase' => Jam::behavior('promotable_purchase'),
));
}
}
```php

Promo codes are attached to the purchase as a string field and the corresponding promo code object (if its valid) is attached to the purchase before validation.
And you'll need to add actual promotions to the database. The promtion modle uses single table inheritence to have a different class for each promotion. Each of these has to define "applies_to" and "price_for_purchase_item" which your promotions will have to implement. There is also the ``Model_Promotion_Promocode`` class which gives the promotion the ability to use promo codes which exhaust themselves when are used.

Example:
There are 2 availbale predefined promotions:

```php
$purchase->_promo_code = '2AXHG';
if ($purchase->check())
{
// we now have our promo code object attached to the purchase
echo $purchase->promo_code->code; // outputs '2AXHG'
}
```
* Model_Promotion_Promocode_Giftcard - to use it you'll need to enter requirement - the minimum price where the promotion applies, and amount - the amount (Jam_Price) to be reducted from the purchase
* Model_Promotion_Promocode_Percent - get a static reduction of some percent (amount). Amount is a value from 0 to 1.

## promo_code_text

If certain promotion or promo code expires it is automatically removed when calling the $purchase->update_promotions() method. Also when a purchase covers the requirements for specific promotion, it is automatically added. Only one promotion from specific type can be added to a purchase.
The ``promotable_purchase`` behavior adds a promo_code_text field to the purchase (its not in the database). When you set a promocode to this field it would try to find it, and then run "validate_purchase" of the appropriate promotion, if found. If everything checks out, the promotion associated with this promocode will be added to the purchase.

## License

Expand Down
18 changes: 12 additions & 6 deletions classes/Kohana/Jam/Behavior/Promotable/Purchase.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
/**
* Promotionable behavior
*
* @package promotions
* @category Behavior
* @author Yasen Yanev <yasen@openbuildings.com>
* @author Ivan Kerin <ivan@openbuildings.com>
* @copyright (c) 2011-2013 Despark Ltd.
* @license http://www.opensource.org/licenses/isc-license.txt
* @package openbuildings\promotions
* @author Ivan Kerin <ikerin@gmail.com>
* @copyright (c) 2013 OpenBuildings Ltd.
* @license http://spdx.org/licenses/BSD-3-Clause
*/
class Kohana_Jam_Behavior_Promotable_Purchase extends Jam_Behavior {

Expand All @@ -29,6 +27,14 @@ public function initialize(Jam_Meta $meta, $name)
->validator('promo_code_text', array('promocode' => TRUE));
}

public function model_after_load(Model_Purchase $purchase)
{
if ($purchase->promo_code_id)
{
$purchase->retrieved('promo_code_text', $purchase->promo_code->code);
}
}

public function model_after_check(Model_Purchase $purchase)
{
if ($purchase->changed('promo_code_text') AND ! $purchase->errors('promo_code_text'))
Expand Down
9 changes: 4 additions & 5 deletions classes/Kohana/Jam/Behavior/Promotable/Store/Purchase.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
/**
* Promotionable behavior
*
* @package promotions
* @category Behavior
* @author Ivan Kerin <ivan@openbuildings.com>
* @copyright (c) 2011-2013 Despark Ltd.
* @license http://www.opensource.org/licenses/isc-license.txt
* @package openbuildings\promotions
* @author Ivan Kerin <ikerin@gmail.com>
* @copyright (c) 2013 OpenBuildings Ltd.
* @license http://spdx.org/licenses/BSD-3-Clause
*/
class Kohana_Jam_Behavior_Promotable_Store_Purchase extends Jam_Behavior {

Expand Down
17 changes: 8 additions & 9 deletions classes/Kohana/Jam/Validator/Rule/Promocode.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,24 @@
/**
* Jam Validatior Rule
*
* @package Jam
* @category Validation
* @author Ivan Kerin
* @copyright (c) 2011-2012 Despark Ltd.
* @license http://www.opensource.org/licenses/isc-license.txt
* @package openbuildings\promotions
* @author Ivan Kerin <ikerin@gmail.com>
* @copyright (c) 2013 OpenBuildings Ltd.
* @license http://spdx.org/licenses/BSD-3-Clause
*/
class Kohana_Jam_Validator_Rule_Promocode extends Jam_Validator_Rule {

public function validate(Jam_Validated $model, $attribute, $value)
{
$promo_code = $this->valid_promo_code($value, $model);

if ( ! $promo_code)
if ($promo_code)
{
$model->errors()->add('promo_code_text', 'invalid');
$promo_code->validate_purchase($model);
}
elseif ( ! $promo_code->set('purchase', $model)->check())
else
{
$model->errors()->add('promo_code_text', 'requirement', array(':error' => (string) $promo_code->errors()));
$model->errors()->add('promo_code_text', 'invalid');
}
}

Expand Down
6 changes: 6 additions & 0 deletions classes/Kohana/Model/Collection/Promo/Code.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<?php defined('SYSPATH') OR die('No direct script access.');

/**
* @package openbuildings\promotions
* @author Ivan Kerin <ikerin@gmail.com>
* @copyright (c) 2013 OpenBuildings Ltd.
* @license http://spdx.org/licenses/BSD-3-Clause
*/
class Kohana_Model_Collection_Promo_Code extends Jam_Query_Builder_Collection {

public function not_expired($current_time = NULL)
Expand Down
6 changes: 6 additions & 0 deletions classes/Kohana/Model/Collection/Promotion.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<?php defined('SYSPATH') OR die('No direct script access.');

/**
* @package openbuildings\promotions
* @author Ivan Kerin <ikerin@gmail.com>
* @copyright (c) 2013 OpenBuildings Ltd.
* @license http://spdx.org/licenses/BSD-3-Clause
*/
class Kohana_Model_Collection_Promotion extends Jam_Query_Builder_Collection {

public function not_expired($current_time = NULL)
Expand Down
12 changes: 12 additions & 0 deletions classes/Kohana/Model/Promo/Code.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
<?php defined('SYSPATH') OR die('No direct script access.');

/**
* @package openbuildings\promotions
* @author Ivan Kerin <ikerin@gmail.com>
* @author Yasen Yanev <yasen@openbuildings.com>
* @copyright (c) 2013 OpenBuildings Ltd.
* @license http://spdx.org/licenses/BSD-3-Clause
*/
class Kohana_Model_Promo_Code extends Jam_Model {

/**
Expand Down Expand Up @@ -31,4 +38,9 @@ public static function initialize(Jam_Meta $meta)
))
->validator('promotion', 'origin', array('present' => TRUE));
}

public function validate_purchase(Model_Purchase $purchase)
{
$this->get_insist('promotion')->validate_purchase($purchase);
}
}
17 changes: 16 additions & 1 deletion classes/Kohana/Model/Promotion.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
<?php
<?php defined('SYSPATH') OR die('No direct script access.');

/**
* This is a base
*
* @package openbuildings\promotions
* @author Ivan Kerin <ikerin@gmail.com>
* @author Yasen Yanev <yasen@openbuildings.com>
* @copyright (c) 2013 OpenBuildings Ltd.
* @license http://spdx.org/licenses/BSD-3-Clause
*/
class Kohana_Model_Promotion extends Jam_Model implements Sellable {

/**
Expand Down Expand Up @@ -31,11 +40,17 @@ public static function initialize(Jam_Meta $meta)
->validator('currency', array('currency' => TRUE));
}

/**
* @codeCoverageIgnore
*/
public function price_for_purchase_item(Model_Purchase_Item $purchase_item)
{
throw new Kohana_Exception('Not a valid promotion');
}

/**
* @codeCoverageIgnore
*/
public function applies_to(Model_Store_Purchase $purchase)
{
throw new Kohana_Exception('Not a valid promotion');
Expand Down
19 changes: 17 additions & 2 deletions classes/Kohana/Model/Promotion/Promocode.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
<?php

<?php defined('SYSPATH') OR die('No direct script access.');

/**
* @package openbuildings\promotions
* @author Ivan Kerin <ikerin@gmail.com>
* @author Yasen Yanev <yasen@openbuildings.com>
* @copyright (c) 2013 OpenBuildings Ltd.
* @license http://spdx.org/licenses/BSD-3-Clause
*/
class Kohana_Model_Promotion_Promocode extends Model_Promotion {

/**
Expand All @@ -23,6 +30,14 @@ public function applies_to(Model_Store_Purchase $store_purchase)
return $this->matches_store_purchase_promo_code($store_purchase);
}

/**
* @codeCoverageIgnore
*/
public function validate_purchase(Model_Purchase $store_purchase)
{
// extend this to add custom validation for the purchase (or promocode)
}

public function matches_store_purchase_promo_code(Model_Store_Purchase $store_purchase)
{
$promo_code = $store_purchase->get_insist('purchase')->promo_code;
Expand Down
20 changes: 13 additions & 7 deletions classes/Kohana/Model/Promotion/Promocode/Giftcard.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<?php
<?php defined('SYSPATH') OR die('No direct script access.');

/**
* @package openbuildings\promotions
* @author Ivan Kerin <ikerin@gmail.com>
* @copyright (c) 2013 OpenBuildings Ltd.
* @license http://spdx.org/licenses/BSD-3-Clause
*/
class Kohana_Model_Promotion_Promocode_Giftcard extends Model_Promotion_Promocode {

/**
Expand All @@ -18,14 +24,14 @@ public static function initialize(Jam_Meta $meta)
->validator('requirement', array('price' => array('greater_than_or_equal_to' => 0)));
}

public function applies_to(Model_Store_Purchase $store_purchase)
public function validate_purchase(Model_Purchase $purchase)
{
if ( ! $this->matches_store_purchase_promo_code($store_purchase))
return FALSE;

$store_purchase_price = $store_purchase->total_price('product');
$purchase_price = $purchase->total_price('product');

return $store_purchase_price->is(Jam_Price::GREATER_THAN_OR_EQUAL_TO, $this->requirement);
if ($purchase_price->is(Jam_Price::LESS_THAN, $this->requirement))
{
$purchase->errors()->add('promo_code_text', 'requirement', array(':more_than' => $this->requirement->humanize()));
}
}

public function price_for_purchase_item(Model_Purchase_Item $purchase_item)
Expand Down
8 changes: 7 additions & 1 deletion classes/Kohana/Model/Promotion/Promocode/Percent.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<?php
<?php defined('SYSPATH') OR die('No direct script access.');

/**
* @package openbuildings\promotions
* @author Ivan Kerin <ikerin@gmail.com>
* @copyright (c) 2013 OpenBuildings Ltd.
* @license http://spdx.org/licenses/BSD-3-Clause
*/
class Kohana_Model_Promotion_Promocode_Percent extends Model_Promotion_Promocode {

/**
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
},
{
"name": "Ivan Kerin",
"email": "ivan@openbuildings.com",
"email": "ikerin@gmail.com",
"role": "Author"
}
],
Expand Down
2 changes: 1 addition & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
Loading

0 comments on commit 738e0aa

Please sign in to comment.