在這部分,我們要完成的工作有:
1:將購物車內的商品變成真正的訂單;
2:理解 父子及一對多關係;
3:寫一個針對 Event Bus 的擴展點;
4:實現一個針對該擴展點的模擬的 支付服務;
一:創建訂單
Views/Checkout.Summary.cshtml:
@using Orchard.ContentManagement
@using TMinji.Shop.Models
@{
Style.Require("TMinji.Shop.Checkout.Summary");
var shoppingCart = Model.ShoppingCart;
var invoiceAddress = Model.InvoiceAddress;
var shippingAddress = Model.ShippingAddress;
var items = (IList<dynamic>)shoppingCart.ShopItems;
var subtotal = (decimal)shoppingCart.Subtotal;
var vat = (decimal)shoppingCart.Vat;
var total = (decimal)shoppingCart.Total;
}
@if (!items.Any())
{
<p>You don't have any items in your shopping cart.</p>
<a class="button" href="#">Continue shopping</a>
}
else
{<article class="shoppingcart">
<h2>Review your order</h2>
<p>Please review the information below. Hit the Place Order button to proceed.</p>
<table>
<thead>
<tr>
<td>Article</td>
<td class="numeric">Unit Price</td>
<td class="numeric">Quantity</td>
<td class="numeric">Total Price</td>
</tr>
</thead>
<tbody>
@for (var i = 0; i < items.Count; i++)
{
var item = items[i];
var product = (ProductPart)item.Product;
var contentItem = (ContentItem)item.ContentItem;
var title = item.Title;
var quantity = (int)item.Quantity;
var unitPrice = product.UnitPrice;
var totalPrice = quantity * unitPrice;
<tr>
<td>@title</td>
<td class="numeric">@unitPrice.ToString("c")</td>
<td class="numeric">@quantity</td>
<td class="numeric">@totalPrice.ToString("c")</td>
</tr>
}</tbody>
<tfoot>
<tr class="separator"><td colspan="4"> </td></tr>
<tr>
<td class="numeric label" colspan="2">Subtotal:</td>
<td class="numeric">@subtotal.ToString("c")</td>
<td></td>
</tr>
<tr>
<td class="numeric label" colspan="2">VAT (19%):</td>
<td class="numeric">@vat.ToString("c")</td>
<td></td>
</tr>
<tr>
<td class="numeric label" colspan="3">Total:</td>
<td class="numeric">@total.ToString("c")</td>
<td></td>
</tr>
</tfoot>
</table>
</article><article class="addresses form">
<div class="invoice-address">
<h2>Invoice Address</h2>
<ul class="address-fields">
<li>@invoiceAddress.Name.Value</li>
<li>@invoiceAddress.AddressLine1.Value</li>
<li>@invoiceAddress.AddressLine2.Value</li>
<li>@invoiceAddress.Zipcode.Value</li>
<li>@invoiceAddress.City.Value</li>
<li>@invoiceAddress.Country.Value</li>
</ul>
</div>
<div class="shipping-address">
<h2>Shipping Address</h2>
<ul class="address-fields">
<li>@shippingAddress.Name.Value</li>
<li>@shippingAddress.AddressLine1.Value</li>
<li>@shippingAddress.AddressLine2.Value</li>
<li>@shippingAddress.Zipcode.Value</li>
<li>@shippingAddress.City.Value</li>
<li>@shippingAddress.Country.Value</li>
</ul>
</div>
</article><article>
<div class="group">
<div class="align left"><a href="#">Cancel</a></div>
<div class="align right">
@using (Html.BeginFormAntiForgeryPost(Url.Action("Create", "Order", new { area = "TMinji.Shop" })))
{
<button type="submit">Place Order</button>
}
</div>
</div>
</article>
}
Controllers/OrderController.cs:
using Orchard;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;
using Orchard.Mvc;
using Orchard.Themes;
using Orchard.Localization;
using Orchard.Security;
using TMinji.Shop.ViewModels;
using TMinji.Shop.Services;
using TMinji.Shop.Models;
using TMinji.Shop.Helpers;
using Orchard.ContentManagement;
using Orchard.DisplayManagement;namespace TMinji.Shop.Controllers
{
public class OrderController : Controller
{
private readonly dynamic _shapeFactory;
private readonly IOrderService _orderService;
private readonly IAuthenticationService _authenticationService;
private readonly IShoppingCart _shoppingCart;
private readonly ICustomerService _customerService;
private readonly Localizer _t;public OrderController(
IShapeFactory shapeFactory,
IOrderService orderService,
IAuthenticationService authenticationService,
IShoppingCart shoppingCart,
ICustomerService customerService)
{
_shapeFactory = shapeFactory;
_orderService = orderService;
_authenticationService = authenticationService;
_shoppingCart = shoppingCart;
_customerService = customerService;
_t = NullLocalizer.Instance;
}[Themed, HttpPost]
public ActionResult Create()
{var user = _authenticationService.GetAuthenticatedUser();
if (user == null)
throw new OrchardSecurityException(_t("Login required"));var customer = user.ContentItem.As<CustomerPart>();
if (customer == null)
throw new InvalidOperationException("The current user is not a customer");var order = _orderService.CreateOrder(customer.Id, _shoppingCart.Items);
// Todo: Give payment service providers a chance to process payment by sending a event. If no PSP handled the event, we'll just continue by displaying the created order.
// Raise an OrderCreated event// If we got here, no PSP handled the OrderCreated event, so we'll just display the order.
var shape = _shapeFactory.Order_Created(
Order: order,
Products: _orderService.GetProducts(order.Details).ToArray(),
Customer: customer,
InvoiceAddress: (dynamic)_customerService.GetAddress(user.Id, "InvoiceAddress"),
ShippingAddress: (dynamic)_customerService.GetAddress(user.Id, "ShippingAddress")
);
return new ShapeResult(this, shape);
}
}}
Views/Order.Created.cshtml:
@using Orchard.ContentManagement
@using Orchard.Core.Title.Models
@using TMinji.Shop.Models
@using TMinji.Shop.ViewModels
@using Orchard.Core;
@{
Style.Require("TMinji.Shop.Common");
var order = (OrderRecord) Model.Order;
var productParts = (IList<ProductPart>) Model.Products;
var invoiceAddress = Model.InvoiceAddress;
var shippingAddress = Model.ShippingAddress;}
<h2>@T("Order {0} has been created", order.GetNumber())</h2>
<p>@T("Please find your order details below")</p><div class="order-wrapper">
<article class="order">
<header>
<ul>
<li>
<div class="field-label">Order Number</div>
<div class="field-value">@order.GetNumber()</div>
</li>
<li>
<div class="field-label">Created</div>
<div class="field-value">@order.CreatedAt.ToString(System.Globalization.CultureInfo.InvariantCulture)</div>
</li>
</ul>
</header>
<table>
<thead>
<tr>
<td>Article</td>
<td class="numeric">Unit Price</td>
<td class="numeric">Quantity</td>
<td class="numeric">Total Price</td>
</tr>
</thead>
<tbody>
@foreach (var detail in order.Details)
{
var productPart = productParts.Single(x => x.Id == detail.ProductId);
var routePart = productPart.As<TitlePart>();
var productTitle = routePart != null ? routePart.Title : "(No RoutePart attached)";
<tr>
<td>@productTitle</td>
<td class="numeric">@detail.UnitPrice.ToString("c")</td>
<td class="numeric">@detail.Quantity</td>
<td class="numeric">@detail.GetSubTotal().ToString("c")</td>
</tr>
}
</tbody>
<tfoot>
<tr class="separator"><td colspan="4"> </td></tr>
<tr>
<td class="numeric label" colspan="2">Subtotal:</td>
<td class="numeric">@order.SubTotal.ToString("c")</td>
</tr>
<tr>
<td class="numeric label" colspan="2">VAT:</td>
<td class="numeric">@order.Vat.ToString("c")</td>
</tr>
<tr>
<td class="numeric label" colspan="2">Total:</td>
<td class="numeric">@order.GetTotal().ToString("c")</td>
</tr>
</tfoot>
</table>
</article><article class="addresses form">
<div class="invoice-address">
<h2>Invoice Address</h2>
<ul class="address-fields">
<li>@invoiceAddress.Name.Value</li>
<li>@invoiceAddress.AddressLine1.Value</li>
<li>@invoiceAddress.AddressLine2.Value</li>
<li>@invoiceAddress.Zipcode.Value</li>
<li>@invoiceAddress.City.Value</li>
<li>@invoiceAddress.Country.Value</li>
</ul>
</div>
<div class="shipping-address">
<h2>Shipping Address</h2>
<ul class="address-fields">
<li>@shippingAddress.Name.Value</li>
<li>@shippingAddress.AddressLine1.Value</li>
<li>@shippingAddress.AddressLine2.Value</li>
<li>@shippingAddress.Zipcode.Value</li>
<li>@shippingAddress.City.Value</li>
<li>@shippingAddress.Country.Value</li>
</ul>
</div>
</article>
</div>
Models/OrderDetailRecord.cs:
using Orchard.ContentManagement.Records;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TMinji.Shop.Models
{
public class OrderDetailRecord
{
public virtual int Id { get; set; }
public virtual int OrderRecord_Id { get; set; }
public virtual int ProductId { get; set; }
public virtual int Quantity { get; set; }
public virtual decimal UnitPrice { get; set; }
public virtual decimal VatRate { get; set; }//private decimal unitVat;
//public virtual decimal UnitVat
//{
// get { return UnitPrice * VatRate; }
// set { unitVat = value; }
//}
public virtual decimal GetUnitVat()
{
return UnitPrice * VatRate;
}//private decimal vat;
//public virtual decimal Vat
//{
// get { return UnitVat * Quantity; }
// set { vat = value; }
//}
public virtual decimal GetVat()
{
return GetUnitVat() * Quantity;
}//private decimal subTotal;
//public virtual decimal SubTotal
//{
// get { return UnitPrice * Quantity; }
// set { subTotal = value; }
//}
public virtual decimal GetSubTotal()
{
return UnitPrice * Quantity;
}//private decimal total;
//public virtual decimal Total
//{
// get { return SubTotal + Vat; }
// set { total = value; }
//}
public virtual decimal GetTotal()
{
return GetSubTotal() + GetVat();
}
}}
Models/OrderRecord.cs:
using Orchard.ContentManagement.Records;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TMinji.Shop.Models
{
public class OrderRecord
{
public virtual int Id { get; set; }
public virtual int CustomerId { get; set; }
public virtual DateTime CreatedAt { get; set; }
public virtual decimal SubTotal { get; set; }
public virtual decimal Vat { get; set; }
public virtual OrderStatus Status { get; set; }
public virtual IList<OrderDetailRecord> Details { get; private set; }
public virtual string PaymentServiceProviderResponse { get; set; }
public virtual string PaymentReference { get; set; }
public virtual DateTime? PaidAt { get; set; }
public virtual DateTime? CompletedAt { get; set; }
public virtual DateTime? CancelledAt { get; set; }////private decimal total;
//public virtual decimal Total
//{
// get { return SubTotal + Vat; }
// //private set { total = value; }
//}public virtual decimal GetTotal()
{
return SubTotal + Vat;
}////private string number;
//public virtual string Number
//{
// get { return (Id + 1000).ToString(CultureInfo.InvariantCulture); }
// //private set { number = value; }
//}
public virtual string GetNumber()
{
return (Id + 1000).ToString(CultureInfo.InvariantCulture);
}public OrderRecord()
{
Details = new List<OrderDetailRecord>();
}public virtual void UpdateTotals()
{
var subTotal = 0m;
var vat = 0m;foreach (var detail in Details)
{
subTotal += detail.GetSubTotal();
vat += detail.GetVat();
}SubTotal = subTotal;
Vat = vat;
}
}}
Models/OrderStatus.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TMinji.Shop.Models
{
public enum OrderStatus
{
/// <summary>
/// The order is new and is yet to be paid for
/// </summary>
New,/// <summary>
/// The order has been paid for, so it's eligable for shipping
/// </summary>
Paid,/// <summary>
/// The order has shipped
/// </summary>
Completed,/// <summary>
/// The order was cancelled
/// </summary>
Cancelled
}}
Migrations.cs:
using Orchard.ContentManagement.MetaData;
using Orchard.Core.Common.Fields;
using Orchard.Core.Contents.Extensions;
using Orchard.Data.Migration;
using Orchard.Users.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TMinji.Shop.Models;namespace TMinji.Shop
{
public class Migrations : DataMigrationImpl
{
public int Create()
{SchemaBuilder.CreateTable("ProductPartRecord", table => table
.ContentPartRecord()
.Column<decimal>("UnitPrice")
.Column<string>("Sku", column => column.WithLength(50))
);return 1;
}public int UpdateFrom1()
{
ContentDefinitionManager.AlterPartDefinition("ProductPart", part => part
.Attachable());return 2;
}public int UpdateFrom2()
{
// Define a new content type called "ShoppingCartWidget"
ContentDefinitionManager.AlterTypeDefinition("ShoppingCartWidget", type => type
// Attach the "ShoppingCartWidgetPart"
.WithPart("ShoppingCartWidgetPart")
// In order to turn this content type into a widget, it needs the WidgetPart
.WithPart("WidgetPart")
// It also needs a setting called "Stereotype" to be set to "Widget"
.WithSetting("Stereotype", "Widget")
);return 3;
}public int UpdateFrom3()
{
// Update the ShoppingCartWidget so that it has a CommonPart attached, which is required for widgets (it's generally a good idea to have this part attached)
ContentDefinitionManager.AlterTypeDefinition("ShoppingCartWidget", type => type
.WithPart("CommonPart")
);return 4;
}public int UpdateFrom4()
{
SchemaBuilder.CreateTable("CustomerPartRecord", table => table
.ContentPartRecord()
.Column<string>("FirstName", c => c.WithLength(50))
.Column<string>("LastName", c => c.WithLength(50))
.Column<string>("Title", c => c.WithLength(10))
.Column<DateTime>("CreatedUtc")
);SchemaBuilder.CreateTable("AddressPartRecord", table => table
.ContentPartRecord()
.Column<int>("CustomerId")
.Column<string>("Type", c => c.WithLength(50))
);ContentDefinitionManager.AlterPartDefinition("CustomerPart", part => part
.Attachable(false)
);ContentDefinitionManager.AlterTypeDefinition("Customer", type => type
.WithPart("CustomerPart")
.WithPart("UserPart")
);ContentDefinitionManager.AlterPartDefinition("AddressPart", part => part
.Attachable(false)
.WithField("Name", f => f.OfType("TextField"))
.WithField("AddressLine1", f => f.OfType("TextField"))
.WithField("AddressLine2", f => f.OfType("TextField"))
.WithField("Zipcode", f => f.OfType("TextField"))
.WithField("City", f => f.OfType("TextField"))
.WithField("Country", f => f.OfType("TextField"))
);ContentDefinitionManager.AlterTypeDefinition("Address", type => type
.WithPart("CommonPart")
.WithPart("AddressPart")
);return 5;
}public int UpdateFrom5()
{
ContentDefinitionManager.AlterPartDefinition(typeof(CustomerPart).Name, p => p
.Attachable(false)
.WithField("Phone", f => f.OfType(typeof(TextField).Name))
);ContentDefinitionManager.AlterTypeDefinition("Customer", t => t
.WithPart(typeof(CustomerPart).Name)
.WithPart(typeof(UserPart).Name)
);ContentDefinitionManager.AlterPartDefinition(typeof(AddressPart).Name, p => p
.Attachable(false)
.WithField("Name", f => f.OfType(typeof(TextField).Name))
.WithField("AddressLine1", f => f.OfType(typeof(TextField).Name))
.WithField("AddressLine2", f => f.OfType(typeof(TextField).Name))
.WithField("Zipcode", f => f.OfType(typeof(TextField).Name))
.WithField("City", f => f.OfType(typeof(TextField).Name))
.WithField("Country", f => f.OfType(typeof(TextField).Name))
);ContentDefinitionManager.AlterTypeDefinition("Address", t => t
.WithPart(typeof(AddressPart).Name)
);return 6;
}public int UpdateFrom6()
{
//FOREIGN KEY 約束"Order_Customer"衝突。表"dbo.TMinji_Shop_CustomerRecord", column 'Id'。
SchemaBuilder.CreateTable("OrderRecord", t => t
.Column<int>("Id", c => c.PrimaryKey().Identity())
.Column<int>("CustomerId", c => c.NotNull())
.Column<DateTime>("CreatedAt", c => c.NotNull())
.Column<decimal>("SubTotal", c => c.NotNull())
.Column<decimal>("Vat", c => c.NotNull())
.Column<string>("Status", c => c.WithLength(50).NotNull())
.Column<string>("PaymentServiceProviderResponse", c => c.WithLength(null))
.Column<string>("PaymentReference", c => c.WithLength(50))
.Column<DateTime>("PaidAt", c => c.Nullable())
.Column<DateTime>("CompletedAt", c => c.Nullable())
.Column<DateTime>("CancelledAt", c => c.Nullable())
);SchemaBuilder.CreateTable("OrderDetailRecord", t => t
.Column<int>("Id", c => c.PrimaryKey().Identity())
.Column<int>("OrderRecord_Id", c => c.NotNull())
.Column<int>("ProductId", c => c.NotNull())
.Column<int>("Quantity", c => c.NotNull())
.Column<decimal>("UnitPrice", c => c.NotNull())
.Column<decimal>("VatRate", c => c.NotNull())
);SchemaBuilder.CreateForeignKey("Order_Customer", "OrderRecord", new[] { "CustomerId" }, "CustomerPartRecord", new[] { "Id" });
SchemaBuilder.CreateForeignKey("OrderDetail_Order", "OrderDetailRecord", new[] { "OrderRecord_Id" }, "OrderRecord", new[] { "Id" });
SchemaBuilder.CreateForeignKey("OrderDetail_Product", "OrderDetailRecord", new[] { "ProductId" }, "ProductPartRecord", new[] { "Id" });return 7;
}}
}
Services/IOrderService.cs:
using Orchard;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TMinji.Shop.Models;namespace TMinji.Shop.Services
{
public interface IOrderService : IDependency
{
/// <summary>
/// Creates a new order based on the specified ShoppingCartItems
/// </summary>
OrderRecord CreateOrder(int customerId, IEnumerable<ShoppingCartItem> items);/// <summary>
/// Gets a list of ProductParts from the specified list of OrderDetails. Useful if you need to use the product as a ProductPart (instead of just having access to the ProductRecord instance).
/// </summary>
IEnumerable<ProductPart> GetProducts(IEnumerable<OrderDetailRecord> orderDetails);
}}
Services/OrderService.cs:
using Orchard;
using Orchard.ContentManagement;
using Orchard.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TMinji.Shop.Models;namespace TMinji.Shop.Services
{
public class OrderService : IOrderService
{
private readonly IDateTimeService _dateTimeService;
private readonly IRepository<ProductPartRecord> _productRepository;
private readonly IContentManager _contentManager;
private readonly IRepository<OrderRecord> _orderRepository;
private readonly IRepository<OrderDetailRecord> _orderDetailRepository;
private readonly IOrchardServices _orchardServices;public OrderService(
IDateTimeService dateTimeService,
IRepository<ProductPartRecord> productRepository,
IContentManager contentManager,
IRepository<OrderRecord> orderRepository,
IRepository<OrderDetailRecord> orderDetailRepository,
IOrchardServices orchardServices)
{
_dateTimeService = dateTimeService;
_productRepository = productRepository;
_contentManager = contentManager;
_orderRepository = orderRepository;
_orderDetailRepository = orderDetailRepository;
_orchardServices = orchardServices;
}public OrderRecord CreateOrder(int customerId, IEnumerable<ShoppingCartItem> items)
{if (items == null)
throw new ArgumentNullException("items");// Convert to an array to avoid re-running the enumerable
var itemsArray = items.ToArray();if (!itemsArray.Any())
throw new ArgumentException("Creating an order with 0 items is not supported", "items");var order = new OrderRecord
{
CreatedAt = _dateTimeService.Now,
CustomerId = customerId,
Status = OrderStatus.New
};_orderRepository.Create(order);
// Get all products in one shot, so we can add the product reference to each order detail
var productIds = itemsArray.Select(x => x.ProductId).ToArray();
var products = _productRepository.Fetch(x => productIds.Contains(x.Id)).ToArray();// Create an order detail for each item
foreach (var item in itemsArray)
{
var product = products.Single(x => x.Id == item.ProductId);var detail = new OrderDetailRecord
{
OrderRecord_Id = order.Id,
ProductId = product.Id,
Quantity = item.Quantity,
UnitPrice = product.UnitPrice,
VatRate = .19m
};_orderDetailRepository.Create(detail);
order.Details.Add(detail);
}order.UpdateTotals();
return order;
}/// <summary>
/// Gets a list of ProductParts from the specified list of OrderDetails. Useful if you need to use the product as a ProductPart (instead of just having access to the ProductRecord instance).
/// </summary>
public IEnumerable<ProductPart> GetProducts(IEnumerable<OrderDetailRecord> orderDetails)
{
var productIds = orderDetails.Select(x => x.ProductId).ToArray();
return _contentManager.GetMany<ProductPart>(productIds, VersionOptions.Latest, QueryHints.Empty);
}
}}
ResourceManifest.cs:
using Orchard.UI.Resources;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace TMinji.Shop
{
public class ResourceManifest : IResourceManifestProvider
{
public void BuildManifests(ResourceManifestBuilder builder)
{
// Create and add a new manifest
var manifest = builder.Add();// Define a "common" style sheet
manifest.DefineStyle("TMinji.Shop.Common").SetUrl("common.css");// Define the "shoppingcart" style sheet
manifest.DefineStyle("TMinji.Shop.ShoppingCart").SetUrl("shoppingcart.css").SetDependencies("TMinji.Shop.Common");manifest.DefineStyle("TMinji.Shop.ShoppingCartWidget").SetUrl("shoppingcartwidget.css").SetDependencies("Webshop.Common");
//manifest.DefineScript("jQuery").SetUrl("jquery-1.9.1.min.js", "jquery-1.9.1.js").SetVersion("1.9.1");
// Define the "shoppingcart" script and set a dependency on the "jQuery" resource
//manifest.DefineScript("TMinji.Shop.ShoppingCart").SetUrl("shoppingcart.js").SetDependencies("jQuery");
manifest.DefineScript("TMinji.Shop.ShoppingCart").SetUrl("shoppingcart.js").SetDependencies("jQuery", "jQuery_LinqJs", "ko");manifest.DefineStyle("TMinji.Shop.Checkout.Summary").SetUrl("checkout-summary.css").SetDependencies("TMinji.Shop.Common");
manifest.DefineStyle("TMinji.Shop.Order").SetUrl("order.css").SetDependencies("TMinji.Shop.Common");
}
}}
最終結果:
數據庫記錄爲:
二:支付之 Event Bus
Event Bus 這個機制可被用於擴展 Orchard 模塊。首先,讓我們看看如果 Event Bus 應用到支付中的話,其機制是怎麼樣的:
首先,我們要定義一個 PaymentRequest,它包含了兩個屬性:Created Order 和 flag,這能告訴 Event Listener 我們需要開始支付流程,我們還會定義 PaymentResponse,它包含了 payment service provider 的反饋。現在,看代碼吧:
TMinji.Shop.Extensibility.PaymentRequest
public class PaymentRequest
{
public OrderRecord Order { get; private set; }
public bool WillHandlePayment { get; set; }
public ActionResult ActionResult { get; set; }public PaymentRequest(OrderRecord order)
{
Order = order;
}
}
TMinji.Shop.Extensibility.PaymentResponse
最終結果:
數據庫記錄爲:
二:支付之 Event Bus
Event Bus 這個機制可被用於擴展 Orchard 模塊。首先,讓我們看看如果 Event Bus 應用到支付中的話,其機制是怎麼樣的:
首先,我們要定義一個 PaymentRequest,它包含了兩個屬性:Created Order 和 flag,這能告訴 Event Listener 我們需要開始支付流程,我們還會定義 PaymentResponse,它包含了 payment service provider 的反饋。現在,看代碼吧:
TMinji.Shop.Extensibility.PaymentRequest
public class PaymentRequest
{
public OrderRecord Order { get; private set; }
public bool WillHandlePayment { get; set; }
public ActionResult ActionResult { get; set; }public PaymentRequest(OrderRecord order)
{
Order = order;
}
}TMinji.Shop.Extensibility.PaymentResponse
public class PaymentResponse
{
public bool WillHandleResponse { get; set; }
public PaymentResponseStatus Status { get; set; }
public string OrderReference { get; set; }
public string PaymentReference { get; set; }
public string ResponseText { get; set; }
public HttpContextBase HttpContext { get; private set; }public PaymentResponse(HttpContextBase httpContext)
{
HttpContext = httpContext;
}
}TMinji.Shop.Extensibility.PaymentResponseStatus
public enum PaymentResponseStatus
{
Success,
Failed,
Cancelled,
Exception
}Extensibility/IPaymentServiceProvider.cs:
public interface IPaymentServiceProvider : IEventHandler
{
void RequestPayment(PaymentRequest e);
void ProcessResponse(PaymentResponse e);
}Controllers/OrderController.cs:
private readonly IEnumerable<IPaymentServiceProvider> _paymentServiceProviders;
private readonly Localizer _t;public OrderController(
IShapeFactory shapeFactory,
IOrderService orderService,
IAuthenticationService authenticationService,
IShoppingCart shoppingCart,
ICustomerService customerService,
IEnumerable<IPaymentServiceProvider> paymentServiceProviders)
{
_shapeFactory = shapeFactory;
_orderService = orderService;
_authenticationService = authenticationService;
_shoppingCart = shoppingCart;
_customerService = customerService;
_paymentServiceProviders = paymentServiceProviders;
//_paymentServiceProvider = new SimulatedPaymentServiceProvider();
_t = NullLocalizer.Instance;
}這裏,需要特別說明哦:
只要模塊中存在 IPaymentServiceProvider 的實現類,注入機制就都會注入進這個列表,這樣一來,就實現了 Event Bus
Module.txt:
name: tminji.shop
antiforgery: enabled
author: tminji.com
website: http://www.tminji.com
version: 1.0.0
orchardversion: 1.0.0
description: The tminji.com module is a shopping module.
Dependencies: Orchard.Projections, Orchard.Forms, Orchard.jQuery, Orchard.jQuery, AIM.LinqJs, Orchard.Knockout, Orchard.Users
features:
shop:
Description: shopping module.
Category: ASample
SimulatedPSP:
Description: Provides a simulated Payment Service Provider for testing purposes only.
Category: ASample然後,到後臺啓動我們的支付模塊:
Services/SimulatedPaymentServiceProvider.cs:
using Orchard.Environment.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;" alt="image" src="/images/450/474c8c718b08dc8653fe272866d129e2.png" />
二:支付之 Event Bus
Event Bus 這個機制可被用於擴展 Orchard 模塊。首先,讓我們看看如果 Event Bus 應用到支付中的話,其機制是怎麼樣的:
首先,我們要定義一個 PaymentRequest,它包含了兩個屬性:Created Order 和 flag,這能告訴 Event Listener 我們需要開始支付流程,我們還會定義 PaymentResponse,它包含了 payment service provider 的反饋。現在,看代碼吧:
TMinji.Shop.Extensibility.PaymentRequest
public class PaymentRequest
{
public OrderRecord Order { get; private set; }
public bool WillHandlePayment { get; set; }
public ActionResult ActionResult { get; set; }public PaymentRequest(OrderRecord order)
{
Order = order;
}
}TMinji.Shop.Extensibility.PaymentResponse
public class PaymentResponse
{
public bool WillHandleResponse { get; set; }
public PaymentResponseStatus Status { get; set; }
public string OrderReference { get; set; }
public string PaymentReference { get; set; }
public string ResponseText { get; set; }
public HttpContextBase HttpContext { get; private set; }public PaymentResponse(HttpContextBase httpContext)
{
HttpContext = httpContext;
}
}TMinji.Shop.Extensibility.PaymentResponseStatus
public enum PaymentResponseStatus
{
Success,
Failed,
Cancelled,
Exception
}Extensibility/IPaymentServiceProvider.cs:
public interface IPaymentServiceProvider : IEventHandler
{
void RequestPayment(PaymentRequest e);
void ProcessResponse(PaymentResponse e);
}Controllers/OrderController.cs:
private readonly IEnumerable<IPaymentServiceProvider> _paymentServiceProviders;
private readonly Localizer _t;public OrderController(
IShapeFactory shapeFactory,
IOrderService orderService,
IAuthenticationService authenticationService,
IShoppingCart shoppingCart,
ICustomerService customerService,
IEnumerable<IPaymentServiceProvider> paymentServiceProviders)
{
_shapeFactory = shapeFactory;
_orderService = orderService;
_authenticationService = authenticationService;
_shoppingCart = shoppingCart;
_customerService = customerService;
_paymentServiceProviders = paymentServiceProviders;
//_paymentServiceProvider = new SimulatedPaymentServiceProvider();
_t = NullLocalizer.Instance;
}這裏,需要特別說明哦:
只要模塊中存在 IPaymentServiceProvider 的實現類,注入機制就都會注入進這個列表,這樣一來,就實現了 Event Bus
Module.txt:
name: tminji.shop
antiforgery: enabled
author: tminji.com
website: http://www.tminji.com
version: 1.0.0
orchardversion: 1.0.0
description: The tminji.com module is a shopping module.
Dependencies: Orchard.Projections, Orchard.Forms, Orchard.jQuery, Orchard.jQuery, AIM.LinqJs, Orchard.Knockout, Orchard.Users
features:
shop:
Description: shopping module.
Category: ASample
SimulatedPSP:
Description: Provides a simulated Payment Service Provider for testing purposes only.
Category: ASample然後,到後臺啓動我們的支付模塊:
Services/SimulatedPaymentServiceProvider.cs:
using Orchard.Environment.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;
using System.Web.Routing;
using TMinji.Shop.Extensibility;namespace TMinji.Shop.Services
{
[OrchardFeature("TMinji.Shop.SimulatedPSP")]
public class SimulatedPaymentServiceProvider : IPaymentServiceProvider
{
&n"font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">首先,我們要定義一個 PaymentRequest,它包含了兩個屬性:Created Order 和 flag,這能告訴 Event Listener 我們需要開始支付流程,我們還會定義 PaymentResponse,它包含了 payment service provider 的反饋。現在,看代碼吧:TMinji.Shop.Extensibility.PaymentRequest
public class PaymentRequest { public OrderRecord Order { get; private set; } public bool Wil