Tuesday, February 26, 2019

Design an Angular 2+ shopping module PART 3

Follow up from PART 2 we now need to fill the blank of payment implementation which is mostly handled by 3rd party payment services, but we still need an adapter or api or something to communicate with these external services.


Additionally, and hopefully, we expect it can adopt any external payment service in the future, so we'll need an abstraction as PaymentMethod to hide the detail from client, i.e. our shopping module here.

The relationship can be simply depicted...


The abstraction PaymentMethod provides 3 function.
  • createPayment: Create a payment record and store in storage at our side.
  • getPaymentId: Get id of payment record from received result transmitted from 3rd party service.
  • checkPaid: Check if payment record is paid according to the received result.
Like such...
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>;
}

While this abstraction provides only the interface to our shopping module, it is the extending subclass's responsibility to decide how to implement these functions, since the howtos will be different between external payment services.

Take Ecpay as an example.
import * as admin from 'firebase-admin';
import { PaymentAgent } from './payment-agent';
import { Ecpay } from './ecpay';

export class PaymentEcpay extends PaymentAgent {
ecpay$: Promise<Ecpay>;

constructor(methodId: string = 'ecpay') {
super(methodId);
this.ecpay$ = this.createAgent(methodId);
}

createAgent(methodId: string): Promise<Ecpay> {
return admin.database()
.ref('site-config')
.child(methodId)
.once('value')
.then(ecpayConfigSnapShot => {
return new Ecpay(ecpayConfigSnapShot.val());
});
}

createPayment(orderInfo: any): Promise<any> {
return this.ecpay$.then(ecpay => ecpay.createParams(orderInfo));
}

getPaymentId(paymentResult: any): Promise<string> {
return this.ecpay$.then(ecpay => ecpay.getBoundId(paymentResult));
}

checkPaid(payment: any, paymentResult: any): Promise<boolean> {
return this.ecpay$.then(ecpay => {
return ecpay.checkPaid(paymentResult);
});
}
}



No comments:

Post a Comment