payment-method-handler.ts 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. import { ConfigArg, RefundOrderInput } from '@vendure/common/lib/generated-types';
  2. import { ConfigArgSubset } from '@vendure/common/lib/shared-types';
  3. import {
  4. argsArrayToHash,
  5. ConfigArgs,
  6. ConfigArgValues,
  7. ConfigurableOperationDef,
  8. ConfigurableOperationDefOptions,
  9. LocalizedStringArray,
  10. } from '../../common/configurable-operation';
  11. import { StateMachineConfig } from '../../common/finite-state-machine/types';
  12. import { Order } from '../../entity/order/order.entity';
  13. import { Payment, PaymentMetadata } from '../../entity/payment/payment.entity';
  14. import {
  15. PaymentState,
  16. PaymentTransitionData,
  17. } from '../../service/helpers/payment-state-machine/payment-state';
  18. import { RefundState } from '../../service/helpers/refund-state-machine/refund-state';
  19. export type PaymentMethodArgType = ConfigArgSubset<'int' | 'string' | 'boolean'>;
  20. export type PaymentMethodArgs = ConfigArgs<PaymentMethodArgType>;
  21. export type OnTransitionStartReturnType = ReturnType<Required<StateMachineConfig<any>>['onTransitionStart']>;
  22. /**
  23. * @description
  24. * The signature of the function defined by `onStateTransitionStart` in {@link PaymentMethodConfigOptions}.
  25. *
  26. * This function is called before the state of a Payment is transitioned. Its
  27. * return value used to determine whether the transition can occur.
  28. *
  29. * @docsCategory payment
  30. * @docsPage Payment Method Types
  31. */
  32. export type OnTransitionStartFn<T extends PaymentMethodArgs> = (
  33. fromState: PaymentState,
  34. toState: PaymentState,
  35. args: ConfigArgValues<T>,
  36. data: PaymentTransitionData,
  37. ) => OnTransitionStartReturnType;
  38. /**
  39. * @description
  40. * This object is the return value of the {@link CreatePaymentFn}.
  41. *
  42. * @docsCategory payment
  43. * @docsPage Payment Method Types
  44. */
  45. export interface CreatePaymentResult {
  46. amount: number;
  47. state: Exclude<PaymentState, 'Refunded' | 'Error'>;
  48. transactionId?: string;
  49. errorMessage?: string;
  50. metadata?: PaymentMetadata;
  51. }
  52. /**
  53. * @description
  54. * This object is the return value of the {@link CreatePaymentFn} when there has been an error.
  55. *
  56. * @docsCategory payment
  57. * @docsPage Payment Method Types
  58. */
  59. export interface CreatePaymentErrorResult {
  60. amount: number;
  61. state: 'Error';
  62. transactionId?: string;
  63. errorMessage: string;
  64. metadata?: PaymentMetadata;
  65. }
  66. /**
  67. * @description
  68. * This object is the return value of the {@link CreateRefundFn}.
  69. *
  70. * @docsCategory payment
  71. * @docsPage Payment Method Types
  72. */
  73. export interface CreateRefundResult {
  74. state: RefundState;
  75. transactionId?: string;
  76. metadata?: PaymentMetadata;
  77. }
  78. /**
  79. * @description
  80. * This object is the return value of the {@link SettlePaymentFn}
  81. *
  82. * @docsCategory payment
  83. * @docsPage Payment Method Types
  84. */
  85. export interface SettlePaymentResult {
  86. success: boolean;
  87. errorMessage?: string;
  88. metadata?: PaymentMetadata;
  89. }
  90. /**
  91. * @description
  92. * This function contains the logic for creating a payment. See {@link PaymentMethodHandler} for an example.
  93. *
  94. * @docsCategory payment
  95. * @docsPage Payment Method Types
  96. */
  97. export type CreatePaymentFn<T extends PaymentMethodArgs> = (
  98. order: Order,
  99. args: ConfigArgValues<T>,
  100. metadata: PaymentMetadata,
  101. ) => CreatePaymentResult | CreatePaymentErrorResult | Promise<CreatePaymentResult | CreatePaymentErrorResult>;
  102. /**
  103. * @description
  104. * This function contains the logic for settling a payment. See {@link PaymentMethodHandler} for an example.
  105. *
  106. * @docsCategory payment
  107. * @docsPage Payment Method Types
  108. */
  109. export type SettlePaymentFn<T extends PaymentMethodArgs> = (
  110. order: Order,
  111. payment: Payment,
  112. args: ConfigArgValues<T>,
  113. ) => SettlePaymentResult | Promise<SettlePaymentResult>;
  114. /**
  115. * @description
  116. * This function contains the logic for creating a refund. See {@link PaymentMethodHandler} for an example.
  117. *
  118. * @docsCategory payment
  119. * @docsPage Payment Method Types
  120. */
  121. export type CreateRefundFn<T extends PaymentMethodArgs> = (
  122. input: RefundOrderInput,
  123. total: number,
  124. order: Order,
  125. payment: Payment,
  126. args: ConfigArgValues<T>,
  127. ) => CreateRefundResult | Promise<CreateRefundResult>;
  128. /**
  129. * @description
  130. * Defines the object which is used to construct the {@link PaymentMethodHandler}.
  131. *
  132. * @docsCategory payment
  133. */
  134. export interface PaymentMethodConfigOptions<T extends PaymentMethodArgs = PaymentMethodArgs>
  135. extends ConfigurableOperationDefOptions<T> {
  136. /**
  137. * @description
  138. * This function provides the logic for creating a payment. For example,
  139. * it may call out to a third-party service with the data and should return a
  140. * {@link CreatePaymentResult} object contains the details of the payment.
  141. */
  142. createPayment: CreatePaymentFn<T>;
  143. /**
  144. * @description
  145. * This function provides the logic for settling a payment, also known as "capturing".
  146. * For payment integrations that settle/capture the payment on creation (i.e. the
  147. * `createPayment()` method returns with a state of `'Settled'`) this method
  148. * need only return `{ success: true }`.
  149. */
  150. settlePayment: SettlePaymentFn<T>;
  151. /**
  152. * @description
  153. * This function provides the logic for refunding a payment created with this
  154. * payment method. Some payment providers may not provide the facility to
  155. * programmatically create a refund. In such a case, this method should be
  156. * omitted and any Refunds will have to be settled manually by an administrator.
  157. */
  158. createRefund?: CreateRefundFn<T>;
  159. /**
  160. * @description
  161. * This function, when specified, will be invoked before any transition from one {@link PaymentState} to another.
  162. * The return value (a sync / async `boolean`) is used to determine whether the transition is permitted.
  163. */
  164. onStateTransitionStart?: OnTransitionStartFn<T>;
  165. }
  166. /**
  167. * @description
  168. * A PaymentMethodHandler contains the code which is used to generate a Payment when a call to the
  169. * `addPaymentToOrder` mutation is made. If contains any necessary steps of interfacing with a
  170. * third-party payment gateway before the Payment is created and can also define actions to fire
  171. * when the state of the payment is changed.
  172. *
  173. * @example
  174. * ```ts
  175. * // A mock 3rd-party payment SDK
  176. * import gripeSDK from 'gripe';
  177. *
  178. * export const examplePaymentHandler = new PaymentMethodHandler({
  179. * code: 'example-payment-provider',
  180. * description: [{
  181. * languageCode: LanguageCode.en,
  182. * value: 'Example Payment Provider',
  183. * }],
  184. * args: {
  185. * apiKey: { type: 'string' },
  186. * },
  187. * createPayment: async (order, args, metadata): Promise<PaymentConfig> => {
  188. * try {
  189. * const result = await gripeSDK.charges.create({
  190. * apiKey: args.apiKey,
  191. * amount: order.total,
  192. * source: metadata.authToken,
  193. * });
  194. * return {
  195. * amount: order.total,
  196. * state: 'Settled' as 'Settled',
  197. * transactionId: result.id.toString(),
  198. * metadata: result.outcome,
  199. * };
  200. * } catch (err) {
  201. * return {
  202. * amount: order.total,
  203. * state: 'Declined' as 'Declined',
  204. * metadata: {
  205. * errorMessage: err.message,
  206. * },
  207. * };
  208. * }
  209. * },
  210. * settlePayment: async (order, payment, args): Promise<SettlePaymentResult> {
  211. * return { success: true };
  212. * }
  213. * });
  214. * ```
  215. *
  216. * @docsCategory payment
  217. */
  218. export class PaymentMethodHandler<
  219. T extends PaymentMethodArgs = PaymentMethodArgs
  220. > extends ConfigurableOperationDef<T> {
  221. private readonly createPaymentFn: CreatePaymentFn<T>;
  222. private readonly settlePaymentFn: SettlePaymentFn<T>;
  223. private readonly createRefundFn?: CreateRefundFn<T>;
  224. private readonly onTransitionStartFn?: OnTransitionStartFn<T>;
  225. constructor(config: PaymentMethodConfigOptions<T>) {
  226. super(config);
  227. this.createPaymentFn = config.createPayment;
  228. this.settlePaymentFn = config.settlePayment;
  229. this.settlePaymentFn = config.settlePayment;
  230. this.createRefundFn = config.createRefund;
  231. this.onTransitionStartFn = config.onStateTransitionStart;
  232. }
  233. /**
  234. * @description
  235. * Called internally to create a new Payment
  236. *
  237. * @internal
  238. */
  239. async createPayment(order: Order, args: ConfigArg[], metadata: PaymentMetadata) {
  240. const paymentConfig = await this.createPaymentFn(order, argsArrayToHash(args), metadata);
  241. return {
  242. method: this.code,
  243. ...paymentConfig,
  244. };
  245. }
  246. /**
  247. * @description
  248. * Called internally to settle a payment
  249. *
  250. * @internal
  251. */
  252. async settlePayment(order: Order, payment: Payment, args: ConfigArg[]) {
  253. return this.settlePaymentFn(order, payment, argsArrayToHash(args));
  254. }
  255. /**
  256. * @description
  257. * Called internally to create a refund
  258. *
  259. * @internal
  260. */
  261. async createRefund(
  262. input: RefundOrderInput,
  263. total: number,
  264. order: Order,
  265. payment: Payment,
  266. args: ConfigArg[],
  267. ) {
  268. return this.createRefundFn
  269. ? this.createRefundFn(input, total, order, payment, argsArrayToHash(args))
  270. : false;
  271. }
  272. /**
  273. * @description
  274. * This function is called before the state of a Payment is transitioned. If the PaymentMethodHandler
  275. * was instantiated with a `onStateTransitionStart` function, that function will be invoked and its
  276. * return value used to determine whether the transition can occur.
  277. *
  278. * @internal
  279. */
  280. onStateTransitionStart(
  281. fromState: PaymentState,
  282. toState: PaymentState,
  283. args: ConfigArg[],
  284. data: PaymentTransitionData,
  285. ): OnTransitionStartReturnType {
  286. if (typeof this.onTransitionStartFn === 'function') {
  287. return this.onTransitionStartFn(fromState, toState, argsArrayToHash(args), data);
  288. } else {
  289. return true;
  290. }
  291. }
  292. }