Guest User

Untitled

a guest
Sep 25th, 2018
83
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 112.40 KB | None | 0 0
  1. package com.klipfolio.saas.webui
  2.  
  3. import com.google.common.cache.CacheBuilder
  4. import com.google.common.cache.CacheLoader
  5. import com.google.common.cache.LoadingCache
  6. import com.klipfolio.logs.LogBuilder
  7. import com.klipfolio.payment.*
  8. import com.klipfolio.saas.AccessDeniedException
  9. import com.klipfolio.saas.DomainComparator
  10. import com.klipfolio.saas.api.ApiBadRequestException
  11. import com.klipfolio.saas.domain.*
  12. import com.klipfolio.saas.service.LimitService
  13. import com.klipfolio.saas.service.RefreshLogService
  14. import com.klipfolio.saas.service.staleaccounts.StaleAccountService
  15. import com.klipfolio.saas.service.staleaccounts.StaleAccountServiceMutexException
  16. import com.klipfolio.saas.util.CreditCardUtil
  17. import com.klipfolio.saas.webui.helpers.StaleAccountParamsValidator
  18. import com.klipfolio.saas.webui.helpers.StaleAccountServiceException
  19. import com.klipfolio.zuora.*
  20. import com.klipfolio.zuora.internals.RatePlanImpl
  21. import com.mongodb.BasicDBObject
  22. import com.mongodb.MongoClient
  23. import flexjson.JSONDeserializer
  24. import flexjson.JSONSerializer
  25. import grails.converters.JSON
  26. import org.codehaus.groovy.grails.commons.ConfigurationHolder
  27. import org.codehaus.groovy.grails.web.json.JSONElement
  28. import org.joda.time.DateTime
  29. import org.joda.time.format.DateTimeFormat
  30. import org.joda.time.format.DateTimeFormatter
  31.  
  32. import javax.servlet.http.HttpServletResponse
  33. import java.text.SimpleDateFormat
  34. import java.util.concurrent.TimeUnit
  35.  
  36. class CompanyController extends DefaultSaasController {
  37.  
  38. def companyService
  39. def dataRefreshService
  40. def eventLogService
  41. def limitService
  42. def mixPanelService
  43. def roleService
  44. def staleAccountService
  45. def testingService
  46. def userService
  47. def zuora2Service
  48. def brandService
  49. def klipTemplateService
  50. def defaultKlipsService
  51. def templateServiceProviderPersistenceService
  52. def companyPersistenceService
  53. def userPersistenceService
  54. def datasourceService
  55. def registeredDatasourcePersistenceService
  56.  
  57. MongoClient mongoPool
  58.  
  59. static private LoadingCache<String, String> htmlCache = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(new CacheLoader<String, String>() {
  60. @Override
  61. String load(String s) throws Exception
  62. {
  63. return s.toURL().getText("utf-8");
  64. }
  65. })
  66.  
  67. def index = {
  68.  
  69. roleService.checkSessionPermission(session.user, "account.settings")
  70.  
  71. def user = userForSession()
  72. def company = user.company
  73.  
  74. def model = [:]
  75. model.user = user
  76. model.company = company
  77.  
  78. model.isDirectBilled = companyService.isDirectBilled(company)
  79. model.isOemAdmin = user.isOemAdmin()
  80. model.isMaster = userService.isMasterAccountMember(user)
  81. model.isWhiteLabel = company.getFeature("whitelabel") || company.partnerParent?.getFeature("whitelabel")
  82.  
  83. Account account = null
  84. try {
  85. account = companyService.getPaymentAccount(company)
  86. } catch (ZuoraSystemDisabledException e) {
  87. model.paymentSystemDisabled = true
  88. model.dynamicPaymentSystemInfo = e.info
  89. }
  90.  
  91. if (account != null) {
  92. model.isPlanCancelled = account.subscription.isCancelled()
  93.  
  94. if (!model.isPlanCancelled) {
  95. if (account.exists()) {
  96. model.address = account.soldTo
  97. }
  98.  
  99. // needed for the side nav
  100. model.cardWarning = getCardWarning(account)
  101. } else {
  102. model.cancelledDate = company.subscriptionExpires
  103. }
  104. }
  105.  
  106. render(view: "company.general", model: model)
  107. }
  108.  
  109. def info_edit = { CompanyInfoCommand infoCmd, AddressCommand addressCmd ->
  110.  
  111. roleService.checkSessionPermission(session.user, "account.settings")
  112.  
  113. def user = userForSession(true)
  114. def company = user.company
  115.  
  116. def model = [:]
  117. model.user = user
  118. model.company = company
  119.  
  120. model.isDirectBilled = companyService.isDirectBilled(company)
  121. model.isOemAdmin = user.isOemAdmin()
  122.  
  123. Account account = null
  124. try {
  125. account = companyService.getPaymentAccount(company)
  126. } catch (ZuoraSystemDisabledException e) {
  127. model.paymentSystemDisabled = true
  128. model.dynamicPaymentSystemInfo = e.info
  129. }
  130.  
  131. if (account != null) {
  132. model.isPlanCancelled = account.subscription.isCancelled()
  133.  
  134. if (!model.isPlanCancelled) {
  135. // needed for the side nav
  136. model.cardWarning = getCardWarning(account)
  137. } else {
  138. model.cancelledDate = company.subscriptionExpires
  139. }
  140. }
  141.  
  142. infoCmd.clearErrors()
  143. addressCmd.clearErrors()
  144. if (params.save_company_info)
  145. {
  146. def infoValid = infoCmd.validate()
  147. def addressValid = addressCmd.validate()
  148.  
  149. if (!params.street && !params.city && !params.postalCode && !params.country) {
  150. addressValid = true
  151. addressCmd.clearErrors()
  152. }
  153.  
  154. if (infoValid && (model.paymentSystemDisabled || model.isPlanCancelled || addressValid))
  155. {
  156. infoCmd.applyTo(company)
  157.  
  158. def primaryUser = User.findByPublicId(params.primaryContact)
  159. if (primaryUser) company.primaryContact = primaryUser
  160.  
  161. def businessUser = User.findByPublicId(params.businessContact)
  162. if (businessUser) company.businessContact = businessUser
  163.  
  164. if (!model.paymentSystemDisabled && !model.isPlanCancelled) {
  165.  
  166. account.setSoldTo(company.primaryContact.firstName, company.primaryContact.lastName, addressCmd.street, null, addressCmd.city, addressCmd.state, addressCmd.country, addressCmd.postalCode, company.primaryContact.email)
  167. }
  168.  
  169. company.save(flush: true)
  170. if(company.isPartner) {
  171. companyService.updateClientSuperUsers(company)
  172. }
  173.  
  174. eventLogService.info("company","company_info_edited","Success",company,userForSession())
  175. flash.status = [msg: "${infoCmd.name.encodeAsHTML()} updated."]
  176. session.jsEnv = userService.getJsEnvInfo(company, user)
  177. redirect(action: "index")
  178. }
  179. else
  180. {
  181. model.infoCmd = infoCmd
  182. model.addressCmd = addressCmd
  183. }
  184. } else {
  185. infoCmd.bindFrom(user.company)
  186. model.infoCmd = infoCmd
  187.  
  188. if (!model.paymentSystemDisabled && !model.isPlanCancelled) {
  189. // TODO need handling like customerService.getCompanyAddress()?
  190. if (account.soldTo) {
  191. addressCmd.bindFrom(account.soldTo)
  192. }
  193. model.addressCmd = addressCmd
  194. }
  195. }
  196.  
  197. model.primaryContactId = params.primaryContact ? params.primaryContact : (company.primaryContact ? company.primaryContact.publicId : 0)
  198.  
  199. def bizContact = (company.businessContact ? company.businessContact : company.primaryContact)
  200. model.businessContactId = params.businessContact ? params.businessContact : (bizContact ? bizContact.publicId : 0)
  201.  
  202. User superAdmin = User.findByPublicId(user.company.getSettingValue('oem.superuserid', 'false'))
  203.  
  204. model.primaryUserList = []
  205. def allAdminUsers = roleService.getAdminUsers(user.company)
  206.  
  207. // Remove the partner super user from the list of users as they should not be able to be set as a primary contact
  208. if (superAdmin) {
  209. allAdminUsers = allAdminUsers.findAll { it.publicId != superAdmin.publicId }
  210. }
  211.  
  212. allAdminUsers.each {
  213. // Get all the admin users that belong to the company
  214. model.primaryUserList.add([key: it.publicId, lastName: it.lastName.encodeAsJavaScript(), firstName: it.firstName.encodeAsJavaScript()])
  215. }
  216. model.primaryUserList.sort({it.lastName})
  217.  
  218. model.businessUserList = []
  219. def allUsers = userService.getAllUsers(user.company)
  220.  
  221. // Remove the partner super user from the list of users as they should not be able to be set as a primary contact
  222. if (superAdmin) {
  223. allUsers = allUsers.findAll { it.user.publicId != superAdmin.publicId }
  224. }
  225.  
  226. allUsers.each {
  227. // Get all users that belong to the company
  228. model.businessUserList.add([key: it.user.publicId, lastName: it.user.lastName.encodeAsJavaScript(), firstName: it.user.firstName.encodeAsJavaScript()])
  229. }
  230. model.businessUserList.sort({it.lastName})
  231.  
  232. render(view: "company.general.edit", model: model)
  233. }
  234.  
  235. def buyNow = {
  236.  
  237. roleService.checkSessionPermission(session.user, "account.settings")
  238.  
  239. def model = [:]
  240. def user = userForSession(true)
  241. def company = user.company
  242.  
  243. model.user = user
  244. model.company = company
  245.  
  246. Account account = null
  247. try {
  248. account = companyService.getPaymentAccount(company)
  249. } catch (ZuoraSystemDisabledException e) {
  250. //this just means there has been no account made yet
  251. }
  252.  
  253. if (company.type != Company.TYPE_TRIAL && (!account.exists() || !account.isFreemium())) throw new AccessDeniedException()
  254.  
  255. // needed for the side nav
  256. model.isDirectBilled = companyService.isDirectBilled(company)
  257. model.isOemAdmin = user.isOemAdmin()
  258. if (!model.isDirectBilled || model.isOemAdmin) throw new AccessDeniedException()
  259.  
  260. // if disabled, company can't buy right now
  261. model.paymentSystemDisabled = !companyService.isPaymentSystemEnabled()
  262.  
  263. if (model.paymentSystemDisabled) {
  264. model.dynamicPaymentSystemInfo = companyService.dynamicPaymentSystemInfo()
  265. }
  266.  
  267. // company must agreement to the partner agreement before they buy
  268. model.showPartnerAgreement = company.isPartner
  269.  
  270. // company might be able to extend their trial
  271. if (userService.isTrialExtendable(model.company) &&
  272. (model.company.state == Company.STATE_EXPIRED ||
  273. (model.company.state == Company.STATE_TRIAL && model.company.trialExpires && (model.company.trialExpires - 1) < new Date()))) {
  274. model.extend = true
  275. }
  276.  
  277. eventLogService.info("company","company_buy_now_viewed","Buy Now!",company,user)
  278. render(view: "company.buy_now", model: model)
  279. }
  280.  
  281. def plan_edit = {
  282.  
  283. roleService.checkSessionPermission(session.user, "account.settings")
  284.  
  285. def user = userForSession(true)
  286. def company = user.company
  287.  
  288. def model = [:]
  289. model.user = user
  290. model.company = company
  291.  
  292. if (company.type == Company.TYPE_TRIAL) throw new AccessDeniedException()
  293.  
  294. // needed for the side nav
  295. model.isDirectBilled = companyService.isDirectBilled(company)
  296. model.isOemAdmin = user.isOemAdmin()
  297. if (!model.isDirectBilled || model.isOemAdmin) throw new AccessDeniedException()
  298.  
  299. Account account = null
  300. try {
  301. account = companyService.getPaymentAccount(company)
  302. } catch (ZuoraSystemDisabledException e) {
  303. model.paymentSystemDisabled = true
  304. model.dynamicPaymentSystemInfo = e.info
  305. }
  306.  
  307. if (account != null) {
  308. model.isPlanCancelled = account.subscription.isCancelled()
  309.  
  310. //we don't have credit card info and account is freemium
  311. if(!account.hasCreditCardPaymentMethod() && account.isFreemium()){
  312. redirect(action: "buyNow")
  313. }
  314.  
  315. if (model.isPlanCancelled) {
  316. model.cancelledDate = company.subscriptionExpires
  317. } else {
  318. // needed for the side nav
  319. model.cardWarning = getCardWarning(account)
  320.  
  321. if (params.update_plan)
  322. {
  323. Map planDelta = (Map) new JSONDeserializer().deserialize(params.plan)
  324.  
  325. PricingModelModification modifications = getSanitizedPricingModelModifications(planDelta.planId, planDelta.addons, planDelta.topups)
  326.  
  327. modifyAccountAndSubscription(company, account, null, null, null, modifications, null)
  328. AmendResult result = account.subscription.amendSubscription(new Date(), false, false)
  329.  
  330. companyService.modifyCompanyEntitlementsToMatchSubscription(company, account)
  331.  
  332. session.user.hasNinjaServices = company.hasFeature(Feature.NINJA)
  333. session.jsEnv = userService.getJsEnvInfo(company, user)
  334.  
  335. clearPartnerProperties(company)
  336.  
  337. eventLogService.info("billing","payment_plan_edited","Success",company,user)
  338. mixPanelService.track( user , "Plan Updated", session.mixPanelEnabled )
  339. flash.status = [msg: "Payment plan updated."]
  340.  
  341. changeCompanyDsRefreshRateToMax(account, company, user);
  342. redirect(action: "billing")
  343. return
  344. }
  345. else
  346. {
  347. def confirmPartner = company.getSettingValue(Company.PROPERTY_CONFIRM_PARTNER, null)
  348. model.showPartnerAgreement = company.isPartner && (confirmPartner != null)
  349. model.buyClientManagement = companyService.needToBuyClientManagement(company)
  350. }
  351. }
  352. }
  353.  
  354. render(view: "company.plan.edit", model:model)
  355. }
  356.  
  357. def billing = {
  358.  
  359. roleService.checkSessionPermission(session.user, "account.settings")
  360.  
  361. def user = userForSession(true)
  362. def company = user.company
  363.  
  364. def model = [:]
  365. model.user = user
  366. model.company = company
  367.  
  368. if (company.type == Company.TYPE_TRIAL) throw new AccessDeniedException()
  369.  
  370. // needed for the side nav
  371. model.isDirectBilled = companyService.isDirectBilled(user.company)
  372. model.isOemAdmin = user.isOemAdmin()
  373. if (!model.isDirectBilled || model.isOemAdmin) throw new AccessDeniedException()
  374.  
  375. Account account = null
  376. try {
  377. account = companyService.getPaymentAccount(company)
  378. } catch (ZuoraSystemDisabledException e) {
  379. model.paymentSystemDisabled = true
  380. model.dynamicPaymentSystemInfo = e.info
  381. }
  382.  
  383. if (account != null) {
  384. model.isPlanCancelled = account.subscription.isCancelled()
  385. if (model.isPlanCancelled) {
  386. model.cancelledDate = company.subscriptionExpires
  387. } else {
  388. // needed for the side nav and page
  389. model.cardWarning = getCardWarning(account)
  390.  
  391. // needed to display plan details
  392. Invoice invoice = account.subscription.previewAmendSubscription(new Date()).invoice
  393. model.invoiceItems = invoice.nextPeriodSummary
  394. model.invoiceAmount = invoice.nextPeriodAmount
  395. model.invoiceAmountWithoutTax = invoice.nextPeriodAmountWithoutTax
  396. model.invoiceTaxAmount = invoice.nextPeriodTaxAmount
  397.  
  398. model.periodLabels = getPeriodLabels()
  399. model.period = account.subscription.billingPeriod()
  400. model.hasInvoicing = !account.getPaymentMethod().autoPay()
  401. model.billTo = account.billTo
  402. model.paymentMethod = account.paymentMethod
  403. model.purchaseOrderNumber = account.purchaseOrderNumber
  404. if (account.hasCreditCardPaymentMethod()) {
  405. model.cardTypeText = CreditCardUtil.cardNameFromType(account.paymentMethod.creditCardType)
  406. }
  407. }
  408. }
  409.  
  410. render(view: "company.billing", model: model)
  411. }
  412.  
  413. def billing_edit = { BillingCommand billingCmd ->
  414.  
  415. roleService.checkSessionPermission(session.user, "account.settings")
  416.  
  417. def user = userForSession(true)
  418. def company = user.company
  419.  
  420. def model = [:]
  421. model.user = user
  422. model.company = company
  423.  
  424. if (company.type == Company.TYPE_TRIAL) throw new AccessDeniedException()
  425.  
  426. // needed for the side nav
  427. model.isDirectBilled = companyService.isDirectBilled(company)
  428. model.isOemAdmin = user.isOemAdmin()
  429. if (!model.isDirectBilled || model.isOemAdmin) throw new AccessDeniedException()
  430.  
  431. Account account = null
  432. try {
  433. account = companyService.getPaymentAccount(company)
  434. } catch (ZuoraSystemDisabledException e) {
  435. model.paymentSystemDisabled = true
  436. model.dynamicPaymentSystemInfo = e.info
  437. }
  438.  
  439. if (account != null) {
  440. if (account.subscription.isCancelled()) throw new AccessDeniedException()
  441.  
  442. // needed for the side nav
  443. model.cardWarning = getCardWarning(account)
  444.  
  445. model.hasInvoicing = !account.paymentMethod.autoPay()
  446. billingCmd.hasInvoicing = model.hasInvoicing
  447. }
  448.  
  449. billingCmd.clearErrors()
  450. if (params.save_billing && !model.paymentSystemDisabled)
  451. {
  452. if (billingCmd.cardNumber)
  453. {
  454. def cardInfo = CreditCardUtil.cardInfoFromNumber(billingCmd.cardNumber)
  455. billingCmd.cardType = cardInfo.type
  456. }
  457.  
  458. if (billingCmd.validate())
  459. {
  460. Boolean error = false;
  461. try {
  462. account.setBillTo(
  463. billingCmd.firstName,
  464. billingCmd.lastName,
  465. billingCmd.street,
  466. null,
  467. billingCmd.city,
  468. billingCmd.state,
  469. billingCmd.country,
  470. billingCmd.postalCode,
  471. account.billTo?.workEmail
  472. )
  473. if (billingCmd.hasInvoicing)
  474. {
  475. account.setChequePaymentMethod()
  476. }
  477. else
  478. {
  479. account.setCreditCardPaymentMethod(
  480. billingCmd.firstName + " " + billingCmd.lastName,
  481. billingCmd.cardNumber,
  482. billingCmd.cardExpiryMonth,
  483. billingCmd.cardExpiryYear,
  484. billingCmd.cvv
  485. )
  486. }
  487. } catch (ZuoraException e) {
  488. error = true;
  489. model.cardError = companyService.formatZuoraErrorMessage(e.getMessage());
  490. }
  491.  
  492. if (!billingCmd.hasErrors() && !error)
  493. {
  494. eventLogService.info("billing","billing_info_edited","Success",company,user)
  495.  
  496. if (company.state == Company.STATE_ARREARS) {
  497. companyService.changeCompanyState(company, Company.STATE_ACTIVE)
  498.  
  499. // update session info
  500. session.user.companyState = company.state
  501. session.user.companyStatus = company.stateAsString()
  502. session.jsEnv = userService.getJsEnvInfo(company, user)
  503. }
  504.  
  505. flash.status = [msg: "Billing information updated."]
  506. redirect(action: "billing")
  507. return
  508. }
  509. else {
  510. model.billingCmd = billingCmd;
  511. }
  512. }
  513. else
  514. {
  515. model.billingCmd = billingCmd
  516. }
  517. } else {
  518. if (!model.paymentSystemDisabled) {
  519. billingCmd.bindFrom(account.billTo, account.paymentMethod, account.purchaseOrderNumber)
  520. model.billingCmd = billingCmd
  521. } else {
  522. model.billingCmd = [:]
  523. }
  524.  
  525. if (company.state == Company.STATE_ARREARS) {
  526. model.billingCmd.cardNumber = ""
  527. model.billingCmd.cardType = ""
  528. model.billingCmd.cardExpiryMonth = 0
  529. model.billingCmd.cardExpiryYear = 0
  530. model.billingCmd.cvv = ""
  531. }
  532. }
  533.  
  534. model.cardTypeInfo = CreditCardUtil.getCreditCardTypeInfo()
  535.  
  536. render(view: "company.billing.edit", model: model)
  537. }
  538.  
  539. def receipts_edit = { ReceiptCommand receiptCmd ->
  540.  
  541. roleService.checkSessionPermission(session.user, "account.settings")
  542.  
  543. def user = userForSession(true)
  544. def company = user.company
  545.  
  546. def model = [:]
  547. model.user = user
  548. model.company = company
  549.  
  550. if (company.type == Company.TYPE_TRIAL) throw new AccessDeniedException()
  551.  
  552. // needed for the side nav
  553. model.isDirectBilled = companyService.isDirectBilled(company)
  554. model.isOemAdmin = user.isOemAdmin()
  555. if (!model.isDirectBilled || model.isOemAdmin) throw new AccessDeniedException()
  556.  
  557. Account account = null
  558. try {
  559. account = companyService.getPaymentAccount(company)
  560. } catch (ZuoraSystemDisabledException e) {
  561. model.paymentSystemDisabled = true
  562. model.dynamicPaymentSystemInfo = e.info
  563. }
  564.  
  565. if (account != null) {
  566. if (account.subscription.isCancelled()) throw new AccessDeniedException()
  567.  
  568. // needed for the side nav
  569. model.cardWarning = getCardWarning(account)
  570.  
  571. model.hasInvoicing = !account.paymentMethod.autoPay()
  572. }
  573.  
  574. receiptCmd.clearErrors()
  575. if (params.save_receipt && !model.paymentSystemDisabled)
  576. {
  577. if (receiptCmd.validate())
  578. {
  579. Contact billTo = account.getBillTo()
  580. account.setBillTo(
  581. billTo.firstName,
  582. billTo.lastName,
  583. billTo.address1,
  584. billTo.address2,
  585. billTo.city,
  586. billTo.state,
  587. billTo.country,
  588. billTo.postalCode,
  589. receiptCmd.email
  590. )
  591. eventLogService.info("billing","receipt_info_edited","Success",company,user)
  592. flash.status = [msg: (model.hasInvoicing ? "Invoice" : "Receipt") + " address updated."]
  593. redirect(action: "billing")
  594. }
  595. else
  596. {
  597. model.receiptCmd = receiptCmd
  598. }
  599. } else {
  600. def receiptInfo = [:]
  601.  
  602. if (!model.paymentSystemDisabled) {
  603. receiptInfo.email = account.billTo?.workEmail
  604. }
  605.  
  606. receiptCmd.bindFrom(receiptInfo)
  607. model.receiptCmd = receiptCmd
  608. }
  609.  
  610. render(view: "company.receipts.edit", model: model)
  611. }
  612.  
  613. def history = {
  614.  
  615. roleService.checkSessionPermission(session.user, "account.settings")
  616.  
  617. def user = userForSession()
  618. def company = user.company
  619.  
  620. def model = [:]
  621. model.user = user
  622. model.company = company
  623.  
  624. if (company.type == Company.TYPE_TRIAL) throw new AccessDeniedException()
  625.  
  626. model.isDirectBilled = companyService.isDirectBilled(company)
  627. model.isOemAdmin = user.isOemAdmin()
  628. if (!model.isDirectBilled || model.isOemAdmin) throw new AccessDeniedException()
  629.  
  630. Account account = null
  631. try {
  632. account = companyService.getPaymentAccount(company)
  633. } catch (ZuoraSystemDisabledException e) {
  634. model.paymentSystemDisabled = true
  635. model.dynamicPaymentSystemInfo = e.info
  636. }
  637.  
  638. if (account != null) {
  639. model.isPlanCancelled = account.subscription.isCancelled()
  640. if (model.isPlanCancelled) {
  641. model.cancelledDate = company.subscriptionExpires
  642. } else {
  643. model.nextPayment = [
  644. date: account.subscription.nextPeriodStart(new Date()),
  645. amount: account.subscription.previewAmount()
  646. ]
  647. }
  648. }
  649.  
  650. render(view: "company.history", model: model)
  651. }
  652.  
  653. def published_tabs = {
  654.  
  655. roleService.checkSessionPermission(session.user, "account.usage")
  656. roleService.checkSessionPermission(session.user, "tab.publish")
  657.  
  658. def user = userForSession()
  659. def company = user.company
  660.  
  661. def model = [:]
  662. model.company = company
  663.  
  664. model.isDirectBilled = companyService.isDirectBilled(company)
  665. model.isOemAdmin = user.isOemAdmin()
  666.  
  667. model.hasPrivateDashboards = company.hasFeature(Feature.PRIVATE_DASHBOARDS)
  668. model.hasPublicDashboards = company.hasFeature(Feature.PUBLIC_DASHBOARDS)
  669.  
  670. render(view: "company.published_tabs", model: model)
  671. }
  672.  
  673. def refreshes = {
  674.  
  675. roleService.checkSessionPermission(session.user, "account.usage")
  676. roleService.checkSessionPermission(session.user, "admin.datasource")
  677.  
  678. def user = userForSession(true)
  679. def company = user.company
  680.  
  681. def model = [:]
  682. model.user = user
  683. model.company = company
  684.  
  685. model.isDirectBilled = companyService.isDirectBilled(company)
  686. model.isOemAdmin = user.isOemAdmin()
  687.  
  688. model.hasPrivateDashboards = company.hasFeature(Feature.PRIVATE_DASHBOARDS)
  689. model.hasPublicDashboards = company.hasFeature(Feature.PUBLIC_DASHBOARDS)
  690.  
  691. Calendar cal = Calendar.getInstance()
  692. SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd")
  693. model.today = cal.getTime()
  694. String date = ""
  695. String oldestDate = ""
  696.  
  697. cal.add(Calendar.DATE,-30)
  698. String dateStr = sdf.format(cal.getTime())
  699. int dateInt = Integer.parseInt(dateStr)
  700.  
  701. model.records = mongoPool.getDatabase(RefreshLogService.MONGO_DB_NAME).getCollection("records", BasicDBObject.class).find(
  702. new BasicDBObject(["cid":model.company.id, "date":[$gte:dateInt]])).projection(
  703. new BasicDBObject(["_id":0,"date":1,"total":1])
  704. ).sort(new BasicDBObject(["date":-1])).into(new ArrayList<Map>())
  705.  
  706. if (model.records.size()) {
  707. oldestDate = model.records[model.records.size() - 1]["date"]
  708. }
  709.  
  710. cal.setTime(model.today)
  711. def map = [:]
  712. model.records.reverseEach{a -> map[a["date"]] = a["total"]}
  713.  
  714. model.dates = new LinkedHashMap()
  715. while (!date.equals(oldestDate)){
  716. date = sdf.format(cal.getTime())
  717. dateInt = Integer.parseInt(date)
  718. // model.dates.put(cal.getTime(), (map[date]? map[date] : 0))
  719. model.dates.put(cal.getTime(), (map[dateInt]? map[dateInt] : 0))
  720. cal.add(Calendar.DATE,-1)
  721. }
  722. render(view: "company.refreshes", model: model)
  723. }
  724.  
  725. def dashboard_properties = {
  726. roleService.checkSessionPermission(session.user, "account.settings")
  727. def model = [:]
  728. def user = userForSession(true)
  729. def company = user.company
  730.  
  731. model.user = user
  732. model.company = company
  733.  
  734. model.isDirectBilled = companyService.isDirectBilled(company)
  735. model.isOemAdmin = user.isOemAdmin()
  736.  
  737. try {
  738. model.cardWarning = getCardWarning(companyService.getPaymentAccount(company))
  739. } catch (ZuoraSystemDisabledException e) {
  740. log.warn(LogBuilder.ex(e))
  741. e.printStackTrace()
  742. }
  743.  
  744. render(view: "company.dashboard_properties", model: model)
  745. }
  746.  
  747. def ajax_getCompanyDashboardProps = {
  748. roleService.checkSessionPermission(session.user, "account.settings")
  749. def model = [:]
  750. def company = Company.findByPublicId(params.id)
  751.  
  752. def props = new ArrayList()
  753. if(params.filter != "editable")
  754. {
  755. props.add(name: CompanyDashboardProperty.COMPANY_ID, value: company.publicId, editable: false)
  756. props.add(name: CompanyDashboardProperty.COMPANY_NAME, value: company.name, editable: false)
  757. }
  758.  
  759. CompanyDashboardProperty.findAllByCompany(company).sort{it.name.toLowerCase()}.each{
  760. props.add(name: it.name, value: it.value, editable: true)
  761. }
  762.  
  763. model.props = props
  764. render model as JSON
  765. }
  766.  
  767. def ajax_checkCompanyDashboardPropName = {
  768. roleService.checkSessionPermission(session.user, "account.settings")
  769. def model = [:]
  770. def company = Company.findByPublicId(params.id)
  771. model.companyProp = CompanyDashboardProperty.findByNameAndCompany(params.name, company)
  772. render model as JSON
  773. }
  774.  
  775. def ajax_createCompanyDashboardProp = {
  776. roleService.checkSessionPermission(session.user, "account.settings")
  777. def company = Company.findByPublicId(params.id)
  778. def prop = new CompanyDashboardProperty(name: params.name, value: params.value)
  779. company.addToDashboardProperties(prop)
  780. company.save()
  781.  
  782. render text: "OK", contentType: "text/plain"
  783. }
  784.  
  785. def ajax_updateCompanyDashboardProp = {
  786. roleService.checkSessionPermission(session.user, "account.settings")
  787. def company = Company.findByPublicId(params.id)
  788. def prop = CompanyDashboardProperty.findByCompanyAndName(company, params.name)
  789. if(prop)
  790. {
  791. prop.value = params.value
  792. prop.save()
  793. }
  794.  
  795. render text: "OK", contentType: "text/plain"
  796. }
  797.  
  798. def ajax_deleteCompanyDashboardProp = {
  799. roleService.checkSessionPermission(session.user, "account.settings")
  800. def company = Company.findByPublicId(params.id)
  801. def prop = CompanyDashboardProperty.findByCompanyAndName(company, params.name)
  802. if(prop)
  803. {
  804. company.removeFromDashboardProperties(prop)
  805. prop.delete()
  806. }
  807.  
  808. render text: "OK", contentType: "text/plain"
  809. }
  810.  
  811. def ajax_createTestAccount = {
  812. if (!ConfigurationHolder.config.debug?.showTestAccountUI) {
  813. throw new AccessDeniedException("You don't have sufficient permissions.");
  814. }
  815.  
  816. String baseEmail = params.baseEmail;
  817. int numKlips = params.int("numKlips");
  818. int numDatasources = params.int("numDatasources");
  819. Map model = [:];
  820.  
  821. Map account = [:];
  822. account.isPartner = params.boolean("partner");
  823. account.features = params.features.tokenize(',');
  824. account.status = "trial";
  825. account.numClients = params.int("numClients");
  826. account.user = [:];
  827. account.user.email = baseEmail + "@klipfolio.com";
  828. account.user.password = "d4shb0ard";
  829. account.model = "2015-11_multiResourceBundles";
  830.  
  831. try {
  832. String parentId = testingService.createTestCompany(account);
  833. model.success = true;
  834. model.user = account.user;
  835.  
  836. Company company = companyPersistenceService.findByPublicId(parentId);
  837. User adminUser = userPersistenceService.findByEmail(account.user.email);
  838. adminUser.apiKey = baseEmail + "_admin_api_key";
  839. testingService.addEditorAndViewOnlyUsersToTestCompany(company, baseEmail, account.user.password);
  840.  
  841. if(account.features.indexOf(Feature.WHITELABEL) > -1) {
  842. company.setSetting("whitelabel.text.productName","Bananio");
  843. company.setSetting("whitelabel.text.widgetName","Banana");
  844. company.setSetting("whitelabel.text.dashboardName","Banana Board");
  845. company.setSetting("brand.light.web.logo", "name");
  846. company.setSetting("brand.dashboard_name", "Banana Board");
  847. }
  848.  
  849. testingService.createTestingKlipsFromTemplate(company, adminUser, "youtube", numKlips);
  850. for (int d = 0; d < numDatasources; d++) {
  851. defaultKlipsService.createDataSource("Test", "Test", "xml", null, "http://static.klipfolio.com/static/customers/klipfolio/scatter.xml", 86400, company, adminUser, [:]);
  852. }
  853.  
  854. if (account.isPartner) {
  855. for (int i = 0; i < account.numClients; i++) {
  856. def client = [:];
  857. client.isPartner = false;
  858. client.features = params.features.tokenize(',');
  859. client.parent = parentId;
  860. client.status = "trial";
  861. client.user = [:];
  862. client.user.email = baseEmail + "_client_" + i + "@klipfolio.com";
  863. client.user.password = "d4shb0ard";
  864. String clientId = testingService.createTestCompany(client);
  865.  
  866. Company clientCompany = companyPersistenceService.findByPublicId(clientId);
  867. User clientAdminUser = userPersistenceService.findByEmail(client.user.email);
  868. testingService.addEditorAndViewOnlyUsersToTestCompany(clientCompany, baseEmail + "_client" + i, account.user.password);
  869.  
  870. testingService.createTestingKlipsFromTemplate(clientCompany, clientAdminUser, "youtube", numKlips);
  871. for (int d = 0; d < numDatasources; d++) {
  872. defaultKlipsService.createDataSource("Test", "Test", "xml", null, "http://static.klipfolio.com/static/customers/klipfolio/scatter.xml", 86400, clientCompany, adminUser, [:]);
  873. }
  874. }
  875. }
  876. } catch (ApiBadRequestException e){
  877. model.success = false;
  878. model.err = e.message;
  879. }
  880. render model as JSON;
  881. }
  882.  
  883. def eventLog = {
  884.  
  885. roleService.checkSessionPermission(session.user, "account.usage")
  886.  
  887. def company = userForSession().company
  888. def model =[:]
  889. model.company = [
  890. name: company.name,
  891. isPartner: company.isPartner
  892. ]
  893.  
  894. model.hasPrivateDashboards = company.hasFeature(Feature.PRIVATE_DASHBOARDS)
  895. model.hasPublicDashboards = company.hasFeature(Feature.PUBLIC_DASHBOARDS)
  896.  
  897. model.users = User.executeQuery("select id,firstName,lastName from User where company=?",[company])
  898. render(view: "company.event_log",model: model)
  899. }
  900.  
  901. def purchaseFlow = {
  902. init {
  903. action {
  904. roleService.checkSessionPermission(session.user, "account.settings")
  905.  
  906. def plan = params.plan
  907. if (!plan) throw new AccessDeniedException()
  908.  
  909. def user = userForSession()
  910. def company = user.company
  911. session.user.billingPeriod = company.billingPeriod
  912.  
  913. AddressCommand addressCmd = new AddressCommand()
  914. flow.leavingFreemium = false;
  915.  
  916. try {
  917. //if account has already been created, pre-populate address information
  918. Account account = companyService.getPaymentAccount(company)
  919. Contact addressInfo = account.getBillTo();
  920. if(addressInfo != null) {
  921. addressCmd.city = addressInfo.getCity()
  922. addressCmd.state = addressInfo.getState()
  923. addressCmd.country = addressInfo.getCountry()
  924. addressCmd.street = addressInfo.getAddress1()
  925. addressCmd.postalCode = addressInfo.getPostalCode()
  926. }
  927.  
  928. // if current account plan is freemium, accessing this page means they chose something that costs $
  929. flow.leavingFreemium = (account.exists() ? account.isFreemium() : false);
  930.  
  931. } catch (Exception e) {
  932. // should never reach this point
  933. }
  934.  
  935. flow.addressCmd = addressCmd
  936.  
  937. // need ID for the cookie that holds the plan
  938. flow.company = [
  939. publicId: company.publicId
  940. ]
  941.  
  942. Map planDelta = (Map) new JSONDeserializer().deserialize(plan)
  943. // plan to show in the edit plan page
  944. flow.plan = planDelta
  945. // delta of what has changed in the plan
  946. flow.delta = getSanitizedPricingModelModifications(planDelta.planId, planDelta.addons, planDelta.topups)
  947. flow.trialDiscount = planDelta.trialDiscount
  948.  
  949. // info to determine the type of card provided
  950. flow.cardTypeInfo = CreditCardUtil.getCreditCardTypeInfo()
  951.  
  952. // used for showing the period unit on the confirmation page
  953. flow.periodLabels = getPeriodLabels()
  954.  
  955. go_enterCompanyDetails()
  956. }
  957.  
  958. on("go_enterCompanyDetails") {
  959. def user = userForSession()
  960.  
  961. CompanyInfoCommand infoCmd = new CompanyInfoCommand()
  962. infoCmd.bindFrom(user.company)
  963.  
  964. [ from:'init', infoCmd: infoCmd ]
  965. }.to("enterCompanyDetails")
  966. }
  967.  
  968. enterCompanyDetails {
  969. render(view: "enter_company_details")
  970.  
  971. on("nextStep") { AddressCommand addressCmd ->
  972. flow.addressCmd = addressCmd
  973. }.to("populateBilling")
  974.  
  975. on("back_confirm").to("confirmDetails")
  976. on("confirm") { AddressCommand addressCmd ->
  977. flow.addressCmd = addressCmd
  978.  
  979. CompanyInfoCommand infoCmd = new CompanyInfoCommand([ name: params.name ])
  980. flow.infoCmd = infoCmd
  981.  
  982. def infoValid = flow.infoCmd.validate()
  983. def addressValid = flow.addressCmd.validate()
  984.  
  985. if (infoValid && addressValid) {
  986. User user = userForSession()
  987.  
  988. Account account = companyService.getPaymentAccount(user.company, session.user.billingPeriod)
  989. SubscribePreview preview = getSubscribePreview(account, [
  990. companyInfo: flow.infoCmd,
  991. address: flow.addressCmd,
  992. billing: flow.billingCmd,
  993. receipt: flow.receiptCmd,
  994. modifications: flow.delta,
  995. trialDiscount: flow.trialDiscount,
  996. ])
  997.  
  998. Invoice invoice = preview.invoice
  999. flow.invoiceItems = invoice.invoiceItemSummary
  1000. flow.invoiceAmount = invoice.amount
  1001. flow.invoiceAmountWithoutTax = invoice.amountWithoutTax
  1002. flow.invoiceTaxAmount = invoice.taxAmount
  1003.  
  1004. flow.period = account.subscription.billingPeriod()
  1005. flow.purchaseError = ""
  1006.  
  1007. success()
  1008. } else {
  1009. error()
  1010. }
  1011. }.to("confirmDetails")
  1012. }
  1013.  
  1014. populateBilling {
  1015. action {
  1016. CompanyInfoCommand infoCmd = new CompanyInfoCommand([ name: params.name ])
  1017. flow.infoCmd = infoCmd
  1018.  
  1019. def infoValid = flow.infoCmd.validate()
  1020. def addressValid = flow.addressCmd.validate()
  1021.  
  1022. if (infoValid && addressValid) {
  1023. def user = userForSession()
  1024.  
  1025. if (!flow.billingCmd) {
  1026. flow.billingCmd = new BillingCommand()
  1027.  
  1028. flow.billingCmd.firstName = user.firstName
  1029. flow.billingCmd.lastName = user.lastName
  1030. flow.billingCmd.street = flow.addressCmd.street
  1031. flow.billingCmd.city = flow.addressCmd.city
  1032. flow.billingCmd.state = flow.addressCmd.state
  1033. flow.billingCmd.postalCode = flow.addressCmd.postalCode
  1034. flow.billingCmd.country = flow.addressCmd.country
  1035. }
  1036.  
  1037. if (!flow.receiptCmd) {
  1038. flow.receiptCmd = new ReceiptCommand()
  1039.  
  1040. flow.receiptCmd.email = user.email
  1041. }
  1042.  
  1043. flow.from = "enterCompanyDetails"
  1044. flow.transition = "next"
  1045.  
  1046. //check if its a free plan
  1047. zuora2Service.enableAccountCacheForThread(user.company.publicId, new ControllerPricingModelResolver(request, response))
  1048. Account account = companyService.getPaymentAccount(user.company, session.user.billingPeriod)
  1049. SubscribePreview preview = getSubscribePreview(account, [
  1050. companyInfo: [name: user.company.name],
  1051. address: flow.addressCmd,
  1052. billing: flow.billingCmd,
  1053. receipt: flow.receiptCmd,
  1054. modifications: flow.delta,
  1055. trialDiscount: flow.trialDiscount,
  1056. ])
  1057.  
  1058. if(preview.invoice.nextPeriodAmount.equals(BigDecimal.ZERO)) {
  1059. //uses invoicing in freemium so we don't have to ask for credit card info
  1060. flow.hasInvoicing = true
  1061. flow.billingCmd.hasInvoicing = true
  1062. flow.isFreemium = true
  1063.  
  1064. Invoice invoice = preview.invoice
  1065. flow.invoiceItems = invoice.invoiceItemSummary
  1066. flow.invoiceAmount = invoice.amount
  1067. flow.invoiceAmountWithoutTax = invoice.amountWithoutTax
  1068. flow.invoiceTaxAmount = invoice.taxAmount
  1069. flow.period = account.subscription.billingPeriod()
  1070. flow.purchaseError = ""
  1071.  
  1072. confirmDetails()
  1073. } else {
  1074. enterBillingDetails()
  1075. }
  1076.  
  1077. } else {
  1078. error()
  1079. }
  1080. }
  1081.  
  1082. on("confirmDetails").to("confirmDetails")
  1083. on("enterBillingDetails").to("enterBillingDetails")
  1084. }
  1085. enterBillingDetails {
  1086. render(view: "enter_billing_details")
  1087.  
  1088. on("back") {
  1089. [ from: 'enterBillingDetails', transition:'back' ]
  1090. }.to("enterCompanyDetails")
  1091.  
  1092. on("back_confirm").to("confirmDetails")
  1093. on("confirm") { BillingCommand billingCmd ->
  1094. flow.billingCmd = billingCmd
  1095.  
  1096. if (flow.billingCmd.cardNumber)
  1097. {
  1098. def cardInfo = CreditCardUtil.cardInfoFromNumber(flow.billingCmd.cardNumber)
  1099. flow.billingCmd.cardType = cardInfo.type
  1100. flow.cardTypeText = cardInfo.name
  1101. flow.maskedCreditCardNumber = CreditCardUtil.maskCreditCardNumber(flow.billingCmd.cardNumber)
  1102. }
  1103.  
  1104. def billingValid = flow.billingCmd.validate()
  1105. def receiptValid = flow.receiptCmd.validate()
  1106.  
  1107. User user = userForSession()
  1108.  
  1109. if (billingValid && receiptValid) {
  1110. Company company = user.company
  1111.  
  1112. zuora2Service.enableAccountCacheForThread(company.publicId, new ControllerPricingModelResolver(request, response))
  1113. Account account = companyService.getPaymentAccount(company, session.user.billingPeriod)
  1114. SubscribePreview preview = getSubscribePreview(account, [
  1115. companyInfo: flow.infoCmd,
  1116. address: flow.addressCmd,
  1117. billing: flow.billingCmd,
  1118. receipt: flow.receiptCmd,
  1119. modifications: flow.delta,
  1120. trialDiscount: flow.trialDiscount,
  1121. ])
  1122.  
  1123. Invoice invoice = preview.invoice
  1124. flow.invoiceItems = invoice.invoiceItemSummary
  1125. flow.invoiceAmount = invoice.amount
  1126. flow.invoiceAmountWithoutTax = invoice.amountWithoutTax
  1127. flow.invoiceTaxAmount = invoice.taxAmount
  1128. flow.period = account.subscription.billingPeriod()
  1129. flow.purchaseError = ""
  1130.  
  1131. success()
  1132. } else {
  1133. /* Gets all error messages after running billingCmd.validate() */
  1134. flow.billingCmd.errors?.allErrors?.each {
  1135. String mixPanelMessage = g.message([error : it]);
  1136. /* Only send credit card related errors to mixpanel */
  1137. if(mixPanelMessage == g.message([code: "company.card.wrong_size"]) || mixPanelMessage == g.message([code: "company.card.bad_checksum"]) || mixPanelMessage == g.message([code: "company.card.bad_expiry_date"]) || mixPanelMessage == g.message([code: "company.card.bad_cvv"])) {
  1138. mixPanelService.track(user, "Purchase Flow Error", session.mixPanelEnabled, ["Error Type": "klipfolio - " + mixPanelMessage]);
  1139. }
  1140. };
  1141. error()
  1142. }
  1143. }.to("confirmDetails")
  1144. }
  1145.  
  1146. confirmDetails {
  1147. render(view: "confirm_details")
  1148.  
  1149. on("editPlan") {
  1150. [ from:'confirmDetails', transition:'edit' ]
  1151. }.to("editPlan")
  1152.  
  1153. on("enterCompanyDetails") {
  1154. [ from:'confirmDetails', transition:'edit' ]
  1155. }.to("enterCompanyDetails")
  1156.  
  1157. on("enterBillingDetails") {
  1158. [ from:'confirmDetails', transition:'edit' ]
  1159. }.to("enterBillingDetails")
  1160. on("back") {
  1161. [ from:'confirmDetails', transition:'back' ]
  1162. }.to("enterBillingDetails")
  1163.  
  1164. on("payNow") {
  1165. def user = userForSession()
  1166. def company = user.company
  1167. try
  1168. {
  1169. if (!company.primaryContact) {
  1170. company.primaryContact = user
  1171. }
  1172.  
  1173. Account account = companyService.getPaymentAccount(company, session.user.billingPeriod)
  1174.  
  1175. PricingModel pricingModel = account.subscription.pricingModel
  1176. Set<String> validPlans = companyService.determineValidPlans(company, pricingModel)
  1177. if (validPlans != null && !validPlans.contains(flow.plan.planId)) {
  1178. flow.planUnavailable = true
  1179. throw new ZuoraException("Selected plan is not available.", null, null)
  1180. }
  1181.  
  1182. AddressCommand addressCommand = flow.addressCmd
  1183. BillingCommand billingCommand = flow.billingCmd
  1184. ReceiptCommand receiptCommand = flow.receiptCmd
  1185. PricingModelModification modifications = flow.delta
  1186. BigDecimal trialDiscount = flow.trialDiscount
  1187.  
  1188. modifyAccountAndSubscription(company, account, addressCommand, billingCommand, receiptCommand, modifications, trialDiscount)
  1189.  
  1190. if(account.exists()){
  1191. //cancel current subscription to create a new one, should only occur when leaving freemium
  1192. account.subscription.cancel();
  1193.  
  1194. //update monthly billing date to today
  1195. String dayOfMonth = String.valueOf(Calendar.getInstance().get(Calendar.DAY_OF_MONTH));
  1196. HashMap<String,String> updateAccountValues = [
  1197. "BillCycleDay": dayOfMonth,
  1198. "BcdSettingOption" : "ManualSet"
  1199. ]
  1200. account.updateAccount(updateAccountValues);
  1201. }
  1202.  
  1203. //create subscription will create account if it doesn't exist
  1204. SubscribeResult result = account.createSubscription(true)
  1205.  
  1206. eventLogService.info("billing","payment_plan_set_up","Success",company,user)
  1207.  
  1208. if (trialDiscount && trialDiscount != BigDecimal.ZERO) {
  1209. eventLogService.info("billing", "payment_plan_one_month_discount", "One month discount applied", company, user)
  1210. }
  1211.  
  1212. zuora2Service.enableAccountCacheForThread(null, null) // reset the cache
  1213. account = companyService.getPaymentAccount(company, session.user.billingPeriod)
  1214. companyService.modifyCompanyEntitlementsToMatchSubscription(company, account)
  1215.  
  1216. session.user.isTrial = false
  1217. session.user.companyState = company.state
  1218. session.user.companyStatus = company.stateAsString()
  1219. session.user.hasNinjaServices = company.hasFeature(Feature.NINJA)
  1220. session.user.mixpanelData = userService.getMixpanelData(user)
  1221.  
  1222. companyService.enableOlark(company, session.user)
  1223. session.jsEnv = userService.getJsEnvInfo(company, user)
  1224.  
  1225. flow.datePurchased = new Date()
  1226. Invoice invoice = result.invoice
  1227. flow.invoiceItems = invoice.invoiceItemSummary
  1228. flow.invoiceAmount = invoice.amount
  1229. flow.invoiceAmountWithoutTax = invoice.amountWithoutTax
  1230. flow.invoiceTaxAmount = invoice.taxAmount
  1231. flow.period = account.subscription.billingPeriod()
  1232. flow.purchaseMade = true
  1233. flow.mixpanelData = [ accountStatus : company.typeAsString() ]
  1234.  
  1235. changeCompanyDsRefreshRateToMax(account, company, user);
  1236.  
  1237. success()
  1238. }
  1239. catch (ZuoraException e)
  1240. {
  1241. flow.purchaseError = companyService.formatZuoraErrorMessage(e.getMessage());
  1242.  
  1243. String mixPanelMessage = (e.getZuoraCCTransactionResultCode() == "Declined") ? "CardDeclined" : e.getZuoraCCTransactionResultCode();
  1244. mixPanelService.track(user , "Purchase Flow Error", session.mixPanelEnabled, [ "Error Type" : "zuora - " + mixPanelMessage]);
  1245.  
  1246. error()
  1247. }
  1248. catch (Exception e)
  1249. {
  1250. flow.purchaseError = 'We were unable to process your payment.<br/><a href="https://klipfolio.com/contact-us" target="_blank">Contact our sales team</a> to proceed with this transaction.';
  1251.  
  1252. log.error(LogBuilder.msg("Unknown exception during payment flow.").exception(e).companyPublicId(company.publicId).userPublicId(user.publicId));
  1253.  
  1254. error();
  1255. }
  1256. }.to("finished")
  1257. }
  1258.  
  1259. editPlan {
  1260. render(view: "edit_plan")
  1261.  
  1262. on("back_confirm").to("confirmDetails")
  1263. on("confirm") {
  1264. Map planDelta = (Map) new JSONDeserializer().deserialize(params.plan)
  1265. if (planDelta.planId != flow.plan.planId || (planDelta?.trialDiscount ?: 0) > 0)
  1266. flow.trialDiscount = planDelta.trialDiscount
  1267. flow.plan = planDelta
  1268. flow.delta = getSanitizedPricingModelModifications(planDelta.planId, planDelta.addons, planDelta.topups)
  1269.  
  1270. User user = userForSession()
  1271.  
  1272. Account account = companyService.getPaymentAccount(user.company, session.user.billingPeriod)
  1273. SubscribePreview preview = getSubscribePreview(account, [
  1274. companyInfo: flow.infoCmd,
  1275. address: flow.addressCmd,
  1276. billing: flow.billingCmd,
  1277. receipt: flow.receiptCmd,
  1278. modifications: flow.delta,
  1279. trialDiscount: flow.trialDiscount,
  1280. ])
  1281.  
  1282. flow.period = account.subscription.billingPeriod()
  1283.  
  1284. Invoice invoice = preview.invoice
  1285. flow.invoiceItems = invoice.invoiceItemSummary
  1286. flow.invoiceAmount = invoice.amount
  1287. flow.invoiceAmountWithoutTax = invoice.amountWithoutTax
  1288. flow.invoiceTaxAmount = invoice.taxAmount
  1289. flow.period = account.subscription.billingPeriod()
  1290. flow.purchaseError = ""
  1291.  
  1292. }.to("confirmDetails")
  1293. }
  1294.  
  1295. finished {
  1296. on("complete").to("complete")
  1297. }
  1298.  
  1299. complete {
  1300. redirect(controller: "dashboard", action: "index")
  1301. }
  1302. }
  1303.  
  1304. void changeCompanyDsRefreshRateToMax(Account account, Company company, User user) {
  1305. if (account.exists()) {
  1306. int fastestRefreshLimit = limitService.getCompanyLimit(company, "refresh_rate");
  1307. List<RegisteredDatasource> datasourcesForUser = registeredDatasourcePersistenceService.findAllByCreatedByAndRefreshIntervalLessThan(user, fastestRefreshLimit)
  1308.  
  1309. datasourcesForUser.each { datasource ->
  1310. datasource.refreshInterval = fastestRefreshLimit
  1311. registeredDatasourcePersistenceService.save(datasource, true)
  1312. }
  1313. }
  1314. }
  1315.  
  1316. private SubscribePreview getSubscribePreview(Account account, Map config)
  1317. {
  1318. account.name = config.companyInfo.name
  1319. account.setBillTo(
  1320. config.billing.firstName,
  1321. config.billing.lastName,
  1322. config.billing.street,
  1323. null,
  1324. config.billing.city,
  1325. config.billing.state,
  1326. config.billing.country,
  1327. config.billing.postalCode,
  1328. config.receipt.email
  1329. )
  1330. account.setSoldTo(
  1331. config.billing.firstName,
  1332. config.billing.lastName,
  1333. config.address.street,
  1334. null,
  1335. config.address.city,
  1336. config.address.state,
  1337. config.address.country,
  1338. config.address.postalCode,
  1339. config.receipt.email
  1340. )
  1341.  
  1342. PricingModel pricingModel = account.subscription.pricingModel
  1343. pricingModel.update(config.modifications)
  1344.  
  1345. applyTrialDiscount(account, config.trialDiscount)
  1346. applyOneTimeDiscount(account)
  1347.  
  1348. SubscribePreview preview = account.previewCreateSubscription()
  1349.  
  1350. return preview
  1351. }
  1352.  
  1353. private void applyTrialDiscount(Account account, BigDecimal trialDiscount) {
  1354.  
  1355. if (trialDiscount)
  1356. {
  1357. for (RatePlan plan : account.subscription.ratePlans)
  1358. {
  1359. if (plan.product.isBaseProduct())
  1360. {
  1361. if (!plan.percentageDiscounts) // if this happens Zuora has been misconfigured, it can be fixed in Zuora
  1362. throw new RuntimeException("Trial discount offered but no percentage discount charge available on selected product.")
  1363.  
  1364. // we use BigDecimal operations directly here rather than just letting Groovy do it (Groovy uses
  1365. // BigDecimal under the covers) because we want to specify the scale
  1366. BigDecimal discountPercent = trialDiscount.multiply(new BigDecimal(100)).divide(plan.getAmount(), Zuora.MAX_SCALE, BigDecimal.ROUND_HALF_EVEN);
  1367. DiscountPercentageCharge discountCharge = plan.percentageDiscounts[0]
  1368. discountCharge.setDiscountPercentage(discountPercent)
  1369. discountCharge.setUpToPeriods(1)
  1370. }
  1371. }
  1372. }
  1373. }
  1374.  
  1375. private void applyOneTimeDiscount(Account account){
  1376. if (!account.subscription.exists()) {
  1377. for (RatePlanImpl plan : account.subscription.ratePlans) {
  1378. if (plan.product.isBaseProduct()){
  1379. plan.applyOneTimeDiscount()
  1380. }
  1381. }
  1382. }
  1383. }
  1384.  
  1385. def stream_invoicePdf = {
  1386.  
  1387. def user = userForSession(true)
  1388.  
  1389. if (!roleService.isAdmin(user)) throw new AccessDeniedException()
  1390.  
  1391. Account account = companyService.getPaymentAccount(user.company)
  1392. Invoice invoice = account.invoices.find {it.id==params.invoice}
  1393. byte[] invoicePdf = account.getInvoicePdf(invoice)
  1394.  
  1395. // browser should cache for 1 hour..
  1396. cache shared:true, validFor: 60*60*1
  1397. response.setHeader("Pragma","cache")
  1398. response.contentType = "application/pdf"
  1399. // response.setHeader("Content-disposition", "attachment;filename=invoice${params.invoice}.pdf")
  1400. response.contentLength = invoicePdf.length
  1401. response.outputStream << invoicePdf
  1402. response.outputStream.flush()
  1403. }
  1404.  
  1405. def ajax_cancelPayment = {
  1406. def user = userForSession(true)
  1407.  
  1408. if (!roleService.isAdmin(user)) throw new AccessDeniedException()
  1409.  
  1410. def model = [:]
  1411.  
  1412. try {
  1413. Account account = companyService.getPaymentAccount(user.company)
  1414. Subscription subscription = account.subscription
  1415. Date subscriptionExpires = subscription.cancel();
  1416. user.company.subscriptionExpires = subscriptionExpires
  1417. user.company.save(flush:true)
  1418. model.isPlanCancelled = true
  1419. model.cancelledDate = g.formatDate(date: subscriptionExpires, format:'MMM dd, yyyy')
  1420. } catch (Exception e) {
  1421. model.isPlanCancelled = false
  1422. }
  1423.  
  1424. if(model.isPlanCancelled) {
  1425. Map meta = [:]
  1426.  
  1427. meta.isPartner = user.company.isPartner
  1428. meta.companyName = user.company.name
  1429. meta.userEmail = user.email
  1430.  
  1431. if(user.company.partnerParent) {
  1432. meta.partnerId = user.company.partnerParent.publicId
  1433. }
  1434.  
  1435. eventLogService.info("company", "company_plan_cancelled", "Plan cancelled", user.company, user, meta)
  1436. }
  1437.  
  1438. render model as JSON
  1439. }
  1440.  
  1441. def ajax_getPaymentHistory = {
  1442.  
  1443. def user = userForSession()
  1444.  
  1445. if (!roleService.isAdmin(user)) throw new AccessDeniedException()
  1446.  
  1447. JSONElement props = JSON.parse(params.pagerQuery) // turn params into groovy usable JSON format
  1448.  
  1449. def model = [:]
  1450.  
  1451. Date zuoraStartDate = grailsApplication.config.zuora.history.startDate
  1452.  
  1453. Account account = companyService.getPaymentAccount(user.company)
  1454. List<Invoice> invoices = account.invoices.findAll { it.invoiceDate.after(zuoraStartDate) && it.amount != 0 }
  1455. List historyList = invoices.collect {[date: it.invoiceDate, amount: it.amount, invoiceId: it.id] }
  1456.  
  1457. if (props.sort) {
  1458. historyList = historyList.sort(new DomainComparator(props.sort.name, props.sort.order))
  1459. } else {
  1460. historyList = historyList.sort(new DomainComparator('date', -1))
  1461. }
  1462.  
  1463. model.records = []
  1464. model.count = historyList.size()
  1465.  
  1466. def end = (props.page * props.limit) <= model.count ? props.page * props.limit : model.count
  1467. def start = (props.page - 1) * props.limit
  1468.  
  1469. DateTimeFormatter fmt = DateTimeFormat.forPattern("MMMM d, yyyy")
  1470.  
  1471. for (int i = start; i < end; i++) {
  1472. def payment = historyList.get(i)
  1473.  
  1474. DateTime payDate = (payment.date == null ? null : new DateTime(payment.date))
  1475.  
  1476. model.records << [
  1477. date: (payDate != null ? fmt.print(payDate) : ""),
  1478. amount: payment.amount,
  1479. invoiceId: payment.invoiceId
  1480. ]
  1481. }
  1482.  
  1483. render model as JSON
  1484. }
  1485.  
  1486. /** See {@link StaleAccountService#STALE_ACCOUNT_DELETE_KEY} for an explanation of why this is here. */
  1487. def admin_deleteExpiredTrials = {
  1488. try {
  1489. def validator = new StaleAccountParamsValidator()
  1490. validator.validatePassphrase(
  1491. staleAccountService.getPassphrase(),
  1492. params.get("superSecretKey")
  1493. )
  1494.  
  1495. String expiredForDays = params.get("expiredForDays")
  1496. String incompleteForDays = params.get("incompleteForDays")
  1497. String deleteLimit = params.get("deleteLimit")
  1498.  
  1499. validator.validateLimits(
  1500. ConfigurationHolder.config.deleteStaleAccount.expiredTrial.minimumGracePeriodDays,
  1501. expiredForDays,
  1502. deleteLimit,
  1503. (null != incompleteForDays)
  1504. )
  1505.  
  1506. validator.validateLimits(
  1507. ConfigurationHolder.config.deleteStaleAccount.expiredIncompleteTrial.minimumGracePeriodDays,
  1508. incompleteForDays,
  1509. deleteLimit,
  1510. (null != expiredForDays)
  1511. )
  1512.  
  1513. } catch (StaleAccountServiceException e) {
  1514. response.status = e.responseCode
  1515. render([
  1516. success: false,
  1517. message: e.getMessage()
  1518. ] as JSON)
  1519. return;
  1520. }
  1521.  
  1522. int expiredForDays = params.int("expiredForDays")?:null
  1523. int deleteLimit = params.int("deleteLimit") ?: 1;
  1524. int incompleteForDays = params.int("incompleteForDays")?:null
  1525. boolean preview = params.boolean("preview")?:false
  1526.  
  1527. try {
  1528. def result = [success: true]
  1529. if (preview) {
  1530. result.preview = true
  1531. }
  1532. if (expiredForDays) {
  1533. List<Long> resultIds = staleAccountService.deleteExpiredTrials(expiredForDays, deleteLimit, preview)
  1534. result.totalIds = resultIds.size()
  1535. result.ids = resultIds
  1536. }
  1537. if (incompleteForDays) {
  1538. List<Long> resultIds = staleAccountService.deleteIncompleteTrials(incompleteForDays, deleteLimit, preview)
  1539. result.totalIncompleteIds = resultIds.size()
  1540. result.incompleteIds = resultIds
  1541. }
  1542. render(result as JSON)
  1543. } catch (StaleAccountServiceMutexException e) {
  1544. response.status = 423
  1545. render([
  1546. success: false,
  1547. message: e.getMessage()
  1548. ] as JSON)
  1549. }
  1550. }
  1551.  
  1552. /** See {@link StaleAccountService#STALE_ACCOUNT_DELETE_KEY} for an explanation of why this is here. */
  1553. def admin_deleteDisabledAccounts = {
  1554. try {
  1555. def validator = new StaleAccountParamsValidator()
  1556. validator.validatePassphrase(
  1557. staleAccountService.getPassphrase(),
  1558. params.get("superSecretKey")
  1559. )
  1560.  
  1561. validator.validateLimits(
  1562. ConfigurationHolder.config.deleteStaleAccount.expiredTrial.minimumGracePeriodDays,
  1563. params.get("lastLoginDaysAgo"),
  1564. params.get("deleteLimit"),
  1565. false
  1566. )
  1567. } catch (StaleAccountServiceException e) {
  1568. response.status = e.responseCode
  1569. render([
  1570. success: false,
  1571. message: e.getMessage()
  1572. ] as JSON)
  1573. return;
  1574. }
  1575.  
  1576. int lastLoginDaysAgo = params.int("lastLoginDaysAgo")
  1577. int deleteLimit = params.int("deleteLimit") ?: 1
  1578. boolean preview = params.boolean("preview")?:false
  1579.  
  1580. try {
  1581. def result = [success: true]
  1582. if (preview) {
  1583. result.preview = true
  1584. }
  1585. List<Long> resultIds = staleAccountService.deleteIncompleteTrials(incompleteForDays, deleteLimit, preview)
  1586. result.totalIds = resultIds.size()
  1587. result.ids = resultIds
  1588. render(result as JSON)
  1589. } catch (StaleAccountServiceMutexException e) {
  1590. response.status = 423
  1591. render([
  1592. success: false,
  1593. message: e.getMessage()
  1594. ] as JSON)
  1595. }
  1596. }
  1597.  
  1598. def ajax_getRefreshHistoryDatasources = {
  1599. User user = userForSession()
  1600. RegisteredDatasource rdi
  1601. DatasourceInstance di
  1602. int totalSumFromRegistered = 0
  1603. int totalRecords = 0
  1604. int remaining = 0
  1605. Map model = [:]
  1606. ArrayList keys = []
  1607. List instances
  1608.  
  1609. model.records = mongoPool.getDatabase(RefreshLogService.MONGO_DB_NAME).getCollection("records", BasicDBObject.class)
  1610. .find(new BasicDBObject(["cid":user.companyId, "date":params.int("date")])).first()
  1611. totalRecords = model.records.get("counts").size()
  1612. model.dsRefreshes = [:]
  1613. keys = dataRefreshService.parseKeysFromMongoRecords(model.records)
  1614. if(keys) {
  1615. instances = DatasourceInstance.createCriteria().list{"in"("id",keys)}
  1616. }
  1617. model.dsRefreshes.instances = [:]
  1618. instances.each{a->
  1619. rdi = a.datasource
  1620. if (!model.dsRefreshes.instances[rdi.id]) model.dsRefreshes.instances[rdi.id] = [rdi.name, 0, rdi.publicId, 0] // [name, total refreshes, publicId, # instances]
  1621. model.dsRefreshes.instances[rdi.id][1] += model.records.counts["${a.id}"]
  1622. model.dsRefreshes.instances[rdi.id][3] += 1
  1623. totalSumFromRegistered += model.records.counts["${a.id}"]
  1624. }
  1625. remaining = model.records.total - totalSumFromRegistered
  1626. if (remaining > 0) {
  1627. model.dsRefreshes.instances["del"] = ["Deleted", remaining, null, totalRecords - (instances ? instances.size() : 0) ]
  1628. }
  1629. model.dsRefreshes.instances = model.dsRefreshes.instances.values()
  1630. model.dsRefreshes.totalInstances = totalRecords
  1631. model.dsRefreshes.total = model.records.total
  1632. render model.dsRefreshes as JSON
  1633. }
  1634.  
  1635.  
  1636. def ajax_setFirstRunProperty = {
  1637. def user = userForSession();
  1638. def company = user?.company
  1639.  
  1640. if (company && params.name)
  1641. {
  1642. company.setSetting(params.name, params.value)
  1643. session.firstrun[params.name] = params.value
  1644. }
  1645.  
  1646. render text: "OK", contentType: "text/plain"
  1647. }
  1648.  
  1649. /**
  1650. * Ajax method to allow logging of client side events
  1651. */
  1652. def ajax_logEvent = {
  1653. def user = userForSession()
  1654. eventLogService.info(params.cat,params.event,params.desc,user.company,user)
  1655. render text: "OK", contentType: "text/plain"
  1656. }
  1657.  
  1658. /**
  1659. * Ajax method to collect event log info from mongodb so that an admin user may view it
  1660. */
  1661. def ajax_getEventLog = {
  1662. JSONElement props = JSON.parse(params.pagerQuery)//turn params into groovy usable JSON format
  1663. Company company =props.params.cid?Company.findByPublicId(props.params.cid): userForSession().company
  1664. def results = [:]
  1665. results.records = []
  1666. results.count = 0
  1667. String uids = ""
  1668. int page = props.page
  1669. int limit = props.limit
  1670. def searchParams = [:]
  1671. def sortParams = [:]
  1672. searchParams['cpy'] = company.id
  1673.  
  1674. try {
  1675. //Filters
  1676. if (!props.params.cid) searchParams['c'] = [$ne: 'exception']
  1677. if (props.params?.user && props.params?.user != 'All') searchParams['usr'] = Integer.parseInt(props.params.user)
  1678. if (props.params?.type && props.params.type != 'All') searchParams['c'] = props.params.type
  1679. if (props.params?.from) {
  1680. SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy")
  1681. Date toDate = sdf.parse(props.params.to)
  1682. Calendar cal = Calendar.getInstance()
  1683. cal.setTime(toDate)
  1684. cal.set(Calendar.HOUR, (cal.get(Calendar.HOUR) + 24))
  1685. toDate = cal.getTime()
  1686. searchParams['ts'] = [$gte: sdf.parse(props.params.from).time, $lte: toDate.time]
  1687. }
  1688.  
  1689. //Sorting - Mongo sort spec only accepts integer. toInteger makes sure it's integer. See: saas-4027
  1690. if (props.sort) sortParams[props.sort.name] = props.sort.order.toInteger();
  1691. //query database
  1692. def cursor = eventLogService.report(searchParams, limit, page, sortParams).iterator()
  1693.  
  1694. //collect results
  1695. try {
  1696. while (cursor.hasNext()) {
  1697. def info = cursor.next()
  1698. info.append("timestampDate", g.formatDate(date: new Date(info.ts), style: "MEDIUM"))
  1699. info.cFormatted = eventLogService.prettyPrintEvents(info.c)
  1700. info.eFormatted = eventLogService.prettyPrintEvents(info.e)
  1701. if (info.usr) uids += info.usr + ","
  1702. else {
  1703. info.userId = "n/a"
  1704. info.userName = "n/a"
  1705. }
  1706. results.records << info
  1707. }
  1708. } finally {
  1709. cursor.close()
  1710. }
  1711.  
  1712.  
  1713. if (uids.length() > 0) {
  1714. uids = uids.substring(0, uids.length() - 1)//remove trailing comma...
  1715. def userData = User.executeQuery("select id,publicId,firstName,lastName from User where company =? and id in (" + uids + ")", [company])
  1716.  
  1717. for (int $x = 0; $x < userData.size(); $x++) {
  1718. def userResults = results.records.findAll { r -> r.usr == userData[$x][0] }
  1719. for (int y = 0; y < userResults.size(); y++) {
  1720. userResults[y].userId = userData[$x][1]
  1721. userResults[y].userName = userData[$x][2] + " " + userData[$x][3]
  1722. }
  1723. }
  1724. results.count = eventLogService.reportCount(searchParams)
  1725. }
  1726. }
  1727. catch (Exception e) {
  1728. log.error(LogBuilder.msg("Failed to get event log entries from mongo").exception(e))
  1729. results.error = true
  1730. }
  1731. render results as JSON
  1732. }
  1733.  
  1734. private Map generatePricingFeatureJSONMap(Company company, PricingFeatureInfo featureInfo, PricingFeature feature)
  1735. {
  1736. Map featureMap = [
  1737. sortKey: feature?.sortKey ?: featureInfo?.sortKey,
  1738. id: feature?.id ?: featureInfo?.id,
  1739. name: feature?.name ?: featureInfo?.name,
  1740. help: feature?.help ?: featureInfo?.help,
  1741. highlight: feature?.highlight ?: false,
  1742. limit: feature ? (feature.unit ? feature.unit.current : 1) : 0,
  1743. altLimit: [
  1744. text: feature ? feature.valueText : featureInfo.missingValueText,
  1745. image: feature ? feature.valueImage : featureInfo.missingValueImage
  1746. ],
  1747. finePrint: feature?.finePrint,
  1748. ]
  1749. if (company && feature?.limitName) {
  1750. featureMap.limitName = feature.limitName
  1751. featureMap.usage = limitService.getCurrentAggregateUsage(company, feature.limitName)
  1752. }
  1753. return featureMap
  1754. }
  1755.  
  1756. private String getHtmlValue(String text)
  1757. {
  1758. if (!text)
  1759. return null;
  1760.  
  1761. if (text.startsWith("http:") || text.startsWith("https:"))
  1762. text = htmlCache.get(text)
  1763.  
  1764. return text
  1765. }
  1766.  
  1767. private Map generateItemMap(PricingItem item)
  1768. {
  1769. Map itemMap = [
  1770. sortKey: item.sortKey,
  1771. id: item.id,
  1772. productId: item.productId,
  1773. name: item.name,
  1774. billingPeriod: item.billingPeriod,
  1775. description: [
  1776. title: item.descriptionTitle,
  1777. body: item.description
  1778. ],
  1779. help: item.help,
  1780.  
  1781. banner: getHtmlValue(item.promotionBanner),
  1782. topContent: getHtmlValue(item.topBanner),
  1783. finePrint: item.finePrint,
  1784. hidden: item.hidden,
  1785. ]
  1786.  
  1787. if (item.discountPercent) {
  1788. itemMap.discount = [
  1789. percentage: item.discountPercent,
  1790. image: item.promotionBadge,
  1791. text: item.promotionText
  1792. ]
  1793. }
  1794.  
  1795. if (item.featureText) {
  1796. itemMap.featured = [text: item.featureText]
  1797. }
  1798.  
  1799. return itemMap
  1800. }
  1801.  
  1802. private Map generateSelectableItemMap(SelectablePricingItem item)
  1803. {
  1804. Map itemMap = generateItemMap(item)
  1805. itemMap.selected = item.selected
  1806. if (itemMap.selected)
  1807. itemMap.remove("discount")
  1808. itemMap.amount = item.amount
  1809. return itemMap
  1810. }
  1811.  
  1812. private Map generateTopUpMap(Company company, PricingTopUp topUp)
  1813. {
  1814. Map itemMap = generateItemMap(topUp)
  1815. itemMap.putAll([
  1816. group: topUp.group,
  1817. picker: topUp.picker,
  1818. slider: topUp.slider,
  1819. limitName: topUp.unit.name,
  1820. unit: [
  1821. current: topUp.unit.current,
  1822. price: topUp.unit.price,
  1823. min: 0,
  1824. max: topUp.maximum
  1825. ]
  1826. ])
  1827. itemMap.description.short = getHtmlValue(topUp.htmlDescription)
  1828. if (company)
  1829. itemMap.unit.usage = limitService.getCurrentAggregateUsage(company, itemMap.limitName)
  1830. if (topUp.unit.tiers && topUp.unit.tiers.size() > 1)
  1831. {
  1832. List tierBoundaries = new ArrayList<Integer>(topUp.unit.tiers.keySet())
  1833. Collections.sort(tierBoundaries)
  1834. List tiers = []
  1835. for (int start : tierBoundaries)
  1836. tiers << [start: start, price: topUp.unit.tiers[start]]
  1837. itemMap.unit.tiers = tiers
  1838. }
  1839.  
  1840. return itemMap
  1841. }
  1842.  
  1843. private Map generateAddOnMap(PricingAddOn addOn, boolean current)
  1844. {
  1845. Map itemMap = generateSelectableItemMap(addOn)
  1846. itemMap.includes = addOn.includes
  1847. itemMap.current = current
  1848. return itemMap
  1849. }
  1850.  
  1851. private Map generatePlanMap(Company company, PricingPlan plan, List<PricingFeatureInfo> featureInfos, boolean enabled, boolean current)
  1852. {
  1853. Map planMap = generateSelectableItemMap(plan)
  1854. List topups = []
  1855. for (PricingTopUp topUp : plan.topUps)
  1856. topups << topUp.id
  1857. planMap.topups = topups
  1858. List addons = []
  1859. for (PricingAddOn addon : plan.addOns)
  1860. addons << addon.id
  1861. planMap.addons = addons
  1862. List features = []
  1863. for (PricingFeatureInfo info : featureInfos)
  1864. features << generatePricingFeatureJSONMap(company, info, plan.features[info.id])
  1865. planMap.features = features
  1866. // for trials, we'll allow the customer to select a lower plan than what they need, and then give them the plan they need with a temporary discount and a warning dialog
  1867. planMap.disabled = !enabled && company.type != Company.TYPE_TRIAL
  1868. planMap.current = current
  1869. return planMap
  1870. }
  1871.  
  1872. private Map generateFeatureInfo(PricingFeatureInfo featureInfo)
  1873. {
  1874. Map infoMap = [
  1875. sortKey: featureInfo.sortKey,
  1876. id: featureInfo.id,
  1877. name: featureInfo.name,
  1878. help: featureInfo.help,
  1879. ]
  1880. return infoMap
  1881. }
  1882.  
  1883. private Map generatePricingModelJSONMap(Company company, PricingModel model, String currentPlanId, List<String> currentAddonIds)
  1884. {
  1885. if (model.customPlanMessage)
  1886. return [custom: model.customPlanMessage]
  1887.  
  1888. if (model.ambiguous)
  1889. return [ambiguous: true]
  1890.  
  1891. List plans = []
  1892. List addons = []
  1893. List topups = []
  1894. List featureInfo = []
  1895.  
  1896. Set<String> validPlans = companyService.determineValidPlans(company, model)
  1897.  
  1898. boolean selectedPlanIsHidden = false
  1899. for (PricingPlan plan : model.plans)
  1900. {
  1901. plans << generatePlanMap(company, plan, model.featureList, (validPlans == null) || validPlans.contains(plan.id), currentPlanId == plan.id)
  1902. if (plan.selected && plan.hidden)
  1903. selectedPlanIsHidden = true
  1904. }
  1905.  
  1906. if (selectedPlanIsHidden)
  1907. {
  1908. boolean newPlanSelected = false
  1909. for (Map plan : plans)
  1910. {
  1911. if (plan.selected)
  1912. {
  1913. plan.selected = false
  1914. }
  1915. else if (!newPlanSelected && !plan.hidden && !plan.disabled)
  1916. {
  1917. plan.selected = true
  1918. newPlanSelected = true
  1919. }
  1920. }
  1921. }
  1922.  
  1923. for (PricingAddOn addon : model.addOns) {
  1924. addons << generateAddOnMap(addon, currentAddonIds.find { it == addon.id } != null)
  1925. }
  1926.  
  1927. for (PricingTopUp topup : model.topUps)
  1928. topups << generateTopUpMap(company, topup)
  1929. for (PricingFeatureInfo info : model.featureList)
  1930. featureInfo << generateFeatureInfo(info)
  1931.  
  1932. Map modelMap = [
  1933. ambiguous: false,
  1934. plans: plans,
  1935. featureList: featureInfo,
  1936. planBottomText: model.bucketsTrailerHtml,
  1937. showAnnual: model.showAnnual(),
  1938. hideComparison: model.isComparisonHidden(),
  1939. topups: topups,
  1940. addons: addons,
  1941. tag: model.tag,
  1942. extraProducts: model.extraProducts,
  1943. customizedProducts: model.modifiedProducts,
  1944. ]
  1945.  
  1946. if (modelMap.plans?.size() == 1)
  1947. modelMap.plans[0].hidden = true
  1948.  
  1949. return modelMap
  1950. }
  1951.  
  1952. def ajax_checkPlanUpgradeOptions = {
  1953. roleService.checkSessionPermission(session.user, "account.settings")
  1954.  
  1955. User user = userForSession()
  1956. Company company = user.company
  1957.  
  1958. if (companyService.ignorePayment(company)) {
  1959. Map paymentIgnoreModel = [
  1960. active: true,
  1961. direct: true,
  1962. changeRequired: false,
  1963. ]
  1964. render paymentIgnoreModel as JSON
  1965. return
  1966. }
  1967.  
  1968. boolean active = companyService.checkSeatsActive(company)
  1969. boolean direct = companyService.isDirectBilled(company)
  1970.  
  1971. Map model = [
  1972. active: active,
  1973. direct: direct
  1974. ]
  1975.  
  1976. Account account = null
  1977. try {
  1978. account = companyService.getPaymentAccount(company)
  1979. } catch (ZuoraSystemDisabledException e) {
  1980. model.paymentSystemDisabled = true
  1981. model.dynamicPaymentSystemInfo = e.info
  1982. } catch (ZuoraException e) {
  1983. model.paymentSystemError = true
  1984. }
  1985.  
  1986. if (!(model.paymentSystemDisabled || model.paymentSystemError) && (active && direct))
  1987. {
  1988. List<UpdateCompanyPreview> optionsPreview
  1989. def updateClient = request.JSON.updateClient
  1990. if (updateClient) {
  1991. if (updateClient.isNewClient) {
  1992. optionsPreview = companyService.previewNewClientRequirements(company, updateClient.clientState, request.JSON.features, request.JSON.limits)
  1993. } else {
  1994. def clientCompany = Company.findByPublicId(updateClient.clientId)
  1995. optionsPreview = companyService.previewUpdateCompany(clientCompany, updateClient.clientState, request.JSON.features, request.JSON.limits)
  1996. }
  1997. } else {
  1998. optionsPreview = companyService.previewUpdateCompany(company, company.state, request.JSON.features, request.JSON.limits)
  1999. }
  2000.  
  2001. if (optionsPreview != null) {
  2002. model.isCancelled = account.subscription.cancelled
  2003. if (model.isCancelled) {
  2004. model.cancellationDate = company.subscriptionExpires
  2005. } else {
  2006. List options = []
  2007. for (UpdateCompanyPreview preview : optionsPreview) {
  2008. options << [
  2009. optionToken: preview.token,
  2010. newPlan: preview.newPlan.name,
  2011. newAddOns: preview.newAddOns*.name,
  2012. modifiedTopUps: preview.modifiedTopUps*.name,
  2013. deltaAmount: preview.deltaAmount
  2014. ]
  2015. }
  2016. model.options = options
  2017. model.changeRequired = true
  2018. }
  2019. } else {
  2020. model.changeRequired = false
  2021. }
  2022. }
  2023.  
  2024. render model as JSON
  2025. }
  2026.  
  2027. def ajax_performPlanUpgradeOption = {
  2028. roleService.checkSessionPermission(session.user, "account.settings")
  2029.  
  2030. User user = userForSession()
  2031. Company company = user.company
  2032. def model = [:]
  2033.  
  2034. boolean active = companyService.checkSeatsActive(company)
  2035. boolean direct = companyService.isDirectBilled(company)
  2036.  
  2037. if (active && direct) {
  2038.  
  2039. String optionToken = request.JSON.optionToken;
  2040. companyService.updateCompany(company, optionToken)
  2041.  
  2042. session.jsEnv = userService.getJsEnvInfo(company, user)
  2043. model.jsEnv = session.jsEnv
  2044. model.success = true
  2045. }
  2046.  
  2047. render model as JSON
  2048. }
  2049.  
  2050. private void modifyAccountAndSubscription(Company company, Account account, AddressCommand addressCommand, BillingCommand billingCommand, ReceiptCommand receiptCommand, PricingModelModification pricingModelModification, BigDecimal trialDiscount)
  2051. {
  2052. // TODO: pull the preview values from config
  2053. if (!account.soldTo && !addressCommand)
  2054. addressCommand = new AddressCommand([country:"usa", state:"CA"])
  2055. if (!account.billTo && !billingCommand)
  2056. billingCommand = new BillingCommand([firstName: "preview", lastName: "preview", country:"usa", state:"CA"])
  2057. if (!receiptCommand)
  2058. receiptCommand = new ReceiptCommand([email: "preview@preview.invalid"])
  2059. Subscription subscription = account.subscription
  2060. account.name = company.name
  2061. if (addressCommand && billingCommand && receiptCommand)
  2062. {
  2063. account.setSoldTo(
  2064. billingCommand.firstName,
  2065. billingCommand.lastName,
  2066. addressCommand.street,
  2067. null,
  2068. addressCommand.city,
  2069. addressCommand.state,
  2070. addressCommand.country,
  2071. addressCommand.postalCode,
  2072. receiptCommand.email)
  2073. }
  2074. if (billingCommand && receiptCommand)
  2075. {
  2076. account.setBillTo(
  2077. billingCommand.firstName,
  2078. billingCommand.lastName,
  2079. billingCommand.street,
  2080. null,
  2081. billingCommand.city,
  2082. billingCommand.state,
  2083. billingCommand.country,
  2084. billingCommand.postalCode,
  2085. receiptCommand.email)
  2086. }
  2087. if (billingCommand)
  2088. {
  2089. if (billingCommand.cardNumber)
  2090. {
  2091. account.setCreditCardPaymentMethod(
  2092. "${billingCommand.firstName} ${billingCommand.lastName}",
  2093. billingCommand.cardNumber,
  2094. billingCommand.cardExpiryMonth,
  2095. billingCommand.cardExpiryYear,
  2096. billingCommand.cvv)
  2097. }
  2098. else
  2099. {
  2100. account.setChequePaymentMethod()
  2101. }
  2102. }
  2103.  
  2104. PricingModel pricingModel = subscription.pricingModel
  2105. pricingModel.update(pricingModelModification)
  2106. applyTrialDiscount(account, trialDiscount)
  2107. applyOneTimeDiscount(account)
  2108. }
  2109.  
  2110. private void updateAccountBillToAddress(Account account, Map json)
  2111. {
  2112. account.setBillTo(
  2113. json.billTo.firstName,
  2114. json.billTo.lastName,
  2115. json.billTo.address1,
  2116. json.billTo.address2,
  2117. json.billTo.city,
  2118. json.billTo.state,
  2119. json.billTo.country,
  2120. json.billTo.postalCode,
  2121. json.billTo.workEmail
  2122. )
  2123. }
  2124.  
  2125. private void updateAccountSoldToAddress(Account account, Map json)
  2126. {
  2127. account.setSoldTo(
  2128. json.soldTo.firstName,
  2129. json.soldTo.lastName,
  2130. json.soldTo.address1,
  2131. json.soldTo.address2,
  2132. json.soldTo.city,
  2133. json.soldTo.state,
  2134. json.soldTo.country,
  2135. json.soldTo.postalCode,
  2136. json.soldTo.workEmail
  2137. )
  2138. }
  2139.  
  2140. private void updateAccountPaymentInfo(Account account, Map json)
  2141. {
  2142. if (json.cardInfo)
  2143. {
  2144. account.setCreditCardPaymentMethod(
  2145. json.cardInfo.cardHolderName,
  2146. json.cardInfo.cardNumber,
  2147. json.cardInfo.expirationMonth,
  2148. json.cardInfo.expirationYear,
  2149. json.cardInfo.securityCode
  2150. )
  2151. }
  2152. else
  2153. {
  2154. account.setChequePaymentMethod()
  2155. }
  2156.  
  2157. }
  2158.  
  2159. private void updateAccountPurchaseOrderNumber(Account account, Map json)
  2160. {
  2161. account.setPurchaseOrderNumber(json.purchaseOrderNumber)
  2162. }
  2163.  
  2164. private void updateAccountName(Account account, Map json)
  2165. {
  2166. account.name = json.companyName
  2167. }
  2168.  
  2169. private PricingModelModification getSanitizedPricingModelModifications(String planId, Map addOns, Map topUps)
  2170. {
  2171. String newPlanId = planId
  2172. Map<String, Boolean> addOnChanges = new HashMap<String, Boolean>()
  2173. /*
  2174. newPlanId: savedPlan.planId,
  2175. addOnChanges: savedPlan.addons,
  2176. topUpChanges: savedPlan.topups
  2177. */
  2178. if (addOns instanceof Map)
  2179. {
  2180. for (Map.Entry<String, Boolean> addOnChange : addOns)
  2181. {
  2182. String addOnId = addOnChange.key
  2183. boolean selected = addOnChange.value
  2184. addOnChanges[addOnId] = selected
  2185. }
  2186. }
  2187. Map<String, BigDecimal> topUpChanges = new HashMap<String, BigDecimal>()
  2188. if (topUps instanceof Map)
  2189. {
  2190. for (Map.Entry<String, BigDecimal> topUpChange : topUps)
  2191. {
  2192. String topUpId = topUpChange.key
  2193. BigDecimal quantity = topUpChange.value
  2194. topUpChanges[topUpId] = quantity
  2195. }
  2196. }
  2197. if (!newPlanId && !addOnChanges && !topUpChanges)
  2198. return null
  2199. else
  2200. return new PricingModelModification(newPlanId, addOnChanges, topUpChanges)
  2201. }
  2202.  
  2203. static private Map getPeriodLabels()
  2204. {
  2205. return [
  2206. 1: [ cycle: "monthly", "billed": "monthly",unit: "mo" ],
  2207. 3: [ cycle: "quarterly", "billed": "quarterly", unit: "qr" ],
  2208. 6: [ cycle: "semiannual", "billed": "semiannually", unit: "6m" ],
  2209. 12: [ cycle: "annual", "billed": "annually", unit: "yr" ],
  2210. 24: [ cycle: "biennial", "billed": "biennially", unit: "2y" ]
  2211. ]
  2212. }
  2213.  
  2214. def ajax_setCompanyBillingPeriod = {
  2215. roleService.checkSessionPermission(session.user, "account.settings")
  2216. User user = userForSession(true)
  2217. try {
  2218. if (params.billingPeriod) {
  2219. if (user?.company) {
  2220. user.company.billingPeriod = Integer.valueOf(params.billingPeriod).intValue()
  2221. // We want to make sure the new billing period is set before returning it to the user.
  2222. if (user.company.save()){
  2223. def model = [billingPeriod: user.company.billingPeriod]
  2224. session.user.billingPeriod = user.company.billingPeriod;
  2225. render model as JSON
  2226. } else {
  2227. throw new ApiBadRequestException("Error validating the new billing period set for this company.")
  2228. }
  2229. } else {
  2230. throw new ApiBadRequestException("Unable to retrieve a company for the user.")
  2231. }
  2232. } else {
  2233. throw new ApiBadRequestException("Parameter billingPeriod must be provided.")
  2234. }
  2235. } catch (ApiBadRequestException e) {
  2236. def model = [
  2237. sucess: false,
  2238. status: HttpServletResponse.SC_BAD_REQUEST,
  2239. message: e.getMessage()
  2240. ]
  2241. response.status = HttpServletResponse.SC_BAD_REQUEST
  2242. render model as JSON
  2243. }
  2244. }
  2245.  
  2246. def ajax_getPlanInfo = {
  2247.  
  2248. Account account = null
  2249. Subscription subscription = null
  2250. User user = userForSession()
  2251. Company company = null
  2252.  
  2253. String currentPlanId = null
  2254. List<String> currentAddonIds = []
  2255.  
  2256. boolean isAnonymous = params.isAnonymous ? Boolean.valueOf((String) params.isAnonymous) : false
  2257.  
  2258.  
  2259. PricingModel pricingModel
  2260.  
  2261. if (user && !isAnonymous)
  2262. {
  2263. roleService.checkSessionPermission(session.user, "account.settings")
  2264. company = user.company
  2265.  
  2266. int billingPeriod = params.billingPeriod? Integer.valueOf(params.billingPeriod).intValue():company.billingPeriod
  2267.  
  2268. zuora2Service.enableAccountCacheForThread(company.publicId, new ControllerPricingModelResolver(request, response, params.pricingPlan))
  2269. account = companyService.getPaymentAccount(company, billingPeriod)
  2270. subscription = account.subscription
  2271. pricingModel = subscription.pricingModel
  2272. if (pricingModel.customPlanMessage == null)
  2273. {
  2274. currentPlanId = pricingModel.plans.find {it.selected}?.id
  2275. pricingModel.addOns.findAll {it.selected}.each {
  2276. currentAddonIds << it.id
  2277. }
  2278. subscription = companyService.modifySubscriptionToMatchCompanyLimits(company, account)
  2279. pricingModel = subscription.pricingModel
  2280. }
  2281. }
  2282. else
  2283. {
  2284. int billingPeriod = params.billingPeriod? Integer.valueOf(params.billingPeriod).intValue():Company.DEFAULT_BILLING_PERIOD
  2285. String defaultPlan = params.pricingPlan
  2286. if (user)
  2287. defaultPlan = user.company.getSettingValue(Company.PROPERTY_PRICING_MODEL, null)
  2288. account = companyService.getAnonymousPaymentAccount(new ControllerPricingModelResolver(request, response, defaultPlan), billingPeriod)
  2289. subscription = account.subscription
  2290. pricingModel = subscription.pricingModel
  2291. }
  2292.  
  2293. if (params.savedPlan)
  2294. {
  2295. def savedPlan = new JSONDeserializer().deserialize(params.savedPlan)
  2296. if (savedPlan instanceof Map)
  2297. {
  2298. if (savedPlan.modelTag == pricingModel.tag && pricingModel.plans.find {it.id == savedPlan.planId}) {
  2299. try
  2300. {
  2301. PricingModelModification modification = getSanitizedPricingModelModifications(savedPlan.planId, savedPlan.addons, savedPlan.topups)
  2302. if (modification)
  2303. {
  2304. // update the model
  2305. pricingModel.update(modification)
  2306. // update the subscription to ensure that the updated model isn't below the current limits
  2307. subscription = companyService.modifySubscriptionToMatchCompanyLimits(company, account)
  2308. // grab the updated model
  2309. pricingModel = subscription.pricingModel
  2310. }
  2311. }
  2312. catch (Exception e)
  2313. {
  2314. // if something bad happens here, just ignore the saved plan
  2315. log.info(LogBuilder.msg("an exception occurred restoring a saved plan, probably simply a bad saved plan and this exception can be ignore.").exception(e))
  2316. }
  2317. }
  2318. }
  2319. }
  2320.  
  2321. Map pricingModelMap = generatePricingModelJSONMap(company, pricingModel, currentPlanId, currentAddonIds)
  2322. def model = [
  2323. isTrial: (!user || user.company.type == Company.TYPE_TRIAL),
  2324. pricingModel: pricingModelMap,
  2325. isCancelled: subscription.isCancelled(),
  2326. cancellationDate: subscription.cancellationEffectiveDate,
  2327. presentation: [
  2328. ambiguousText: "Your plan cannot be displayed on this page.",
  2329.  
  2330. periodLabels: getPeriodLabels(),
  2331.  
  2332. plans: [
  2333. display: [
  2334. number: 4,
  2335. selectedPositions: [ 2, 3, 1, 4 ]
  2336. ],
  2337. features: [
  2338. altLimits: [
  2339. 'altlimit.unlimited': [ text: "Unl." ],
  2340. 'altlimit.month': [ suffix: "mth" ],
  2341. 'altlimit.hour': [ divide: 3600, suffix: "hr" ],
  2342. 'altlimit.minute': [ divide: 60, suffix: "m" ],
  2343. 'altlimit.thousand': [ truncate: 3, suffix: "K" ],
  2344. 'altlimit.checkmark': [ image: "https://static.klipfolio.com/static/pricing/images/feature-checkmark.png" ],
  2345. 'altlimit.cross': [ image: "https://static.klipfolio.com/static/pricing/images/feature-cross.png" ]
  2346. ]
  2347. ]
  2348. ],
  2349.  
  2350. topups: [
  2351. groups: [
  2352. [ id: "topup-group-users", name: "Users" ],
  2353. [ id: "topup-group-extras", name: "Extras" ]
  2354. ]
  2355. ]
  2356. ]
  2357. ]
  2358.  
  2359. if (params.callback) {
  2360. String dataString = new JSONSerializer().deepSerialize(model)
  2361. String jsonpCallback = params.callback + "(" + dataString + ")"
  2362.  
  2363. render text: jsonpCallback, contentType: "application/javascript"
  2364. } else {
  2365. render model as JSON
  2366. }
  2367. }
  2368.  
  2369. def ajax_validatePlan = {
  2370.  
  2371. def model = [
  2372. valid: false,
  2373. offerDiscount: false,
  2374. ]
  2375.  
  2376. if (params.planId)
  2377. {
  2378. User user = userForSession()
  2379. Company company = user.company
  2380.  
  2381. zuora2Service.enableAccountCacheForThread(company.publicId, new ControllerPricingModelResolver(request, response, null))
  2382. Account account = companyService.getPaymentAccount(company)
  2383. Subscription subscription = account.subscription
  2384. PricingModel pricingModel = subscription.pricingModel
  2385.  
  2386. if (pricingModel.customPlanMessage == null && !pricingModel.ambiguous)
  2387. {
  2388. pricingModel.update(new PricingModelModification(params.planId, [:], [:]))
  2389. pricingModel = subscription.pricingModel
  2390. Map selectedPlan = [
  2391. amount: pricingModel.selectedPlan.amount,
  2392. name: pricingModel.selectedPlan.name,
  2393. planId: pricingModel.selectedPlan.id,
  2394. billingPeriod: pricingModel.selectedPlan.billingPeriod
  2395. ]
  2396.  
  2397. // compute the delta between current usage and the selected plan
  2398. Map<String, Map> limits = new HashMap<String, Map>()
  2399. for (String limitName : pricingModel.allManagedLimitNames)
  2400. {
  2401. PricingEntitlementLimit limit = pricingModel.getLimit(limitName)
  2402. if (limit.currentLimit == null) // unlimited
  2403. continue
  2404. List limitConfigs = grailsApplication.config.webui.limits
  2405. Map limitConfig = limitConfigs.find {it.key == limitName}
  2406. BigDecimal used = limitService.getCurrentAggregateUsage(company, limitName)
  2407. if (used > limit.currentLimit || used == LimitService.UNLIMITED)
  2408. {
  2409. limits[limitName] = [
  2410. name: limitConfig.name,
  2411. usage: used,
  2412. limit: limit.currentLimit
  2413. ]
  2414. }
  2415. }
  2416. subscription = companyService.modifySubscriptionToMatchCompanyLimits(company, account)
  2417. pricingModel = subscription.pricingModel
  2418. Map actualPlan = [
  2419. amount: pricingModel.selectedPlan.amount,
  2420. name: pricingModel.selectedPlan.name,
  2421. planId: pricingModel.selectedPlan.id,
  2422. billingPeriod: pricingModel.selectedPlan.billingPeriod
  2423. ]
  2424.  
  2425. Set<String> validPlans = companyService.determineValidPlans(company, pricingModel)
  2426. model.valid = (validPlans == null || validPlans.contains(params.planId))
  2427. if (!model.valid)
  2428. {
  2429. model.offerDiscount = company.type == Company.TYPE_TRIAL
  2430. model.limits = limits
  2431. model.actualPlan = actualPlan
  2432. model.selectedPlan = selectedPlan
  2433. model.periodLabels = getPeriodLabels()
  2434. model.trialDiscount = actualPlan.amount - selectedPlan.amount
  2435. }
  2436. }
  2437. }
  2438.  
  2439. render model as JSON
  2440. }
  2441.  
  2442. def ajax_createPlanSubscription = {
  2443.  
  2444. roleService.checkSessionPermission(session.user, "account.settings")
  2445.  
  2446. User user = userForSession()
  2447. Company company = user.company
  2448. zuora2Service.enableAccountCacheForThread(company.publicId, new ControllerPricingModelResolver(request, response, params.pricingPlan))
  2449. Account account = companyService.getPaymentAccount(company)
  2450. Subscription subscription = account.subscription
  2451. if (subscription.exists())
  2452. {
  2453. render ([success: false, error: "Subscription for company ${account.accountNumber} already exists."]) as JSON
  2454. return
  2455. }
  2456. updateAccountName(account, request.JSON)
  2457. updateAccountBillToAddress(account, request.JSON)
  2458. updateAccountSoldToAddress(account, request.JSON)
  2459. updateAccountPaymentInfo(account, request.JSON)
  2460. PricingModel pricingModel = subscription.pricingModel
  2461. println("${pricingModel.plans[0].name}: ${pricingModel.plans[0].id}")
  2462. PricingModelModification modification = getSanitizedPricingModelModifications(request.JSON)
  2463. pricingModel.update(modification)
  2464. SubscribeResult result = account.createSubscription(true)
  2465.  
  2466. zuora2Service.enableAccountCacheForThread(null, null) // reset the cache
  2467. account = companyService.getPaymentAccount(company)
  2468. companyService.modifyCompanyEntitlementsToMatchSubscription(company, account)
  2469.  
  2470. render ([success: true, invoice: result.invoice]) as JSON
  2471. }
  2472.  
  2473. private PricingModelModification getSanitizedPricingModelModifications(Map json)
  2474. {
  2475. String newPlanId = json.newPlanId
  2476. Map<String, Boolean> addOnChanges = new HashMap<String, Boolean>()
  2477. if (json.addOnChanges)
  2478. {
  2479. for (Map.Entry<String, Boolean> addOnChange : json.addOnChanges.entrySet())
  2480. {
  2481. String addOnId = addOnChange.key
  2482. boolean selected = addOnChange.value
  2483. addOnChanges[addOnId] = selected
  2484. }
  2485. }
  2486. Map<String, BigDecimal> topUpChanges = new HashMap<String, BigDecimal>()
  2487. if (json.topUpChanges)
  2488. {
  2489. for (Map.Entry<String, BigDecimal> topUpChange : json.topUpChanges.entrySet())
  2490. {
  2491. String topUpId = topUpChange.key
  2492. BigDecimal quantity = topUpChange.value
  2493. addOnChanges[topUpId] = quantity
  2494. }
  2495. }
  2496. return new PricingModelModification(newPlanId, addOnChanges, topUpChanges)
  2497. }
  2498.  
  2499. def ajax_previewUpdateCompany = {
  2500.  
  2501. roleService.checkSessionPermission(session.user, "account.settings")
  2502.  
  2503. User user = userForSession()
  2504.  
  2505. Company company = user.company
  2506.  
  2507. zuora2Service.enableAccountCacheForThread(company.publicId, new ControllerPricingModelResolver(request, response))
  2508. Account account = getPaymentAccount(company, request, response)
  2509.  
  2510. List<ReconcileLimitsPreview> preview = companyService.previewUpdateCompany(company, request.JSON.newState, request.JSON.features, request.JSON.limits)
  2511.  
  2512. if (preview == null)
  2513. render "null"
  2514. else
  2515. render preview as JSON
  2516.  
  2517. }
  2518.  
  2519. def ajax_previewNewClientRequirements = {
  2520.  
  2521. roleService.checkSessionPermission(session.user, "account.settings")
  2522.  
  2523. User user = userForSession()
  2524.  
  2525. Company company = user.company
  2526.  
  2527. zuora2Service.enableAccountCacheForThread(company.publicId, new ControllerPricingModelResolver(request, response))
  2528. List<ReconcileLimitsPreview> preview = companyService.previewNewClientRequirements(company, request.JSON.newState, request.JSON.features, request.JSON.limits)
  2529. render preview as JSON
  2530. }
  2531.  
  2532. def ajax_updateCompany = {
  2533.  
  2534. roleService.checkSessionPermission(session.user, "account.settings")
  2535.  
  2536. User user = userForSession()
  2537.  
  2538. Company company = user.company
  2539.  
  2540. companyService.updateCompany(company, request.JSON.previewToken)
  2541.  
  2542. userService.updateUserSession(user, session)
  2543.  
  2544. render ([success:true]) as JSON
  2545. }
  2546.  
  2547. def ajax_updateForNewClientRequirements = {
  2548.  
  2549. roleService.checkSessionPermission(session.user, "account.settings")
  2550.  
  2551. User user = userForSession()
  2552.  
  2553. Company company = user.company
  2554.  
  2555. companyService.updateForNewClientRequirements(company, request.JSON.newState, request.JSON.features, request.JSON.limits)
  2556.  
  2557. render ([success:true]) as JSON
  2558. }
  2559.  
  2560. def ajax_confirmedPartner = {
  2561.  
  2562. roleService.checkSessionPermission(session.user, "account.settings")
  2563.  
  2564. def company = userForSession().company
  2565.  
  2566. clearPartnerProperties(company)
  2567.  
  2568. render text: "OK", contentType: "text/plain"
  2569. }
  2570.  
  2571. private void clearPartnerProperties(Company company) {
  2572. // remove partner setup company properties
  2573. company.setSetting(Company.PROPERTY_TEMPORARY_PARTNER, null)
  2574. company.setSetting(Company.PROPERTY_CONFIRM_PARTNER, null)
  2575. }
  2576.  
  2577. private Map getCardWarning(Account account)
  2578. {
  2579. if (!account.exists() || !account.hasCreditCardPaymentMethod())
  2580. return null
  2581.  
  2582. def lastTransactionStatus = account.paymentMethod.lastTransactionStatus
  2583. if (!lastTransactionStatus) return null
  2584. if (lastTransactionStatus != "Approved") return [notProcessed:true]
  2585.  
  2586. int year = account.paymentMethod.creditCardExpirationYear
  2587. int month = account.paymentMethod.creditCardExpirationMonth
  2588. String expiryString = "${year}-${month}-01"
  2589. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd")
  2590. Date expiryDate = sdf.parse(expiryString)
  2591. Date nowDate = new Date()
  2592. if (nowDate < expiryDate)
  2593. return null
  2594.  
  2595. Calendar expiryCal = new GregorianCalendar()
  2596. expiryCal.setTime(expiryDate)
  2597. expiryCal.add(Calendar.MONTH, 1)
  2598. if (nowDate < expiryCal.time)
  2599. return [expiring:true]
  2600. else
  2601. return [expired:true]
  2602. }
  2603.  
  2604. }
  2605.  
  2606. class CompanyInfoCommand implements Serializable {
  2607. String name
  2608.  
  2609. static constraints = {
  2610. name(nullable: false, blank: false)
  2611. }
  2612.  
  2613. def bindFrom(Company c) {
  2614. this.name = c.name
  2615. }
  2616.  
  2617. def applyTo(Company c) {
  2618. c.name = this.name
  2619. }
  2620.  
  2621. }
  2622.  
  2623. class AddressCommand implements Serializable {
  2624. String street
  2625. String city
  2626. String state
  2627. String postalCode
  2628. String country
  2629.  
  2630. static constraints = {
  2631. street(blank:false)
  2632. city(blank:false)
  2633. state(validator: { state, obj ->
  2634. if (state == "--") {
  2635. return ['default.blank.message']
  2636. }
  2637.  
  2638. if (obj.country == "can" || obj.country == "usa") {
  2639. if (state == null || state == "") {
  2640. return ['default.blank.message']
  2641. }
  2642. }
  2643.  
  2644. return true
  2645. })
  2646. postalCode(blank:false)
  2647. country(blank:false, validator: { country, obj ->
  2648. if (country == "---") {
  2649. return ['default.blank.message']
  2650. }
  2651.  
  2652. if (!checkCountryStates(country, obj.state)) {
  2653. return ['company.state.wrong_country']
  2654. }
  2655.  
  2656. return true
  2657. })
  2658. }
  2659.  
  2660. static Map countryStates = [
  2661. "usa": [ 'AA', 'AE', 'AP', 'AK', 'AL', 'AR', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL',
  2662. 'GA', 'GU', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME',
  2663. 'MI', 'MN', 'MO', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY',
  2664. 'OH', 'OK', 'OR', 'PA', 'PR', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VI',
  2665. 'VT', 'WA', 'WI', 'WV', 'WY'],
  2666. "can": [ 'AB', 'BC', 'MB', 'NB', 'NL', 'NS', 'NT', 'NU', 'ON', 'PE', 'QC', 'SK', 'YT' ]
  2667. ]
  2668. static boolean checkCountryStates(String country, String state)
  2669. {
  2670. ArrayList states = (ArrayList) countryStates[country]
  2671.  
  2672. if (states) {
  2673. return (states.indexOf(state) != -1)
  2674. } else {
  2675. return (state == null || state == "")
  2676. }
  2677. }
  2678.  
  2679. def bindFrom(def address) {
  2680. this.street = address.address1
  2681. this.city = address.city
  2682. this.state = address.state
  2683. this.postalCode = address.postalCode
  2684. this.country = address.country
  2685. }
  2686.  
  2687. def applyTo(Contact address) {
  2688. address.address1 = this.street
  2689. address.city = this.city
  2690. address.state = this.state
  2691. address.postalCode = this.postalCode
  2692. address.country = this.country
  2693. }
  2694.  
  2695. }
  2696.  
  2697. class ReceiptCommand implements Serializable {
  2698. String email
  2699.  
  2700. static constraints = {
  2701. email(nullable:false, blank:false, email:true)
  2702. }
  2703.  
  2704. def bindFrom(Map receiptInfo) {
  2705. this.email = receiptInfo.email
  2706. }
  2707.  
  2708.  
  2709. def applyTo(Map receiptInfo) {
  2710. receiptInfo.email = this.email
  2711. }
  2712.  
  2713. }
  2714.  
  2715. class BillingCommand implements Serializable {
  2716. String firstName
  2717. String lastName
  2718.  
  2719. String street
  2720. String city
  2721. String state
  2722. String postalCode
  2723. String country
  2724.  
  2725. boolean hasInvoicing
  2726. String cardType
  2727. String cardNumber
  2728. Integer cardExpiryMonth
  2729. Integer cardExpiryYear
  2730. String cvv
  2731.  
  2732. String purchaseOrderNum
  2733.  
  2734. static constraints = {
  2735. firstName(blank:false)
  2736. lastName(blank:false)
  2737. street(blank:false)
  2738. city(blank:false)
  2739. state(validator: { state, obj ->
  2740. if (state == "--") {
  2741. return ['default.blank.message']
  2742. }
  2743.  
  2744. if (obj.country == "can" || obj.country == "usa") {
  2745. if (state == null || state == "") {
  2746. return ['default.blank.message']
  2747. }
  2748. }
  2749.  
  2750. return true
  2751. })
  2752. postalCode(blank:false)
  2753. country(blank:false, validator: { country, obj ->
  2754. if (country == "---") {
  2755. return ['default.blank.message']
  2756. }
  2757.  
  2758. if (!checkCountryStates(country, obj.state)) {
  2759. return ['company.state.wrong_country']
  2760. }
  2761.  
  2762. return true
  2763. })
  2764.  
  2765. cardNumber(validator: { cardNumber, obj ->
  2766. if (!obj.hasInvoicing){
  2767. if(!cardNumber) {
  2768. return ['default.blank.message']
  2769. }
  2770.  
  2771. if(cardNumber && (cardNumber.length() < 13 || cardNumber.length() > 20)) {
  2772. return ['company.card.wrong_size']
  2773. }
  2774.  
  2775. if (cardNumber && !checkLuhn(cardNumber)) {
  2776. return ['company.card.bad_checksum']
  2777. }
  2778. }
  2779. return true
  2780. })
  2781. cardExpiryMonth(validator: { cardExpiryMonth, obj ->
  2782. if (!obj.hasInvoicing) {
  2783. if (!cardExpiryMonth) {
  2784. return ['default.blank.message']
  2785. }
  2786.  
  2787. if (cardExpiryMonth < 1 || cardExpiryMonth > 12) {
  2788. return ['company.card.bad_expiry_date']
  2789. }
  2790.  
  2791. Calendar cal = Calendar.getInstance()
  2792. if (cardExpiryMonth && cardExpiryMonth < cal.get(Calendar.MONTH) + 1 && obj.cardExpiryYear == cal.get(Calendar.YEAR)) {
  2793. return ['company.card.bad_expiry_date']
  2794. }
  2795. }
  2796. return true
  2797. })
  2798. cardExpiryYear(validator: { cardExpiryYear, obj ->
  2799. if (!obj.hasInvoicing) {
  2800. if (!cardExpiryYear) {
  2801. return ['default.blank.message']
  2802. }
  2803.  
  2804. Calendar cal = Calendar.getInstance()
  2805. if (cardExpiryYear && cardExpiryYear < cal.get(Calendar.YEAR)) {
  2806. return ['company.card.bad_expiry_date']
  2807. }
  2808. }
  2809. return true
  2810. })
  2811. cvv(validator: { cvv, obj ->
  2812. if (!obj.hasInvoicing) {
  2813. if( !cvv) {
  2814. return ['default.blank.message']
  2815. }
  2816.  
  2817. try {
  2818. Integer.parseInt(cvv)
  2819. } catch (Exception e) {
  2820. return ['company.card.bad_cvv']
  2821. }
  2822. }
  2823.  
  2824. return true
  2825. })
  2826. }
  2827.  
  2828. static Map countryStates = [
  2829. "usa": [ 'AA', 'AE', 'AP', 'AK', 'AL', 'AR', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL',
  2830. 'GA', 'GU', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME',
  2831. 'MI', 'MN', 'MO', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY',
  2832. 'OH', 'OK', 'OR', 'PA', 'PR', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VI',
  2833. 'VT', 'WA', 'WI', 'WV', 'WY'],
  2834. "can": [ 'AB', 'BC', 'MB', 'NB', 'NL', 'NS', 'NT', 'NU', 'ON', 'PE', 'QC', 'SK', 'YT' ]
  2835. ]
  2836. static boolean checkCountryStates(String country, String state)
  2837. {
  2838. ArrayList states = (ArrayList) countryStates[country]
  2839.  
  2840. if (states) {
  2841. return (states.indexOf(state) != -1)
  2842. } else {
  2843. return (state == null || state == "")
  2844. }
  2845. }
  2846.  
  2847. static boolean checkLuhn(String cardNumber)
  2848. {
  2849. int checksum = 0
  2850. for (int i = 0; i < cardNumber.length(); ++i)
  2851. {
  2852. int digitNumber = cardNumber.length()-i-1
  2853.  
  2854. try {
  2855. int digit = Integer.parseInt(cardNumber.substring(digitNumber, digitNumber+1))
  2856. if (i & 1) digit *= 2
  2857. if (digit >= 10) digit = 1 + digit - 10
  2858. checksum += digit
  2859. } catch (Exception e) {
  2860. return false
  2861. }
  2862. }
  2863. return !(checksum % 10)
  2864. }
  2865.  
  2866. def bindFrom(Contact billTo, PaymentMethod paymentMethod, String purchaseOrderNumber) {
  2867. this.firstName = billTo.firstName
  2868. this.lastName = billTo.lastName
  2869.  
  2870. this.street = billTo.address1
  2871. this.city = billTo.city
  2872. this.state = billTo.state
  2873. this.postalCode = billTo.postalCode
  2874. this.country = billTo.country
  2875.  
  2876. this.cardNumber = paymentMethod.creditCardMaskNumber
  2877. this.cardExpiryMonth = paymentMethod.creditCardExpirationMonth
  2878. this.cardExpiryYear = paymentMethod.creditCardExpirationYear
  2879.  
  2880. this.purchaseOrderNum = purchaseOrderNumber
  2881. }
  2882. }
Add Comment
Please, Sign In to add comment