@owlmeans/server-payment 0.1.18-rc.2 → 0.1.18-rc.21

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 (222) hide show
  1. package/README.md +109 -25
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/server-payment/SKILL.md +475 -52
  4. package/build/actions/index.d.ts +1 -0
  5. package/build/actions/index.d.ts.map +1 -1
  6. package/build/actions/index.js +1 -0
  7. package/build/actions/index.js.map +1 -1
  8. package/build/actions/resync-subscriptions.d.ts +3 -0
  9. package/build/actions/resync-subscriptions.d.ts.map +1 -0
  10. package/build/actions/resync-subscriptions.js +7 -0
  11. package/build/actions/resync-subscriptions.js.map +1 -0
  12. package/build/actions/resync.d.ts +1 -0
  13. package/build/actions/resync.d.ts.map +1 -1
  14. package/build/actions/resync.js +7 -3
  15. package/build/actions/resync.js.map +1 -1
  16. package/build/actions/webhook.d.ts.map +1 -1
  17. package/build/actions/webhook.js +6 -3
  18. package/build/actions/webhook.js.map +1 -1
  19. package/build/config.d.ts +60 -5
  20. package/build/config.d.ts.map +1 -1
  21. package/build/config.js +237 -5
  22. package/build/config.js.map +1 -1
  23. package/build/consts.d.ts +75 -3
  24. package/build/consts.d.ts.map +1 -1
  25. package/build/consts.js +94 -5
  26. package/build/consts.js.map +1 -1
  27. package/build/consumer/capture.d.ts +74 -0
  28. package/build/consumer/capture.d.ts.map +1 -0
  29. package/build/consumer/capture.js +291 -0
  30. package/build/consumer/capture.js.map +1 -0
  31. package/build/consumer/format.d.ts +27 -0
  32. package/build/consumer/format.d.ts.map +1 -0
  33. package/build/consumer/format.js +81 -0
  34. package/build/consumer/format.js.map +1 -0
  35. package/build/consumer/handlers.d.ts +28 -0
  36. package/build/consumer/handlers.d.ts.map +1 -0
  37. package/build/consumer/handlers.js +173 -0
  38. package/build/consumer/handlers.js.map +1 -0
  39. package/build/consumer/index.d.ts +7 -0
  40. package/build/consumer/index.d.ts.map +1 -0
  41. package/build/consumer/index.js +6 -0
  42. package/build/consumer/index.js.map +1 -0
  43. package/build/consumer/mail.d.ts +27 -0
  44. package/build/consumer/mail.d.ts.map +1 -0
  45. package/build/consumer/mail.js +314 -0
  46. package/build/consumer/mail.js.map +1 -0
  47. package/build/consumer/origin.d.ts +14 -0
  48. package/build/consumer/origin.d.ts.map +1 -0
  49. package/build/consumer/origin.js +47 -0
  50. package/build/consumer/origin.js.map +1 -0
  51. package/build/consumer/reconcile.d.ts +12 -0
  52. package/build/consumer/reconcile.d.ts.map +1 -0
  53. package/build/consumer/reconcile.js +317 -0
  54. package/build/consumer/reconcile.js.map +1 -0
  55. package/build/consumer/records.d.ts +78 -0
  56. package/build/consumer/records.d.ts.map +1 -0
  57. package/build/consumer/records.js +296 -0
  58. package/build/consumer/records.js.map +1 -0
  59. package/build/consumer/service.d.ts +51 -0
  60. package/build/consumer/service.d.ts.map +1 -0
  61. package/build/consumer/service.js +760 -0
  62. package/build/consumer/service.js.map +1 -0
  63. package/build/consumer/withdrawal.d.ts +57 -0
  64. package/build/consumer/withdrawal.d.ts.map +1 -0
  65. package/build/consumer/withdrawal.js +247 -0
  66. package/build/consumer/withdrawal.js.map +1 -0
  67. package/build/entitlement.d.ts +10 -0
  68. package/build/entitlement.d.ts.map +1 -0
  69. package/build/entitlement.js +69 -0
  70. package/build/entitlement.js.map +1 -0
  71. package/build/entrypoints.d.ts +1 -1
  72. package/build/entrypoints.d.ts.map +1 -1
  73. package/build/entrypoints.js +2 -1
  74. package/build/entrypoints.js.map +1 -1
  75. package/build/gate.d.ts +32 -4
  76. package/build/gate.d.ts.map +1 -1
  77. package/build/gate.js +59 -19
  78. package/build/gate.js.map +1 -1
  79. package/build/index.d.ts +18 -3
  80. package/build/index.d.ts.map +1 -1
  81. package/build/index.js +15 -3
  82. package/build/index.js.map +1 -1
  83. package/build/limit.d.ts +13 -0
  84. package/build/limit.d.ts.map +1 -0
  85. package/build/limit.js +47 -0
  86. package/build/limit.js.map +1 -0
  87. package/build/model.d.ts +10 -1
  88. package/build/model.d.ts.map +1 -1
  89. package/build/model.js +215 -17
  90. package/build/model.js.map +1 -1
  91. package/build/observer.d.ts +9 -0
  92. package/build/observer.d.ts.map +1 -1
  93. package/build/observer.js +38 -12
  94. package/build/observer.js.map +1 -1
  95. package/build/plan.d.ts +22 -0
  96. package/build/plan.d.ts.map +1 -0
  97. package/build/plan.js +72 -0
  98. package/build/plan.js.map +1 -0
  99. package/build/plugins/checkout-plugins.d.ts +49 -0
  100. package/build/plugins/checkout-plugins.d.ts.map +1 -0
  101. package/build/plugins/checkout-plugins.js +124 -0
  102. package/build/plugins/checkout-plugins.js.map +1 -0
  103. package/build/plugins/estimate.d.ts +43 -0
  104. package/build/plugins/estimate.d.ts.map +1 -0
  105. package/build/plugins/estimate.js +268 -0
  106. package/build/plugins/estimate.js.map +1 -0
  107. package/build/plugins/events.d.ts +45 -10
  108. package/build/plugins/events.d.ts.map +1 -1
  109. package/build/plugins/events.js +605 -136
  110. package/build/plugins/events.js.map +1 -1
  111. package/build/plugins/fx.d.ts +31 -0
  112. package/build/plugins/fx.d.ts.map +1 -0
  113. package/build/plugins/fx.js +81 -0
  114. package/build/plugins/fx.js.map +1 -0
  115. package/build/plugins/portal.d.ts +37 -0
  116. package/build/plugins/portal.d.ts.map +1 -0
  117. package/build/plugins/portal.js +265 -0
  118. package/build/plugins/portal.js.map +1 -0
  119. package/build/plugins/refunds.d.ts +33 -0
  120. package/build/plugins/refunds.d.ts.map +1 -0
  121. package/build/plugins/refunds.js +80 -0
  122. package/build/plugins/refunds.js.map +1 -0
  123. package/build/plugins/stripe.d.ts +36 -4
  124. package/build/plugins/stripe.d.ts.map +1 -1
  125. package/build/plugins/stripe.js +492 -97
  126. package/build/plugins/stripe.js.map +1 -1
  127. package/build/plugins/webhook-manager.d.ts +46 -0
  128. package/build/plugins/webhook-manager.d.ts.map +1 -0
  129. package/build/plugins/webhook-manager.js +231 -0
  130. package/build/plugins/webhook-manager.js.map +1 -0
  131. package/build/reconcile.d.ts +15 -0
  132. package/build/reconcile.d.ts.map +1 -0
  133. package/build/reconcile.js +88 -0
  134. package/build/reconcile.js.map +1 -0
  135. package/build/resource.d.ts +12 -1
  136. package/build/resource.d.ts.map +1 -1
  137. package/build/resource.js +97 -6
  138. package/build/resource.js.map +1 -1
  139. package/build/service.d.ts +28 -3
  140. package/build/service.d.ts.map +1 -1
  141. package/build/service.js +146 -19
  142. package/build/service.js.map +1 -1
  143. package/build/subscription.d.ts +48 -0
  144. package/build/subscription.d.ts.map +1 -0
  145. package/build/subscription.js +176 -0
  146. package/build/subscription.js.map +1 -0
  147. package/build/sync.d.ts +28 -1
  148. package/build/sync.d.ts.map +1 -1
  149. package/build/sync.js +208 -31
  150. package/build/sync.js.map +1 -1
  151. package/build/types.d.ts +1115 -36
  152. package/build/types.d.ts.map +1 -1
  153. package/build/usage.d.ts +63 -0
  154. package/build/usage.d.ts.map +1 -0
  155. package/build/usage.js +363 -0
  156. package/build/usage.js.map +1 -0
  157. package/build/utils.d.ts +52 -1
  158. package/build/utils.d.ts.map +1 -1
  159. package/build/utils.js +69 -7
  160. package/build/utils.js.map +1 -1
  161. package/package.json +17 -13
  162. package/src/actions/index.ts +1 -0
  163. package/src/actions/resync-subscriptions.ts +10 -0
  164. package/src/actions/resync.ts +6 -3
  165. package/src/actions/webhook.ts +5 -3
  166. package/src/config.ts +264 -8
  167. package/src/consts.ts +114 -6
  168. package/src/consumer/capture.ts +362 -0
  169. package/src/consumer/format.ts +90 -0
  170. package/src/consumer/handlers.ts +211 -0
  171. package/src/consumer/index.ts +6 -0
  172. package/src/consumer/mail.ts +368 -0
  173. package/src/consumer/origin.ts +63 -0
  174. package/src/consumer/reconcile.ts +329 -0
  175. package/src/consumer/records.ts +374 -0
  176. package/src/consumer/service.ts +868 -0
  177. package/src/consumer/withdrawal.ts +302 -0
  178. package/src/entitlement.ts +84 -0
  179. package/src/entrypoints.ts +2 -1
  180. package/src/gate.ts +88 -21
  181. package/src/index.ts +24 -3
  182. package/src/limit.ts +57 -0
  183. package/src/model.ts +237 -18
  184. package/src/observer.ts +44 -11
  185. package/src/plan.ts +89 -0
  186. package/src/plugins/checkout-plugins.ts +155 -0
  187. package/src/plugins/estimate.ts +339 -0
  188. package/src/plugins/events.ts +677 -121
  189. package/src/plugins/fx.ts +122 -0
  190. package/src/plugins/portal.ts +306 -0
  191. package/src/plugins/refunds.ts +108 -0
  192. package/src/plugins/stripe.ts +581 -96
  193. package/src/plugins/webhook-manager.ts +270 -0
  194. package/src/reconcile.ts +103 -0
  195. package/src/resource.ts +152 -7
  196. package/src/service.ts +174 -18
  197. package/src/subscription.ts +231 -0
  198. package/src/sync.ts +249 -29
  199. package/src/types.ts +1227 -32
  200. package/src/usage.ts +453 -0
  201. package/src/utils.ts +127 -10
  202. package/tests/checkout-consumer.spec.ts +348 -0
  203. package/tests/checkout-plugins.spec.ts +164 -0
  204. package/tests/checkout.spec.ts +184 -83
  205. package/tests/consumer-events.spec.ts +218 -0
  206. package/tests/consumer-fixtures.ts +132 -0
  207. package/tests/consumer-ops.spec.ts +351 -0
  208. package/tests/consumer-rights.integration.spec.ts +150 -0
  209. package/tests/consumer-rights.spec.ts +501 -0
  210. package/tests/context.ts +109 -0
  211. package/tests/entitlement.spec.ts +103 -0
  212. package/tests/estimate.spec.ts +240 -0
  213. package/tests/events.spec.ts +356 -0
  214. package/tests/fake-stripe.ts +972 -0
  215. package/tests/gate.spec.ts +62 -72
  216. package/tests/limit-gate.spec.ts +68 -0
  217. package/tests/portal.spec.ts +200 -0
  218. package/tests/protocol.spec.ts +23 -6
  219. package/tests/sync.spec.ts +114 -0
  220. package/tests/usage.integration.spec.ts +101 -0
  221. package/tests/usage.spec.ts +171 -0
  222. package/tests/webhook-manager.spec.ts +152 -0
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAA;AAG3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,KAAK,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAKjE,OAAO,KAAK,EACV,kBAAkB,EAAE,mBAAmB,EAAE,cAAc,EAAE,uBAAuB,EAChF,yBAAyB,EAAE,2BAA2B,EAAE,kBAAkB,EAC3E,MAAM,YAAY,CAAA;AAEnB,eAAO,MAAM,OAAO,QAAS,UAAU,KAAG,cAA8D,CAAA;AACxG,eAAO,MAAM,OAAO,QAAS,UAAU,KAAG,cAA8D,CAAA;AACxG,eAAO,MAAM,QAAQ,QAAS,UAAU,KAAG,kBAAuE,CAAA;AAClH,eAAO,MAAM,gBAAgB,QAAS,UAAU,KAAG,uBACU,CAAA;AAC7D,eAAO,MAAM,aAAa,QAAS,UAAU,KAAG,2BACqB,CAAA;AACrE,eAAO,MAAM,YAAY,QAAS,UAAU,KAAG,mBACa,CAAA;AAC5D,eAAO,MAAM,YAAY,QAAe,UAAU,KAAG,OAAO,CAAC,kBAAkB,CAEA,CAAA;AAC/E,eAAO,MAAM,YAAY,QAAe,UAAU,KAAG,OAAO,CAAC,MAAM,CAA8C,CAAA;AACjH,eAAO,MAAM,kBAAkB,QACxB,UAAU,YAAY,MAAM,cAAc,MAAM,KACpD,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAGzC,CAAA"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
package/build/utils.js CHANGED
@@ -1,18 +1,80 @@
1
1
  import Stripe from 'stripe';
2
2
  import { PLUGINS } from '@owlmeans/config';
3
- import { DEFAULT_ALIAS as PAYMENT_SERVICE, SubscriptionStatus } from '@owlmeans/payment';
4
- import { GATEWAY_SERVICE, PAYMENT_OBSERVER, RES_PAYGATE_CUSTOMER, RES_PAYMENT_FINGERPRINT, RES_PAYMENT_SUBSCRIPTION, STRIPE_PLUGIN_CONFIG, } from './consts.js';
3
+ import { MONGO_DUPLICATE_KEY } from '@owlmeans/mongo-resource';
4
+ import { DEFAULT_ALIAS as PAYMENT_SERVICE, ENTITLING_STATUSES, WebhookSetupError } from '@owlmeans/payment';
5
+ import { CONSUMER_RIGHTS_MAIL_PLUGIN_CONFIG, CONSUMER_RIGHTS_SERVICE, ENTITLEMENT_SERVICE, GATEWAY_SERVICE, PAYMENT_OBSERVER, RES_BILLING_PROFILE, RES_CONSUMER_CONSENT, RES_CONSUMER_DECLARATION, RES_CONSUMER_EVENT, RES_PAYGATE_CUSTOMER, RES_PAYMENT_FINGERPRINT, RES_PAYMENT_FULFILLMENT, RES_PAYMENT_PURCHASE, RES_PAYMENT_SUBSCRIPTION, RES_PAYMENT_USAGE, RES_PAYMENT_USAGE_COUNTER, RES_PAYMENT_WEBHOOK, STRIPE_PLUGIN_CONFIG, STRIPE_PORTAL_PLUGIN_CONFIG, STRIPE_PRICING_PLUGIN_CONFIG, } from './consts.js';
5
6
  export const payment = (ctx) => ctx.service(PAYMENT_SERVICE);
6
7
  export const gateway = (ctx) => ctx.service(GATEWAY_SERVICE);
7
8
  export const observer = (ctx) => ctx.service(PAYMENT_OBSERVER);
9
+ export const entitlements = (ctx) => ctx.service(ENTITLEMENT_SERVICE);
8
10
  export const paygateCustomers = (ctx) => ctx.resource(RES_PAYGATE_CUSTOMER);
9
11
  export const subscriptions = (ctx) => ctx.resource(RES_PAYMENT_SUBSCRIPTION);
12
+ export const fulfillments = (ctx) => ctx.resource(RES_PAYMENT_FULFILLMENT);
13
+ export const paymentWebhooks = (ctx) => ctx.resource(RES_PAYMENT_WEBHOOK);
14
+ export const usageEvents = (ctx) => ctx.resource(RES_PAYMENT_USAGE);
15
+ export const usageCounters = (ctx) => ctx.resource(RES_PAYMENT_USAGE_COUNTER);
10
16
  export const fingerprints = (ctx) => ctx.resource(RES_PAYMENT_FINGERPRINT);
11
- export const stripeConfig = async (ctx) => await ctx
12
- .getConfigResource(PLUGINS).get(STRIPE_PLUGIN_CONFIG);
17
+ export const billingProfiles = (ctx) => ctx.resource(RES_BILLING_PROFILE);
18
+ export const purchases = (ctx) => ctx.resource(RES_PAYMENT_PURCHASE);
19
+ export const consumerConsents = (ctx) => ctx.resource(RES_CONSUMER_CONSENT);
20
+ export const consumerDeclarations = (ctx) => ctx.resource(RES_CONSUMER_DECLARATION);
21
+ export const consumerEvents = (ctx) => ctx.resource(RES_CONSUMER_EVENT);
22
+ /** The consumer-rights service. @throws when it is not registered */
23
+ export const consumerRights = (ctx, alias = CONSUMER_RIGHTS_SERVICE) => ctx.service(alias);
24
+ /** The consumer-rights service, or `null` in a process that registered none. */
25
+ export const consumerRightsOf = (ctx, alias = CONSUMER_RIGHTS_SERVICE) => ctx.hasService?.(alias) === true
26
+ ? ctx.service(alias) : null;
27
+ export const stripeConfig = async (ctx) => await ctx.getConfigResource(PLUGINS).get(STRIPE_PLUGIN_CONFIG);
28
+ /** The portal branding declared with `portalBranding`, or `null`. */
29
+ export const portalBrandingConfig = async (ctx) => await ctx.getConfigResource(PLUGINS).load(STRIPE_PORTAL_PLUGIN_CONFIG);
30
+ /** The consumer-rights mail options declared with `declareConsumerRights`, or `null`. */
31
+ export const consumerMailConfig = async (ctx) => await ctx.getConfigResource(PLUGINS).load(CONSUMER_RIGHTS_MAIL_PLUGIN_CONFIG);
32
+ /** The Stripe-only pricing settings declared with `declarePaymentPricing`, or `null`. */
33
+ export const stripePricingConfig = async (ctx) => await ctx.getConfigResource(PLUGINS).load(STRIPE_PRICING_PLUGIN_CONFIG);
34
+ /**
35
+ * A Stripe client pinned to the API version the installed SDK is typed for (its default), so every
36
+ * object shape this package reads is the one the types describe.
37
+ */
13
38
  export const stripeClient = async (ctx) => new Stripe((await stripeConfig(ctx)).api);
39
+ /**
40
+ * The API version a client was built with — read back from the client, never written as a literal,
41
+ * so a webhook endpoint always receives the payload shapes the client itself parses.
42
+ */
43
+ export const apiVersionOf = (stripe) => {
44
+ const version = stripe.getApiField?.('version');
45
+ if (typeof version !== 'string' || version === '') {
46
+ throw new WebhookSetupError('api-version');
47
+ }
48
+ return version;
49
+ };
50
+ export const isDuplicateKey = (error) => error?.code === MONGO_DUPLICATE_KEY;
51
+ /** A paygate answer meaning the object does not exist (deleted, or never did). */
52
+ export const isMissingObject = (error) => {
53
+ const typed = error;
54
+ return typed?.code === 'resource_missing' || typed?.raw?.code === 'resource_missing' || typed?.statusCode === 404;
55
+ };
56
+ /** A paygate object reference, expanded or not, as its id. */
57
+ export const idOf = (value) => value == null ? undefined : typeof value === 'string' ? value : value.id;
58
+ /** Epoch seconds → `Date`. */
59
+ export const dateOf = (seconds) => seconds == null ? undefined : new Date(seconds * 1000);
60
+ /** Drop `null` and `undefined` properties — a stored record reads an absent field back as `null`. */
61
+ export const compact = (value) => Object.fromEntries(Object.entries(value).filter(([, entry]) => entry != null));
62
+ /** The entity's highest-ranked entitling subscription of one product. */
14
63
  export const activeSubscription = async (ctx, entityId, productSku) => await subscriptions(ctx).load({
15
- entityId, productSku, kind: 'subscription',
16
- status: [SubscriptionStatus.Active, SubscriptionStatus.Trial],
17
- });
64
+ entityId, productSku, status: [...ENTITLING_STATUSES],
65
+ }, { sort: [{ field: 'rank', order: 'desc' }, { field: 'createdAt', order: 'desc' }] });
66
+ /** A conditional single-document `$set` — `true` when the filter matched (the guard held). */
67
+ export const conditionalSet = async (resource, filter, set) => {
68
+ const collection = resource.collection;
69
+ const result = await collection.updateOne(filter, { $set: set });
70
+ return (result.matchedCount ?? result.modifiedCount ?? 0) > 0;
71
+ };
72
+ /** A conditional single-document delete — `true` when the filter matched (the guard held). */
73
+ export const conditionalDelete = async (resource, filter) => {
74
+ const collection = resource.collection;
75
+ const result = await collection.deleteOne(filter);
76
+ return (result.deletedCount ?? 0) > 0;
77
+ };
78
+ /** An error's message for an audit record, never a stack. */
79
+ export const errorText = (error) => (error instanceof Error ? error.message : String(error)).slice(0, 1000);
18
80
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAA;AAC3B,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,aAAa,IAAI,eAAe,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAGxF,OAAO,EACL,eAAe,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,uBAAuB,EAChF,wBAAwB,EAAE,oBAAoB,GAC/C,MAAM,aAAa,CAAA;AAMpB,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,GAAe,EAAkB,EAAE,CAAC,GAAG,CAAC,OAAO,CAAiB,eAAe,CAAC,CAAA;AACxG,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,GAAe,EAAkB,EAAE,CAAC,GAAG,CAAC,OAAO,CAAiB,eAAe,CAAC,CAAA;AACxG,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,GAAe,EAAsB,EAAE,CAAC,GAAG,CAAC,OAAO,CAAqB,gBAAgB,CAAC,CAAA;AAClH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,GAAe,EAA2B,EAAE,CAC3E,GAAG,CAAC,QAAQ,CAA0B,oBAAoB,CAAC,CAAA;AAC7D,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAe,EAA+B,EAAE,CAC5E,GAAG,CAAC,QAAQ,CAA8B,wBAAwB,CAAC,CAAA;AACrE,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAe,EAAuB,EAAE,CACnE,GAAG,CAAC,QAAQ,CAAsB,uBAAuB,CAAC,CAAA;AAC5D,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAE,GAAe,EAA+B,EAAE,CACjF,MAAO,GAAoG;KACxG,iBAAiB,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,oBAAoB,CAAuB,CAAA;AAC/E,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAE,GAAe,EAAmB,EAAE,CAAC,IAAI,MAAM,CAAC,CAAC,MAAM,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;AACjH,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EACrC,GAAe,EAAE,QAAgB,EAAE,UAAkB,EACV,EAAE,CAAC,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;IAC5E,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc;IAC1C,MAAM,EAAE,CAAC,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,CAAC,KAAK,CAAC;CAC9D,CAAC,CAAA"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/server-payment",
3
- "version": "0.1.18-rc.2",
3
+ "version": "0.1.18-rc.21",
4
4
  "license": "MIT",
5
5
  "description": "Protocol-bound payment resources, entitlement gates, and Stripe checkout for OwlMeans servers.",
6
6
  "type": "module",
@@ -26,23 +26,27 @@
26
26
  "ajv": "*"
27
27
  },
28
28
  "dependencies": {
29
- "@owlmeans/auth": "^0.1.18-rc.14",
30
- "@owlmeans/config": "^0.1.18-rc.17",
31
- "@owlmeans/context": "^0.1.18-rc.13",
32
- "@owlmeans/entrypoint": "^0.1.18-rc.16",
33
- "@owlmeans/mongo-resource": "^0.1.18-rc.17",
34
- "@owlmeans/payment": "^0.1.18-rc.18",
35
- "@owlmeans/resource": "^0.1.18-rc.14",
36
- "@owlmeans/route": "^0.1.18-rc.14",
37
- "@owlmeans/server-api": "^0.1.18-rc.22",
38
- "@owlmeans/server-app": "^0.1.18-rc.24",
39
- "@owlmeans/server-context": "^0.1.18-rc.17",
40
- "@owlmeans/server-entrypoint": "^0.1.18-rc.16",
29
+ "@owlmeans/auth": "^0.1.18-rc.35",
30
+ "@owlmeans/config": "^0.1.18-rc.37",
31
+ "@owlmeans/context": "^0.1.18-rc.32",
32
+ "@owlmeans/entrypoint": "^0.1.18-rc.36",
33
+ "@owlmeans/iam": "^0.1.18-rc.40",
34
+ "@owlmeans/mailer": "^0.1.18-rc.32",
35
+ "@owlmeans/mongo-resource": "^0.1.18-rc.36",
36
+ "@owlmeans/payment": "^0.1.18-rc.38",
37
+ "@owlmeans/resource": "^0.1.18-rc.35",
38
+ "@owlmeans/route": "^0.1.18-rc.33",
39
+ "@owlmeans/server-api": "^0.1.18-rc.41",
40
+ "@owlmeans/server-app": "^0.1.18-rc.42",
41
+ "@owlmeans/server-context": "^0.1.18-rc.36",
42
+ "@owlmeans/server-entrypoint": "^0.1.18-rc.35",
41
43
  "async-mutex": "^0.5.0",
42
44
  "stripe": "^17.7.0"
43
45
  },
44
46
  "devDependencies": {
45
47
  "@owlmeans/dep-config": "workspace:*",
48
+ "@owlmeans/mongo": "^0.1.18-rc.35",
49
+ "@owlmeans/test-integration": "^0.1.18-rc.29",
46
50
  "@types/bun": "^1.4.0",
47
51
  "@types/node": "^26.1.0",
48
52
  "nodemon": "^3.1.14",
@@ -1,2 +1,3 @@
1
1
  export * from './webhook.js'
2
2
  export * from './resync.js'
3
+ export * from './resync-subscriptions.js'
@@ -0,0 +1,10 @@
1
+ import { handlers } from '@owlmeans/server-api'
2
+ import { paymentGate } from '../consts.js'
3
+ import { gateway } from '../utils.js'
4
+ import type { Context } from '../types.js'
5
+
6
+ const bind = handlers<Context>()
7
+
8
+ /** Re-read every live paygate subscription — the manually or periodically triggered repair. */
9
+ export const resyncSubscriptions = bind.request(paymentGate.resyncSubscriptions, async (_request, context) =>
10
+ await gateway(context).resyncAll(context))
@@ -1,13 +1,16 @@
1
+ import { PaygateError } from '@owlmeans/payment'
1
2
  import { handlers } from '@owlmeans/server-api'
2
3
  import { paymentGate } from '../consts.js'
3
- import { fingerprints } from '../utils.js'
4
- import { initialize } from '../sync.js'
4
+ import { bootstrapStripe } from '../service.js'
5
+ import { fingerprints, gateway, stripeClient } from '../utils.js'
5
6
  import type { Context } from '../types.js'
6
7
 
7
8
  const bind = handlers<Context>()
8
9
 
10
+ /** Forget every sync fingerprint and bring Stripe back to the declared catalogue, portal and webhook. */
9
11
  export const resync = bind.request(paymentGate.resync, async (_request, context) => {
12
+ if (!gateway(context).managed) throw new PaygateError('unmanaged')
10
13
  await fingerprints(context).clear()
11
- await initialize(context)
14
+ await bootstrapStripe(context, await stripeClient(context), { force: true })
12
15
  return { ok: true }
13
16
  })
@@ -1,12 +1,14 @@
1
- import { UnknownPaygate } from '@owlmeans/payment'
1
+ import { PaygateError, UnknownPaygate } from '@owlmeans/payment'
2
2
  import { handlers } from '@owlmeans/server-api'
3
3
  import { paymentGate, STRIPE_PAYGATE_ALIAS } from '../consts.js'
4
- import { stripePlugin } from '../plugins/stripe.js'
4
+ import { handleStripeWebhook } from '../plugins/stripe.js'
5
+ import { gateway, stripeClient } from '../utils.js'
5
6
  import type { Context } from '../types.js'
6
7
 
7
8
  const bind = handlers<Context>()
8
9
 
9
10
  export const webhook = bind.params(paymentGate.webhook, async ({ paygate }, context, request) => {
10
11
  if (paygate !== STRIPE_PAYGATE_ALIAS) throw new UnknownPaygate(paygate)
11
- await stripePlugin.handleWebhook(request, context)
12
+ if (!gateway(context).managed) throw new PaygateError('unmanaged')
13
+ await handleStripeWebhook(context, await stripeClient(context), request)
12
14
  })
package/src/config.ts CHANGED
@@ -1,11 +1,21 @@
1
1
  import { toConfigRecord } from '@owlmeans/server-app'
2
- import { plugin } from '@owlmeans/config'
2
+ import { plugin, PLUGIN_RECORD } from '@owlmeans/config'
3
3
  import {
4
- assertAmountCheckoutPolicy, assertQuantityCheckoutPolicy, CheckoutPricingMode,
5
- PLAN_RECORD_PREFIX, PLAN_RECORD_TYPE, PlanStatus, PRODUCT_RECORD_PREFIX, PRODUCT_RECORD_TYPE,
4
+ assertAmountCheckoutPolicy, assertPricingPolicy, assertQuantityCheckoutPolicy, CAPABILITY_LIMIT_SCOPE,
5
+ CheckoutPricingMode, CONSUMER_RIGHTS_RECORD_ID, CONSUMER_RIGHTS_RECORD_TYPE, ConsumerRightsError, LimitKind,
6
+ LimitMisdeclared, LimitWindow, makeConsumerRightsPolicy, PLAN_RECORD_PREFIX, PLAN_RECORD_TYPE, PaymentError,
7
+ PlanDuration, PlanRankConflict, PlanStatus, PRICING_POLICY_RECORD_ID, PRICING_POLICY_RECORD_TYPE,
8
+ PRODUCT_RECORD_PREFIX, PRODUCT_RECORD_TYPE, ProductError,
6
9
  } from '@owlmeans/payment'
7
- import { STRIPE_PAYGATE_ALIAS, STRIPE_PLUGIN_CONFIG } from './consts.js'
8
- import type { Config, PaymentPlan, PaymentPlanDef, PaymentProduct, PaymentProductDef } from './types.js'
10
+ import type { ConsumerRightsPolicy, LimitDeclaration, PlanWithdrawalComponent } from '@owlmeans/payment'
11
+ import {
12
+ CONSUMER_RIGHTS_MAIL_PLUGIN_CONFIG, STRIPE_PAYGATE_ALIAS, STRIPE_PLUGIN_CONFIG, STRIPE_PORTAL_PLUGIN_CONFIG,
13
+ STRIPE_PRICING_PLUGIN_CONFIG,
14
+ } from './consts.js'
15
+ import type {
16
+ Config, ConsumerRightsDef, PaymentPlan, PaymentPlanDef, PaymentProduct, PaymentProductDef, PortalBrandingDef,
17
+ PricingDef, StripeSecretsDef,
18
+ } from './types.js'
9
19
 
10
20
  export const declarePaymentProduct = (cfg: Config, def: PaymentProductDef): void => {
11
21
  cfg.records = cfg.records ?? []
@@ -18,8 +28,56 @@ export const declarePaymentProduct = (cfg: Config, def: PaymentProductDef): void
18
28
  cfg.records.push({ ...toConfigRecord(product), recordType: PRODUCT_RECORD_TYPE, id: `${PRODUCT_RECORD_PREFIX}:${def.sku}` })
19
29
  }
20
30
 
31
+ const isSafeCount = (value: unknown): value is number =>
32
+ typeof value === 'number' && Number.isSafeInteger(value) && value >= 0
33
+
34
+ const LIMIT_KINDS = Object.values(LimitKind) as string[]
35
+ const LIMIT_WINDOWS = Object.values(LimitWindow) as string[]
36
+
37
+ const assertLimitDeclaration = (key: string, limit: LimitDeclaration): void => {
38
+ if (!LIMIT_KINDS.includes(limit.kind)) {
39
+ throw new LimitMisdeclared(`${key}:kind`)
40
+ }
41
+ if (limit.kind === LimitKind.Window) {
42
+ if (limit.window == null) {
43
+ throw new LimitMisdeclared(`${key}:window-missing`)
44
+ }
45
+ if (!LIMIT_WINDOWS.includes(limit.window)) {
46
+ throw new LimitMisdeclared(`${key}:window`)
47
+ }
48
+ } else if (limit.window != null) {
49
+ throw new LimitMisdeclared(`${key}:window-forbidden`)
50
+ }
51
+ if (!isSafeCount(limit.limit)) {
52
+ throw new LimitMisdeclared(`${key}:limit`)
53
+ }
54
+ if (limit.promo != null && !(limit.promo.until instanceof Date && !Number.isNaN(limit.promo.until.getTime()))) {
55
+ throw new LimitMisdeclared(`${key}:promo-until`)
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Declare a plan into the configuration catalogue.
61
+ *
62
+ * Validated before anything is pushed: `rank` a safe integer `>= 0`; a `free` plan costs nothing
63
+ * and is sold through no gateway; no capability set under the reserved `limit` scope; every limit
64
+ * well-formed (a `window` limit names its window, the others do not, the ceiling is a safe
65
+ * integer, a promo ends at a real `Date`). Amount and quantity policies are asserted as before.
66
+ */
21
67
  export const declarePaymentPlan = (cfg: Config, def: PaymentPlanDef): void => {
22
- cfg.records = cfg.records ?? []
68
+ if (def.rank != null && !isSafeCount(def.rank)) {
69
+ throw new PlanRankConflict(`rank:${def.sku}`)
70
+ }
71
+ if (def.free === true && (def.price !== 0 || (def.gateways ?? []).length > 0)) {
72
+ throw new PlanRankConflict(`free:${def.sku}`)
73
+ }
74
+ if ((def.capabilities ?? []).some(set => set.scope === CAPABILITY_LIMIT_SCOPE)) {
75
+ throw new LimitMisdeclared(`${def.sku}:reserved-scope`)
76
+ }
77
+ for (const [key, limit] of Object.entries(def.limits ?? {})) {
78
+ assertLimitDeclaration(key, limit)
79
+ }
80
+
23
81
  const pricingMode = def.pricingMode
24
82
  if (pricingMode === CheckoutPricingMode.Amount) {
25
83
  if (def.amountPolicy == null) throw new TypeError(`Amount checkout plan '${def.sku}' requires amountPolicy`)
@@ -35,19 +93,217 @@ export const declarePaymentPlan = (cfg: Config, def: PaymentPlanDef): void => {
35
93
  : undefined
36
94
  )
37
95
  if (quantityPolicy != null) assertQuantityCheckoutPolicy(quantityPolicy)
96
+ const currencyPrices = currencyPricesOf(def)
97
+ assertWithdrawalComponents(def)
38
98
 
99
+ cfg.records = cfg.records ?? []
39
100
  const plan: PaymentPlan = {
40
101
  productSku: def.productSku, sku: def.sku, status: PlanStatus.Active,
102
+ ...(def.rank != null ? { rank: def.rank } : {}),
103
+ ...(def.free === true ? { free: true, gateways: [] } : def.gateways != null ? { gateways: def.gateways } : {}),
41
104
  duration: def.duration, price: def.price, currency: def.currency ?? def.amountPolicy?.currency ?? 'usd',
42
105
  order: def.order ?? 0, title: def.title ?? def.sku, capabilities: def.capabilities,
43
106
  limits: def.limits, recurring: def.recurring, pricingMode, amountPolicy: def.amountPolicy,
44
107
  quantityPolicy,
45
108
  minQuantity: quantityPolicy?.minimum, maxQuantity: quantityPolicy?.maximum,
46
109
  defaultQuantity: quantityPolicy?.default,
110
+ ...(currencyPrices != null ? { currencyPrices } : {}),
111
+ ...(def.withdrawal != null ? { withdrawal: { components: def.withdrawal.components.map(item => ({ ...item })) } } : {}),
47
112
  }
48
113
  cfg.records.push({ ...toConfigRecord(plan), recordType: PLAN_RECORD_TYPE, id: `${PLAN_RECORD_PREFIX}:${def.sku}` })
49
114
  }
50
115
 
51
- export const stripeSecrets = (cfg: Config, paths: { api: string; webhook: string }): void => {
52
- plugin(cfg, { api: paths.api, webhook: paths.webhook }, STRIPE_PLUGIN_CONFIG)
116
+ const CURRENCY = /^[a-z]{3}$/
117
+
118
+ /** `currencyPrices` normalized to lowercase codes; each a positive amount of whole minor units. */
119
+ const currencyPricesOf = (def: PaymentPlanDef): Record<string, number> | undefined => {
120
+ if (def.currencyPrices == null) {
121
+ return undefined
122
+ }
123
+ if (def.pricingMode === CheckoutPricingMode.Amount || def.free === true) {
124
+ throw new ProductError(`currency-prices:${def.sku}:mode`)
125
+ }
126
+ const prices: Record<string, number> = {}
127
+ for (const [raw, amount] of Object.entries(def.currencyPrices)) {
128
+ const currency = raw.toLowerCase()
129
+ const minor = Math.round(amount * 100)
130
+ if (!CURRENCY.test(currency) || !Number.isFinite(amount) || amount <= 0 || !Number.isSafeInteger(minor)
131
+ || Math.abs(minor - amount * 100) > 1e-6 || prices[currency] != null) {
132
+ throw new ProductError(`currency-prices:${def.sku}:${raw}`)
133
+ }
134
+ prices[currency] = amount
135
+ }
136
+
137
+ return prices
138
+ }
139
+
140
+ /** The components' shares must add up to the plan's price in minor units. */
141
+ const assertWithdrawalComponents = (def: PaymentPlanDef): void => {
142
+ const components: PlanWithdrawalComponent[] | undefined = def.withdrawal?.components
143
+ if (components == null) {
144
+ return
145
+ }
146
+ const keys = new Set<string>()
147
+ let sum = 0
148
+ for (const component of components) {
149
+ if (typeof component.key !== 'string' || component.key === '' || keys.has(component.key)
150
+ || (component.basis !== 'time' && component.basis !== 'units')
151
+ || !Number.isSafeInteger(component.shareMinor) || component.shareMinor < 0) {
152
+ throw new ProductError(`withdrawal:${def.sku}:component`)
153
+ }
154
+ keys.add(component.key)
155
+ sum += component.shareMinor
156
+ }
157
+ if (components.length === 0 || sum !== Math.round(def.price * 100)) {
158
+ throw new ProductError(`withdrawal:${def.sku}:shares`)
159
+ }
160
+ }
161
+
162
+ /** Every plan declared into a configuration. */
163
+ export const declaredPlansOf = (cfg: Config): PaymentPlan[] =>
164
+ (cfg.records ?? []).filter(record => record.recordType === PLAN_RECORD_TYPE) as unknown as PaymentPlan[]
165
+
166
+ /**
167
+ * Cross-plan rules a single declaration cannot see:
168
+ *
169
+ * - at most one free plan per rank;
170
+ * - no two paid plans of one product at the same rank (one-time `consumable` plans are never an
171
+ * entity's plan and are not ranked against each other).
172
+ *
173
+ * A limit key MAY change kind between plans (lifetime on one, a monthly window on another): counters
174
+ * are keyed by `(entity, key, window)` and the window is derived from the kind, so each kind keeps its
175
+ * own counter and a lifetime count still sticks to the entity across plan changes.
176
+ *
177
+ * @throws PlanRankConflict
178
+ */
179
+ export const assertPlanDeclarations = (cfg: Config): void => {
180
+ const plans = declaredPlansOf(cfg)
181
+ const freeRanks = new Set<number>()
182
+ const paidRanks = new Set<string>()
183
+ for (const plan of plans) {
184
+ const rank = plan.rank ?? 0
185
+ if (plan.free === true) {
186
+ if (freeRanks.has(rank)) {
187
+ throw new PlanRankConflict(`free:${rank}`)
188
+ }
189
+ freeRanks.add(rank)
190
+ } else if (plan.duration !== PlanDuration.Consumable) {
191
+ const key = `${plan.productSku}:${rank}`
192
+ if (paidRanks.has(key)) {
193
+ throw new PlanRankConflict(key)
194
+ }
195
+ paidRanks.add(key)
196
+ }
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Where the Stripe secrets live. A value that looks like a path is read into the configuration at
202
+ * boot. The webhook secret is an optional override of the one the managed endpoint stores.
203
+ */
204
+ export const stripeSecrets = (cfg: Config, paths: StripeSecretsDef): void => {
205
+ plugin(cfg, {
206
+ api: paths.api, ...(paths.webhook != null && paths.webhook !== '' ? { webhook: paths.webhook } : {}),
207
+ }, STRIPE_PLUGIN_CONFIG)
208
+ }
209
+
210
+ /** Branding and the default return URL of the managed customer portal configuration. */
211
+ export const portalBranding = (cfg: Config, def: PortalBrandingDef): void => {
212
+ plugin(cfg, {
213
+ returnUrl: def.returnUrl,
214
+ ...(def.headline != null ? { headline: def.headline } : {}),
215
+ ...(def.privacyPolicyUrl != null ? { privacyPolicyUrl: def.privacyPolicyUrl } : {}),
216
+ ...(def.termsOfServiceUrl != null ? { termsOfServiceUrl: def.termsOfServiceUrl } : {}),
217
+ }, STRIPE_PORTAL_PLUGIN_CONFIG)
218
+ }
219
+
220
+ /**
221
+ * Declare the pricing policy: whether checkout collects tax and a VAT/GST id, what `tax_behavior`
222
+ * synced prices are given, Adaptive Pricing, and whether the estimate endpoints are served. A
223
+ * config never carries more than one — a second call replaces the first, so re-declaring (a test, a
224
+ * config module re-run) is safe rather than silently ignored (a config resource resolves an id to
225
+ * its FIRST match).
226
+ *
227
+ * `def.stripe` (the FX Quotes preview version, the unspecified-price migration switch) is
228
+ * Stripe-only and goes to a backend plugin, never the advertised `PricingPolicy` record.
229
+ *
230
+ * @throws PaymentError (`assertPricingPolicy`) when an estimate is declared without the tax or
231
+ * currency capability it requires.
232
+ */
233
+ export const declarePaymentPricing = (cfg: Config, def: PricingDef): void => {
234
+ const { stripe, ...policy } = def
235
+ assertPricingPolicy(policy)
236
+ cfg.records = (cfg.records ?? []).filter(record => record.id !== PRICING_POLICY_RECORD_ID)
237
+ cfg.records.push({
238
+ ...toConfigRecord(policy), recordType: PRICING_POLICY_RECORD_TYPE, id: PRICING_POLICY_RECORD_ID,
239
+ })
240
+ if (stripe != null) {
241
+ const settlementCurrency = stripe.settlementCurrency?.toLowerCase()
242
+ if (settlementCurrency != null && !/^[a-z]{3}$/.test(settlementCurrency)) {
243
+ throw new PaymentError('pricing-policy:settlement-currency')
244
+ }
245
+ const subscriptionPaymentMethodTypes = stripe.subscriptionPaymentMethodTypes?.map(type => type.toLowerCase())
246
+ if (subscriptionPaymentMethodTypes != null && (subscriptionPaymentMethodTypes.length === 0
247
+ || subscriptionPaymentMethodTypes.some(type => !/^[a-z][a-z0-9_]*$/.test(type))
248
+ || new Set(subscriptionPaymentMethodTypes).size !== subscriptionPaymentMethodTypes.length)) {
249
+ throw new PaymentError('pricing-policy:subscription-payment-methods')
250
+ }
251
+ plugin(cfg, {
252
+ ...stripe,
253
+ ...(settlementCurrency != null ? { settlementCurrency } : {}),
254
+ ...(subscriptionPaymentMethodTypes != null ? { subscriptionPaymentMethodTypes } : {}),
255
+ }, STRIPE_PRICING_PLUGIN_CONFIG)
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Declare the consumer-rights policy (EU withdrawal, spend consent, start requests, cancellation,
261
+ * country lock) — a singleton config record like the pricing policy: `DEFAULT_CONSUMER_RIGHTS`
262
+ * filled in, asserted, and replacing any earlier declaration. It is ADVERTISED to the browser; the
263
+ * `mail` options (mailer alias, sender, archive copies) go to a backend-only plugin config.
264
+ *
265
+ * `trader` (who the consumer contracts with: `name` for the statements, `legalName`/`address`/`email`
266
+ * for the mails and the withdrawal information) is backend-only as well.
267
+ *
268
+ * @throws ConsumerRightsError (`policy:<field>`) — see `assertConsumerRightsPolicy`; `policy:trader`
269
+ * when a mechanism is on and no trader `name` and `legalName` are declared.
270
+ */
271
+ export const declareConsumerRights = (cfg: Config, def: ConsumerRightsDef): ConsumerRightsPolicy => {
272
+ const { mail, trader, ...declaration } = def
273
+ const policy = makeConsumerRightsPolicy(declaration)
274
+ const anyMechanism = Object.values(policy.mechanisms).some(on => on === true)
275
+ if (anyMechanism && (trader?.name == null || trader.name.trim() === '' || trader.legalName == null
276
+ || trader.legalName.trim() === '')) {
277
+ throw new ConsumerRightsError('policy:trader')
278
+ }
279
+ if (mail?.bcc?.some(address => !/^[^\s@]+@[^\s@]+$/.test(address)) === true) {
280
+ throw new ConsumerRightsError('policy:mail-bcc')
281
+ }
282
+ cfg.records = (cfg.records ?? []).filter(record => record.id !== CONSUMER_RIGHTS_RECORD_ID)
283
+ cfg.records.push({
284
+ ...toConfigRecord(policy), recordType: CONSUMER_RIGHTS_RECORD_TYPE, id: CONSUMER_RIGHTS_RECORD_ID,
285
+ })
286
+ const plugins = (cfg as unknown as Record<string, Array<{ id?: string }> | undefined>)[PLUGIN_RECORD]
287
+ if (plugins != null) {
288
+ (cfg as unknown as Record<string, unknown>)[PLUGIN_RECORD] = plugins
289
+ .filter(record => record.id !== CONSUMER_RIGHTS_MAIL_PLUGIN_CONFIG)
290
+ }
291
+ if (mail != null || trader != null) {
292
+ plugin(cfg, {
293
+ ...(trader != null ? {
294
+ trader: {
295
+ name: trader.name.trim(), legalName: trader.legalName.trim(),
296
+ ...(trader.address != null && trader.address.trim() !== '' ? { address: trader.address.trim() } : {}),
297
+ ...(trader.email != null && trader.email.trim() !== '' ? { email: trader.email.trim() } : {}),
298
+ ...(trader.website != null && trader.website.trim() !== '' ? { website: trader.website.trim() } : {}),
299
+ },
300
+ } : {}),
301
+ ...(mail?.alias != null ? { alias: mail.alias } : {}),
302
+ ...(mail?.from != null ? { from: mail.from } : {}),
303
+ ...(mail?.replyTo != null ? { replyTo: mail.replyTo } : {}),
304
+ ...(mail?.bcc != null && mail.bcc.length > 0 ? { bcc: [...mail.bcc] } : {}),
305
+ }, CONSUMER_RIGHTS_MAIL_PLUGIN_CONFIG)
306
+ }
307
+
308
+ return policy
53
309
  }