@infrab4a/connect-angular 5.1.0 → 6.0.0-alpha.0

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 (212) hide show
  1. package/eslint.config.js +18 -0
  2. package/index.ts +1 -0
  3. package/karma.conf.js +32 -0
  4. package/ng-package.json +8 -0
  5. package/package.json +10 -28
  6. package/project.json +37 -0
  7. package/src/angular-connect.module.ts +256 -0
  8. package/src/angular-elastic-search.module.ts +23 -0
  9. package/src/angular-firebase-auth.module.ts +101 -0
  10. package/src/angular-firestore.module.ts +371 -0
  11. package/src/angular-hasura-graphql.module.ts +219 -0
  12. package/src/angular-vertex-search.module.ts +23 -0
  13. package/src/consts/backend-url.const.ts +1 -0
  14. package/src/consts/category-structure.ts +1 -0
  15. package/src/consts/default-shop.const.ts +1 -0
  16. package/src/consts/es-config.const.ts +1 -0
  17. package/src/consts/firebase-const.ts +5 -0
  18. package/src/consts/hasura-options.const.ts +1 -0
  19. package/src/consts/index.ts +8 -0
  20. package/src/consts/persistence.const.ts +1 -0
  21. package/src/consts/storage-base-url.const.ts +1 -0
  22. package/src/consts/vertex-config.const.ts +1 -0
  23. package/src/helpers/index.ts +1 -0
  24. package/src/helpers/mobile-operation-system-checker.helper.ts +10 -0
  25. package/src/index.ts +6 -0
  26. package/src/interfaces/catalog-strategies.interface.ts +36 -0
  27. package/src/interfaces/category-facades.interface.ts +7 -0
  28. package/src/interfaces/index.ts +2 -0
  29. package/src/persistence/cookie-data-persistence.ts +21 -0
  30. package/src/persistence/data-persistence.ts +7 -0
  31. package/src/persistence/index.ts +2 -0
  32. package/src/services/auth.service.ts +39 -0
  33. package/src/services/cart/cart-services.facade.ts +14 -0
  34. package/src/services/cart/index.ts +1 -0
  35. package/src/services/cart.service.ts +124 -0
  36. package/src/services/catalog/adapters/category-structure.adapter.ts +5 -0
  37. package/src/services/catalog/adapters/index.ts +3 -0
  38. package/src/services/catalog/adapters/new-category-structure.adapter.ts +42 -0
  39. package/src/services/catalog/adapters/old-category-structure.adapter.ts +17 -0
  40. package/src/services/catalog/catalog.service.ts +153 -0
  41. package/src/services/catalog/category.service.ts +52 -0
  42. package/src/services/catalog/context/catalog-search.context.ts +61 -0
  43. package/src/services/catalog/enums/index.ts +1 -0
  44. package/src/services/catalog/enums/product-sorts.enum.ts +9 -0
  45. package/src/services/catalog/facades/catalog-service.facade.ts +32 -0
  46. package/src/services/catalog/facades/catalog-strategies.facade.ts +12 -0
  47. package/src/services/catalog/facades/category-repository.facade.ts +10 -0
  48. package/src/services/catalog/facades/category-service.facade.ts +25 -0
  49. package/src/services/catalog/facades/index.ts +5 -0
  50. package/src/services/catalog/facades/product-catalog.facade.ts +13 -0
  51. package/src/services/catalog/helpers/brand-manager.helper.ts +63 -0
  52. package/src/services/catalog/helpers/catalog-filter.helper.ts +50 -0
  53. package/src/services/catalog/helpers/catalog-sort.helper.ts +54 -0
  54. package/src/services/catalog/helpers/index.ts +4 -0
  55. package/src/services/catalog/helpers/product-fields.helper.ts +35 -0
  56. package/src/services/catalog/index.ts +7 -0
  57. package/src/services/catalog/models/category-with-tree.model.ts +7 -0
  58. package/src/services/catalog/models/index.ts +1 -0
  59. package/src/services/catalog/services/catalog-helpers.service.ts +35 -0
  60. package/src/services/catalog/services/catalog-operations.facade.ts +24 -0
  61. package/src/services/catalog/services/catalog-repository.service.ts +20 -0
  62. package/src/services/catalog/services/index.ts +4 -0
  63. package/src/services/catalog/services/product-management.facade.ts +20 -0
  64. package/src/services/catalog/strategies/category-search.strategy.ts +172 -0
  65. package/src/services/catalog/strategies/index.ts +3 -0
  66. package/src/services/catalog/strategies/profile-search.strategy.ts +51 -0
  67. package/src/services/catalog/strategies/term-search.strategy.ts +178 -0
  68. package/src/services/catalog/strategies/types/strategy-params.type.ts +58 -0
  69. package/src/services/catalog/types/fetch-products-options.type.ts +10 -0
  70. package/src/services/catalog/types/fetch-products-params.type.ts +27 -0
  71. package/src/services/catalog/types/fetch-products-response.type.ts +11 -0
  72. package/src/services/catalog/types/index.ts +5 -0
  73. package/src/services/catalog/types/method-params.type.ts +21 -0
  74. package/src/services/catalog/types/product-sort.type.ts +3 -0
  75. package/src/services/catalog/wishlist.service.ts +368 -0
  76. package/src/services/checkout/checkout-dependencies.facade.ts +13 -0
  77. package/src/services/checkout/checkout-repositories.facade.ts +10 -0
  78. package/src/services/checkout/index.ts +2 -0
  79. package/src/services/checkout-subscription.service.ts +76 -0
  80. package/src/services/checkout.service.ts +196 -0
  81. package/src/services/coupon/coupon-repositories.facade.ts +11 -0
  82. package/src/services/coupon/index.ts +1 -0
  83. package/src/services/coupon/types/coupon-params.type.ts +15 -0
  84. package/src/services/coupon.service.ts +347 -0
  85. package/src/services/errors/group-invalid-coupon.error.ts +9 -0
  86. package/src/services/errors/index.ts +2 -0
  87. package/src/services/errors/invalid-coupon.error.ts +7 -0
  88. package/src/services/helpers/index.ts +1 -0
  89. package/src/services/helpers/util.helper.ts +17 -0
  90. package/src/services/home-shop/home-shop-repositories.facade.ts +11 -0
  91. package/src/services/home-shop/index.ts +1 -0
  92. package/src/services/home-shop.service.ts +196 -0
  93. package/src/services/index.ts +9 -0
  94. package/src/services/order.service.ts +23 -0
  95. package/src/services/shared/configuration.facade.ts +21 -0
  96. package/src/services/shared/index.ts +1 -0
  97. package/src/services/types/index.ts +2 -0
  98. package/src/services/types/required-checkout-data.type.ts +3 -0
  99. package/src/services/types/required-checkout-subscription-data.type.ts +3 -0
  100. package/src/types/firebase-app-config.type.ts +1 -0
  101. package/src/types/index.ts +1 -0
  102. package/tsconfig.json +17 -0
  103. package/tsconfig.lib.json +13 -0
  104. package/tsconfig.lib.prod.json +13 -0
  105. package/tsconfig.spec.json +17 -0
  106. package/angular-connect.module.d.ts +0 -26
  107. package/angular-elastic-search.module.d.ts +0 -9
  108. package/angular-firebase-auth.module.d.ts +0 -11
  109. package/angular-firestore.module.d.ts +0 -18
  110. package/angular-hasura-graphql.module.d.ts +0 -16
  111. package/consts/backend-url.const.d.ts +0 -1
  112. package/consts/category-structure.d.ts +0 -1
  113. package/consts/default-shop.const.d.ts +0 -1
  114. package/consts/es-config.const.d.ts +0 -1
  115. package/consts/firebase-const.d.ts +0 -3
  116. package/consts/hasura-options.const.d.ts +0 -1
  117. package/consts/index.d.ts +0 -7
  118. package/consts/persistence.const.d.ts +0 -1
  119. package/consts/storage-base-url.const.d.ts +0 -1
  120. package/esm2020/angular-connect.module.mjs +0 -115
  121. package/esm2020/angular-elastic-search.module.mjs +0 -34
  122. package/esm2020/angular-firebase-auth.module.mjs +0 -115
  123. package/esm2020/angular-firestore.module.mjs +0 -504
  124. package/esm2020/angular-hasura-graphql.module.mjs +0 -265
  125. package/esm2020/consts/backend-url.const.mjs +0 -2
  126. package/esm2020/consts/category-structure.mjs +0 -2
  127. package/esm2020/consts/default-shop.const.mjs +0 -2
  128. package/esm2020/consts/es-config.const.mjs +0 -2
  129. package/esm2020/consts/firebase-const.mjs +0 -4
  130. package/esm2020/consts/hasura-options.const.mjs +0 -2
  131. package/esm2020/consts/index.mjs +0 -8
  132. package/esm2020/consts/persistence.const.mjs +0 -2
  133. package/esm2020/consts/storage-base-url.const.mjs +0 -2
  134. package/esm2020/helpers/index.mjs +0 -2
  135. package/esm2020/helpers/mobile-operation-system-checker.helper.mjs +0 -7
  136. package/esm2020/index.mjs +0 -7
  137. package/esm2020/infrab4a-connect-angular.mjs +0 -5
  138. package/esm2020/persistence/cookie-data-persistence.mjs +0 -22
  139. package/esm2020/persistence/data-persistence.mjs +0 -2
  140. package/esm2020/persistence/index.mjs +0 -3
  141. package/esm2020/services/auth.service.mjs +0 -37
  142. package/esm2020/services/cart.service.mjs +0 -281
  143. package/esm2020/services/catalog/adapters/category-structure.adapter.mjs +0 -2
  144. package/esm2020/services/catalog/adapters/index.mjs +0 -4
  145. package/esm2020/services/catalog/adapters/new-category-structure.adapter.mjs +0 -43
  146. package/esm2020/services/catalog/adapters/old-category-structure.adapter.mjs +0 -23
  147. package/esm2020/services/catalog/catalog.service.mjs +0 -194
  148. package/esm2020/services/catalog/category.service.mjs +0 -51
  149. package/esm2020/services/catalog/enums/index.mjs +0 -2
  150. package/esm2020/services/catalog/enums/product-sorts.enum.mjs +0 -11
  151. package/esm2020/services/catalog/index.mjs +0 -8
  152. package/esm2020/services/catalog/models/category-with-tree.model.mjs +0 -10
  153. package/esm2020/services/catalog/models/index.mjs +0 -2
  154. package/esm2020/services/catalog/types/index.mjs +0 -2
  155. package/esm2020/services/catalog/types/product-sort.type.mjs +0 -2
  156. package/esm2020/services/catalog/wishlist.service.mjs +0 -115
  157. package/esm2020/services/checkout-subscription.service.mjs +0 -50
  158. package/esm2020/services/checkout.service.mjs +0 -68
  159. package/esm2020/services/coupon.service.mjs +0 -235
  160. package/esm2020/services/helpers/index.mjs +0 -2
  161. package/esm2020/services/helpers/util.helper.mjs +0 -18
  162. package/esm2020/services/home-shop.service.mjs +0 -125
  163. package/esm2020/services/index.mjs +0 -11
  164. package/esm2020/services/order.service.mjs +0 -30
  165. package/esm2020/services/shipping.service.mjs +0 -96
  166. package/esm2020/services/types/index.mjs +0 -3
  167. package/esm2020/services/types/required-checkout-data.type.mjs +0 -2
  168. package/esm2020/services/types/required-checkout-subscription-data.type.mjs +0 -2
  169. package/esm2020/services/types/shipping-methods.type.mjs +0 -2
  170. package/esm2020/types/firebase-app-config.type.mjs +0 -2
  171. package/esm2020/types/index.mjs +0 -2
  172. package/fesm2015/infrab4a-connect-angular.mjs +0 -2417
  173. package/fesm2015/infrab4a-connect-angular.mjs.map +0 -1
  174. package/fesm2020/infrab4a-connect-angular.mjs +0 -2352
  175. package/fesm2020/infrab4a-connect-angular.mjs.map +0 -1
  176. package/helpers/index.d.ts +0 -1
  177. package/helpers/mobile-operation-system-checker.helper.d.ts +0 -3
  178. package/index.d.ts +0 -6
  179. package/persistence/cookie-data-persistence.d.ts +0 -10
  180. package/persistence/data-persistence.d.ts +0 -6
  181. package/persistence/index.d.ts +0 -2
  182. package/services/auth.service.d.ts +0 -18
  183. package/services/cart.service.d.ts +0 -42
  184. package/services/catalog/adapters/category-structure.adapter.d.ts +0 -4
  185. package/services/catalog/adapters/index.d.ts +0 -3
  186. package/services/catalog/adapters/new-category-structure.adapter.d.ts +0 -12
  187. package/services/catalog/adapters/old-category-structure.adapter.d.ts +0 -10
  188. package/services/catalog/catalog.service.d.ts +0 -86
  189. package/services/catalog/category.service.d.ts +0 -20
  190. package/services/catalog/enums/index.d.ts +0 -1
  191. package/services/catalog/enums/product-sorts.enum.d.ts +0 -9
  192. package/services/catalog/index.d.ts +0 -7
  193. package/services/catalog/models/category-with-tree.model.d.ts +0 -4
  194. package/services/catalog/models/index.d.ts +0 -1
  195. package/services/catalog/types/index.d.ts +0 -1
  196. package/services/catalog/types/product-sort.type.d.ts +0 -2
  197. package/services/catalog/wishlist.service.d.ts +0 -38
  198. package/services/checkout-subscription.service.d.ts +0 -19
  199. package/services/checkout.service.d.ts +0 -24
  200. package/services/coupon.service.d.ts +0 -29
  201. package/services/helpers/index.d.ts +0 -1
  202. package/services/helpers/util.helper.d.ts +0 -3
  203. package/services/home-shop.service.d.ts +0 -26
  204. package/services/index.d.ts +0 -10
  205. package/services/order.service.d.ts +0 -13
  206. package/services/shipping.service.d.ts +0 -19
  207. package/services/types/index.d.ts +0 -2
  208. package/services/types/required-checkout-data.type.d.ts +0 -2
  209. package/services/types/required-checkout-subscription-data.type.d.ts +0 -2
  210. package/services/types/shipping-methods.type.d.ts +0 -12
  211. package/types/firebase-app-config.type.d.ts +0 -1
  212. package/types/index.d.ts +0 -1
@@ -0,0 +1,10 @@
1
+ import { Inject, Injectable } from '@angular/core'
2
+ import { CheckoutRepository, UserRepository } from '@infrab4a/connect'
3
+
4
+ @Injectable()
5
+ export class CheckoutRepositoriesFacade {
6
+ constructor(
7
+ @Inject('CheckoutRepository') public readonly checkoutRepository: CheckoutRepository,
8
+ @Inject('UserRepository') public readonly userRepository: UserRepository,
9
+ ) {}
10
+ }
@@ -0,0 +1,2 @@
1
+ export { CheckoutRepositoriesFacade } from './checkout-repositories.facade'
2
+ export { CheckoutDependenciesFacade } from './checkout-dependencies.facade'
@@ -0,0 +1,76 @@
1
+ import { Inject, Injectable } from '@angular/core'
2
+ import {
3
+ CheckoutSubscription,
4
+ CheckoutSubscriptionRepository,
5
+ CheckoutTypes,
6
+ Coupon,
7
+ isNil,
8
+ pick,
9
+ } from '@infrab4a/connect'
10
+ import { Observable } from 'rxjs'
11
+ import { concatMap } from 'rxjs/operators'
12
+
13
+
14
+ import { PERSISTENCE_PROVIDER } from '../consts'
15
+ import { DataPersistence } from '../persistence'
16
+
17
+ import { RequiredCheckoutSubscriptionData } from './types'
18
+ import { CouponService } from './coupon.service'
19
+
20
+ @Injectable()
21
+ export class CheckoutSubscriptionService {
22
+ constructor(
23
+ @Inject('CheckoutSubscriptionRepository')
24
+ private readonly checkoutSubscriptionRepository: CheckoutSubscriptionRepository,
25
+ @Inject(PERSISTENCE_PROVIDER) private readonly dataPersistence: DataPersistence,
26
+ private readonly couponService: CouponService,
27
+ ) {}
28
+
29
+ getCheckoutSubscription(checkoutData?: RequiredCheckoutSubscriptionData): Observable<CheckoutSubscription> {
30
+ return this.dataPersistence
31
+ .get('checkoutSubscriptionId')
32
+ .pipe(
33
+ concatMap((id) =>
34
+ !isNil(id) ? this.checkoutSubscriptionRepository.get({ id }) : this.createCheckoutSubscription(checkoutData),
35
+ ),
36
+ )
37
+ }
38
+
39
+ clearCheckoutSubscriptionFromSession(): Observable<void> {
40
+ return this.dataPersistence.remove('checkoutSubscriptionId')
41
+ }
42
+
43
+ checkCoupon(nickname: string, _userEmail: string): Observable<Coupon> {
44
+ return this.getCheckoutSubscription().pipe(
45
+ concatMap((checkout) =>
46
+ this.couponService
47
+ .checkCoupon({
48
+ nickname,
49
+ checkoutType: CheckoutTypes.SUBSCRIPTION,
50
+ checkout,
51
+ plan: checkout.subscriptionPlan.name,
52
+ })
53
+ .pipe(),
54
+ ),
55
+ )
56
+ }
57
+
58
+ calcDiscountSubscription(coupon: Coupon) {
59
+ return this.getCheckoutSubscription().pipe(
60
+ concatMap((checkout) => this.couponService.calcDiscountSubscription(coupon, checkout).pipe()),
61
+ )
62
+ }
63
+
64
+ private async createCheckoutSubscription(
65
+ checkoutData?: RequiredCheckoutSubscriptionData,
66
+ ): Promise<CheckoutSubscription> {
67
+ const checkout = await this.checkoutSubscriptionRepository.create({
68
+ createdAt: new Date(),
69
+ ...CheckoutSubscription.toInstance(pick(checkoutData, ['user', 'shop'])).toPlain(),
70
+ })
71
+
72
+ await this.dataPersistence.set('checkoutSubscriptionId', checkout.id).toPromise()
73
+
74
+ return checkout
75
+ }
76
+ }
@@ -0,0 +1,196 @@
1
+ import {
2
+ Checkout,
3
+ CheckoutPayloadRequest,
4
+ CheckoutResponse,
5
+ CheckoutStockValidation,
6
+ CheckoutTypes,
7
+ Coupon,
8
+ isNil,
9
+ NotFoundError,
10
+ pick,
11
+ ShippingMethodResponse,
12
+ ShippingProductRequest,
13
+ User,
14
+ UserAddress,
15
+ } from '@infrab4a/connect'
16
+ import { from, Observable, of, throwError } from 'rxjs'
17
+ import { concatMap, map } from 'rxjs/operators'
18
+ import { Injectable } from '@angular/core'
19
+
20
+ import { RequiredCheckoutData } from './types'
21
+ import { CheckoutDependenciesFacade, CheckoutRepositoriesFacade } from './checkout'
22
+ import { ConfigurationFacade } from './shared'
23
+
24
+ @Injectable()
25
+ export class CheckoutService {
26
+ private checkoutUrl: string = null
27
+
28
+ constructor(
29
+ private readonly repositoriesFacade: CheckoutRepositoriesFacade,
30
+ private readonly dependenciesFacade: CheckoutDependenciesFacade,
31
+ private readonly configurationFacade: ConfigurationFacade,
32
+ ) {
33
+ this.checkoutUrl = this.configurationFacade.getCheckoutUrl()
34
+ }
35
+
36
+ getCheckout(checkoutData?: RequiredCheckoutData): Observable<Checkout> {
37
+ return this.dependenciesFacade.dataPersistence
38
+ .get('checkoutId')
39
+ .pipe(
40
+ concatMap((id) =>
41
+ !isNil(id) ? this.repositoriesFacade.checkoutRepository.get({ id }) : this.createCheckout(checkoutData),
42
+ ),
43
+ )
44
+ }
45
+
46
+ getUserByCheckout(checkoutId: string): Observable<User> {
47
+ return from(this.repositoriesFacade.checkoutRepository.get({ id: checkoutId })).pipe(
48
+ concatMap((checkout) =>
49
+ checkout?.user?.id
50
+ ? of(checkout.user)
51
+ : from(this.repositoriesFacade.userRepository.get({ id: checkout.user.id })),
52
+ ),
53
+ concatMap((user) => of(user) || throwError(() => new NotFoundError('User is not found'))),
54
+ )
55
+ }
56
+
57
+ updateCheckoutLineItems(checkout: Partial<Checkout>): Observable<Checkout> {
58
+ return from(
59
+ this.repositoriesFacade.checkoutRepository.update(
60
+ Checkout.toInstance({ id: checkout.id, lineItems: checkout.lineItems }),
61
+ ),
62
+ )
63
+ }
64
+
65
+ updateCheckoutUser(checkout: Partial<Checkout>): Observable<Checkout> {
66
+ return from(
67
+ this.repositoriesFacade.checkoutRepository.update(Checkout.toInstance({ id: checkout.id, user: checkout.user })),
68
+ )
69
+ }
70
+
71
+ clearCheckoutFromSession(): Observable<void> {
72
+ return this.dependenciesFacade.dataPersistence.remove('checkoutId')
73
+ }
74
+
75
+ resetCheckoutValues(): Observable<Checkout> {
76
+ return this.getCheckout().pipe(
77
+ concatMap((checkout) =>
78
+ this.dependenciesFacade.httpClient.post(`${this.checkoutUrl}/checkoutResetValues`, {
79
+ checkoutId: checkout.id,
80
+ }),
81
+ ),
82
+ concatMap(() => this.getCheckout()),
83
+ )
84
+ }
85
+
86
+ applyCouponDiscount(nickname: string, checkoutType: CheckoutTypes): Observable<Coupon> {
87
+ return this.getCheckout().pipe(
88
+ concatMap((checkout) =>
89
+ this.dependenciesFacade.httpClient.post(`${this.checkoutUrl}/checkoutApplyDiscount`, {
90
+ checkoutId: checkout.id,
91
+ coupon: nickname,
92
+ checkoutType,
93
+ }),
94
+ ),
95
+ concatMap(() => this.getCheckout()),
96
+ map((checkout) => checkout.coupon),
97
+ )
98
+ }
99
+
100
+ removeCouponDiscount(): Observable<Checkout> {
101
+ return this.getCheckout().pipe(
102
+ concatMap((checkout) =>
103
+ this.dependenciesFacade.httpClient.post(`${this.checkoutUrl}/checkoutRemoveDiscount`, {
104
+ checkoutId: checkout.id,
105
+ }),
106
+ ),
107
+ concatMap(() => this.getCheckout()),
108
+ )
109
+ }
110
+
111
+ validateStockProducts(): Observable<CheckoutStockValidation> {
112
+ return this.getCheckout().pipe(
113
+ concatMap((checkout) =>
114
+ this.dependenciesFacade.httpClient.post<{
115
+ valid: boolean
116
+ outOfStock: string[]
117
+ limitedStock: string[]
118
+ }>(`${this.checkoutUrl}/checkoutValidateProductStock`, {
119
+ checkoutId: checkout.id,
120
+ }),
121
+ ),
122
+ )
123
+ }
124
+
125
+ selectShippingAddress(address: UserAddress): Observable<Checkout> {
126
+ return this.getCheckout().pipe(
127
+ concatMap((checkout) => {
128
+ return this.repositoriesFacade.checkoutRepository.update(
129
+ Checkout.toInstance({ id: checkout.id, shippingAddress: address, billingAddress: address }),
130
+ )
131
+ }),
132
+ )
133
+ }
134
+
135
+ getAvailableShippingForProduct(productShipping: ShippingProductRequest): Observable<ShippingMethodResponse[]> {
136
+ return this.dependenciesFacade.httpClient.post<ShippingMethodResponse[]>(
137
+ `${this.checkoutUrl}/checkoutGetAvailableShipping`,
138
+ {
139
+ checkoutId: null,
140
+ productShipping,
141
+ },
142
+ )
143
+ }
144
+
145
+ getAvailableShippingForCheckout(): Observable<ShippingMethodResponse[]> {
146
+ return this.getCheckout().pipe(
147
+ concatMap((checkout) =>
148
+ this.dependenciesFacade.httpClient.post<ShippingMethodResponse[]>(
149
+ `${this.checkoutUrl}/checkoutGetAvailableShipping`,
150
+ {
151
+ checkoutId: checkout.id,
152
+ productShipping: null,
153
+ },
154
+ ),
155
+ ),
156
+ )
157
+ }
158
+
159
+ selectShipping(option: string): Observable<Checkout> {
160
+ return this.getCheckout().pipe(
161
+ concatMap((checkout) =>
162
+ this.dependenciesFacade.httpClient.post(`${this.checkoutUrl}/checkoutSelectShipping`, {
163
+ checkoutId: checkout.id,
164
+ shippingOption: option,
165
+ }),
166
+ ),
167
+ concatMap(() => this.getCheckout()),
168
+ )
169
+ }
170
+
171
+ createOrder(
172
+ checkoutPayload: CheckoutPayloadRequest,
173
+ paymentProvider: string,
174
+ applicationVersion: string,
175
+ ): Observable<CheckoutResponse> {
176
+ return this.dependenciesFacade.httpClient.post<CheckoutResponse>(`${this.checkoutUrl}/checkout`, {
177
+ data: {
178
+ ...checkoutPayload,
179
+ applicationVersion,
180
+ paymentProvider,
181
+ },
182
+ })
183
+ }
184
+
185
+ private async createCheckout(checkoutData?: RequiredCheckoutData): Promise<Checkout> {
186
+ const checkout = await this.repositoriesFacade.checkoutRepository.create({
187
+ createdAt: new Date(),
188
+ ...Checkout.toInstance(pick(checkoutData, ['user', 'shop'])).toPlain(),
189
+ shop: checkoutData?.shop || this.configurationFacade.defaultShop,
190
+ })
191
+
192
+ await this.dependenciesFacade.dataPersistence.set('checkoutId', checkout.id).toPromise()
193
+
194
+ return checkout
195
+ }
196
+ }
@@ -0,0 +1,11 @@
1
+ import { Inject, Injectable } from '@angular/core'
2
+ import { CategoryRepository, CouponRepository, OrderRepository } from '@infrab4a/connect'
3
+
4
+ @Injectable()
5
+ export class CouponRepositoriesFacade {
6
+ constructor(
7
+ @Inject('CouponRepository') public readonly couponRepository: CouponRepository,
8
+ @Inject('OrderRepository') public readonly orderRepository: OrderRepository,
9
+ @Inject('CategoryRepository') public readonly categoryRepository: CategoryRepository,
10
+ ) {}
11
+ }
@@ -0,0 +1 @@
1
+ export { CouponRepositoriesFacade } from './coupon-repositories.facade'
@@ -0,0 +1,15 @@
1
+ import { Checkout, CheckoutTypes, Coupon } from '@infrab4a/connect'
2
+
3
+ export type CheckCouponParams = {
4
+ nickname: string
5
+ checkoutType: CheckoutTypes
6
+ checkout: Partial<Checkout>
7
+ plan: string
8
+ }
9
+
10
+ export type CouponRulesValidationParams = {
11
+ coupon: Coupon
12
+ checkoutType: CheckoutTypes
13
+ checkout: Partial<Checkout>
14
+ plan: string
15
+ }
@@ -0,0 +1,347 @@
1
+ import { Injectable } from '@angular/core'
2
+ import {
3
+ Category,
4
+ Checkout,
5
+ CheckoutSubscription,
6
+ CheckoutTypes,
7
+ Coupon,
8
+ CouponTypes,
9
+ Exclusivities,
10
+ LineItem,
11
+ Order,
12
+ OrderStatus,
13
+ Shops,
14
+ User,
15
+ Where,
16
+ } from '@infrab4a/connect'
17
+ import { from, Observable, of } from 'rxjs'
18
+ import { concatMap, map } from 'rxjs/operators'
19
+
20
+ import { CouponRepositoriesFacade } from './coupon'
21
+ import { CheckCouponParams, CouponRulesValidationParams } from './coupon/types/coupon-params.type'
22
+ import { ConfigurationFacade } from './shared'
23
+
24
+ @Injectable({
25
+ providedIn: 'root',
26
+ })
27
+ export class CouponService {
28
+ constructor(
29
+ private readonly repositoriesFacade: CouponRepositoriesFacade,
30
+ private readonly configurationFacade: ConfigurationFacade,
31
+ ) {}
32
+
33
+ checkCoupon(params: CheckCouponParams): Observable<Coupon> {
34
+ const { nickname, checkoutType, checkout, plan } = params
35
+
36
+ return from(
37
+ this.repositoriesFacade.couponRepository
38
+ .find({
39
+ filters: {
40
+ nickname: { operator: Where.EQUALS, value: nickname },
41
+ active: { operator: Where.EQUALS, value: true },
42
+ },
43
+ })
44
+ .then((result) => result.data[0]),
45
+ ).pipe(
46
+ concatMap((coupon) => this.couponValidation(coupon, checkoutType)),
47
+ concatMap((couponValid: Coupon) =>
48
+ this.couponRulesValidation({ coupon: couponValid, checkoutType, checkout, plan }),
49
+ ),
50
+ map((couponValidated: Coupon) => couponValidated as Coupon),
51
+ )
52
+ }
53
+
54
+ private async couponValidation(coupon: Coupon, checkoutType: CheckoutTypes) {
55
+ if (!coupon) throw 'Cupom inválido.'
56
+
57
+ if (coupon?.beginAt && coupon?.beginAt.getTime() > new Date().getTime()) throw 'Cupom inválido.'
58
+
59
+ if (coupon?.expiresIn && (coupon?.expiresIn as Date).getTime() < new Date().getTime()) throw 'Cupom expirado.'
60
+
61
+ const isInShop =
62
+ coupon.shopAvailability === Shops.ALL || coupon.shopAvailability === this.configurationFacade.defaultShop
63
+
64
+ if (!isInShop) throw 'Cupom inválido para loja.'
65
+
66
+ const isCheckoutType = coupon.checkoutType === CheckoutTypes.ALL || coupon.checkoutType === checkoutType
67
+
68
+ if (!isCheckoutType) throw 'Cupom inválido. Erro de checkout.'
69
+
70
+ return coupon
71
+ }
72
+
73
+ private async couponRulesValidation(params: CouponRulesValidationParams) {
74
+ const { coupon, checkoutType, checkout, plan } = params
75
+
76
+ if (checkoutType == CheckoutTypes.SUBSCRIPTION) {
77
+ return this.validateSubscriptionCoupon(coupon, plan)
78
+ }
79
+
80
+ return this.validateEcommerceCoupon(coupon, checkoutType, checkout)
81
+ }
82
+
83
+ private validateSubscriptionCoupon(coupon: Coupon, plan: string): Coupon {
84
+ if (coupon.plan && coupon.plan.toUpperCase() !== plan.toUpperCase()) {
85
+ throw 'Cupom inválido para sua assinatura.'
86
+ }
87
+
88
+ return coupon
89
+ }
90
+
91
+ private async validateEcommerceCoupon(
92
+ coupon: Coupon,
93
+ checkoutType: CheckoutTypes,
94
+ checkout: Partial<Checkout>,
95
+ ): Promise<Coupon> {
96
+ this.validateUserEligibility(coupon, checkout?.user)
97
+ await this.validateUsageLimits(coupon, checkoutType, checkout)
98
+ await this.validateProductEligibility(coupon, checkout)
99
+
100
+ return coupon
101
+ }
102
+
103
+ private validateUserEligibility(coupon: Coupon, user: User): void {
104
+ const validUser = this.coupomUserValidation(coupon, user)
105
+
106
+ if (!validUser) throw 'Usuário não elegível.'
107
+ }
108
+
109
+ private async validateUsageLimits(
110
+ coupon: Coupon,
111
+ checkoutType: CheckoutTypes,
112
+ checkout: Partial<Checkout>,
113
+ ): Promise<void> {
114
+ const couponUseLimits = this.getCouponUseLimits(coupon, checkoutType, checkout.user)
115
+
116
+ if (couponUseLimits.firstOrder) {
117
+ await this.validateFirstOrderLimit(checkout.user.email)
118
+ }
119
+
120
+ if (!couponUseLimits.unlimited || couponUseLimits.limitedPerUser) {
121
+ await this.validateCouponUsageLimits(coupon, couponUseLimits, checkout.user.email)
122
+ }
123
+ }
124
+
125
+ private async validateFirstOrderLimit(userEmail: string): Promise<void> {
126
+ const ordersUser = await this.getOrdersFromUser(userEmail.toLocaleLowerCase())
127
+
128
+ if (ordersUser.length >= 1) throw 'Limite de uso atingido'
129
+ }
130
+
131
+ private async validateCouponUsageLimits(coupon: Coupon, couponUseLimits: any, userEmail: string): Promise<void> {
132
+ const ordersCoupon = await this.getOrdersWithCoupon(coupon)
133
+
134
+ if (!couponUseLimits.unlimited && couponUseLimits.total && ordersCoupon.length >= couponUseLimits.total) {
135
+ throw 'Limite de uso atingido.'
136
+ }
137
+
138
+ if (couponUseLimits.limitedPerUser) {
139
+ const ordersWithUser = this.countOrdersWithUser(ordersCoupon, userEmail)
140
+
141
+ if (ordersWithUser > 0) throw 'Limite de uso por usuário atingido.'
142
+ }
143
+ }
144
+
145
+ private async validateProductEligibility(coupon: Coupon, checkout: Partial<Checkout>): Promise<void> {
146
+ const hasProductCategories = await this.hasProductCategories(coupon, checkout)
147
+
148
+ if (!hasProductCategories) throw 'Seu carrinho não possui produtos elegíveis para desconto.'
149
+
150
+ const hasMinSubTotal = await this.hasMinSubTotal(coupon, checkout)
151
+
152
+ if (!hasMinSubTotal) {
153
+ this.throwMinSubTotalError(coupon)
154
+ }
155
+ }
156
+
157
+ private throwMinSubTotalError(coupon: Coupon): void {
158
+ const formattedValue = Intl.NumberFormat('pt-BR', {
159
+ style: 'currency',
160
+ currency: 'BRL',
161
+ }).format(coupon.minSubTotalValue)
162
+
163
+ if (coupon.productsCategories?.length) {
164
+ throw `Valor mínimo de ${formattedValue} não atingido na(s) categoria(s) elegíveis para o desconto.`
165
+ }
166
+
167
+ throw `Valor mínimo de ${formattedValue} não atingido.`
168
+ }
169
+
170
+ public calcDiscountSubscription(coupon: Coupon, checkout: Partial<CheckoutSubscription>): Observable<number> {
171
+ let discount = 0
172
+
173
+ if (coupon.discount.subscription.type == CouponTypes.ABSOLUTE) discount = coupon.discount.subscription.value
174
+ else discount = checkout.subscriptionPlan.recurrencePrice * (coupon.discount.subscription.value / 100)
175
+
176
+ return of(discount)
177
+ }
178
+
179
+ private async hasMinSubTotal(coupon: Coupon, checkout: Partial<Checkout>): Promise<boolean> {
180
+ if (!coupon.minSubTotalValue) return true
181
+
182
+ const lineItensDiscount = await this.getLineItensEligebleForDiscount(coupon.productsCategories, checkout)
183
+ const subTotal = this.calcCheckoutSubtotal(lineItensDiscount, checkout.user)
184
+
185
+ if (coupon.minSubTotalValue <= subTotal) return true
186
+
187
+ return false
188
+ }
189
+
190
+ private async hasProductCategories(coupon: Coupon, checkout: Partial<Checkout>): Promise<boolean> {
191
+ if (!coupon.productsCategories?.length) {
192
+ return true
193
+ }
194
+
195
+ const couponCategories = await this.getCouponCategoriesId(coupon.productsCategories)
196
+ const hasCategories = checkout.lineItems?.filter((item) => {
197
+ if (item.isGift) return false
198
+
199
+ if (!item.categories?.length) return true
200
+
201
+ return item.categories.some((c) => couponCategories.some((cat) => cat == c))
202
+ })
203
+
204
+ return hasCategories?.length ? true : false
205
+ }
206
+
207
+ private coupomUserValidation(coupon: Coupon, user: User) {
208
+ if (!user || coupon.exclusivityType.includes(Exclusivities.ALL_USERS)) return true
209
+
210
+ const userTypes: Exclusivities[] = []
211
+
212
+ if (
213
+ coupon.exclusivityType.includes(Exclusivities.COLLABORATORS) &&
214
+ this.emailIsFromCollaborator(user.email.toLocaleLowerCase())
215
+ )
216
+ userTypes.push(Exclusivities.COLLABORATORS)
217
+
218
+ if (
219
+ coupon.exclusivityType.includes(Exclusivities.SPECIFIC_USER) &&
220
+ coupon.userExclusiveEmail.includes(user.email.toLocaleLowerCase())
221
+ )
222
+ userTypes.push(Exclusivities.SPECIFIC_USER)
223
+
224
+ if (
225
+ coupon.exclusivityType.includes(Exclusivities.ACTIVE_SUBSCRIBER) &&
226
+ user.isSubscriber &&
227
+ user.subscriptionPlan != ''
228
+ )
229
+ userTypes.push(Exclusivities.ACTIVE_SUBSCRIBER)
230
+
231
+ if (
232
+ user.isSubscriber &&
233
+ user.subscriptionPlan == '' &&
234
+ coupon.exclusivityType.includes(Exclusivities.INACTIVE_SUBSCRIBER)
235
+ )
236
+ userTypes.push(Exclusivities.INACTIVE_SUBSCRIBER)
237
+
238
+ if (coupon.exclusivityType.includes(Exclusivities.NON_SUBSCRIBER) && !user.isSubscriber)
239
+ userTypes.push(Exclusivities.NON_SUBSCRIBER)
240
+
241
+ return coupon.exclusivityType.some((r) => userTypes.includes(r))
242
+ }
243
+
244
+ private async getCouponCategoriesId(productsCategories: string[]): Promise<Array<String>> {
245
+ const couponCategories: Array<String> = []
246
+
247
+ for (let index = 0; index < productsCategories.length; index++) {
248
+ const category: Category & { firestoreId?: string } = await this.repositoriesFacade.categoryRepository.get({
249
+ id: productsCategories[index],
250
+ })
251
+
252
+ if (category) {
253
+ const children = await this.repositoriesFacade.categoryRepository.getChildren(
254
+ parseInt(productsCategories[index]),
255
+ )
256
+
257
+ couponCategories.push(category.id, ...children.map((c) => c.id.toString()))
258
+ }
259
+ }
260
+
261
+ return [...new Set(couponCategories)]
262
+ }
263
+
264
+ private emailIsFromCollaborator = (userEmail: string): boolean => !!userEmail?.match(/@b4a.com.br/g)
265
+
266
+ private async getLineItensEligebleForDiscount(
267
+ productsCategories: string[],
268
+ checkout: Partial<Checkout>,
269
+ ): Promise<LineItem[]> {
270
+ let lineItensDiscount = []
271
+ const couponCategories = await this.getCouponCategoriesId(productsCategories)
272
+
273
+ if (productsCategories?.length) {
274
+ lineItensDiscount = checkout.lineItems?.filter((item) => {
275
+ if (item.isGift) return false
276
+
277
+ if (item.categories?.length) {
278
+ return item.categories.some((c) => couponCategories.some((cat) => cat == c))
279
+ }
280
+
281
+ return true
282
+ })
283
+ } else {
284
+ lineItensDiscount = checkout.lineItems.filter((item) => !item.isGift)
285
+ }
286
+
287
+ return lineItensDiscount
288
+ }
289
+
290
+ private calcCheckoutSubtotal(lineItens: LineItem[], user: User): number {
291
+ return (
292
+ lineItens
293
+ ?.filter((item) => !item.isGift)
294
+ .reduce(
295
+ (acc, curr) =>
296
+ user?.isSubscriber && curr.price?.subscriberPrice
297
+ ? acc + curr.price?.subscriberPrice * curr.quantity
298
+ : acc + curr.pricePaid * curr.quantity,
299
+ 0,
300
+ ) || 0
301
+ )
302
+ }
303
+
304
+ private async getOrdersWithCoupon(coupon: Coupon): Promise<Order[]> {
305
+ return await this.repositoriesFacade.orderRepository
306
+ .find({
307
+ filters: {
308
+ coupon: { id: coupon.id },
309
+ status: { operator: Where.NOTEQUALS, value: OrderStatus.CANCELADO },
310
+ },
311
+ })
312
+ .then((result) => result.data)
313
+ }
314
+
315
+ private async getOrdersFromUser(email: string) {
316
+ return await this.repositoriesFacade.orderRepository
317
+ .find({
318
+ filters: {
319
+ user: { email: { operator: Where.EQUALS, value: email } },
320
+ status: { operator: Where.NOTEQUALS, value: OrderStatus.CANCELADO },
321
+ },
322
+ })
323
+ .then((result) => result.data)
324
+ }
325
+
326
+ private countOrdersWithUser(orders: Order[], email: string): number {
327
+ return orders.filter((o) => o.user.email == email).length
328
+ }
329
+
330
+ private getCouponUseLimits(coupon: Coupon, checkoutType: CheckoutTypes, user: User) {
331
+ let couponUseLimits: { unlimited?: boolean; total?: number; limitedPerUser?: boolean; firstOrder?: boolean }
332
+
333
+ if (checkoutType == CheckoutTypes.ECOMMERCE || checkoutType == CheckoutTypes.ALL) {
334
+ if (
335
+ coupon.exclusivityType.length === 1 &&
336
+ (coupon.exclusivityType.at(0) === Exclusivities.SPECIFIC_USER ||
337
+ coupon.exclusivityType.at(0) === Exclusivities.COLLABORATORS)
338
+ )
339
+ couponUseLimits = coupon.useLimits.non_subscriber
340
+ else couponUseLimits = user?.isSubscriber ? coupon.useLimits.subscriber : coupon.useLimits.non_subscriber
341
+ } else {
342
+ couponUseLimits = coupon.useLimits.subscription
343
+ }
344
+
345
+ return couponUseLimits
346
+ }
347
+ }
@@ -0,0 +1,9 @@
1
+ import { CustomError } from 'ts-custom-error'
2
+
3
+ import { InvalidCouponError } from './invalid-coupon.error'
4
+
5
+ export class GroupInvalidCouponError extends CustomError {
6
+ constructor(readonly errors: InvalidCouponError[]) {
7
+ super('Many coupon errors throw')
8
+ }
9
+ }
@@ -0,0 +1,2 @@
1
+ export * from './group-invalid-coupon.error'
2
+ export * from './invalid-coupon.error'
@@ -0,0 +1,7 @@
1
+ import { CustomError } from 'ts-custom-error'
2
+
3
+ export class InvalidCouponError extends CustomError {
4
+ constructor(message: string) {
5
+ super(message)
6
+ }
7
+ }
@@ -0,0 +1 @@
1
+ export * from './util.helper'