PHPackages                             openbuildings/purchases - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. openbuildings/purchases

ActiveKohana-module

openbuildings/purchases
=======================

Multi Store Purchases

0.12.1(6y ago)3154.4k[2 issues](https://github.com/OpenBuildings/purchases/issues)2BSD-3-ClausePHPPHP ^7.1CI failing

Since Aug 30Pushed 6y ago10 watchersCompare

[ Source](https://github.com/OpenBuildings/purchases)[ Packagist](https://packagist.org/packages/openbuildings/purchases)[ Docs](https://github.com/OpenBuildings/purchases)[ RSS](/packages/openbuildings-purchases/feed)WikiDiscussions master Synced today

READMEChangelog (10)Dependencies (15)Versions (112)Used By (2)

Purchases Module
================

[](#purchases-module)

[![Build Status](https://camo.githubusercontent.com/3d855449ffb949fbe35e025ba18b6855b5f1da0bdf8637837b9a4f64e224992a/68747470733a2f2f7472617669732d63692e6f72672f4f70656e4275696c64696e67732f7075726368617365732e706e673f6272616e63683d6d6173746572)](https://travis-ci.org/OpenBuildings/purchases)[![Scrutinizer Quality Score](https://camo.githubusercontent.com/aaf740f5b35e87d382e6efc0d8f82e6b8dd7a883b22019d8b77fb99533a36603/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f4f70656e4275696c64696e67732f7075726368617365732f6261646765732f7175616c6974792d73636f72652e706e673f733d65396438666235366261363238376163343039653335613438353434353037316164353265656265)](https://scrutinizer-ci.com/g/OpenBuildings/purchases/)[![Code Coverage](https://camo.githubusercontent.com/fe0e24a2f1dd8bc80b61735bc3a287c2630101fad314d93e28a74b92e0ec5708/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f4f70656e4275696c64696e67732f7075726368617365732f6261646765732f636f7665726167652e706e673f733d38306233383137663761613164366231346535366134356261303534663434636234646636393562)](https://scrutinizer-ci.com/g/OpenBuildings/purchases/)[![Latest Stable Version](https://camo.githubusercontent.com/7ebc43c14e786a855376997457d1b97319d1f0a4cc23db5487cc693b90106f50/68747470733a2f2f706f7365722e707567782e6f72672f6f70656e6275696c64696e67732f7075726368617365732f762f737461626c652e706e67)](https://packagist.org/packages/openbuildings/purchases)

This is a Kohana module that gives you out of the box functionality for multi-brand purchases (each purchase may have Items from different sellers, each handling their portion of products independently)

It has support for eMerchantPay and Paypal at the moment

Instalation
-----------

[](#instalation)

All the purchase models work out of the box. However to use it properly you need to configure the models you want to sell by implementing Sellable interface. E.g

```
class Model_Product extends Jam_Model implements Sellable {

	public static function initialize(Jam_Meta $meta)
	{
		$meta
			->fields(array(
				'id' => Jam::field('primary'),
				'name' => Jam::field('string'),
				'currency' => Jam::field('string'),
				'price' => Jam::field('price'),
			));
	}

	public function price_for_purchase_item(Model_Purchase_Item $item)
	{
		return $this->price;
	}

	public function currency()
	{
		return $this->currency;
	}
}
```

You need to add the "Buyer" behavior for you user model. It adds `current_purchase` and `purchases` associations:

```
class Model_User extends Kohana_Model_User {

	public static function initialize(Jam_Meta $meta)
	{
		$meta
			->behaviors(array(
				'buyer' => Jam::association('buyer'),
			));
		// ...
	}
}
```

Purchase, Brand Purchase and Purchase Items
-------------------------------------------

[](#purchase-brand-purchase-and-purchase-items)

The basic structure of the purchase is one purchase, representing the user's view of what is happening, that has several Brand\_Purchase objects, one for each brand, and each one of these has a "Purchase\_Items" assigned to it.

Prices
------

[](#prices)

This module heavily utilizes jam-monetary - all of its price related methods and fields are Jam\_Price objects allowing you to safely conduct price calculations, so that currency conversions happen transparently. For more informations see: [https://github.com/OpenBuildings/jam-monetary](openbuildings/jam-monetary)

**Purchase\_Item flags**

Purchase item has important "flags"

- **is\_payable** - that means that this is an item that should be "payed for" by the buyer, and will be added to his total bill. This is needed as some items need to be visible (and calculated) only for the seller for example, but should not appear on the buyer's bill.
- **is\_discount** - this means that the purchase item's price should be negative value. This enforces a validation - discounted items can only have negative prices, whereas normal ones must always have positive prices.

**Item Querying**You can query items of the Brand Purchase with the `items()` method:

```
$brand_purchase->items(); // return all the purchase items as an array
$brand_purchase->items('product'); // return all the purchase items with model "purchase_item_product" as an array
$brand_purchase->items(array('product', 'shipping')); // return all the purchase items with model "purchase_item_product" or "purchase_item_shipping" as an array
$brand_purchase->items(array('is_payable' => TRUE)); // return all the purchase items with flag "is_payable" set to TRUE as an array
$brand_purchase->items(array('is_payable' => TRUE, 'product')); // return all the purchase items with flag "is_payable" set to TRUE and are with model "purchase_item_product" as an array
$brand_purchase->items(array('not' => 'shipping')); // return all the purchase items that are not instance of model "purchase_item_shipping"
```

All of these types of queries can be used by `items_count()` and `total_price()`

There is also "items\_quantity" which sums the quantities of all the items, matched by the filters.

```
$brand_purchase->items_count(array('product', 'shipping'));
$brand_purchase->total_price(array('is_payable' = TRUE));
$brand_purchase->items_quantity(array('is_payable' = TRUE));
```

All of these methods can also be executed on Model\_Purchase objects, giving you an aggregate of all the brand\_purchases. For example:

```
// This will return the quantity of all the payable items in all the brand_purchases of this purchase.
$purchase->items_quantity(array('is_payable' => TRUE));
```

There is a special method that is available only on the Model\_Brand\_Purchase object. `total_price_ratio` - it will return what part of the whole purchase is the particular brand purchase (from 0 to 1). You can pass filters to it too so only certain purchase\_items will be taken into account.

```
$brand_purchase->total_price_ratio(array('is_payable' => TRUE)); // Will return e.g. 0.6
```

Price Freezing
--------------

[](#price-freezing)

Normally all prices for purchase items are derived dynamically, by calling -&gt;price\_for\_purchase\_item() method on the reference object (be that a product, a promotion etc.), calculated with the current monetary exchange rates. Once you call the `freeze()` method on the purchase (and save it) both the exchange rates and the prices are set to the purchase disallowing any further modification of the purchase, even if the reference's price changes, the purchase item's prices will stay the same as in the moment of freezing.

```
$purchase
	->freeze()
	->save();
```

If you want to modify the purchase after that, you'll have to `unfreeze()` it. Also if you want to know the state of the purchase, there is `isFrozen` flag.

```
$purchase->unfreeze();
$purchase->isFrozen();
```

Once the purchase has been frozen and saved, any change to the frozen fields / associations will be treated as validation errors.

Freezable traits
----------------

[](#freezable-traits)

In order for that to work across all the models of the purchase, the [`clippings/freezable`](https://github.com/clippings/freezable) package is used. It has useful traits for keeping some values or collections frozen.

```
class Model_Purchase extends Jam_Model {

	use Clippings\Freezable\FreezableCollectionTrait {
		performFreeze as freezeCollection;
		performUnfreeze as unfreezeCollection;
	};

	public static function initialize(Jam_Meta $meta)
	{
		$meta
			->associations(array(
				'brand_purchases' => Jam::association('has_many'),
			))
			->fields(array(
				'is_frozen' => Jam::field('boolean'),
				'price' => Jam::field('serializable'),
			));
	}

	public function price()
	{
		return $this->isFrozen() ? $this->price : $this->computePrice();
	}

	public function isFrozen()
	{
		return $this->is_frozen;
	}

	public function setFrozen($frozen)
	{
		$this->is_frozen = (bool) $frozen;

		return $this;
	}

	public function performFreeze()
	{
		$this->freezeCollection();

		$this->price = $this->price();
	}

	public function performUnfreeze()
	{
		$this->unfreezeCollection();

		$this->price = NULL;
	}

	public function getItems()
	{
		return $this->books;
	}
	//...
}
```

That means that whenever the model is "frozen" then the field named "price" will be assigned the value of the method "price()". And all the associations will be also "frozen". The associations themselves have to be Freezable (implement the `FreezableInterface`) in order for this to work. And the price() method, as well as any other fields, have to take into account if the object is frozen. E.g.

```
public function price()
{
	return $this->isFrozen() ? $this->price : $this->compute_price();
}

```

Adding / Updating single items
------------------------------

[](#adding--updating-single-items)

You can add an item to the purchase with the `add_item()` method. It would search all the purchase items in all the brand\_items, If the same item is found elsewhere it would update its quantity, otherwise it would add it to the appropriate brand\_item (or create that if none exist):

```

$purchse
	->add_item($brand, $new_purchase_item);

```

EMP Processor
-------------

[](#emp-processor)

To Use the emp processor you'll need a form on your page (you can use the included Model\_Emp\_Form). To supply the processor with the cc data.

in the controller:

```
class Controller_Payment extends Controller_Template {

	public function action_index()
	{
		$purchase = // Load purchase from somewhere

		$form = Jam::build('emp_form', array($this->post()));
		if ($this->request->method() === Request::POST AND $form->check())
		{
			$purchase
				->build('payment', array('model' => 'payment_emp'))
					->execute($form->as_array());

			$this->redirect('payment/complete');
		}

		$this->template->content = View::factory('payment/index', array('form' => Jam::form($form)))
	}
}
```

The form is a simple Jam\_Form inside your view:

```
