richie-education 2.28.2-dev26 → 2.28.2-dev53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/js/api/joanie.ts +42 -17
  2. package/js/api/lms/dummy.ts +1 -12
  3. package/js/components/ContractFrame/AbstractContractFrame.spec.tsx +16 -9
  4. package/js/components/ContractFrame/AbstractContractFrame.tsx +28 -23
  5. package/js/components/ContractFrame/LearnerContractFrame.tsx +2 -2
  6. package/js/components/ContractFrame/_styles.scss +6 -14
  7. package/js/components/{SaleTunnel/CreditCardSelector → CreditCardSelector}/index.spec.tsx +15 -45
  8. package/js/components/{SaleTunnel/CreditCardSelector → CreditCardSelector}/index.tsx +17 -24
  9. package/js/components/DownloadContractButton/index.spec.tsx +1 -1
  10. package/js/components/OpenEdxFullNameForm/index.spec.tsx +229 -0
  11. package/js/components/OpenEdxFullNameForm/index.tsx +7 -7
  12. package/js/components/PaymentInterfaces/LyraPopIn.tsx +2 -2
  13. package/js/components/PaymentInterfaces/PayplugLightbox.tsx +1 -1
  14. package/js/components/PaymentInterfaces/__mocks__/index.tsx +1 -4
  15. package/js/components/PaymentInterfaces/types.ts +5 -2
  16. package/js/components/PaymentScheduleGrid/_styles.scss +13 -0
  17. package/js/components/PaymentScheduleGrid/index.tsx +50 -70
  18. package/js/components/PurchaseButton/index.spec.tsx +84 -37
  19. package/js/components/SaleTunnel/AddressSelector/index.spec.tsx +2 -1
  20. package/js/components/SaleTunnel/CertificateSaleTunnel/index.tsx +2 -2
  21. package/js/components/SaleTunnel/CredentialSaleTunnel/index.tsx +6 -10
  22. package/js/components/SaleTunnel/GenericSaleTunnel.tsx +80 -27
  23. package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +16 -20
  24. package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/_styles.scss +12 -0
  25. package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/index.tsx +160 -0
  26. package/js/components/SaleTunnel/SaleTunnelSuccess/index.tsx +15 -29
  27. package/js/components/SaleTunnel/Sponsors/SaleTunnelSponsors.scss +4 -5
  28. package/js/components/SaleTunnel/Sponsors/SaleTunnelSponsors.tsx +39 -11
  29. package/js/components/SaleTunnel/SubscriptionButton/_styles.scss +7 -0
  30. package/js/components/SaleTunnel/SubscriptionButton/index.tsx +201 -0
  31. package/js/components/SaleTunnel/_styles.scss +16 -5
  32. package/js/components/SaleTunnel/hooks/useTerms.tsx +0 -77
  33. package/js/components/SaleTunnel/index.credential.spec.tsx +14 -25
  34. package/js/components/SaleTunnel/index.full-process.spec.tsx +116 -48
  35. package/js/components/SaleTunnel/index.spec.tsx +334 -717
  36. package/js/components/SignContractButton/index.omniscientOrders.spec.tsx +16 -11
  37. package/js/components/SignContractButton/index.spec.tsx +16 -20
  38. package/js/components/SignContractButton/index.tsx +3 -1
  39. package/js/hooks/useCreditCards/index.spec.tsx +70 -6
  40. package/js/hooks/useCreditCards/index.ts +49 -11
  41. package/js/hooks/useOrders/index.spec.tsx +322 -0
  42. package/js/hooks/{useOrders.ts → useOrders/index.ts} +40 -14
  43. package/js/hooks/usePaymentSchedule.tsx +23 -0
  44. package/js/hooks/useProductOrder/index.spec.tsx +77 -60
  45. package/js/hooks/useProductOrder/index.tsx +2 -2
  46. package/js/hooks/useResources/useResourcesRoot.ts +4 -3
  47. package/js/index.tsx +2 -0
  48. package/js/pages/DashboardCreditCardsManagement/CreditCardBrandLogo.spec.tsx +1 -1
  49. package/js/pages/DashboardCreditCardsManagement/CreditCardBrandLogo.tsx +4 -2
  50. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +8 -5
  51. package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +8 -9
  52. package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.spec.tsx +1 -1
  53. package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.tsx +1 -6
  54. package/js/settings/settings.test.ts +11 -2
  55. package/js/types/Joanie.ts +77 -31
  56. package/js/utils/OrderHelper/index.ts +47 -38
  57. package/js/utils/test/factories/joanie.ts +66 -68
  58. package/js/widgets/Dashboard/components/DashboardItem/CourseEnrolling/index.tsx +8 -18
  59. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +26 -32
  60. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx +11 -6
  61. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.spec.tsx +114 -5
  62. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +99 -12
  63. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.spec.tsx +3 -1
  64. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +6 -7
  65. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/_styles.scss +7 -0
  66. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/index.tsx +126 -0
  67. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentRetryModal/index.tsx +209 -0
  68. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.spec.tsx +18 -71
  69. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.tsx +40 -25
  70. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateMessage/index.tsx +28 -22
  71. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.spec.tsx +18 -73
  72. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.tsx +32 -16
  73. package/js/widgets/Dashboard/components/DashboardOrderLoader/index.tsx +3 -11
  74. package/js/widgets/Dashboard/components/Signature/SignatureDummy.tsx +25 -3
  75. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/EnrollableCourseRunList.tsx +2 -6
  76. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/index.spec.tsx +7 -14
  77. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.spec.tsx +7 -5
  78. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.tsx +5 -7
  79. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +242 -332
  80. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +12 -13
  81. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +10 -21
  82. package/js/widgets/SyllabusCourseRunsList/components/CourseRunEnrollment/index.joanie.spec.tsx +2 -2
  83. package/package.json +2 -1
  84. package/scss/components/_index.scss +4 -2
  85. package/js/components/PaymentButton/_styles.scss +0 -27
  86. package/js/components/SaleTunnel/GenericPaymentButton/index.tsx +0 -338
  87. package/js/components/SaleTunnel/SaleTunnelNotValidated/index.tsx +0 -70
  88. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/ProductSignatureHeader/index.tsx +0 -41
  89. /package/js/components/{SaleTunnel/CreditCardSelector → CreditCardSelector}/_styles.scss +0 -0
@@ -3,6 +3,7 @@ import { render, screen } from '@testing-library/react';
3
3
  import { IntlProvider } from 'react-intl';
4
4
  import { PropsWithChildren } from 'react';
5
5
  import { QueryClientProvider } from '@tanstack/react-query';
6
+ import { CunninghamProvider } from '@openfun/cunningham-react';
6
7
  import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
7
8
  import JoanieApiProvider from 'contexts/JoanieApiContext';
8
9
 
@@ -50,11 +51,13 @@ jest.mock('pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/
50
51
  describe('TeacherDashboardContractsLayout/ContractActionsBar', () => {
51
52
  const Wrapper = ({ children }: PropsWithChildren) => {
52
53
  return (
53
- <IntlProvider locale="en">
54
- <QueryClientProvider client={createTestQueryClient({ user: true })}>
55
- <JoanieApiProvider>{children}</JoanieApiProvider>
56
- </QueryClientProvider>
57
- </IntlProvider>
54
+ <CunninghamProvider>
55
+ <IntlProvider locale="en">
56
+ <QueryClientProvider client={createTestQueryClient({ user: true })}>
57
+ <JoanieApiProvider>{children}</JoanieApiProvider>
58
+ </QueryClientProvider>
59
+ </IntlProvider>
60
+ </CunninghamProvider>
58
61
  );
59
62
  };
60
63
 
@@ -5,6 +5,7 @@ import { IntlProvider } from 'react-intl';
5
5
  import { QueryClientProvider } from '@tanstack/react-query';
6
6
  import fetchMock from 'fetch-mock';
7
7
  import userEvent from '@testing-library/user-event';
8
+ import { CunninghamProvider } from '@openfun/cunningham-react';
8
9
  import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
9
10
  import JoanieApiProvider from 'contexts/JoanieApiContext';
10
11
  import { createTestQueryClient } from 'utils/test/createTestQueryClient';
@@ -21,11 +22,13 @@ jest.mock('utils/context', () => ({
21
22
  describe('TeacherDashboardContractsLayout/SignOrganizationContractButton', () => {
22
23
  const Wrapper = ({ children }: PropsWithChildren) => {
23
24
  return (
24
- <IntlProvider locale="en">
25
- <QueryClientProvider client={createTestQueryClient({ user: true })}>
26
- <JoanieApiProvider>{children}</JoanieApiProvider>
27
- </QueryClientProvider>
28
- </IntlProvider>
25
+ <CunninghamProvider>
26
+ <IntlProvider locale="en">
27
+ <QueryClientProvider client={createTestQueryClient({ user: true })}>
28
+ <JoanieApiProvider>{children}</JoanieApiProvider>
29
+ </QueryClientProvider>
30
+ </IntlProvider>
31
+ </CunninghamProvider>
29
32
  );
30
33
  };
31
34
 
@@ -46,8 +49,6 @@ describe('TeacherDashboardContractsLayout/SignOrganizationContractButton', () =>
46
49
  expect(
47
50
  screen.getByRole('button', { name: 'Sign all pending contracts (12)' }),
48
51
  ).toBeInTheDocument();
49
- const DashboardContractFramePortal = document.getElementsByClassName('ReactModalPortal');
50
- expect(DashboardContractFramePortal).toHaveLength(1);
51
52
  });
52
53
 
53
54
  it("shouldn't display sign button user don't have some contract to sign", () => {
@@ -63,8 +64,6 @@ describe('TeacherDashboardContractsLayout/SignOrganizationContractButton', () =>
63
64
  expect(
64
65
  screen.queryByRole('button', { name: /Sign all pending contracts/ }),
65
66
  ).not.toBeInTheDocument();
66
- const DashboardContractFramePortal = document.getElementsByClassName('ReactModalPortal');
67
- expect(DashboardContractFramePortal).toHaveLength(1);
68
67
  });
69
68
 
70
69
  it.each([
@@ -14,7 +14,7 @@ import CourseLearnerDataGrid from '.';
14
14
  describe('pages/CourseLearnerDataGrid', () => {
15
15
  it('should render a list of user', async () => {
16
16
  const courseOrderList = NestedCourseOrderFactory({
17
- state: OrderState.VALIDATED,
17
+ state: OrderState.COMPLETED,
18
18
  certificate_id: faker.string.uuid(),
19
19
  }).many(3);
20
20
  render(
@@ -74,12 +74,7 @@ const CourseLearnerDataGrid = ({
74
74
  headerName: intl.formatMessage(messages.columnState),
75
75
  enableSorting: false,
76
76
  renderCell: (params: { row: Row }) => {
77
- return (
78
- <OrderStateTeacherMessage
79
- order={params.row.courseOrder}
80
- contractDefinition={params.row.courseOrder.product.contract_definition_id}
81
- />
82
- );
77
+ return <OrderStateTeacherMessage order={params.row.courseOrder} />;
83
78
  },
84
79
  },
85
80
  {
@@ -1,5 +1,5 @@
1
- // This confiuration file is used for testing.
2
- // Mostly usefull to test our test tools.
1
+ // This configuration file is used for testing.
2
+ // Mostly useful to test our test tools.
3
3
  import { DevDemoUser } from 'api/lms/dummy';
4
4
 
5
5
  /**
@@ -14,3 +14,12 @@ import { DevDemoUser } from 'api/lms/dummy';
14
14
  * * student_user
15
15
  */
16
16
  export const CURRENT_JOANIE_DEV_DEMO_USER: DevDemoUser = 'admin';
17
+
18
+ export const CONTRACT_SETTINGS = {
19
+ // Interval in ms to poll the related order when a signature has succeeded.
20
+ pollInterval: 150,
21
+ // Number of retries
22
+ pollLimit: 45,
23
+ // Simulated sign request delay
24
+ dummySignatureSignTimeout: 100,
25
+ };
@@ -259,14 +259,42 @@ export interface EnrollmentLight {
259
259
 
260
260
  // Order
261
261
  export enum OrderState {
262
- DRAFT = 'draft',
263
- SUBMITTED = 'submitted',
262
+ ASSIGNED = 'assigned',
264
263
  CANCELED = 'canceled',
264
+ COMPLETED = 'completed',
265
+ DRAFT = 'draft',
266
+ FAILED_PAYMENT = 'failed_payment',
267
+ NO_PAYMENT = 'no_payment',
265
268
  PENDING = 'pending',
266
- VALIDATED = 'validated',
267
- }
268
-
269
- export const ACTIVE_ORDER_STATES = [OrderState.PENDING, OrderState.VALIDATED, OrderState.SUBMITTED];
269
+ PENDING_PAYMENT = 'pending_payment',
270
+ SIGNING = 'signing',
271
+ TO_SAVE_PAYMENT_METHOD = 'to_save_payment_method',
272
+ TO_SIGN = 'to_sign',
273
+ }
274
+
275
+ export const PURCHASABLE_ORDER_STATES = [
276
+ OrderState.DRAFT,
277
+ OrderState.ASSIGNED,
278
+ OrderState.TO_SIGN,
279
+ OrderState.SIGNING,
280
+ OrderState.TO_SAVE_PAYMENT_METHOD,
281
+ ];
282
+
283
+ export const ACTIVE_ORDER_STATES = [
284
+ OrderState.PENDING,
285
+ OrderState.PENDING_PAYMENT,
286
+ OrderState.NO_PAYMENT,
287
+ OrderState.FAILED_PAYMENT,
288
+ OrderState.COMPLETED,
289
+ ];
290
+
291
+ export const NOT_CANCELED_ORDER_STATES = [...ACTIVE_ORDER_STATES, ...PURCHASABLE_ORDER_STATES];
292
+
293
+ export const ENROLLABLE_ORDER_STATES = [
294
+ OrderState.COMPLETED,
295
+ OrderState.PENDING_PAYMENT,
296
+ OrderState.FAILED_PAYMENT,
297
+ ];
270
298
 
271
299
  export interface Order {
272
300
  id: string;
@@ -286,6 +314,8 @@ export interface Order {
286
314
  organization_id: Organization['id'];
287
315
  organization: Organization;
288
316
  order_group_id?: OrderGroup['id'];
317
+ payment_schedule?: PaymentSchedule;
318
+ credit_card_id?: CreditCard['id'];
289
319
  }
290
320
 
291
321
  export interface CredentialOrder extends Order {
@@ -293,20 +323,12 @@ export interface CredentialOrder extends Order {
293
323
  enrollment: null;
294
324
  }
295
325
 
296
- export interface CredentialOrderWithPaymentInfo extends CredentialOrder {
297
- payment_info: Payment;
298
- }
299
-
300
326
  export interface CertificateOrder extends Order {
301
327
  course: null;
302
328
  enrollment: EnrollmentLight;
303
329
  target_courses: never[];
304
330
  }
305
331
 
306
- export interface CertificateOrderWithPaymentInfo extends CertificateOrder {
307
- payment_info: Payment;
308
- }
309
-
310
332
  export type OrderLite = Pick<
311
333
  Order,
312
334
  | 'id'
@@ -325,9 +347,11 @@ export interface AbstractNestedOrder {
325
347
  product_title: string;
326
348
  owner_name: string;
327
349
  state: OrderState;
350
+ course: Nullable<CourseLight>;
351
+ enrollment: Nullable<EnrollmentLight>;
328
352
  }
329
353
  export interface NestedCertificateOrder extends AbstractNestedOrder {
330
- course: undefined;
354
+ course: null;
331
355
  enrollment: EnrollmentLight;
332
356
  }
333
357
  export const isNestedCredentialOrder = (
@@ -338,7 +362,7 @@ export const isNestedCredentialOrder = (
338
362
 
339
363
  export interface NestedCredentialOrder extends AbstractNestedOrder {
340
364
  course: CourseLight;
341
- enrollment: undefined;
365
+ enrollment: null;
342
366
  }
343
367
 
344
368
  export type OrderEnrollment = Pick<Order, 'id' | 'state' | 'product_id' | 'certificate_id'>;
@@ -372,10 +396,10 @@ export interface OrderGroup {
372
396
  }
373
397
 
374
398
  export enum CreditCardBrand {
375
- MASTERCARD = 'Mastercard',
376
- MAESTRO = 'Maestro',
377
- VISA = 'Visa',
378
- CB = 'CB',
399
+ MASTERCARD = 'mastercard',
400
+ MAESTRO = 'maestro',
401
+ VISA = 'visa',
402
+ CB = 'cb',
379
403
  }
380
404
 
381
405
  // Credit Card
@@ -416,6 +440,22 @@ export interface OrderPaymentInfo {
416
440
  payment_info: Payment;
417
441
  }
418
442
 
443
+ export enum PaymentScheduleState {
444
+ PENDING = 'pending',
445
+ PAID = 'paid',
446
+ REFUSED = 'refused',
447
+ }
448
+
449
+ export interface PaymentInstallment {
450
+ id: string;
451
+ amount: number;
452
+ currency: string;
453
+ due_date: string;
454
+ state: PaymentScheduleState;
455
+ }
456
+
457
+ export type PaymentSchedule = readonly PaymentInstallment[];
458
+
419
459
  // - API
420
460
  export interface AddressCreationPayload extends Omit<Address, 'id' | 'is_main'> {
421
461
  is_main?: boolean;
@@ -424,7 +464,7 @@ export interface AddressCreationPayload extends Omit<Address, 'id' | 'is_main'>
424
464
  interface AbstractOrderProductCreationPayload {
425
465
  product_id: Product['id'];
426
466
  order_group_id?: OrderGroup['id'];
427
- has_consent_to_terms: boolean;
467
+ billing_address: Omit<Address, 'id' | 'is_main'>;
428
468
  }
429
469
 
430
470
  interface OrderCertificateCreationPayload extends AbstractOrderProductCreationPayload {
@@ -436,15 +476,13 @@ export interface OrderCredentialCreationPayload extends AbstractOrderProductCrea
436
476
 
437
477
  export type OrderCreationPayload = OrderCertificateCreationPayload | OrderCredentialCreationPayload;
438
478
 
439
- interface OrderAbortPayload {
440
- id: Order['id'];
441
- payment_id?: string;
442
- }
479
+ export type OrderSubmitInstallmentPayment = {
480
+ credit_card_id?: string;
481
+ };
443
482
 
444
- interface OrderSubmitPayload {
483
+ interface OrderSetPaymentMethodPayload {
445
484
  id: Order['id'];
446
- billing_address: Omit<Address, 'id' | 'is_main'>;
447
- credit_card_id?: CreditCard['id'];
485
+ credit_card_id: CreditCard['id'];
448
486
  }
449
487
 
450
488
  export interface PaginatedResourceQuery extends ResourcesQuery {
@@ -539,14 +577,15 @@ interface APIUser {
539
577
  update(payload: Address): Promise<Address>;
540
578
  };
541
579
  creditCards: {
542
- create(payload: Omit<CreditCard, 'id'>): Promise<CreditCard>;
543
580
  delete(id: CreditCard['id']): Promise<void>;
581
+ get(): Promise<PaginatedResponse<CreditCard>>;
544
582
  get(filters?: ResourcesQuery): Promise<CreditCard>;
545
583
  get(): Promise<CreditCard[]>;
546
584
  update(payload: CreditCard): Promise<CreditCard>;
585
+ tokenize(): Promise<Payment>;
547
586
  };
548
587
  orders: {
549
- abort(payload: OrderAbortPayload): Promise<void>;
588
+ cancel(id: Order['id']): Promise<void>;
550
589
  create(payload: OrderCreationPayload): Promise<CredentialOrder | CertificateOrder>;
551
590
  get<Filters extends OrderResourcesQuery = OrderResourcesQuery>(
552
591
  filters?: Filters,
@@ -556,8 +595,12 @@ interface APIUser {
556
595
  invoice: {
557
596
  download(payload: { order_id: Order['id']; invoice_reference: string }): Promise<File>;
558
597
  };
559
- submit(payload: OrderSubmitPayload): Promise<OrderPaymentInfo>;
560
598
  submit_for_signature(id: string): Promise<ContractInvitationLinkResponse>;
599
+ submit_installment_payment(
600
+ id: string,
601
+ payload?: OrderSubmitInstallmentPayment,
602
+ ): Promise<Payment>;
603
+ set_payment_method(payload: OrderSetPaymentMethodPayload): Promise<void>;
561
604
  };
562
605
  certificates: {
563
606
  download(id: string): Promise<File>;
@@ -614,6 +657,9 @@ export interface API {
614
657
  : Promise<PaginatedResponse<CourseListItem>>;
615
658
  products: {
616
659
  get(filters?: CourseProductQueryFilters): Promise<Nullable<CourseProductRelation>>;
660
+ paymentSchedule: {
661
+ get(filters?: CourseProductQueryFilters): Promise<Nullable<PaymentSchedule>>;
662
+ };
617
663
  };
618
664
  orders: {
619
665
  get(
@@ -1,49 +1,52 @@
1
1
  import {
2
- OrderEnrollment,
3
2
  ACTIVE_ORDER_STATES,
3
+ ENROLLABLE_ORDER_STATES,
4
+ NestedCourseOrder,
4
5
  Order,
6
+ OrderEnrollment,
5
7
  OrderState,
6
- ContractDefinition,
7
- NestedCourseOrder,
8
+ PaymentScheduleState,
8
9
  } from 'types/Joanie';
9
10
 
10
11
  export enum OrderStatus {
12
+ ASSIGNED = 'assigned',
13
+ CANCELED = 'canceled',
14
+ COMPLETED = 'completed',
11
15
  DRAFT = 'draft',
12
- SUBMITTED = 'submitted',
16
+ FAILED_PAYMENT = 'failed_payment',
17
+ NO_PAYMENT = 'no_payment',
18
+ PASSED = 'passed',
13
19
  PENDING = 'pending',
14
- CANCELED = 'canceled',
15
- WAITING_SIGNATURE = 'waiting_signature',
20
+ PENDING_PAYMENT = 'pending_payment',
16
21
  WAITING_COUNTER_SIGNATURE = 'waiting_counter_signature',
17
- COMPLETED = 'completed',
18
- ON_GOING = 'on_going',
22
+ WAITING_PAYMENT_METHOD = 'waiting_payment_method',
23
+ WAITING_SIGNATURE = 'waiting_signature',
19
24
  }
20
25
 
21
26
  /**
22
27
  * Helper class for orders
23
28
  */
24
29
  export class OrderHelper {
25
- static getState(order: Order | NestedCourseOrder, contractDefinition?: ContractDefinition) {
26
- const { certificate_id: certificateId } = order;
27
-
28
- if (order.state === OrderState.VALIDATED) {
29
- if (OrderHelper.orderNeedsSignature(order, contractDefinition)) {
30
- return OrderStatus.WAITING_SIGNATURE;
31
- }
32
- if (OrderHelper.orderNeedsCounterSignature(order)) {
33
- return OrderStatus.WAITING_COUNTER_SIGNATURE;
34
- }
35
- if (certificateId) {
36
- return OrderStatus.COMPLETED;
37
- } else {
38
- return OrderStatus.ON_GOING;
39
- }
30
+ static getState(order: Order | NestedCourseOrder) {
31
+ if (OrderHelper.allowEnrollment(order) && OrderHelper.orderNeedsCounterSignature(order)) {
32
+ return OrderStatus.WAITING_COUNTER_SIGNATURE;
33
+ }
34
+ if (order.state === OrderState.COMPLETED && order.certificate_id) {
35
+ return OrderStatus.PASSED;
40
36
  }
41
37
 
42
38
  const orderStatusMap = {
39
+ [OrderState.ASSIGNED]: OrderStatus.ASSIGNED,
40
+ [OrderState.CANCELED]: OrderStatus.CANCELED,
41
+ [OrderState.COMPLETED]: OrderStatus.COMPLETED,
43
42
  [OrderState.DRAFT]: OrderStatus.DRAFT,
44
- [OrderState.SUBMITTED]: OrderStatus.SUBMITTED,
43
+ [OrderState.FAILED_PAYMENT]: OrderStatus.FAILED_PAYMENT,
44
+ [OrderState.NO_PAYMENT]: OrderStatus.NO_PAYMENT,
45
45
  [OrderState.PENDING]: OrderStatus.PENDING,
46
- [OrderState.CANCELED]: OrderStatus.CANCELED,
46
+ [OrderState.PENDING_PAYMENT]: OrderStatus.PENDING_PAYMENT,
47
+ [OrderState.SIGNING]: OrderStatus.WAITING_SIGNATURE,
48
+ [OrderState.TO_SAVE_PAYMENT_METHOD]: OrderStatus.WAITING_PAYMENT_METHOD,
49
+ [OrderState.TO_SIGN]: OrderStatus.WAITING_SIGNATURE,
47
50
  };
48
51
 
49
52
  if (order.state in orderStatusMap) {
@@ -62,18 +65,8 @@ export class OrderHelper {
62
65
  return orders.find(filter);
63
66
  }
64
67
 
65
- /**
66
- * tell us if a order need to be sign by it's owner (the learner user).
67
- */
68
- static orderNeedsSignature(
69
- order: Order | NestedCourseOrder,
70
- contractDefinition?: ContractDefinition,
71
- ) {
72
- return (
73
- order?.state === OrderState.VALIDATED &&
74
- contractDefinition &&
75
- !(order.contract && order.contract.student_signed_on)
76
- );
68
+ static orderNeedsSignature(order: Order | NestedCourseOrder) {
69
+ return [OrderState.TO_SIGN, OrderState.SIGNING].includes(order.state);
77
70
  }
78
71
 
79
72
  /**
@@ -81,10 +74,26 @@ export class OrderHelper {
81
74
  */
82
75
  static orderNeedsCounterSignature(order: Order | NestedCourseOrder) {
83
76
  return (
84
- order?.state === OrderState.VALIDATED &&
77
+ ACTIVE_ORDER_STATES.includes(order.state) &&
85
78
  order.contract &&
86
79
  order.contract.student_signed_on &&
87
80
  !order.contract.organization_signed_on
88
81
  );
89
82
  }
83
+
84
+ static getFailedInstallment(order: Order) {
85
+ return order.payment_schedule?.find(
86
+ (installment) => installment.state === PaymentScheduleState.REFUSED,
87
+ );
88
+ }
89
+
90
+ static allowEnrollment(order?: Order | NestedCourseOrder | OrderEnrollment) {
91
+ if (!order) return false;
92
+ return ENROLLABLE_ORDER_STATES.includes(order.state);
93
+ }
94
+
95
+ static isActive(order?: Order | NestedCourseOrder | OrderEnrollment) {
96
+ if (!order) return false;
97
+ return ACTIVE_ORDER_STATES.includes(order.state);
98
+ }
90
99
  }
@@ -4,45 +4,48 @@ import {
4
4
  Address,
5
5
  Certificate,
6
6
  CertificateDefinition,
7
- CourseListItem,
7
+ CertificateOrder,
8
+ CertificateProduct,
9
+ Contract,
10
+ ContractDefinition,
11
+ ContractLight,
8
12
  CourseLight,
13
+ CourseListItem,
9
14
  CourseProduct,
10
15
  CourseProductRelation,
11
16
  CourseRun,
17
+ CredentialOrder,
18
+ CredentialProduct,
12
19
  CreditCard,
13
20
  CreditCardBrand,
21
+ DefinitionResourcesProduct,
14
22
  Enrollment,
23
+ EnrollmentLight,
15
24
  EnrollmentState,
25
+ JoanieFile,
26
+ NestedCertificateOrder,
27
+ NestedCourseOrder,
28
+ NestedCredentialOrder,
29
+ Order,
30
+ OrderEnrollment,
31
+ OrderGroup,
32
+ PaymentInstallment,
16
33
  OrderLite,
17
34
  OrderState,
18
35
  Organization,
19
36
  OrganizationLight,
37
+ PaymentScheduleState,
20
38
  ProductType,
21
39
  TargetCourse,
22
- JoanieFile,
23
- Contract,
24
- OrderEnrollment,
25
- ContractDefinition,
26
- NestedCredentialOrder,
27
- NestedCertificateOrder,
28
- Order,
29
- CertificateOrder,
30
- CredentialOrder,
31
- CertificateOrderWithPaymentInfo,
32
- CredentialOrderWithPaymentInfo,
33
- EnrollmentLight,
34
- OrderGroup,
35
- CertificateProduct,
36
- CredentialProduct,
37
- NestedCourseOrder,
38
40
  UserLight,
39
- ContractLight,
40
- DefinitionResourcesProduct,
41
41
  } from 'types/Joanie';
42
42
  import { Payment, PaymentProviders } from 'components/PaymentInterfaces/types';
43
43
  import { CourseStateFactory } from 'utils/test/factories/richie';
44
44
  import { FactoryHelper } from 'utils/test/factories/helper';
45
45
  import { JoanieUserApiAbilityActions, JoanieUserProfile } from 'types/User';
46
+ import { SaleTunnelContextType, SaleTunnelStep } from 'components/SaleTunnel/GenericSaleTunnel';
47
+ import { SaleTunnelProps } from 'components/SaleTunnel';
48
+ import { noop } from 'utils/index';
46
49
  import { factory } from './factories';
47
50
 
48
51
  export const UserLightFactory = factory((): UserLight => {
@@ -138,7 +141,7 @@ export const ContractFactory = factory((): Contract => {
138
141
  organization_signatory: null,
139
142
  created_on: faker.date.past().toISOString(),
140
143
  definition: ContractDefinitionFactory().one(),
141
- order: NestedCredentialOrderFactory().one(),
144
+ order: NestedCredentialOrderFactory({ state: OrderState.TO_SIGN }).one(),
142
145
  };
143
146
  });
144
147
 
@@ -312,7 +315,7 @@ export const NestedCourseOrderFactory = factory((): NestedCourseOrder => {
312
315
  owner: UserLightFactory().one(),
313
316
  course_id: faker.string.uuid(),
314
317
  product_id: faker.string.uuid(),
315
- state: OrderState.VALIDATED,
318
+ state: OrderState.COMPLETED,
316
319
  enrollment_id: faker.string.uuid(),
317
320
  organization: OrganizationFactory().one(),
318
321
  certificate_id: faker.string.uuid(),
@@ -347,11 +350,21 @@ export const CourseListItemFactory = factory((): CourseListItem => {
347
350
  };
348
351
  });
349
352
 
353
+ export const PaymentInstallmentFactory = factory((): PaymentInstallment => {
354
+ return {
355
+ id: faker.string.uuid(),
356
+ currency: faker.finance.currencyCode(),
357
+ amount: faker.number.int(),
358
+ due_date: faker.date.future().toISOString(),
359
+ state: PaymentScheduleState.PAID,
360
+ };
361
+ });
362
+
350
363
  export const OrderEnrollmentFactory = factory((): OrderEnrollment => {
351
364
  return {
352
365
  id: faker.string.uuid(),
353
366
  product_id: faker.string.uuid(),
354
- state: OrderState.VALIDATED,
367
+ state: OrderState.COMPLETED,
355
368
  };
356
369
  });
357
370
 
@@ -363,19 +376,19 @@ export const OrderLiteFactory = factory((): OrderLite => {
363
376
  main_invoice_reference: faker.string.uuid(),
364
377
  total: faker.number.int(),
365
378
  product_id: faker.string.uuid(),
366
- state: OrderState.VALIDATED,
379
+ state: OrderState.COMPLETED,
367
380
  };
368
381
  });
369
382
 
370
383
  export const NestedCertificateOrderFactory = factory((): NestedCertificateOrder => {
371
384
  return {
372
385
  id: faker.string.uuid(),
373
- course: undefined,
386
+ course: null,
374
387
  enrollment: EnrollmentLightFactory().one(),
375
388
  organization: OrganizationFactory().one(),
376
389
  product_title: FactoryHelper.unique(faker.lorem.words, { args: [1] }),
377
390
  owner_name: faker.internet.userName(),
378
- state: OrderState.VALIDATED,
391
+ state: OrderState.COMPLETED,
379
392
  };
380
393
  });
381
394
 
@@ -383,11 +396,11 @@ export const NestedCredentialOrderFactory = factory((): NestedCredentialOrder =>
383
396
  return {
384
397
  id: faker.string.uuid(),
385
398
  course: CourseLightFactory().one(),
386
- enrollment: undefined,
399
+ enrollment: null,
387
400
  organization: OrganizationFactory().one(),
388
401
  product_title: FactoryHelper.unique(faker.lorem.words, { args: [1] }),
389
402
  owner_name: faker.internet.userName(),
390
- state: OrderState.VALIDATED,
403
+ state: OrderState.COMPLETED,
391
404
  };
392
405
  });
393
406
 
@@ -399,7 +412,7 @@ const AbstractOrderFactory = factory((): Order => {
399
412
  total: faker.number.int(),
400
413
  total_currency: faker.finance.currencyCode(),
401
414
  main_invoice_reference: faker.string.uuid(),
402
- state: OrderState.VALIDATED,
415
+ state: OrderState.COMPLETED,
403
416
  product_id: faker.string.uuid(),
404
417
  target_courses: TargetCourseFactory().many(5),
405
418
  target_enrollments: [],
@@ -410,55 +423,22 @@ const AbstractOrderFactory = factory((): Order => {
410
423
  };
411
424
  });
412
425
 
413
- export const CredentialOrderFactory = factory((): CredentialOrder => {
414
- const order = {
426
+ export const CredentialOrderFactory = factory(
427
+ (): CredentialOrder => ({
415
428
  ...AbstractOrderFactory().one(),
416
429
  course: CourseLightFactory().one(),
417
430
  enrollment: null,
418
- };
419
- return order;
420
- });
421
-
422
- export const CredentialOrderWithPaymentFactory = factory((): CredentialOrderWithPaymentInfo => {
423
- return {
424
- ...CredentialOrderFactory().one(),
425
- payment_info: PaymentFactory().one(),
426
- };
427
- });
428
-
429
- export const CredentialOrderWithOneClickPaymentFactory = factory(
430
- (): CredentialOrderWithPaymentInfo => {
431
- return {
432
- ...CredentialOrderFactory().one(),
433
- payment_info: PaymentFactory().one(),
434
- };
435
- },
431
+ payment_schedule: PaymentInstallmentFactory().many(3),
432
+ }),
436
433
  );
437
434
 
438
- export const CertificateOrderFactory = factory((): CertificateOrder => {
439
- const order = {
435
+ export const CertificateOrderFactory = factory(
436
+ (): CertificateOrder => ({
440
437
  ...AbstractOrderFactory().one(),
441
438
  course: null,
442
439
  target_courses: [],
443
440
  enrollment: EnrollmentLightFactory().one(),
444
- };
445
- return order;
446
- });
447
-
448
- export const CertificateOrderWithPaymentFactory = factory((): CertificateOrderWithPaymentInfo => {
449
- return {
450
- ...CertificateOrderFactory().one(),
451
- payment_info: PaymentFactory().one(),
452
- };
453
- });
454
-
455
- export const CertificateOrderWithOneClickPaymentFactory = factory(
456
- (): CertificateOrderWithPaymentInfo => {
457
- return {
458
- ...CertificateOrderFactory().one(),
459
- payment_info: PaymentFactory().one(),
460
- };
461
- },
441
+ }),
462
442
  );
463
443
 
464
444
  export const AddressFactory = factory((): Address => {
@@ -494,3 +474,21 @@ export const PaymentFactory = factory((): Payment => {
494
474
  url: faker.internet.url(),
495
475
  };
496
476
  });
477
+
478
+ export const SaleTunnelContextFactory = factory(
479
+ (): SaleTunnelContextType => ({
480
+ webAnalyticsEventKey: 'eventKey',
481
+ order: CredentialOrderFactory().one(),
482
+ product: ProductFactory().one(),
483
+ props: {} as SaleTunnelProps,
484
+ billingAddress: undefined,
485
+ setBillingAddress: noop,
486
+ setCreditCard: noop,
487
+ onPaymentSuccess: noop,
488
+ step: SaleTunnelStep.IDLE,
489
+ registerSubmitCallback: noop,
490
+ unregisterSubmitCallback: noop,
491
+ runSubmitCallbacks: () => new Promise((resolve) => resolve()),
492
+ nextStep: noop,
493
+ }),
494
+ );