Orchard模塊開發全接觸7:訂單與支付之Event Bus

在這部分,我們要完成的工作有:

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">&nbsp;</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">&nbsp;</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");

        }
    }

}

最終結果:

image

數據庫記錄爲:

image

 

二:支付之 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

最終結果:

image

數據庫記錄爲:

image

 

二:支付之 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

然後,到後臺啓動我們的支付模塊:

image

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

然後,到後臺啓動我們的支付模塊:

image

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

相關文章
相關標籤/搜索