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 ?