PaymentProviderEntities.mjs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  1. // @ts-check
  2. /**
  3. * @import { PaymentProvider } from '../../../../types/subscription/dashboard/subscription'
  4. * @import { CurrencyCode, StripeCurrencyCode } from '../../../../types/subscription/currency'
  5. * @import { AddOn } from '../../../../types/subscription/plan'
  6. */
  7. /**
  8. * @typedef {object} ImmediateChargeLineItem
  9. * @property {string | null | undefined} planCode
  10. * @property {string} description
  11. * @property {number} subtotal
  12. * @property {number} discount
  13. * @property {number} tax
  14. * @property {boolean} [isAiAssist]
  15. */
  16. import OError from '@overleaf/o-error'
  17. import { DuplicateAddOnError, AddOnNotPresentError } from './Errors.mjs'
  18. import PlansLocator from './PlansLocator.mjs'
  19. import SubscriptionHelper from './SubscriptionHelper.mjs'
  20. import { AI_ADD_ON_CODE, isStandaloneAiAddOnPlanCode } from './AiHelper.mjs'
  21. export const MEMBERS_LIMIT_ADD_ON_CODE = 'additional-license'
  22. export class PaymentProviderSubscription {
  23. /**
  24. * @param {object} props
  25. * @param {string} props.id
  26. * @param {string} props.userId
  27. * @param {string} props.planCode
  28. * @param {string} props.planName
  29. * @param {number} props.planPrice
  30. * @param {PaymentProviderSubscriptionAddOn[]} [props.addOns]
  31. * @param {number} props.subtotal
  32. * @param {number} [props.taxRate]
  33. * @param {number} [props.taxAmount]
  34. * // Recurly uses uppercase currency codes, but Stripe uses lowercase
  35. * @param {CurrencyCode | StripeCurrencyCode} props.currency
  36. * @param {number} props.total
  37. * @param {Date} props.periodStart
  38. * @param {Date} props.periodEnd
  39. * @param {string} props.collectionMethod
  40. * @param {number} [props.netTerms]
  41. * @param {string} [props.poNumber]
  42. * @param {string} [props.termsAndConditions]
  43. * @param {PaymentProviderSubscriptionChange} [props.pendingChange]
  44. * @param {PaymentProvider['service']} [props.service]
  45. * @param {string} [props.state]
  46. * @param {Date|null} [props.trialPeriodStart]
  47. * @param {Date|null} [props.trialPeriodEnd]
  48. * @param {Date|null} [props.pausePeriodStart]
  49. * @param {Date|null} [props.pausePeriodEnd]
  50. * @param {number|null} [props.remainingPauseCycles]
  51. * @param {boolean} [props.isMigratedFromRecurly]
  52. */
  53. constructor(props) {
  54. this.id = props.id
  55. this.userId = props.userId
  56. this.planCode = props.planCode
  57. this.planName = props.planName
  58. this.planPrice = props.planPrice
  59. this.addOns = props.addOns ?? []
  60. this.subtotal = props.subtotal
  61. this.taxRate = props.taxRate ?? 0
  62. this.taxAmount = props.taxAmount ?? 0
  63. this.currency = props.currency.toUpperCase() // ensure that currency codes are always uppercase
  64. this.total = props.total
  65. this.periodStart = props.periodStart
  66. this.periodEnd = props.periodEnd
  67. this.collectionMethod = props.collectionMethod
  68. this.netTerms = props.netTerms ?? 0
  69. this.poNumber = props.poNumber ?? ''
  70. this.termsAndConditions = props.termsAndConditions ?? ''
  71. this.pendingChange = props.pendingChange ?? null
  72. this.service = props.service ?? 'recurly'
  73. this.state = props.state ?? 'active'
  74. this.trialPeriodStart = props.trialPeriodStart ?? null
  75. this.trialPeriodEnd = props.trialPeriodEnd ?? null
  76. this.pausePeriodStart = props.pausePeriodStart ?? null
  77. this.pausePeriodEnd = props.pausePeriodEnd ?? null
  78. this.remainingPauseCycles = props.remainingPauseCycles ?? null
  79. this.isMigratedFromRecurly = props.isMigratedFromRecurly ?? false
  80. }
  81. /**
  82. * Returns whether this subscription currently has the given add-on
  83. *
  84. * @param {string} code
  85. * @return {boolean}
  86. */
  87. hasAddOn(code) {
  88. return this.addOns.some(addOn => addOn.code === code)
  89. }
  90. /**
  91. * Returns whether this subscription is a standalone AI add-on subscription
  92. *
  93. * @return {boolean}
  94. */
  95. isStandaloneAiAddOn() {
  96. return isStandaloneAiAddOnPlanCode(this.planCode)
  97. }
  98. /**
  99. * Returns whether this subscription is a group subscription
  100. *
  101. * @return {boolean}
  102. */
  103. isGroupSubscription() {
  104. return PlansLocator.isGroupPlanCode(this.planCode)
  105. }
  106. /**
  107. * Returns whether this subcription will have the given add-on next billing
  108. * period.
  109. *
  110. * There are two cases: either the subscription already has the add-on and
  111. * won't change next period, or the subscription will change next period and
  112. * the change includes the add-on.
  113. *
  114. * @param {string} code
  115. * @return {boolean}
  116. */
  117. hasAddOnNextPeriod(code) {
  118. if (this.pendingChange != null) {
  119. return this.pendingChange.nextAddOns.some(addOn => addOn.code === code)
  120. } else {
  121. return this.hasAddOn(code)
  122. }
  123. }
  124. /**
  125. * Returns the add-ons that should be used as the base for a change request.
  126. *
  127. * For immediate changes (timeframe = 'now'), we use the current add-ons because
  128. * the user is still paying for them until term_end. Any pending term_end changes
  129. * will need to be re-applied after the immediate change by the payment provider client.
  130. *
  131. * For term_end changes, if there's already a pending change, we use its nextAddOns
  132. * as the base to merge scheduled changes together.
  133. *
  134. * @param {"now" | "term_end"} timeframe - when the change will be applied
  135. * @return {PaymentProviderSubscriptionAddOn[]}
  136. */
  137. #getBaseAddOnsForChangeRequest(timeframe) {
  138. if (timeframe === 'term_end' && this.pendingChange != null) {
  139. return this.pendingChange.nextAddOns
  140. }
  141. return this.addOns
  142. }
  143. /**
  144. * Change this subscription's plan
  145. * @param {string} planCode - the new plan code
  146. * @param {number} [quantity] - the quantity of the plan
  147. * @param {boolean} [shouldChangeAtTermEnd] - whether the change should be applied at the end of the term
  148. * @return {PaymentProviderSubscriptionChangeRequest}
  149. */
  150. getRequestForPlanChange(planCode, quantity, shouldChangeAtTermEnd) {
  151. const timeframe = shouldChangeAtTermEnd ? 'term_end' : 'now'
  152. const baseAddOns = this.#getBaseAddOnsForChangeRequest(timeframe)
  153. // Start with all existing add-ons, but filter out additional-license
  154. // since we'll handle it specially for group plans below
  155. const addOnUpdates = baseAddOns
  156. .filter(addOn => addOn.code !== MEMBERS_LIMIT_ADD_ON_CODE)
  157. .map(addOn => addOn.toAddOnUpdate())
  158. if (quantity !== 1) {
  159. // Only group plans in Stripe can have larger than 1 quantity
  160. // This is because in Stripe, the group plans are configured with per-seat pricing
  161. // and the quantity is the number of seats
  162. // Setting the members limit add-on quantity accordingly
  163. // so it is compatible with Recurly's group plan model (1 base plan + add-on for each member)
  164. addOnUpdates.push(
  165. new PaymentProviderSubscriptionAddOnUpdate({
  166. code: MEMBERS_LIMIT_ADD_ON_CODE,
  167. quantity,
  168. })
  169. )
  170. }
  171. // When upgrading from standalone AI plan, add the AI add-on since
  172. // the standalone plan doesn't have it as an add-on
  173. if (this.isStandaloneAiAddOn() && !isStandaloneAiAddOnPlanCode(planCode)) {
  174. addOnUpdates.push(
  175. new PaymentProviderSubscriptionAddOnUpdate({
  176. code: AI_ADD_ON_CODE,
  177. quantity: 1,
  178. })
  179. )
  180. }
  181. const changeRequest = new PaymentProviderSubscriptionChangeRequest({
  182. subscription: this,
  183. timeframe,
  184. planCode,
  185. addOnUpdates,
  186. })
  187. return changeRequest
  188. }
  189. /**
  190. * Purchase an add-on on this subscription
  191. *
  192. * @param {string} code
  193. * @param {number} [quantity]
  194. * @param {number} [unitPrice]
  195. * @return {PaymentProviderSubscriptionChangeRequest} - the change request to send to
  196. * Recurly
  197. *
  198. * @throws {DuplicateAddOnError} if the add-on is already present on the subscription
  199. */
  200. getRequestForAddOnPurchase(code, quantity = 1, unitPrice) {
  201. if (this.hasAddOn(code)) {
  202. throw new DuplicateAddOnError('Subscription already has add-on', {
  203. subscriptionId: this.id,
  204. addOnCode: code,
  205. })
  206. }
  207. const baseAddOns = this.#getBaseAddOnsForChangeRequest('now')
  208. const addOnUpdates = baseAddOns.map(addOn => addOn.toAddOnUpdate())
  209. addOnUpdates.push(
  210. new PaymentProviderSubscriptionAddOnUpdate({ code, quantity, unitPrice })
  211. )
  212. return new PaymentProviderSubscriptionChangeRequest({
  213. subscription: this,
  214. timeframe: 'now',
  215. addOnUpdates,
  216. })
  217. }
  218. /**
  219. * Update an add-on on this subscription
  220. *
  221. * @param {string} code
  222. * @param {number} quantity
  223. * @return {PaymentProviderSubscriptionChangeRequest} - the change request to send to
  224. * Recurly
  225. *
  226. * @throws {AddOnNotPresentError} if the subscription doesn't have the add-on
  227. */
  228. getRequestForAddOnUpdate(code, quantity) {
  229. if (!this.hasAddOn(code)) {
  230. throw new AddOnNotPresentError(
  231. 'Subscription does not have add-on to update',
  232. {
  233. subscriptionId: this.id,
  234. addOnCode: code,
  235. }
  236. )
  237. }
  238. const baseAddOns = this.#getBaseAddOnsForChangeRequest('now')
  239. const addOnUpdates = baseAddOns.map(addOn => {
  240. const update = addOn.toAddOnUpdate()
  241. if (update.code === code) {
  242. update.quantity = quantity
  243. }
  244. return update
  245. })
  246. return new PaymentProviderSubscriptionChangeRequest({
  247. subscription: this,
  248. timeframe: 'now',
  249. addOnUpdates,
  250. })
  251. }
  252. /**
  253. * Remove an add-on from this subscription
  254. *
  255. * @param {string} code
  256. * @return {PaymentProviderSubscriptionChangeRequest}
  257. *
  258. * @throws {AddOnNotPresentError} if the subscription doesn't have the add-on
  259. */
  260. getRequestForAddOnRemoval(code) {
  261. if (!this.hasAddOn(code)) {
  262. throw new AddOnNotPresentError(
  263. 'Subscription does not have add-on to remove',
  264. {
  265. subscriptionId: this.id,
  266. addOnCode: code,
  267. }
  268. )
  269. }
  270. const isInTrial = SubscriptionHelper.isInTrial(this.trialPeriodEnd)
  271. const timeframe = isInTrial ? 'now' : 'term_end'
  272. const baseAddOns = this.#getBaseAddOnsForChangeRequest(timeframe)
  273. const addOnUpdates = baseAddOns
  274. .filter(addOn => addOn.code !== code)
  275. .map(addOn => addOn.toAddOnUpdate())
  276. // preserve pending plan change when scheduling add-on removal at term_end
  277. const planCode =
  278. timeframe === 'term_end' ? this.pendingChange?.nextPlanCode : undefined
  279. return new PaymentProviderSubscriptionChangeRequest({
  280. subscription: this,
  281. timeframe,
  282. planCode,
  283. addOnUpdates,
  284. })
  285. }
  286. /**
  287. * Reactivate an add-on on this subscription
  288. *
  289. * @param {string} code - add-on code
  290. * @return {PaymentProviderSubscriptionChangeRequest}
  291. *
  292. * @throws {AddOnNotPresentError} if the add-on is not pending cancellation
  293. */
  294. getRequestForAddOnReactivation(code) {
  295. const reactivatedAddOn = this.addOns.find(addOn => addOn.code === code)
  296. const pendingChange = this.pendingChange
  297. if (reactivatedAddOn == null || pendingChange == null) {
  298. throw new AddOnNotPresentError('Add-on is not pending cancellation', {
  299. subscriptionId: this.id,
  300. addOnCode: code,
  301. })
  302. }
  303. const addOnUpdates = pendingChange.nextAddOns
  304. .filter(addOn => addOn.code !== code)
  305. .map(addOn => addOn.toAddOnUpdate())
  306. addOnUpdates.push(reactivatedAddOn.toAddOnUpdate())
  307. // preserve pending plan change when reactivating add-on
  308. const planCode = pendingChange.nextPlanCode
  309. return new PaymentProviderSubscriptionChangeRequest({
  310. subscription: this,
  311. timeframe: 'term_end',
  312. planCode,
  313. addOnUpdates,
  314. })
  315. }
  316. /**
  317. * Cancel only the pending plan change while preserving any pending add-on changes
  318. *
  319. * This is used when a user wants to "keep their current plan" but has also
  320. * scheduled add-on changes (additions or removals) that should still happen.
  321. *
  322. * @return {PaymentProviderSubscriptionChangeRequest | null}
  323. */
  324. getRequestForPlanChangeCancellation() {
  325. const pendingChange = this.pendingChange
  326. if (pendingChange == null) {
  327. return null
  328. }
  329. const currentAddOnCodes = this.addOns.map(a => a.code)
  330. const pendingAddOnCodes = pendingChange.nextAddOns.map(a => a.code)
  331. const hasAddOnChanges =
  332. currentAddOnCodes.length !== pendingAddOnCodes.length ||
  333. !currentAddOnCodes.every(code => pendingAddOnCodes.includes(code))
  334. if (!hasAddOnChanges) {
  335. return null
  336. }
  337. const addOnUpdates = pendingChange.nextAddOns
  338. .filter(addOn => {
  339. if (
  340. addOn.code === MEMBERS_LIMIT_ADD_ON_CODE &&
  341. !this.isGroupSubscription() &&
  342. PlansLocator.isGroupPlanCode(pendingChange.nextPlanCode)
  343. ) {
  344. // if there was a pending group plan upgrade, removing the plan change
  345. // means we also need to remove the members limit add-on
  346. return false
  347. }
  348. return true
  349. })
  350. .map(addOn => addOn.toAddOnUpdate())
  351. return new PaymentProviderSubscriptionChangeRequest({
  352. subscription: this,
  353. timeframe: 'term_end',
  354. planCode: this.planCode,
  355. addOnUpdates,
  356. })
  357. }
  358. /**
  359. * Upgrade group plan with the plan code provided
  360. *
  361. * @param {string} newPlanCode
  362. * @return {PaymentProviderSubscriptionChangeRequest}
  363. */
  364. getRequestForGroupPlanUpgrade(newPlanCode) {
  365. const baseAddOns = this.#getBaseAddOnsForChangeRequest('now')
  366. const addOns = baseAddOns.map(
  367. addOn =>
  368. new PaymentProviderSubscriptionAddOnUpdate({
  369. code: addOn.code,
  370. quantity: addOn.quantity,
  371. })
  372. )
  373. return new PaymentProviderSubscriptionChangeRequest({
  374. subscription: this,
  375. timeframe: 'now',
  376. addOnUpdates: addOns,
  377. planCode: newPlanCode,
  378. })
  379. }
  380. /**
  381. * Update the "PO number" and "Terms and conditions" in a subscription
  382. *
  383. * @param {string} poNumber
  384. * @param {string} termsAndConditions
  385. * @return {PaymentProviderSubscriptionUpdateRequest} - the update request to send to
  386. * Recurly
  387. */
  388. getRequestForPoNumberAndTermsAndConditionsUpdate(
  389. poNumber,
  390. termsAndConditions
  391. ) {
  392. return new PaymentProviderSubscriptionUpdateRequest({
  393. subscription: this,
  394. poNumber,
  395. termsAndConditions,
  396. })
  397. }
  398. /**
  399. * Update the "Terms and conditions" in a subscription
  400. *
  401. * @param {string} termsAndConditions
  402. * @return {PaymentProviderSubscriptionUpdateRequest} - the update request to send to
  403. * Recurly
  404. */
  405. getRequestForTermsAndConditionsUpdate(termsAndConditions) {
  406. return new PaymentProviderSubscriptionUpdateRequest({
  407. subscription: this,
  408. termsAndConditions,
  409. })
  410. }
  411. /**
  412. * Returns whether this subscription is manually collected
  413. *
  414. * @return {boolean}
  415. */
  416. get isCollectionMethodManual() {
  417. return this.collectionMethod === 'manual'
  418. }
  419. /**
  420. * Determine if a plan change should be applied at the end of the term
  421. *
  422. * @param {string} newPlanCode
  423. * @returns {boolean}
  424. */
  425. shouldPlanChangeAtTermEnd(newPlanCode) {
  426. const currentPlan = PlansLocator.findLocalPlanInSettings(this.planCode)
  427. if (currentPlan == null) {
  428. throw new OError('Unable to find plan in settings', {
  429. planCode: this.planCode,
  430. })
  431. }
  432. const newPlan = PlansLocator.findLocalPlanInSettings(newPlanCode)
  433. if (newPlan == null) {
  434. throw new OError('Unable to find plan in settings', { newPlanCode })
  435. }
  436. const isInTrial = SubscriptionHelper.isInTrial(this.trialPeriodEnd)
  437. return SubscriptionHelper.shouldPlanChangeAtTermEnd(
  438. currentPlan,
  439. newPlan,
  440. isInTrial
  441. )
  442. }
  443. }
  444. /**
  445. * An add-on attached to a subscription
  446. */
  447. export class PaymentProviderSubscriptionAddOn {
  448. /**
  449. * @param {object} props
  450. * @param {string} props.code
  451. * @param {string} props.name
  452. * @param {number} props.quantity
  453. * @param {number} props.unitPrice
  454. */
  455. constructor(props) {
  456. this.code = props.code
  457. this.name = props.name
  458. this.quantity = props.quantity
  459. this.unitPrice = props.unitPrice
  460. this.preTaxTotal = this.quantity * this.unitPrice
  461. }
  462. /**
  463. * Return an add-on update that doesn't modify the add-on
  464. */
  465. toAddOnUpdate() {
  466. return new PaymentProviderSubscriptionAddOnUpdate({
  467. code: this.code,
  468. quantity: this.quantity,
  469. unitPrice: this.unitPrice,
  470. })
  471. }
  472. }
  473. export class PaymentProviderSubscriptionUpdateRequest {
  474. /**
  475. * @param {object} props
  476. * @param {PaymentProviderSubscription} props.subscription
  477. * @param {string} [props.poNumber]
  478. * @param {string} [props.termsAndConditions]
  479. */
  480. constructor(props) {
  481. this.subscription = props.subscription
  482. this.poNumber = props.poNumber ?? ''
  483. this.termsAndConditions = props.termsAndConditions ?? ''
  484. }
  485. }
  486. export class PaymentProviderSubscriptionChangeRequest {
  487. /**
  488. * @param {object} props
  489. * @param {PaymentProviderSubscription} props.subscription
  490. * @param {"now" | "term_end"} props.timeframe
  491. * @param {string} [props.planCode]
  492. * @param {PaymentProviderSubscriptionAddOnUpdate[]} [props.addOnUpdates]
  493. */
  494. constructor(props) {
  495. if (props.planCode == null && props.addOnUpdates == null) {
  496. throw new OError('Invalid PaymentProviderSubscriptionChangeRequest', {
  497. props,
  498. })
  499. }
  500. this.subscription = props.subscription
  501. this.timeframe = props.timeframe
  502. this.planCode = props.planCode ?? null
  503. this.addOnUpdates = props.addOnUpdates ?? null
  504. }
  505. }
  506. export class PaymentProviderSubscriptionAddOnUpdate {
  507. /**
  508. * @param {object} props
  509. * @param {string} props.code
  510. * @param {number} [props.quantity]
  511. * @param {number} [props.unitPrice]
  512. */
  513. constructor(props) {
  514. this.code = props.code
  515. this.quantity = props.quantity
  516. this.unitPrice = props.unitPrice ?? null
  517. }
  518. }
  519. export class PaymentProviderSubscriptionChange {
  520. /**
  521. * @param {object} props
  522. * @param {PaymentProviderSubscription} props.subscription
  523. * @param {string} props.nextPlanCode
  524. * @param {string} props.nextPlanName
  525. * @param {number} props.nextPlanPrice
  526. * @param {PaymentProviderSubscriptionAddOn[]} props.nextAddOns
  527. * @param {PaymentProviderImmediateCharge} [props.immediateCharge]
  528. */
  529. constructor(props) {
  530. this.subscription = props.subscription
  531. this.nextPlanCode = props.nextPlanCode
  532. this.nextPlanName = props.nextPlanName
  533. this.nextPlanPrice = props.nextPlanPrice
  534. this.nextAddOns = props.nextAddOns
  535. this.immediateCharge =
  536. props.immediateCharge ??
  537. new PaymentProviderImmediateCharge({
  538. subtotal: 0,
  539. tax: 0,
  540. total: 0,
  541. discount: 0,
  542. })
  543. this.subtotal = this.nextPlanPrice
  544. for (const addOn of this.nextAddOns) {
  545. this.subtotal += addOn.preTaxTotal
  546. }
  547. this.tax = Math.round(this.subtotal * 100 * this.subscription.taxRate) / 100
  548. this.total = this.subtotal + this.tax
  549. }
  550. getAddOn(addOnCode) {
  551. return this.nextAddOns.find(addOn => addOn.code === addOnCode)
  552. }
  553. }
  554. export class PaypalPaymentMethod {
  555. toString() {
  556. return 'Paypal'
  557. }
  558. }
  559. export class CreditCardPaymentMethod {
  560. /**
  561. * @param {object} props
  562. * @param {string} props.cardType
  563. * @param {string} props.lastFour
  564. */
  565. constructor(props) {
  566. this.cardType = props.cardType
  567. this.lastFour = props.lastFour
  568. }
  569. toString() {
  570. return `${this.cardType} **** ${this.lastFour}`
  571. }
  572. }
  573. export class PaymentProviderImmediateCharge {
  574. /**
  575. * @param {object} props
  576. * @param {number} props.subtotal
  577. * @param {number} props.tax
  578. * @param {number} props.total
  579. * @param {number} props.discount
  580. * @param {ImmediateChargeLineItem[]} [props.lineItems]
  581. */
  582. constructor(props) {
  583. this.subtotal = props.subtotal
  584. this.tax = props.tax
  585. this.total = props.total
  586. this.discount = props.discount
  587. this.lineItems = props.lineItems ?? []
  588. }
  589. }
  590. /**
  591. * An add-on configuration, independent of any subscription
  592. */
  593. export class PaymentProviderAddOn {
  594. /**
  595. * @param {object} props
  596. * @param {string} props.code
  597. * @param {string} props.name
  598. */
  599. constructor(props) {
  600. this.code = props.code
  601. this.name = props.name
  602. }
  603. }
  604. /**
  605. * A plan configuration
  606. */
  607. export class PaymentProviderPlan {
  608. /**
  609. * @param {object} props
  610. * @param {string} props.code
  611. * @param {string} props.name
  612. */
  613. constructor(props) {
  614. this.code = props.code
  615. this.name = props.name
  616. }
  617. }
  618. /**
  619. * A coupon in the payment provider
  620. */
  621. export class PaymentProviderCoupon {
  622. /**
  623. * @param {object} props
  624. * @param {string} props.code
  625. * @param {string} props.name
  626. * @param {string} [props.description]
  627. * @param {boolean} [props.isSingleUse]
  628. * @param {number | null} [props.discountMonths]
  629. */
  630. constructor(props) {
  631. this.code = props.code
  632. this.name = props.name
  633. this.description = props.description
  634. this.isSingleUse = props.isSingleUse
  635. this.discountMonths = props.discountMonths
  636. }
  637. }
  638. /**
  639. * An account in the payment provider
  640. */
  641. export class PaymentProviderAccount {
  642. /**
  643. * @param {object} props
  644. * @param {string} props.code
  645. * @param {string} props.email
  646. * @param {boolean} [props.hasPastDueInvoice]
  647. * @param {object} [props.metadata]
  648. * @param {string} [props.metadata.userId]
  649. * @param {string} [props.metadata.segment]
  650. */
  651. constructor(props) {
  652. this.code = props.code
  653. this.email = props.email
  654. this.hasPastDueInvoice = props.hasPastDueInvoice ?? false
  655. this.metadata = props.metadata ?? {}
  656. }
  657. }
  658. export default {
  659. MEMBERS_LIMIT_ADD_ON_CODE,
  660. PaymentProviderSubscription,
  661. PaymentProviderSubscriptionAddOn,
  662. PaymentProviderSubscriptionChange,
  663. PaymentProviderSubscriptionChangeRequest,
  664. PaymentProviderSubscriptionUpdateRequest,
  665. PaymentProviderSubscriptionAddOnUpdate,
  666. PaypalPaymentMethod,
  667. CreditCardPaymentMethod,
  668. PaymentProviderAddOn,
  669. PaymentProviderPlan,
  670. PaymentProviderCoupon,
  671. PaymentProviderAccount,
  672. PaymentProviderImmediateCharge,
  673. }