Sunday, February 24, 2019

Design an Angular 2+ shopping module PART 1

I should have done this way before my code of this module being tangled like a hell.

A shopping module...

What the fudge is it ?

It's an Angular 2+ shopping module, which is meant to be integrated into any Angular 2+ APP and enabling users to purchase items in the APP, to create an order, and to pay for that order.



How does one use it ?

One can...

  • Add/Purchase items into shopping cart.
  • Modify the quantity of a purchased item.
  • Fill in contact information of buyer.
  • Choose payment method among some 3rd-party payment service or local custom ones.
  • Create an order packing above data: purchased items, contact, payment.
  • Receive payment information from 3rd-party payment service.
  • Check weather a payment is paid.
  • Query orders created by oneself.


What things should be there in it ?


  • Product: An item which is for sale or buyable, its definition comes from outside the module, so it should be an interface, must has properties as name and price.
    export interface Product {
    id?: string;
    name: string;
    price: number;
    thumbnail?: string;
    link?: string;
    [propName: string]: any;
    }
  • Purchase: Consist of a product and the quantity of it to be purchased.
    export class Purchase {
    product: Product;
    quantity: number;

    constructor(product: Product, quantity: number = 1) {
    this.product = product;
    this.quantity = quantity;
    }

    toData(): any {
    return {
    product: _.pick(this.product, ['id', 'name', 'price', 'thumbnail', 'link']),
    quantity: this.quantity
    };
    }
    }
  • Contact: Contact information of the buyer, also an interface, must has properties as name, phone and email.
    export interface Contact {
    id?: string;
    name: string;
    phone?: string;
    email?: string;
    lineID?: string;
    [propName: string]: any;
    }
  • PaymentMethod: This is an abstraction which declares...
    • How to create a payment.
    • How to check a payment is paid.
    • How to bind local payment record identifier with 3rd-party payment record.
      export abstract class PaymentAgent {
      // tslint:disable-next-line:no-empty
      constructor(methodId: string = '') {};
      abstract createPayment(orderInfo: any): Promise<any>;
      abstract getPaymentId(paymentResult: any): Promise<string>;
      abstract checkPaid(payment: any, paymentResult: any): Promise<boolean>;
      }
  • Payment: A local payment record consists of ...
    • Method ID: which method is used.
    • Due Date: When will this payment expire.
    • Paid Date: When did this payment get paid.
    • Amount: payment amount.
    • Order ID: which order it contributes to.
    • isPaid: this payment is paid or not.
    • logs: history payment results from 3rd-party service.

      import * as _ from 'lodash';
      import * as moment from 'moment';
      import { Base } from "../../baseClasses/base";

      export class PaymentLog {
      timestamp: string;
      type: string;
      data: string;
      }

      export class Payment extends Base {
      amount: number;
      dueDate: moment.Moment;
      paidDate: moment.Moment;
      methodId: string;
      isPaid: boolean;
      logs: PaymentLog[];
      orderId: string;
      auditorId: string;

      constructor(data?: any) {
      super(data);

      this.addType('payment');

      _.defaults(this, {
      amount: 0,
      dueDate: moment().add(1, 'week'),
      methodId: 'ecpay',
      logs: [],
      isPaid: false,
      });
      }
      }
  • Order: Packing purchases, contact, and payment informations:
    • Owner ID: reference to the APP's user id who creates this order.
    • Purchases: array of purchases in this order.
    • Contact: contact information.
    • Payment IDs: array of references to the payments which contribute to this order.
      import * as _ from 'lodash';
      import * as moment from "moment";
      import { Base } from "../../baseClasses/base";
      import { Purchase } from "../purchase/purchase";
      import { Contact } from "../contact/contact";
      import { Payment } from "../payment/payment";

      export enum OrderState {
      UNKNOWN = -1,
      NEW,
      CONFIRMED,
      PAID,
      COMPLETED,
      CANCELED,
      REFUNDING,
      REFUNDED
      }

      export class Order extends Base {
      state: OrderState;
      contact: Contact;
      purchases: Purchase[];
      paymentIds: Set<string>;

      constructor(data?: any) {
      super(data);

      if (_.isArray(this.purchases)) {
      this.purchases = this.purchases.map(purchase => new Purchase(purchase.product, purchase.quantity));
      }

      _.defaults(this, {
      state: OrderState.NEW,
      purchases: [],
      paymentIds: new Set<string>()
      });
      }
      }
To be continued...


No comments:

Post a Comment