PaymentProviderEntities.js 19 KB

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