Hello everyone! I really would like to know your opinion about how better handle and keep different payment methods in a single web account. I consider the account as aggregate root in DDD terms and I want keep list of payment methods there. Also account has to have methods such as makePaymentMethodDefault
, addPaymentMethod
, removePaymentMethod
etc. There is business rule saying that there is only one default payment method so when I remove or add new one I have to make other payment methods as not default. Bellow I showed how it looks now for me but I really don't like it. Actually there are other ways to do it, make a service to handle payment methods for account for instance but I want to make everything in account to be sure that all business rules always go correct and keep related items in one place.
Also in system can be a few payment methods:
- Stripe tokenized card
- PayPal
- Something else in a future
Making payment happens in a different module so my question is not about how to charge money or make a payment but about how to keep payment methods.
This is my common payment method interface (TypeScript):
export interface IPaymentMethod {
isDefault(): boolean;
makeDefault(): void;
getClass(): new(...args: any[]) => any;
getPaymentProvider(): PaymentProvider;
getPaymentMethodType(): PaymentMethodType;
remove(): void;
init(owner: IOwningPaymentMethod): void;
}
This is my first concrete payment method (TypeScript):
export class StripeTokenizedCardPaymentMethod implements IPaymentMethod {
readonly token: string;
readonly last4: string;
readonly expYear: number;
readonly expMonth: number;
readonly country: string;
readonly brand: string;
private _owner: IOwningPaymentMethod;
constructor(props: IStripeTokenizedCardPaymentMethodProps) {
this.token = props.token;
this.last4 = props.last4;
this.expYear = props.expYear;
this.expMonth = props.expMonth;
this.country = props.country;
this.brand = props.brand;
}
init(owner: IOwningPaymentMethod): void {
this._owner = owner;
}
getClass() {
return StripeTokenizedCardPaymentMethod;
}
getPaymentMethodType(): PaymentMethodType {
return PaymentMethodType.StripeTokenizedCard;
}
getPaymentProvider(): PaymentProvider {
return PaymentProvider.Stripe;
}
isDefault(): boolean {
return this._owner.getDefaultPaymentMethod().getPaymentProvider() === this.getPaymentProvider()
&& this._owner.getDefaultPaymentMethod().getPaymentMethodType() === this.getPaymentMethodType();
}
makeDefault(): void {
this._owner.makePaymentMethodDefault(this);
}
remove(): void {
this._owner.removePaymentMethod(this);
}
}
And finally aggregate root account (TypeScript):
export class Account extends BaseEntity<AccountId> implements IOwningPaymentMethod {
get updatedAt() {
return this._updatedAt;
}
readonly id: AccountId;
readonly user: User;
readonly createdAt: Moment;
private _updatedAt: Moment;
private _paymentMethods: IPaymentMethod[] = [];
private _defaultPaymentMethod: IPaymentMethod;
constructor(props: IAccountProps) {
super();
this.user = props.user;
this.id = props.id;
this._updatedAt = props.updatedAt;
this.createdAt = props.createdAt;
}
getDefaultPaymentMethod(): IPaymentMethod {
return this._defaultPaymentMethod;
}
makePaymentMethodDefault(paymentMethod: IPaymentMethod): void {
this._defaultPaymentMethod = paymentMethod;
}
removePaymentMethod(paymentMethod: IPaymentMethod): void {
throw new Error("Method not implemented.");
}
addPaymentMethod(paymentMethod: IPaymentMethod): void {
paymentMethod.init(this);
this._paymentMethods.push(paymentMethod);
}
static create(props: IAccountProps): Result<Account> {
const guardResult = Guard.againstNullOrUndefinedBulk([
{ argument: props.user, argumentName: "user" }
]);
if (!guardResult.succeeded) {
return Result.fail(new Error(guardResult.message));
}
const defaultProps: IAccountProps = {
...props,
id: props.id ?? GUID.generateUUID4(),
createdAt: props.createdAt ?? moment(),
updatedAt: props.updatedAt ?? moment()
};
const account = new Account(defaultProps);
return Result.ok(account);
}
}
Top comments (4)
You can encapsulate payment in his own ValueObject :
And I thinks your method create is too generic, you can be more explicit :
It looks really interesting and I like your approach to encapsulate logic with payment methods in one object. Thanks!
Hello,
Why you don't like it ?