payment-helpers.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import { ID } from '@vendure/common/lib/shared-types';
  2. import {
  3. ChannelService,
  4. LanguageCode,
  5. OrderService,
  6. PaymentMethodEligibilityChecker,
  7. PaymentService,
  8. RequestContext,
  9. assertFound,
  10. } from '@vendure/core';
  11. import { SimpleGraphQLClient, TestServer } from '@vendure/testing';
  12. import { createCouponDocument, refundOrderDocument } from './graphql/admin-definitions';
  13. import { refundFragment } from './graphql/fragments-admin';
  14. import { FragmentOf } from './graphql/graphql-admin';
  15. import {
  16. getEligibleShippingMethodsDocument,
  17. setShippingAddressDocument,
  18. setShippingMethodDocument,
  19. transitionToStateDocument,
  20. } from './graphql/shop-definitions';
  21. export async function setShipping(shopClient: SimpleGraphQLClient): Promise<void> {
  22. const { setOrderShippingAddress } = await shopClient.query(setShippingAddressDocument, {
  23. input: {
  24. fullName: 'name',
  25. streetLine1: '12 the street',
  26. city: 'Leeuwarden',
  27. postalCode: '123456',
  28. countryCode: 'AT',
  29. },
  30. });
  31. if (!setOrderShippingAddress || 'errorCode' in setOrderShippingAddress) {
  32. throw Error('Failed to set shipping address');
  33. }
  34. const order = setOrderShippingAddress;
  35. const { eligibleShippingMethods } = await shopClient.query(getEligibleShippingMethodsDocument);
  36. if (!eligibleShippingMethods?.length) {
  37. throw Error(
  38. `No eligible shipping methods found for order '${String(order.code)}' with a total of '${String(order.totalWithTax)}'`,
  39. );
  40. }
  41. await shopClient.query(setShippingMethodDocument, {
  42. id: [eligibleShippingMethods[1].id],
  43. });
  44. }
  45. export async function proceedToArrangingPayment(shopClient: SimpleGraphQLClient): Promise<ID> {
  46. await setShipping(shopClient);
  47. const { transitionOrderToState } = await shopClient.query(transitionToStateDocument, {
  48. state: 'ArrangingPayment',
  49. });
  50. if (!transitionOrderToState || 'errorCode' in transitionOrderToState) {
  51. throw Error('Failed to transition to ArrangingPayment');
  52. }
  53. return transitionOrderToState.id;
  54. }
  55. export async function refundOrderLine(
  56. adminClient: SimpleGraphQLClient,
  57. orderLineId: string,
  58. quantity: number,
  59. paymentId: string,
  60. adjustment: number,
  61. ): Promise<FragmentOf<typeof refundFragment>> {
  62. const { refundOrder } = await adminClient.query(refundOrderDocument, {
  63. input: {
  64. lines: [{ orderLineId, quantity }],
  65. shipping: 0,
  66. adjustment,
  67. paymentId,
  68. },
  69. });
  70. if (!refundOrder || 'errorCode' in refundOrder) {
  71. throw Error('Failed to refund order');
  72. }
  73. return refundOrder;
  74. }
  75. /**
  76. * Add a partial payment to an order. This happens, for example, when using Gift cards
  77. */
  78. export async function addManualPayment(server: TestServer, orderId: ID, amount: number): Promise<void> {
  79. const ctx = new RequestContext({
  80. apiType: 'admin',
  81. isAuthorized: true,
  82. authorizedAsOwnerOnly: false,
  83. channel: await server.app.get(ChannelService).getDefaultChannel(),
  84. });
  85. const order = await assertFound(server.app.get(OrderService).findOne(ctx, orderId));
  86. // tslint:disable-next-line:no-non-null-assertion
  87. await server.app.get(PaymentService).createManualPayment(ctx, order, amount, {
  88. method: 'Gift card',
  89. orderId: order.id,
  90. metadata: {
  91. bogus: 'test',
  92. },
  93. });
  94. }
  95. /**
  96. * Create a coupon with the given code and discount amount.
  97. */
  98. export async function createFixedDiscountCoupon(
  99. adminClient: SimpleGraphQLClient,
  100. amount: number,
  101. couponCode: string,
  102. ): Promise<void> {
  103. const { createPromotion } = await adminClient.query(createCouponDocument, {
  104. input: {
  105. conditions: [],
  106. actions: [
  107. {
  108. code: 'order_fixed_discount',
  109. arguments: [
  110. {
  111. name: 'discount',
  112. value: String(amount),
  113. },
  114. ],
  115. },
  116. ],
  117. couponCode,
  118. startsAt: null,
  119. endsAt: null,
  120. perCustomerUsageLimit: null,
  121. usageLimit: null,
  122. enabled: true,
  123. translations: [
  124. {
  125. languageCode: 'en',
  126. name: `Coupon ${couponCode}`,
  127. description: '',
  128. customFields: {},
  129. },
  130. ],
  131. customFields: {},
  132. },
  133. });
  134. if (createPromotion.__typename !== 'Promotion') {
  135. throw new Error(`Error creating coupon: ${createPromotion.errorCode}`);
  136. }
  137. }
  138. /**
  139. * Create a coupon that discounts the shipping costs
  140. */
  141. export async function createFreeShippingCoupon(
  142. adminClient: SimpleGraphQLClient,
  143. couponCode: string,
  144. ): Promise<void> {
  145. const { createPromotion } = await adminClient.query(createCouponDocument, {
  146. input: {
  147. conditions: [],
  148. actions: [
  149. {
  150. code: 'free_shipping',
  151. arguments: [],
  152. },
  153. ],
  154. couponCode,
  155. startsAt: null,
  156. endsAt: null,
  157. perCustomerUsageLimit: null,
  158. usageLimit: null,
  159. enabled: true,
  160. translations: [
  161. {
  162. languageCode: 'en',
  163. name: `Coupon ${couponCode}`,
  164. description: '',
  165. customFields: {},
  166. },
  167. ],
  168. customFields: {},
  169. },
  170. });
  171. if (createPromotion.__typename !== 'Promotion') {
  172. throw new Error(`Error creating coupon: ${createPromotion.errorCode}`);
  173. }
  174. }
  175. /**
  176. * Test payment eligibility checker that doesn't allow orders with quantity 9 on an order line,
  177. * just so that we can easily mock non-eligibility
  178. */
  179. export const testPaymentEligibilityChecker = new PaymentMethodEligibilityChecker({
  180. code: 'test-payment-eligibility-checker',
  181. description: [{ languageCode: LanguageCode.en, value: 'Do not allow 9 items' }],
  182. args: {},
  183. check: (ctx, order, args) => {
  184. const hasLineWithQuantity9 = order.lines.find(line => line.quantity === 9);
  185. if (hasLineWithQuantity9) {
  186. return false;
  187. } else {
  188. return true;
  189. }
  190. },
  191. });