philipbrown / basket
您的产品页面与支付网关之间的缺失链接
Requires
- php: >=5.4
- ext-intl: *
- mathiasverraes/money: ~1.0
Requires (Dev)
- phpunit/phpunit: ~4.0
This package is not auto-updated.
Last update: 2024-01-06 12:39:33 UTC
README
您的产品页面与支付网关之间的缺失链接
安装
使用composer
$ composer require philipbrown/basket
警告! 此包仍为预 1.0
版本,因此可能会有破坏性更改。请在自己的风险下使用!
货币和货币单位
在电子商务应用中处理货币和货币单位可能充满困难。我们不是传递愚蠢的值,而是可以使用不可变的值对象来处理这些值,从而保护我们希望表示的项目的属性不变性。
use Money\Money; use Money\Currency; $price = new Money(500, new Currency('GBP'));
当与许多不同类型的货币一起工作时,等价性非常重要。您不应该能够盲目地添加两种不同的货币而没有任何交换过程。
$money1 = new Money(500, new Currency('GBP')); $money2 = new Money(500, new Currency('USD')); // Throws Money\InvalidArgumentException $money->add($money2);
此包使用mathiasverraes/money,由@mathiasverraes提供,以表示货币和货币单位值。
税率
处理国际商业的一个大问题就是几乎每个人都有自己的税收规则。
为了使税率可以互换,我们可以将它们封装为实现了公共 TaxRate
接口的对象。
interface TaxRate { /** * Return the Tax Rate as a float * * @return float */ public function float(); /** * Return the Tax Rate as a percentage * * @return int */ public function percentage(); }
此包中可以找到一个示例 UnitedKingdomValueAddedTax
实现。如果您想为您的国家、州或地区添加税率实现,请随时提交一个 pull request。
司法管辖区
世界上几乎每个国家都有不同的货币和税率组合。例如,美国在每个州都有自己的税率。
为了更容易地处理货币和税率组合,您可以将其视为封装的“司法管辖区”。这意味着您可以根据当前客户的所在地轻松指定要使用的货币和税率。
司法管辖区应实现 Jurisdiction
接口
interface Jurisdiction { /** * Return the Tax Rate * * @return TaxRate */ public function rate(); /** * Return the currency * * @return Money\Currency */ public function currency(); }
如果您想为您的国家、州或地区添加实现,请随时提交一个 pull request。
产品
购物车中的每个项目都被封装为 Product
实例。您与 Product
类的大部分交互将通过 Basket
进行,但了解 Product
类的工作方式非常重要。
Product
类捕获购物车中每个项目的当前状态。这包括价格、数量以及应应用的任何折扣。
要创建一个新的 Product
,传递产品的 SKU、名称、价格和税率
use Money\Money; use Money\Currency; use PhilipBrown\Basket\TaxRates\UnitedKingdomValueAddedTax; $sku = '1'; $name = 'Four Steps to the Epiphany'; $rate = new UnitedKingdomValueAddedTax; $price = new Money(1000, new Currency('GBP')); $product = new Product($sku, $name, $price, $rate);
SKU、名称、价格和税率在创建 Product
后不应更改,因此对象上没有这些属性的 setter 方法。
Product
对象的每个私有属性都可通过 __get()
魔法方法作为伪公共属性访问
$product->sku; // '1' $product->name; // 'Four Steps to the Epiphany' $product->rate; // UnitedKingdomValueAddedTax $product->price; // Money\Money
数量
默认情况下,每个Product
实例将自动设置数量为1。您可以以三种方式之一设置产品的数量:
$product->quantity(2); $product->increment(); $product->decrement(); // Return the `quantity` $product->quantity;
赠品
可以将产品可选地设置为赠品。这意味着产品的价值将不会在结算过程中计算在内。
$product->freebie(true); // Return the `freebie` status $product->freebie;
默认情况下,每个Product
的freebie
状态设置为false
。
应税
您还可以将产品标记为非应税。默认情况下,所有产品都会产生税费。将taxable
状态设置为false
,则产品在结算过程中不会计算应税价值。
$product->taxable(false); // Return the `taxable` status $product->taxable;
配送
如果您想为产品添加额外的配送费用,可以通过向delivery()
方法传递一个Money\Money
实例来实现。
use Money\Money; use Money\Currency; $product->delivery(new Money(500, new Currency('GBP'))); // Return the `delivery` charge $product->delivery;
配送费用的Currency
必须与对象实例化时设置的price
相同。默认情况下,配送费用设置为0
。
优惠券
如果您想在产品上记录优惠券,可以通过向coupon()
方法传递一个值来实现。
$product->coupons('FREE99'); // Return the `coupons` Collection $product->coupons;
您可以为每个产品添加任意数量的优惠券。coupons
类属性是一个Collection
实例。这是一个可迭代的对象,允许您以面向对象的方式处理数组。
优惠券本身不会导致产品设置折扣,它只是您记录优惠券已应用于产品的一种方式。
标签
类似于优惠券,标签允许您标记产品,以便您可以记录实验或A/B测试。
$product->tags('campaign_123456'); // Return the `tags` Collection $product->tags;
tags
类属性也是一个Collection
实例。
属性
类似于标签,属性允许您将属性设置为产品。
$product->attributes('key', 'value'); // Return the `attributes` Collection $product->attributes;
attributes
类属性也是一个Collection
实例。
折扣
折扣是可以在结算过程中应用以降低产品价格的对象。每个折扣对象应实现Discount
接口。
interface Discount { /** * Calculate the discount on a Product * * @param Product * @return Money\Money */ public function product(Product $product); /** * Return the rate of the Discount * * @return mixed */ public function rate(); }
此包提供了两个折扣对象,允许您设置固定价值折扣或百分比折扣。
use PhilipBrown\Basket\Discounts\ValueDiscount; use PhilipBrown\Basket\Discounts\PercentageDiscount; $product->discount(new PercentageDiscount(20)); $product->discount(new ValueDiscount(new Money(500, new Currency('GBP')))); // Return the `Discount` instance $product->discount;
类别
如果您想将一组规则应用于特定类型的所有产品,可以定义一个可以应用于Product
实例的类别对象。
每个类别对象应实现Category
接口。
interface Category { /** * Categorise a Product * * @param Product $product * @return void */ public function categorise(Product $product); }
PhysicalBook
是此包提供的Category
对象的一个示例。当应用于产品时,PhysicalBook
将自动将taxable
状态设置为false
。
use PhilipBrown\Basket\Categories\PhysicalBook; $product->category(new PhysicalBook); // Return the `Category` instance $product->category;
操作
最后,如果您想在产品上运行一系列操作,可以将一个Closure
传递给action()
方法。
$product->action(function ($product) { $product->quantity(3); $product->freebie(true); $product->taxable(false); });
购物篮
您的应用程序内部的交互主界面将通过 Basket
对象实现。Basket
对象负责管理产品列表中产品的添加和移除。
要创建一个新的 Basket
实例,传递当前的 Jurisdiction
use PhilipBrown\Basket\Basket; use PhilipBrown\Basket\Jurisdictions\UnitedKingdom; $basket = new Basket(new UnitedKingdom);
Basket
接受 Jurisdiction
实例,但将税率货币作为两个单独的属性管理。这两个对象可以通过以下两个方法获取
$basket->rate(); // PhilipBrown\Basket\TaxRate $basket->currency(); // Money\Currency
Basket
将自动创建一个新的 Collection
实例,以内部管理当前订单的产品实例。
您可以使用以下方法与产品列表交互
// Get the count of the products $basket->count(); // Pick a product from the basket via it's SKU $product = $basket->pick('abc123'); // Iterate over the Collection of products $basket->products()->filter(function ($product) { // Do something });
要将产品添加到购物车中,向 add() 方法传递 SKU、名称和价格
$sku = 'abc123'; $name = 'The Lion King'; $price = new Money(1000, new Currency('GBP')); $basket->add($sku, $name, $price);
您还可以可选地传递一个第四个参数 Closure
来在新的产品上运行操作
$sku = 'abc123'; $name = 'The Lion King'; $price = new Money(1000, new Currency('GBP')); $basket->add($sku, $name, $price, function ($product) { $product->quantity(3); $product->discount(new PercentageDiscount(20)); });
要更新产品,向 update()
方法传递 SKU 和操作 Closure
$basket->update('abc123', function ($product) { $product->increment(); });
要移除产品,向 remove()
方法传递 SKU
$basket->remove('abc123');
对账
每个 Product
对象是其当前状态的产品。为了计算电子商务应用程序所需的各项总计,我们需要通过对账过程将其传递
在研究此软件包时,我遇到的一个问题是,人们似乎对对账过程的工作方式有不同的看法。
为了解决这个问题,我定义了一个 Reconciler
接口,以便您可以实现自己的对账过程
interface Reconciler { /** * Return the value of the Product * * @param Product $product * @return Money */ public function value(Product $product); /** * Return the discount of the Product * * @param Product $product * @return Money */ public function discount(Product $product); /** * Return the delivery charge of the Product * * @param Product $product * @return Money */ public function delivery(Product $product); /** * Return the tax of the Product * * @param Product $product * @return Money */ public function tax(Product $product); /** * Return the subtotal of the Product * * @param Product $product * @return Money */ public function subtotal(Product $product); /** * Return the total of the Product * * @param Product $product * @return Money */ public function total(Product $product); }
我包含了一个 DefaultReconciler
作为对账购物车中项目的一个标准过程。
元数据
所有电子商务应用程序都需要有关订单的元数据,例如产品数量、产品价值和订单税额的价值。
虽然某些类型的电子商务应用程序可能只需要很少的元数据,但其他类型的应用程序将需要更深入的数据来描述流经系统的每个交易。
为了不强迫简单应用程序对每个订单进行深入分析,同时也给大型应用程序实施自己的元数据计算提供自由度,每个元数据项都是可选的,并且定义起来非常简单。
每个元数据项应封装为一个类,并实现 MetaData
接口
interface MetaData { /** * Generate the Meta Data * * @param Basket $basket * @return mixed */ public function generate(Basket $basket); /** * Return the name of the Meta Data * * @return string */ public function name(); }
generate()
方法接受 Basket
实例,并应返回您想要返回的元数据项的值。
name()
方法应该是对象在对账输出中显示的名称。
此软件包默认包含以下元数据项
DeliveryMetaData
DiscountMetaData
ProductsMetaData
SubtotalMetaData
TaxableMetaData
TaxMetaData
TotalMetaData
ValueMetaData
处理订单
一旦您准备好处理 Basket
中的项目并将其转换为不可变的 Order
,您可以使用 Processor
类
use PhilipBrown\Basket\MetaData\TotalMetaData; use PhilipBrown\Basket\MetaData\ProductsMetaData; use PhilipBrown\Basket\Processor; use PhilipBrown\Basket\Reconcilers\DefaultReconciler; $reconciler = new DefaultReconciler; $processor = new Processor($reconciler, [ new TotalMetaData($reconciler), new ProductsMetaData ]); $order = $processor->process($basket);
Processor
类将在购物车上运行每个 MetaData
实例,并将每个 Product
实例转换为属性数组。
现在您可以使用 Order
对象更新您的数据库或发送订单到您选择的支付网关。
转换订单
您不可避免地想在视图中显示订单详情或以HTTP响应的形式返回已处理的订单。
为了将对象的显示与对象本身分离,您可以使用实现了Formatter
接口的特殊类
interface Formatter { /** * Format an input to an output * * @param mixed $value * @return mixed */ public function format($value); }
该包有5个示例格式化器类
CategoryFormatter
CollectionFormatter
MoneyFormatter
PercentFormatter
TaxRateFormatter
使用Formatter
实例转换对象的过程被封装在Converter
对象中
use Money\Money; use Money\Currency; use PhilipBrown\Basket\Converter; $converter = new Converter; $converter->convert(new Money(500, new Currency('GBP'))); // => £10.00
Converter
类使用默认的Formatter
实例进行初始化。如果您想覆盖任何默认格式化器,只需在实例化时传递一个数组即可
$converter = new Converter(['Money' => new CustomerMoneyFormatter]);
最后,要将Order
转换成适当的输出,您可以使用ArrayTransformer
或JSONTransformer
类
use PhilipBrown\Basket\Processor; use PhilipBrown\Basket\Converter; use PhilipBrown\Basket\MetaData\TaxMetaData; use PhilipBrown\Basket\Fixtures\BasketFixture; use PhilipBrown\Basket\MetaData\ValueMetaData; use PhilipBrown\Basket\MetaData\TotalMetaData; use PhilipBrown\Basket\MetaData\TaxableMetaData; use PhilipBrown\Basket\MetaData\DeliveryMetaData; use PhilipBrown\Basket\MetaData\DiscountMetaData; use PhilipBrown\Basket\MetaData\SubtotalMetaData; use PhilipBrown\Basket\MetaData\ProductsMetaData; use PhilipBrown\Basket\Transformers\ArrayTransformer; use PhilipBrown\Basket\Reconcilers\DefaultReconciler; $reconciler = new DefaultReconciler; $meta = [ new DeliveryMetaData($reconciler), new DiscountMetaData($reconciler), new ProductsMetaData, new SubtotalMetaData($reconciler), new TaxableMetaData, new TaxMetaData($reconciler), new TotalMetaData($reconciler), new ValueMetaData($reconciler) ]; $processor = new Processor($reconciler, $meta); $transformer = new ArrayTransformer(new Converter); $order = $processor->process($basket); $payload = $transformer->transform($order); /* [ 'delivery' => "£0.00", 'discount' => "£0.00", 'products_count' => 1, 'subtotal' => "£10.00", 'taxable' => 1, 'tax' => "£2.00", 'total' => "£12.00", 'value' => "£10.00", 'products' => [ [ 'sku' => "0", 'name' => "Back to the Future Blu-ray", 'price' => "£10.00", 'rate' => "20%", 'quantity' => 1, 'freebie' => false, 'taxable' => true, 'delivery' => "£0.00" 'coupons' => [], 'tags' => [], 'discount' => null, 'category' => null, 'total_value' => "£10.00", 'total_discount' => "£0.00", 'total_delivery' => "£0.00", 'total_tax' => "£2.00", 'subtotal' => "£10.00", 'total' => "£12.00" ] ] ] */