payment-helpers.ts 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. import { ID } from '@vendure/common/lib/shared-types';
  2. import {
  3. ChannelService,
  4. ErrorResult,
  5. LanguageCode,
  6. OrderService,
  7. PaymentMethodEligibilityChecker,
  8. PaymentService,
  9. RequestContext,
  10. assertFound,
  11. } from '@vendure/core';
  12. import { SimpleGraphQLClient, TestServer } from '@vendure/testing';
  13. import gql from 'graphql-tag';
  14. import { CREATE_COUPON, REFUND_ORDER } from './graphql/admin-queries';
  15. import {
  16. RefundFragment,
  17. RefundOrderMutation,
  18. RefundOrderMutationVariables,
  19. } from './graphql/generated-admin-types';
  20. import {
  21. GetShippingMethodsQuery,
  22. SetShippingMethodMutation,
  23. SetShippingMethodMutationVariables,
  24. TestOrderFragmentFragment,
  25. TransitionToStateMutation,
  26. TransitionToStateMutationVariables,
  27. } from './graphql/generated-shop-types';
  28. import {
  29. GET_ELIGIBLE_SHIPPING_METHODS,
  30. SET_SHIPPING_ADDRESS,
  31. SET_SHIPPING_METHOD,
  32. TRANSITION_TO_STATE,
  33. } from './graphql/shop-queries';
  34. export async function setShipping(shopClient: SimpleGraphQLClient): Promise<void> {
  35. const { setOrderShippingAddress: order } = await shopClient.query(SET_SHIPPING_ADDRESS, {
  36. input: {
  37. fullName: 'name',
  38. streetLine1: '12 the street',
  39. city: 'Leeuwarden',
  40. postalCode: '123456',
  41. countryCode: 'AT',
  42. },
  43. });
  44. const { eligibleShippingMethods } = await shopClient.query<GetShippingMethodsQuery>(
  45. GET_ELIGIBLE_SHIPPING_METHODS,
  46. );
  47. if (!eligibleShippingMethods?.length) {
  48. throw Error(
  49. `No eligible shipping methods found for order '${String(order.code)}' with a total of '${String(order.totalWithTax)}'`,
  50. );
  51. }
  52. await shopClient.query<SetShippingMethodMutation, SetShippingMethodMutationVariables>(
  53. SET_SHIPPING_METHOD,
  54. {
  55. id: eligibleShippingMethods[1].id,
  56. },
  57. );
  58. }
  59. export async function proceedToArrangingPayment(shopClient: SimpleGraphQLClient): Promise<ID> {
  60. await setShipping(shopClient);
  61. const { transitionOrderToState } = await shopClient.query<
  62. TransitionToStateMutation,
  63. TransitionToStateMutationVariables
  64. >(TRANSITION_TO_STATE, { state: 'ArrangingPayment' });
  65. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  66. return (transitionOrderToState as TestOrderFragmentFragment)!.id;
  67. }
  68. export async function refundOrderLine(
  69. adminClient: SimpleGraphQLClient,
  70. orderLineId: string,
  71. quantity: number,
  72. paymentId: string,
  73. adjustment: number,
  74. ): Promise<RefundFragment> {
  75. const { refundOrder } = await adminClient.query<RefundOrderMutation, RefundOrderMutationVariables>(
  76. REFUND_ORDER,
  77. {
  78. input: {
  79. lines: [{ orderLineId, quantity }],
  80. shipping: 0,
  81. adjustment,
  82. paymentId,
  83. },
  84. },
  85. );
  86. return refundOrder as RefundFragment;
  87. }
  88. /**
  89. * Add a partial payment to an order. This happens, for example, when using Gift cards
  90. */
  91. export async function addManualPayment(server: TestServer, orderId: ID, amount: number): Promise<void> {
  92. const ctx = new RequestContext({
  93. apiType: 'admin',
  94. isAuthorized: true,
  95. authorizedAsOwnerOnly: false,
  96. channel: await server.app.get(ChannelService).getDefaultChannel(),
  97. });
  98. const order = await assertFound(server.app.get(OrderService).findOne(ctx, orderId));
  99. // tslint:disable-next-line:no-non-null-assertion
  100. await server.app.get(PaymentService).createManualPayment(ctx, order, amount, {
  101. method: 'Gift card',
  102. orderId: order.id,
  103. metadata: {
  104. bogus: 'test',
  105. },
  106. });
  107. }
  108. /**
  109. * Create a coupon with the given code and discount amount.
  110. */
  111. export async function createFixedDiscountCoupon(
  112. adminClient: SimpleGraphQLClient,
  113. amount: number,
  114. couponCode: string,
  115. ): Promise<void> {
  116. const { createPromotion } = await adminClient.query(CREATE_COUPON, {
  117. input: {
  118. conditions: [],
  119. actions: [
  120. {
  121. code: 'order_fixed_discount',
  122. arguments: [
  123. {
  124. name: 'discount',
  125. value: String(amount),
  126. },
  127. ],
  128. },
  129. ],
  130. couponCode,
  131. startsAt: null,
  132. endsAt: null,
  133. perCustomerUsageLimit: null,
  134. usageLimit: null,
  135. enabled: true,
  136. translations: [
  137. {
  138. languageCode: 'en',
  139. name: `Coupon ${couponCode}`,
  140. description: '',
  141. customFields: {},
  142. },
  143. ],
  144. customFields: {},
  145. },
  146. });
  147. if (createPromotion.__typename === 'ErrorResult') {
  148. throw new Error(`Error creating coupon: ${(createPromotion as ErrorResult).errorCode}`);
  149. }
  150. }
  151. /**
  152. * Create a coupon that discounts the shipping costs
  153. */
  154. export async function createFreeShippingCoupon(
  155. adminClient: SimpleGraphQLClient,
  156. couponCode: string,
  157. ): Promise<void> {
  158. const { createPromotion } = await adminClient.query(CREATE_COUPON, {
  159. input: {
  160. conditions: [],
  161. actions: [
  162. {
  163. code: 'free_shipping',
  164. arguments: [],
  165. },
  166. ],
  167. couponCode,
  168. startsAt: null,
  169. endsAt: null,
  170. perCustomerUsageLimit: null,
  171. usageLimit: null,
  172. enabled: true,
  173. translations: [
  174. {
  175. languageCode: 'en',
  176. name: `Coupon ${couponCode}`,
  177. description: '',
  178. customFields: {},
  179. },
  180. ],
  181. customFields: {},
  182. },
  183. });
  184. if (createPromotion.__typename === 'ErrorResult') {
  185. throw new Error(`Error creating coupon: ${(createPromotion as ErrorResult).errorCode}`);
  186. }
  187. }
  188. /**
  189. * Test payment eligibility checker that doesn't allow orders with quantity 9 on an order line,
  190. * just so that we can easily mock non-eligibility
  191. */
  192. export const testPaymentEligibilityChecker = new PaymentMethodEligibilityChecker({
  193. code: 'test-payment-eligibility-checker',
  194. description: [{ languageCode: LanguageCode.en, value: 'Do not allow 9 items' }],
  195. args: {},
  196. check: (ctx, order, args) => {
  197. const hasLineWithQuantity9 = order.lines.find(line => line.quantity === 9);
  198. if (hasLineWithQuantity9) {
  199. return false;
  200. } else {
  201. return true;
  202. }
  203. },
  204. });
  205. export const CREATE_MOLLIE_PAYMENT_INTENT = gql`
  206. mutation createMolliePaymentIntent($input: MolliePaymentIntentInput!) {
  207. createMolliePaymentIntent(input: $input) {
  208. ... on MolliePaymentIntent {
  209. url
  210. }
  211. ... on MolliePaymentIntentError {
  212. errorCode
  213. message
  214. }
  215. }
  216. }
  217. `;
  218. export const CREATE_STRIPE_PAYMENT_INTENT = gql`
  219. mutation createStripePaymentIntent {
  220. createStripePaymentIntent
  221. }
  222. `;
  223. export const CREATE_CUSTOM_STRIPE_PAYMENT_INTENT = gql`
  224. mutation createCustomStripePaymentIntent {
  225. createCustomStripePaymentIntent
  226. }
  227. `;
  228. export const GET_MOLLIE_PAYMENT_METHODS = gql`
  229. query molliePaymentMethods($input: MolliePaymentMethodsInput!) {
  230. molliePaymentMethods(input: $input) {
  231. id
  232. code
  233. description
  234. minimumAmount {
  235. value
  236. currency
  237. }
  238. maximumAmount {
  239. value
  240. currency
  241. }
  242. image {
  243. size1x
  244. size2x
  245. svg
  246. }
  247. }
  248. }
  249. `;