payment-method.service.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import { Injectable } from '@nestjs/common';
  2. import { InjectConnection } from '@nestjs/typeorm';
  3. import { ConfigArg, ConfigArgType, UpdatePaymentMethodInput } from '@vendure/common/lib/generated-types';
  4. import { omit } from '@vendure/common/lib/omit';
  5. import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
  6. import { assertNever } from '@vendure/common/lib/shared-utils';
  7. import { Connection } from 'typeorm';
  8. import { UserInputError } from '../../common/error/errors';
  9. import { ListQueryOptions } from '../../common/types/common-types';
  10. import { getConfig } from '../../config/config-helpers';
  11. import { ConfigService } from '../../config/config.service';
  12. import {
  13. PaymentMethodArgs,
  14. PaymentMethodArgType,
  15. PaymentMethodHandler,
  16. } from '../../config/payment-method/payment-method-handler';
  17. import { Order } from '../../entity/order/order.entity';
  18. import { PaymentMethod } from '../../entity/payment-method/payment-method.entity';
  19. import { Payment, PaymentMetadata } from '../../entity/payment/payment.entity';
  20. import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
  21. import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
  22. import { patchEntity } from '../helpers/utils/patch-entity';
  23. @Injectable()
  24. export class PaymentMethodService {
  25. constructor(
  26. @InjectConnection() private connection: Connection,
  27. private configService: ConfigService,
  28. private listQueryBuilder: ListQueryBuilder,
  29. ) {}
  30. async initPaymentMethods() {
  31. await this.ensurePaymentMethodsExist();
  32. }
  33. findAll(options?: ListQueryOptions<PaymentMethod>): Promise<PaginatedList<PaymentMethod>> {
  34. return this.listQueryBuilder
  35. .build(PaymentMethod, options)
  36. .getManyAndCount()
  37. .then(([items, totalItems]) => ({
  38. items,
  39. totalItems,
  40. }));
  41. }
  42. findOne(paymentMethodId: ID): Promise<PaymentMethod | undefined> {
  43. return this.connection.manager.findOne(PaymentMethod, paymentMethodId);
  44. }
  45. async update(input: UpdatePaymentMethodInput): Promise<PaymentMethod> {
  46. const paymentMethod = await getEntityOrThrow(this.connection, PaymentMethod, input.id);
  47. const updatedPaymentMethod = patchEntity(paymentMethod, omit(input, ['configArgs']));
  48. if (input.configArgs) {
  49. const handler = this.configService.paymentOptions.paymentMethodHandlers.find(
  50. h => h.code === paymentMethod.code,
  51. );
  52. if (handler) {
  53. updatedPaymentMethod.configArgs = input.configArgs;
  54. }
  55. }
  56. return this.connection.getRepository(PaymentMethod).save(updatedPaymentMethod);
  57. }
  58. async createPayment(order: Order, method: string, metadata: PaymentMetadata): Promise<Payment> {
  59. const { paymentMethod, handler } = await this.getMethodAndHandler(method);
  60. const result = await handler.createPayment(order, paymentMethod.configArgs, metadata || {});
  61. const payment = new Payment(result);
  62. return this.connection.getRepository(Payment).save(payment);
  63. }
  64. async settlePayment(payment: Payment, order: Order) {
  65. const { paymentMethod, handler } = await this.getMethodAndHandler(payment.method);
  66. return handler.settlePayment(order, payment, paymentMethod.configArgs);
  67. }
  68. private async getMethodAndHandler(method: string): Promise<{ paymentMethod: PaymentMethod, handler: PaymentMethodHandler }> {
  69. const paymentMethod = await this.connection.getRepository(PaymentMethod).findOne({
  70. where: {
  71. code: method,
  72. enabled: true,
  73. },
  74. });
  75. if (!paymentMethod) {
  76. throw new UserInputError(`error.payment-method-not-found`, { method });
  77. }
  78. const handler = this.configService.paymentOptions.paymentMethodHandlers.find(h => h.code === paymentMethod.code);
  79. if (!handler) {
  80. throw new UserInputError(`error.no-payment-handler-with-code`, { code: paymentMethod.code });
  81. }
  82. return { paymentMethod, handler };
  83. }
  84. private async ensurePaymentMethodsExist() {
  85. const paymentMethodHandlers: Array<PaymentMethodHandler<PaymentMethodArgs>> = this.configService
  86. .paymentOptions.paymentMethodHandlers;
  87. const existingPaymentMethods = await this.connection.getRepository(PaymentMethod).find();
  88. const toCreate = paymentMethodHandlers.filter(
  89. h => !existingPaymentMethods.find(pm => pm.code === h.code),
  90. );
  91. const toRemove = existingPaymentMethods.filter(
  92. h => !paymentMethodHandlers.find(pm => pm.code === h.code),
  93. );
  94. const toUpdate = existingPaymentMethods.filter(
  95. h => !toCreate.find(x => x.code === h.code) && !toRemove.find(x => x.code === h.code),
  96. );
  97. for (const paymentMethod of toUpdate) {
  98. const handler = paymentMethodHandlers.find(h => h.code === paymentMethod.code);
  99. if (!handler) {
  100. continue;
  101. }
  102. paymentMethod.configArgs = this.buildConfigArgsArray(handler, paymentMethod.configArgs);
  103. await this.connection.getRepository(PaymentMethod).save(paymentMethod);
  104. }
  105. for (const handler of toCreate) {
  106. let paymentMethod = existingPaymentMethods.find(pm => pm.code === handler.code);
  107. if (!paymentMethod) {
  108. paymentMethod = new PaymentMethod({
  109. code: handler.code,
  110. enabled: true,
  111. configArgs: [],
  112. });
  113. }
  114. paymentMethod.configArgs = this.buildConfigArgsArray(handler, paymentMethod.configArgs);
  115. await this.connection.getRepository(PaymentMethod).save(paymentMethod);
  116. }
  117. await this.connection.getRepository(PaymentMethod).remove(toRemove);
  118. }
  119. private buildConfigArgsArray(handler: PaymentMethodHandler, existingConfigArgs: ConfigArg[]): ConfigArg[] {
  120. let configArgs: ConfigArg[] = [];
  121. for (const [name, type] of Object.entries(handler.args)) {
  122. if (!existingConfigArgs.find(ca => ca.name === name)) {
  123. configArgs.push({
  124. name,
  125. type,
  126. value: this.getDefaultValue(type),
  127. });
  128. }
  129. }
  130. configArgs = configArgs.filter(ca =>
  131. handler.args.hasOwnProperty(ca.name),
  132. );
  133. return [...existingConfigArgs, ...configArgs];
  134. }
  135. private getDefaultValue(type: PaymentMethodArgType): string {
  136. switch (type) {
  137. case ConfigArgType.STRING:
  138. return '';
  139. case ConfigArgType.BOOLEAN:
  140. return 'false';
  141. case ConfigArgType.INT:
  142. return '0';
  143. default:
  144. assertNever(type);
  145. return '';
  146. }
  147. }
  148. }