Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package com.klipfolio.saas.webui
- import com.google.common.cache.CacheBuilder
- import com.google.common.cache.CacheLoader
- import com.google.common.cache.LoadingCache
- import com.klipfolio.logs.LogBuilder
- import com.klipfolio.payment.*
- import com.klipfolio.saas.AccessDeniedException
- import com.klipfolio.saas.DomainComparator
- import com.klipfolio.saas.api.ApiBadRequestException
- import com.klipfolio.saas.domain.*
- import com.klipfolio.saas.service.LimitService
- import com.klipfolio.saas.service.RefreshLogService
- import com.klipfolio.saas.service.staleaccounts.StaleAccountService
- import com.klipfolio.saas.service.staleaccounts.StaleAccountServiceMutexException
- import com.klipfolio.saas.util.CreditCardUtil
- import com.klipfolio.saas.webui.helpers.StaleAccountParamsValidator
- import com.klipfolio.saas.webui.helpers.StaleAccountServiceException
- import com.klipfolio.zuora.*
- import com.klipfolio.zuora.internals.RatePlanImpl
- import com.mongodb.BasicDBObject
- import com.mongodb.MongoClient
- import flexjson.JSONDeserializer
- import flexjson.JSONSerializer
- import grails.converters.JSON
- import org.codehaus.groovy.grails.commons.ConfigurationHolder
- import org.codehaus.groovy.grails.web.json.JSONElement
- import org.joda.time.DateTime
- import org.joda.time.format.DateTimeFormat
- import org.joda.time.format.DateTimeFormatter
- import javax.servlet.http.HttpServletResponse
- import java.text.SimpleDateFormat
- import java.util.concurrent.TimeUnit
- class CompanyController extends DefaultSaasController {
- def companyService
- def dataRefreshService
- def eventLogService
- def limitService
- def mixPanelService
- def roleService
- def staleAccountService
- def testingService
- def userService
- def zuora2Service
- def brandService
- def klipTemplateService
- def defaultKlipsService
- def templateServiceProviderPersistenceService
- def companyPersistenceService
- def userPersistenceService
- def datasourceService
- def registeredDatasourcePersistenceService
- MongoClient mongoPool
- static private LoadingCache<String, String> htmlCache = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(new CacheLoader<String, String>() {
- @Override
- String load(String s) throws Exception
- {
- return s.toURL().getText("utf-8");
- }
- })
- def index = {
- roleService.checkSessionPermission(session.user, "account.settings")
- def user = userForSession()
- def company = user.company
- def model = [:]
- model.user = user
- model.company = company
- model.isDirectBilled = companyService.isDirectBilled(company)
- model.isOemAdmin = user.isOemAdmin()
- model.isMaster = userService.isMasterAccountMember(user)
- model.isWhiteLabel = company.getFeature("whitelabel") || company.partnerParent?.getFeature("whitelabel")
- Account account = null
- try {
- account = companyService.getPaymentAccount(company)
- } catch (ZuoraSystemDisabledException e) {
- model.paymentSystemDisabled = true
- model.dynamicPaymentSystemInfo = e.info
- }
- if (account != null) {
- model.isPlanCancelled = account.subscription.isCancelled()
- if (!model.isPlanCancelled) {
- if (account.exists()) {
- model.address = account.soldTo
- }
- // needed for the side nav
- model.cardWarning = getCardWarning(account)
- } else {
- model.cancelledDate = company.subscriptionExpires
- }
- }
- render(view: "company.general", model: model)
- }
- def info_edit = { CompanyInfoCommand infoCmd, AddressCommand addressCmd ->
- roleService.checkSessionPermission(session.user, "account.settings")
- def user = userForSession(true)
- def company = user.company
- def model = [:]
- model.user = user
- model.company = company
- model.isDirectBilled = companyService.isDirectBilled(company)
- model.isOemAdmin = user.isOemAdmin()
- Account account = null
- try {
- account = companyService.getPaymentAccount(company)
- } catch (ZuoraSystemDisabledException e) {
- model.paymentSystemDisabled = true
- model.dynamicPaymentSystemInfo = e.info
- }
- if (account != null) {
- model.isPlanCancelled = account.subscription.isCancelled()
- if (!model.isPlanCancelled) {
- // needed for the side nav
- model.cardWarning = getCardWarning(account)
- } else {
- model.cancelledDate = company.subscriptionExpires
- }
- }
- infoCmd.clearErrors()
- addressCmd.clearErrors()
- if (params.save_company_info)
- {
- def infoValid = infoCmd.validate()
- def addressValid = addressCmd.validate()
- if (!params.street && !params.city && !params.postalCode && !params.country) {
- addressValid = true
- addressCmd.clearErrors()
- }
- if (infoValid && (model.paymentSystemDisabled || model.isPlanCancelled || addressValid))
- {
- infoCmd.applyTo(company)
- def primaryUser = User.findByPublicId(params.primaryContact)
- if (primaryUser) company.primaryContact = primaryUser
- def businessUser = User.findByPublicId(params.businessContact)
- if (businessUser) company.businessContact = businessUser
- if (!model.paymentSystemDisabled && !model.isPlanCancelled) {
- account.setSoldTo(company.primaryContact.firstName, company.primaryContact.lastName, addressCmd.street, null, addressCmd.city, addressCmd.state, addressCmd.country, addressCmd.postalCode, company.primaryContact.email)
- }
- company.save(flush: true)
- if(company.isPartner) {
- companyService.updateClientSuperUsers(company)
- }
- eventLogService.info("company","company_info_edited","Success",company,userForSession())
- flash.status = [msg: "${infoCmd.name.encodeAsHTML()} updated."]
- session.jsEnv = userService.getJsEnvInfo(company, user)
- redirect(action: "index")
- }
- else
- {
- model.infoCmd = infoCmd
- model.addressCmd = addressCmd
- }
- } else {
- infoCmd.bindFrom(user.company)
- model.infoCmd = infoCmd
- if (!model.paymentSystemDisabled && !model.isPlanCancelled) {
- // TODO need handling like customerService.getCompanyAddress()?
- if (account.soldTo) {
- addressCmd.bindFrom(account.soldTo)
- }
- model.addressCmd = addressCmd
- }
- }
- model.primaryContactId = params.primaryContact ? params.primaryContact : (company.primaryContact ? company.primaryContact.publicId : 0)
- def bizContact = (company.businessContact ? company.businessContact : company.primaryContact)
- model.businessContactId = params.businessContact ? params.businessContact : (bizContact ? bizContact.publicId : 0)
- User superAdmin = User.findByPublicId(user.company.getSettingValue('oem.superuserid', 'false'))
- model.primaryUserList = []
- def allAdminUsers = roleService.getAdminUsers(user.company)
- // Remove the partner super user from the list of users as they should not be able to be set as a primary contact
- if (superAdmin) {
- allAdminUsers = allAdminUsers.findAll { it.publicId != superAdmin.publicId }
- }
- allAdminUsers.each {
- // Get all the admin users that belong to the company
- model.primaryUserList.add([key: it.publicId, lastName: it.lastName.encodeAsJavaScript(), firstName: it.firstName.encodeAsJavaScript()])
- }
- model.primaryUserList.sort({it.lastName})
- model.businessUserList = []
- def allUsers = userService.getAllUsers(user.company)
- // Remove the partner super user from the list of users as they should not be able to be set as a primary contact
- if (superAdmin) {
- allUsers = allUsers.findAll { it.user.publicId != superAdmin.publicId }
- }
- allUsers.each {
- // Get all users that belong to the company
- model.businessUserList.add([key: it.user.publicId, lastName: it.user.lastName.encodeAsJavaScript(), firstName: it.user.firstName.encodeAsJavaScript()])
- }
- model.businessUserList.sort({it.lastName})
- render(view: "company.general.edit", model: model)
- }
- def buyNow = {
- roleService.checkSessionPermission(session.user, "account.settings")
- def model = [:]
- def user = userForSession(true)
- def company = user.company
- model.user = user
- model.company = company
- Account account = null
- try {
- account = companyService.getPaymentAccount(company)
- } catch (ZuoraSystemDisabledException e) {
- //this just means there has been no account made yet
- }
- if (company.type != Company.TYPE_TRIAL && (!account.exists() || !account.isFreemium())) throw new AccessDeniedException()
- // needed for the side nav
- model.isDirectBilled = companyService.isDirectBilled(company)
- model.isOemAdmin = user.isOemAdmin()
- if (!model.isDirectBilled || model.isOemAdmin) throw new AccessDeniedException()
- // if disabled, company can't buy right now
- model.paymentSystemDisabled = !companyService.isPaymentSystemEnabled()
- if (model.paymentSystemDisabled) {
- model.dynamicPaymentSystemInfo = companyService.dynamicPaymentSystemInfo()
- }
- // company must agreement to the partner agreement before they buy
- model.showPartnerAgreement = company.isPartner
- // company might be able to extend their trial
- if (userService.isTrialExtendable(model.company) &&
- (model.company.state == Company.STATE_EXPIRED ||
- (model.company.state == Company.STATE_TRIAL && model.company.trialExpires && (model.company.trialExpires - 1) < new Date()))) {
- model.extend = true
- }
- eventLogService.info("company","company_buy_now_viewed","Buy Now!",company,user)
- render(view: "company.buy_now", model: model)
- }
- def plan_edit = {
- roleService.checkSessionPermission(session.user, "account.settings")
- def user = userForSession(true)
- def company = user.company
- def model = [:]
- model.user = user
- model.company = company
- if (company.type == Company.TYPE_TRIAL) throw new AccessDeniedException()
- // needed for the side nav
- model.isDirectBilled = companyService.isDirectBilled(company)
- model.isOemAdmin = user.isOemAdmin()
- if (!model.isDirectBilled || model.isOemAdmin) throw new AccessDeniedException()
- Account account = null
- try {
- account = companyService.getPaymentAccount(company)
- } catch (ZuoraSystemDisabledException e) {
- model.paymentSystemDisabled = true
- model.dynamicPaymentSystemInfo = e.info
- }
- if (account != null) {
- model.isPlanCancelled = account.subscription.isCancelled()
- //we don't have credit card info and account is freemium
- if(!account.hasCreditCardPaymentMethod() && account.isFreemium()){
- redirect(action: "buyNow")
- }
- if (model.isPlanCancelled) {
- model.cancelledDate = company.subscriptionExpires
- } else {
- // needed for the side nav
- model.cardWarning = getCardWarning(account)
- if (params.update_plan)
- {
- Map planDelta = (Map) new JSONDeserializer().deserialize(params.plan)
- PricingModelModification modifications = getSanitizedPricingModelModifications(planDelta.planId, planDelta.addons, planDelta.topups)
- modifyAccountAndSubscription(company, account, null, null, null, modifications, null)
- AmendResult result = account.subscription.amendSubscription(new Date(), false, false)
- companyService.modifyCompanyEntitlementsToMatchSubscription(company, account)
- session.user.hasNinjaServices = company.hasFeature(Feature.NINJA)
- session.jsEnv = userService.getJsEnvInfo(company, user)
- clearPartnerProperties(company)
- eventLogService.info("billing","payment_plan_edited","Success",company,user)
- mixPanelService.track( user , "Plan Updated", session.mixPanelEnabled )
- flash.status = [msg: "Payment plan updated."]
- changeCompanyDsRefreshRateToMax(account, company, user);
- redirect(action: "billing")
- return
- }
- else
- {
- def confirmPartner = company.getSettingValue(Company.PROPERTY_CONFIRM_PARTNER, null)
- model.showPartnerAgreement = company.isPartner && (confirmPartner != null)
- model.buyClientManagement = companyService.needToBuyClientManagement(company)
- }
- }
- }
- render(view: "company.plan.edit", model:model)
- }
- def billing = {
- roleService.checkSessionPermission(session.user, "account.settings")
- def user = userForSession(true)
- def company = user.company
- def model = [:]
- model.user = user
- model.company = company
- if (company.type == Company.TYPE_TRIAL) throw new AccessDeniedException()
- // needed for the side nav
- model.isDirectBilled = companyService.isDirectBilled(user.company)
- model.isOemAdmin = user.isOemAdmin()
- if (!model.isDirectBilled || model.isOemAdmin) throw new AccessDeniedException()
- Account account = null
- try {
- account = companyService.getPaymentAccount(company)
- } catch (ZuoraSystemDisabledException e) {
- model.paymentSystemDisabled = true
- model.dynamicPaymentSystemInfo = e.info
- }
- if (account != null) {
- model.isPlanCancelled = account.subscription.isCancelled()
- if (model.isPlanCancelled) {
- model.cancelledDate = company.subscriptionExpires
- } else {
- // needed for the side nav and page
- model.cardWarning = getCardWarning(account)
- // needed to display plan details
- Invoice invoice = account.subscription.previewAmendSubscription(new Date()).invoice
- model.invoiceItems = invoice.nextPeriodSummary
- model.invoiceAmount = invoice.nextPeriodAmount
- model.invoiceAmountWithoutTax = invoice.nextPeriodAmountWithoutTax
- model.invoiceTaxAmount = invoice.nextPeriodTaxAmount
- model.periodLabels = getPeriodLabels()
- model.period = account.subscription.billingPeriod()
- model.hasInvoicing = !account.getPaymentMethod().autoPay()
- model.billTo = account.billTo
- model.paymentMethod = account.paymentMethod
- model.purchaseOrderNumber = account.purchaseOrderNumber
- if (account.hasCreditCardPaymentMethod()) {
- model.cardTypeText = CreditCardUtil.cardNameFromType(account.paymentMethod.creditCardType)
- }
- }
- }
- render(view: "company.billing", model: model)
- }
- def billing_edit = { BillingCommand billingCmd ->
- roleService.checkSessionPermission(session.user, "account.settings")
- def user = userForSession(true)
- def company = user.company
- def model = [:]
- model.user = user
- model.company = company
- if (company.type == Company.TYPE_TRIAL) throw new AccessDeniedException()
- // needed for the side nav
- model.isDirectBilled = companyService.isDirectBilled(company)
- model.isOemAdmin = user.isOemAdmin()
- if (!model.isDirectBilled || model.isOemAdmin) throw new AccessDeniedException()
- Account account = null
- try {
- account = companyService.getPaymentAccount(company)
- } catch (ZuoraSystemDisabledException e) {
- model.paymentSystemDisabled = true
- model.dynamicPaymentSystemInfo = e.info
- }
- if (account != null) {
- if (account.subscription.isCancelled()) throw new AccessDeniedException()
- // needed for the side nav
- model.cardWarning = getCardWarning(account)
- model.hasInvoicing = !account.paymentMethod.autoPay()
- billingCmd.hasInvoicing = model.hasInvoicing
- }
- billingCmd.clearErrors()
- if (params.save_billing && !model.paymentSystemDisabled)
- {
- if (billingCmd.cardNumber)
- {
- def cardInfo = CreditCardUtil.cardInfoFromNumber(billingCmd.cardNumber)
- billingCmd.cardType = cardInfo.type
- }
- if (billingCmd.validate())
- {
- Boolean error = false;
- try {
- account.setBillTo(
- billingCmd.firstName,
- billingCmd.lastName,
- billingCmd.street,
- null,
- billingCmd.city,
- billingCmd.state,
- billingCmd.country,
- billingCmd.postalCode,
- account.billTo?.workEmail
- )
- if (billingCmd.hasInvoicing)
- {
- account.setChequePaymentMethod()
- }
- else
- {
- account.setCreditCardPaymentMethod(
- billingCmd.firstName + " " + billingCmd.lastName,
- billingCmd.cardNumber,
- billingCmd.cardExpiryMonth,
- billingCmd.cardExpiryYear,
- billingCmd.cvv
- )
- }
- } catch (ZuoraException e) {
- error = true;
- model.cardError = companyService.formatZuoraErrorMessage(e.getMessage());
- }
- if (!billingCmd.hasErrors() && !error)
- {
- eventLogService.info("billing","billing_info_edited","Success",company,user)
- if (company.state == Company.STATE_ARREARS) {
- companyService.changeCompanyState(company, Company.STATE_ACTIVE)
- // update session info
- session.user.companyState = company.state
- session.user.companyStatus = company.stateAsString()
- session.jsEnv = userService.getJsEnvInfo(company, user)
- }
- flash.status = [msg: "Billing information updated."]
- redirect(action: "billing")
- return
- }
- else {
- model.billingCmd = billingCmd;
- }
- }
- else
- {
- model.billingCmd = billingCmd
- }
- } else {
- if (!model.paymentSystemDisabled) {
- billingCmd.bindFrom(account.billTo, account.paymentMethod, account.purchaseOrderNumber)
- model.billingCmd = billingCmd
- } else {
- model.billingCmd = [:]
- }
- if (company.state == Company.STATE_ARREARS) {
- model.billingCmd.cardNumber = ""
- model.billingCmd.cardType = ""
- model.billingCmd.cardExpiryMonth = 0
- model.billingCmd.cardExpiryYear = 0
- model.billingCmd.cvv = ""
- }
- }
- model.cardTypeInfo = CreditCardUtil.getCreditCardTypeInfo()
- render(view: "company.billing.edit", model: model)
- }
- def receipts_edit = { ReceiptCommand receiptCmd ->
- roleService.checkSessionPermission(session.user, "account.settings")
- def user = userForSession(true)
- def company = user.company
- def model = [:]
- model.user = user
- model.company = company
- if (company.type == Company.TYPE_TRIAL) throw new AccessDeniedException()
- // needed for the side nav
- model.isDirectBilled = companyService.isDirectBilled(company)
- model.isOemAdmin = user.isOemAdmin()
- if (!model.isDirectBilled || model.isOemAdmin) throw new AccessDeniedException()
- Account account = null
- try {
- account = companyService.getPaymentAccount(company)
- } catch (ZuoraSystemDisabledException e) {
- model.paymentSystemDisabled = true
- model.dynamicPaymentSystemInfo = e.info
- }
- if (account != null) {
- if (account.subscription.isCancelled()) throw new AccessDeniedException()
- // needed for the side nav
- model.cardWarning = getCardWarning(account)
- model.hasInvoicing = !account.paymentMethod.autoPay()
- }
- receiptCmd.clearErrors()
- if (params.save_receipt && !model.paymentSystemDisabled)
- {
- if (receiptCmd.validate())
- {
- Contact billTo = account.getBillTo()
- account.setBillTo(
- billTo.firstName,
- billTo.lastName,
- billTo.address1,
- billTo.address2,
- billTo.city,
- billTo.state,
- billTo.country,
- billTo.postalCode,
- receiptCmd.email
- )
- eventLogService.info("billing","receipt_info_edited","Success",company,user)
- flash.status = [msg: (model.hasInvoicing ? "Invoice" : "Receipt") + " address updated."]
- redirect(action: "billing")
- }
- else
- {
- model.receiptCmd = receiptCmd
- }
- } else {
- def receiptInfo = [:]
- if (!model.paymentSystemDisabled) {
- receiptInfo.email = account.billTo?.workEmail
- }
- receiptCmd.bindFrom(receiptInfo)
- model.receiptCmd = receiptCmd
- }
- render(view: "company.receipts.edit", model: model)
- }
- def history = {
- roleService.checkSessionPermission(session.user, "account.settings")
- def user = userForSession()
- def company = user.company
- def model = [:]
- model.user = user
- model.company = company
- if (company.type == Company.TYPE_TRIAL) throw new AccessDeniedException()
- model.isDirectBilled = companyService.isDirectBilled(company)
- model.isOemAdmin = user.isOemAdmin()
- if (!model.isDirectBilled || model.isOemAdmin) throw new AccessDeniedException()
- Account account = null
- try {
- account = companyService.getPaymentAccount(company)
- } catch (ZuoraSystemDisabledException e) {
- model.paymentSystemDisabled = true
- model.dynamicPaymentSystemInfo = e.info
- }
- if (account != null) {
- model.isPlanCancelled = account.subscription.isCancelled()
- if (model.isPlanCancelled) {
- model.cancelledDate = company.subscriptionExpires
- } else {
- model.nextPayment = [
- date: account.subscription.nextPeriodStart(new Date()),
- amount: account.subscription.previewAmount()
- ]
- }
- }
- render(view: "company.history", model: model)
- }
- def published_tabs = {
- roleService.checkSessionPermission(session.user, "account.usage")
- roleService.checkSessionPermission(session.user, "tab.publish")
- def user = userForSession()
- def company = user.company
- def model = [:]
- model.company = company
- model.isDirectBilled = companyService.isDirectBilled(company)
- model.isOemAdmin = user.isOemAdmin()
- model.hasPrivateDashboards = company.hasFeature(Feature.PRIVATE_DASHBOARDS)
- model.hasPublicDashboards = company.hasFeature(Feature.PUBLIC_DASHBOARDS)
- render(view: "company.published_tabs", model: model)
- }
- def refreshes = {
- roleService.checkSessionPermission(session.user, "account.usage")
- roleService.checkSessionPermission(session.user, "admin.datasource")
- def user = userForSession(true)
- def company = user.company
- def model = [:]
- model.user = user
- model.company = company
- model.isDirectBilled = companyService.isDirectBilled(company)
- model.isOemAdmin = user.isOemAdmin()
- model.hasPrivateDashboards = company.hasFeature(Feature.PRIVATE_DASHBOARDS)
- model.hasPublicDashboards = company.hasFeature(Feature.PUBLIC_DASHBOARDS)
- Calendar cal = Calendar.getInstance()
- SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd")
- model.today = cal.getTime()
- String date = ""
- String oldestDate = ""
- cal.add(Calendar.DATE,-30)
- String dateStr = sdf.format(cal.getTime())
- int dateInt = Integer.parseInt(dateStr)
- model.records = mongoPool.getDatabase(RefreshLogService.MONGO_DB_NAME).getCollection("records", BasicDBObject.class).find(
- new BasicDBObject(["cid":model.company.id, "date":[$gte:dateInt]])).projection(
- new BasicDBObject(["_id":0,"date":1,"total":1])
- ).sort(new BasicDBObject(["date":-1])).into(new ArrayList<Map>())
- if (model.records.size()) {
- oldestDate = model.records[model.records.size() - 1]["date"]
- }
- cal.setTime(model.today)
- def map = [:]
- model.records.reverseEach{a -> map[a["date"]] = a["total"]}
- model.dates = new LinkedHashMap()
- while (!date.equals(oldestDate)){
- date = sdf.format(cal.getTime())
- dateInt = Integer.parseInt(date)
- // model.dates.put(cal.getTime(), (map[date]? map[date] : 0))
- model.dates.put(cal.getTime(), (map[dateInt]? map[dateInt] : 0))
- cal.add(Calendar.DATE,-1)
- }
- render(view: "company.refreshes", model: model)
- }
- def dashboard_properties = {
- roleService.checkSessionPermission(session.user, "account.settings")
- def model = [:]
- def user = userForSession(true)
- def company = user.company
- model.user = user
- model.company = company
- model.isDirectBilled = companyService.isDirectBilled(company)
- model.isOemAdmin = user.isOemAdmin()
- try {
- model.cardWarning = getCardWarning(companyService.getPaymentAccount(company))
- } catch (ZuoraSystemDisabledException e) {
- log.warn(LogBuilder.ex(e))
- e.printStackTrace()
- }
- render(view: "company.dashboard_properties", model: model)
- }
- def ajax_getCompanyDashboardProps = {
- roleService.checkSessionPermission(session.user, "account.settings")
- def model = [:]
- def company = Company.findByPublicId(params.id)
- def props = new ArrayList()
- if(params.filter != "editable")
- {
- props.add(name: CompanyDashboardProperty.COMPANY_ID, value: company.publicId, editable: false)
- props.add(name: CompanyDashboardProperty.COMPANY_NAME, value: company.name, editable: false)
- }
- CompanyDashboardProperty.findAllByCompany(company).sort{it.name.toLowerCase()}.each{
- props.add(name: it.name, value: it.value, editable: true)
- }
- model.props = props
- render model as JSON
- }
- def ajax_checkCompanyDashboardPropName = {
- roleService.checkSessionPermission(session.user, "account.settings")
- def model = [:]
- def company = Company.findByPublicId(params.id)
- model.companyProp = CompanyDashboardProperty.findByNameAndCompany(params.name, company)
- render model as JSON
- }
- def ajax_createCompanyDashboardProp = {
- roleService.checkSessionPermission(session.user, "account.settings")
- def company = Company.findByPublicId(params.id)
- def prop = new CompanyDashboardProperty(name: params.name, value: params.value)
- company.addToDashboardProperties(prop)
- company.save()
- render text: "OK", contentType: "text/plain"
- }
- def ajax_updateCompanyDashboardProp = {
- roleService.checkSessionPermission(session.user, "account.settings")
- def company = Company.findByPublicId(params.id)
- def prop = CompanyDashboardProperty.findByCompanyAndName(company, params.name)
- if(prop)
- {
- prop.value = params.value
- prop.save()
- }
- render text: "OK", contentType: "text/plain"
- }
- def ajax_deleteCompanyDashboardProp = {
- roleService.checkSessionPermission(session.user, "account.settings")
- def company = Company.findByPublicId(params.id)
- def prop = CompanyDashboardProperty.findByCompanyAndName(company, params.name)
- if(prop)
- {
- company.removeFromDashboardProperties(prop)
- prop.delete()
- }
- render text: "OK", contentType: "text/plain"
- }
- def ajax_createTestAccount = {
- if (!ConfigurationHolder.config.debug?.showTestAccountUI) {
- throw new AccessDeniedException("You don't have sufficient permissions.");
- }
- String baseEmail = params.baseEmail;
- int numKlips = params.int("numKlips");
- int numDatasources = params.int("numDatasources");
- Map model = [:];
- Map account = [:];
- account.isPartner = params.boolean("partner");
- account.features = params.features.tokenize(',');
- account.status = "trial";
- account.numClients = params.int("numClients");
- account.user = [:];
- account.user.email = baseEmail + "@klipfolio.com";
- account.user.password = "d4shb0ard";
- account.model = "2015-11_multiResourceBundles";
- try {
- String parentId = testingService.createTestCompany(account);
- model.success = true;
- model.user = account.user;
- Company company = companyPersistenceService.findByPublicId(parentId);
- User adminUser = userPersistenceService.findByEmail(account.user.email);
- adminUser.apiKey = baseEmail + "_admin_api_key";
- testingService.addEditorAndViewOnlyUsersToTestCompany(company, baseEmail, account.user.password);
- if(account.features.indexOf(Feature.WHITELABEL) > -1) {
- company.setSetting("whitelabel.text.productName","Bananio");
- company.setSetting("whitelabel.text.widgetName","Banana");
- company.setSetting("whitelabel.text.dashboardName","Banana Board");
- company.setSetting("brand.light.web.logo", "name");
- company.setSetting("brand.dashboard_name", "Banana Board");
- }
- testingService.createTestingKlipsFromTemplate(company, adminUser, "youtube", numKlips);
- for (int d = 0; d < numDatasources; d++) {
- defaultKlipsService.createDataSource("Test", "Test", "xml", null, "http://static.klipfolio.com/static/customers/klipfolio/scatter.xml", 86400, company, adminUser, [:]);
- }
- if (account.isPartner) {
- for (int i = 0; i < account.numClients; i++) {
- def client = [:];
- client.isPartner = false;
- client.features = params.features.tokenize(',');
- client.parent = parentId;
- client.status = "trial";
- client.user = [:];
- client.user.email = baseEmail + "_client_" + i + "@klipfolio.com";
- client.user.password = "d4shb0ard";
- String clientId = testingService.createTestCompany(client);
- Company clientCompany = companyPersistenceService.findByPublicId(clientId);
- User clientAdminUser = userPersistenceService.findByEmail(client.user.email);
- testingService.addEditorAndViewOnlyUsersToTestCompany(clientCompany, baseEmail + "_client" + i, account.user.password);
- testingService.createTestingKlipsFromTemplate(clientCompany, clientAdminUser, "youtube", numKlips);
- for (int d = 0; d < numDatasources; d++) {
- defaultKlipsService.createDataSource("Test", "Test", "xml", null, "http://static.klipfolio.com/static/customers/klipfolio/scatter.xml", 86400, clientCompany, adminUser, [:]);
- }
- }
- }
- } catch (ApiBadRequestException e){
- model.success = false;
- model.err = e.message;
- }
- render model as JSON;
- }
- def eventLog = {
- roleService.checkSessionPermission(session.user, "account.usage")
- def company = userForSession().company
- def model =[:]
- model.company = [
- name: company.name,
- isPartner: company.isPartner
- ]
- model.hasPrivateDashboards = company.hasFeature(Feature.PRIVATE_DASHBOARDS)
- model.hasPublicDashboards = company.hasFeature(Feature.PUBLIC_DASHBOARDS)
- model.users = User.executeQuery("select id,firstName,lastName from User where company=?",[company])
- render(view: "company.event_log",model: model)
- }
- def purchaseFlow = {
- init {
- action {
- roleService.checkSessionPermission(session.user, "account.settings")
- def plan = params.plan
- if (!plan) throw new AccessDeniedException()
- def user = userForSession()
- def company = user.company
- session.user.billingPeriod = company.billingPeriod
- AddressCommand addressCmd = new AddressCommand()
- flow.leavingFreemium = false;
- try {
- //if account has already been created, pre-populate address information
- Account account = companyService.getPaymentAccount(company)
- Contact addressInfo = account.getBillTo();
- if(addressInfo != null) {
- addressCmd.city = addressInfo.getCity()
- addressCmd.state = addressInfo.getState()
- addressCmd.country = addressInfo.getCountry()
- addressCmd.street = addressInfo.getAddress1()
- addressCmd.postalCode = addressInfo.getPostalCode()
- }
- // if current account plan is freemium, accessing this page means they chose something that costs $
- flow.leavingFreemium = (account.exists() ? account.isFreemium() : false);
- } catch (Exception e) {
- // should never reach this point
- }
- flow.addressCmd = addressCmd
- // need ID for the cookie that holds the plan
- flow.company = [
- publicId: company.publicId
- ]
- Map planDelta = (Map) new JSONDeserializer().deserialize(plan)
- // plan to show in the edit plan page
- flow.plan = planDelta
- // delta of what has changed in the plan
- flow.delta = getSanitizedPricingModelModifications(planDelta.planId, planDelta.addons, planDelta.topups)
- flow.trialDiscount = planDelta.trialDiscount
- // info to determine the type of card provided
- flow.cardTypeInfo = CreditCardUtil.getCreditCardTypeInfo()
- // used for showing the period unit on the confirmation page
- flow.periodLabels = getPeriodLabels()
- go_enterCompanyDetails()
- }
- on("go_enterCompanyDetails") {
- def user = userForSession()
- CompanyInfoCommand infoCmd = new CompanyInfoCommand()
- infoCmd.bindFrom(user.company)
- [ from:'init', infoCmd: infoCmd ]
- }.to("enterCompanyDetails")
- }
- enterCompanyDetails {
- render(view: "enter_company_details")
- on("nextStep") { AddressCommand addressCmd ->
- flow.addressCmd = addressCmd
- }.to("populateBilling")
- on("back_confirm").to("confirmDetails")
- on("confirm") { AddressCommand addressCmd ->
- flow.addressCmd = addressCmd
- CompanyInfoCommand infoCmd = new CompanyInfoCommand([ name: params.name ])
- flow.infoCmd = infoCmd
- def infoValid = flow.infoCmd.validate()
- def addressValid = flow.addressCmd.validate()
- if (infoValid && addressValid) {
- User user = userForSession()
- Account account = companyService.getPaymentAccount(user.company, session.user.billingPeriod)
- SubscribePreview preview = getSubscribePreview(account, [
- companyInfo: flow.infoCmd,
- address: flow.addressCmd,
- billing: flow.billingCmd,
- receipt: flow.receiptCmd,
- modifications: flow.delta,
- trialDiscount: flow.trialDiscount,
- ])
- Invoice invoice = preview.invoice
- flow.invoiceItems = invoice.invoiceItemSummary
- flow.invoiceAmount = invoice.amount
- flow.invoiceAmountWithoutTax = invoice.amountWithoutTax
- flow.invoiceTaxAmount = invoice.taxAmount
- flow.period = account.subscription.billingPeriod()
- flow.purchaseError = ""
- success()
- } else {
- error()
- }
- }.to("confirmDetails")
- }
- populateBilling {
- action {
- CompanyInfoCommand infoCmd = new CompanyInfoCommand([ name: params.name ])
- flow.infoCmd = infoCmd
- def infoValid = flow.infoCmd.validate()
- def addressValid = flow.addressCmd.validate()
- if (infoValid && addressValid) {
- def user = userForSession()
- if (!flow.billingCmd) {
- flow.billingCmd = new BillingCommand()
- flow.billingCmd.firstName = user.firstName
- flow.billingCmd.lastName = user.lastName
- flow.billingCmd.street = flow.addressCmd.street
- flow.billingCmd.city = flow.addressCmd.city
- flow.billingCmd.state = flow.addressCmd.state
- flow.billingCmd.postalCode = flow.addressCmd.postalCode
- flow.billingCmd.country = flow.addressCmd.country
- }
- if (!flow.receiptCmd) {
- flow.receiptCmd = new ReceiptCommand()
- flow.receiptCmd.email = user.email
- }
- flow.from = "enterCompanyDetails"
- flow.transition = "next"
- //check if its a free plan
- zuora2Service.enableAccountCacheForThread(user.company.publicId, new ControllerPricingModelResolver(request, response))
- Account account = companyService.getPaymentAccount(user.company, session.user.billingPeriod)
- SubscribePreview preview = getSubscribePreview(account, [
- companyInfo: [name: user.company.name],
- address: flow.addressCmd,
- billing: flow.billingCmd,
- receipt: flow.receiptCmd,
- modifications: flow.delta,
- trialDiscount: flow.trialDiscount,
- ])
- if(preview.invoice.nextPeriodAmount.equals(BigDecimal.ZERO)) {
- //uses invoicing in freemium so we don't have to ask for credit card info
- flow.hasInvoicing = true
- flow.billingCmd.hasInvoicing = true
- flow.isFreemium = true
- Invoice invoice = preview.invoice
- flow.invoiceItems = invoice.invoiceItemSummary
- flow.invoiceAmount = invoice.amount
- flow.invoiceAmountWithoutTax = invoice.amountWithoutTax
- flow.invoiceTaxAmount = invoice.taxAmount
- flow.period = account.subscription.billingPeriod()
- flow.purchaseError = ""
- confirmDetails()
- } else {
- enterBillingDetails()
- }
- } else {
- error()
- }
- }
- on("confirmDetails").to("confirmDetails")
- on("enterBillingDetails").to("enterBillingDetails")
- }
- enterBillingDetails {
- render(view: "enter_billing_details")
- on("back") {
- [ from: 'enterBillingDetails', transition:'back' ]
- }.to("enterCompanyDetails")
- on("back_confirm").to("confirmDetails")
- on("confirm") { BillingCommand billingCmd ->
- flow.billingCmd = billingCmd
- if (flow.billingCmd.cardNumber)
- {
- def cardInfo = CreditCardUtil.cardInfoFromNumber(flow.billingCmd.cardNumber)
- flow.billingCmd.cardType = cardInfo.type
- flow.cardTypeText = cardInfo.name
- flow.maskedCreditCardNumber = CreditCardUtil.maskCreditCardNumber(flow.billingCmd.cardNumber)
- }
- def billingValid = flow.billingCmd.validate()
- def receiptValid = flow.receiptCmd.validate()
- User user = userForSession()
- if (billingValid && receiptValid) {
- Company company = user.company
- zuora2Service.enableAccountCacheForThread(company.publicId, new ControllerPricingModelResolver(request, response))
- Account account = companyService.getPaymentAccount(company, session.user.billingPeriod)
- SubscribePreview preview = getSubscribePreview(account, [
- companyInfo: flow.infoCmd,
- address: flow.addressCmd,
- billing: flow.billingCmd,
- receipt: flow.receiptCmd,
- modifications: flow.delta,
- trialDiscount: flow.trialDiscount,
- ])
- Invoice invoice = preview.invoice
- flow.invoiceItems = invoice.invoiceItemSummary
- flow.invoiceAmount = invoice.amount
- flow.invoiceAmountWithoutTax = invoice.amountWithoutTax
- flow.invoiceTaxAmount = invoice.taxAmount
- flow.period = account.subscription.billingPeriod()
- flow.purchaseError = ""
- success()
- } else {
- /* Gets all error messages after running billingCmd.validate() */
- flow.billingCmd.errors?.allErrors?.each {
- String mixPanelMessage = g.message([error : it]);
- /* Only send credit card related errors to mixpanel */
- 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"])) {
- mixPanelService.track(user, "Purchase Flow Error", session.mixPanelEnabled, ["Error Type": "klipfolio - " + mixPanelMessage]);
- }
- };
- error()
- }
- }.to("confirmDetails")
- }
- confirmDetails {
- render(view: "confirm_details")
- on("editPlan") {
- [ from:'confirmDetails', transition:'edit' ]
- }.to("editPlan")
- on("enterCompanyDetails") {
- [ from:'confirmDetails', transition:'edit' ]
- }.to("enterCompanyDetails")
- on("enterBillingDetails") {
- [ from:'confirmDetails', transition:'edit' ]
- }.to("enterBillingDetails")
- on("back") {
- [ from:'confirmDetails', transition:'back' ]
- }.to("enterBillingDetails")
- on("payNow") {
- def user = userForSession()
- def company = user.company
- try
- {
- if (!company.primaryContact) {
- company.primaryContact = user
- }
- Account account = companyService.getPaymentAccount(company, session.user.billingPeriod)
- PricingModel pricingModel = account.subscription.pricingModel
- Set<String> validPlans = companyService.determineValidPlans(company, pricingModel)
- if (validPlans != null && !validPlans.contains(flow.plan.planId)) {
- flow.planUnavailable = true
- throw new ZuoraException("Selected plan is not available.", null, null)
- }
- AddressCommand addressCommand = flow.addressCmd
- BillingCommand billingCommand = flow.billingCmd
- ReceiptCommand receiptCommand = flow.receiptCmd
- PricingModelModification modifications = flow.delta
- BigDecimal trialDiscount = flow.trialDiscount
- modifyAccountAndSubscription(company, account, addressCommand, billingCommand, receiptCommand, modifications, trialDiscount)
- if(account.exists()){
- //cancel current subscription to create a new one, should only occur when leaving freemium
- account.subscription.cancel();
- //update monthly billing date to today
- String dayOfMonth = String.valueOf(Calendar.getInstance().get(Calendar.DAY_OF_MONTH));
- HashMap<String,String> updateAccountValues = [
- "BillCycleDay": dayOfMonth,
- "BcdSettingOption" : "ManualSet"
- ]
- account.updateAccount(updateAccountValues);
- }
- //create subscription will create account if it doesn't exist
- SubscribeResult result = account.createSubscription(true)
- eventLogService.info("billing","payment_plan_set_up","Success",company,user)
- if (trialDiscount && trialDiscount != BigDecimal.ZERO) {
- eventLogService.info("billing", "payment_plan_one_month_discount", "One month discount applied", company, user)
- }
- zuora2Service.enableAccountCacheForThread(null, null) // reset the cache
- account = companyService.getPaymentAccount(company, session.user.billingPeriod)
- companyService.modifyCompanyEntitlementsToMatchSubscription(company, account)
- session.user.isTrial = false
- session.user.companyState = company.state
- session.user.companyStatus = company.stateAsString()
- session.user.hasNinjaServices = company.hasFeature(Feature.NINJA)
- session.user.mixpanelData = userService.getMixpanelData(user)
- companyService.enableOlark(company, session.user)
- session.jsEnv = userService.getJsEnvInfo(company, user)
- flow.datePurchased = new Date()
- Invoice invoice = result.invoice
- flow.invoiceItems = invoice.invoiceItemSummary
- flow.invoiceAmount = invoice.amount
- flow.invoiceAmountWithoutTax = invoice.amountWithoutTax
- flow.invoiceTaxAmount = invoice.taxAmount
- flow.period = account.subscription.billingPeriod()
- flow.purchaseMade = true
- flow.mixpanelData = [ accountStatus : company.typeAsString() ]
- changeCompanyDsRefreshRateToMax(account, company, user);
- success()
- }
- catch (ZuoraException e)
- {
- flow.purchaseError = companyService.formatZuoraErrorMessage(e.getMessage());
- String mixPanelMessage = (e.getZuoraCCTransactionResultCode() == "Declined") ? "CardDeclined" : e.getZuoraCCTransactionResultCode();
- mixPanelService.track(user , "Purchase Flow Error", session.mixPanelEnabled, [ "Error Type" : "zuora - " + mixPanelMessage]);
- error()
- }
- catch (Exception e)
- {
- 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.';
- log.error(LogBuilder.msg("Unknown exception during payment flow.").exception(e).companyPublicId(company.publicId).userPublicId(user.publicId));
- error();
- }
- }.to("finished")
- }
- editPlan {
- render(view: "edit_plan")
- on("back_confirm").to("confirmDetails")
- on("confirm") {
- Map planDelta = (Map) new JSONDeserializer().deserialize(params.plan)
- if (planDelta.planId != flow.plan.planId || (planDelta?.trialDiscount ?: 0) > 0)
- flow.trialDiscount = planDelta.trialDiscount
- flow.plan = planDelta
- flow.delta = getSanitizedPricingModelModifications(planDelta.planId, planDelta.addons, planDelta.topups)
- User user = userForSession()
- Account account = companyService.getPaymentAccount(user.company, session.user.billingPeriod)
- SubscribePreview preview = getSubscribePreview(account, [
- companyInfo: flow.infoCmd,
- address: flow.addressCmd,
- billing: flow.billingCmd,
- receipt: flow.receiptCmd,
- modifications: flow.delta,
- trialDiscount: flow.trialDiscount,
- ])
- flow.period = account.subscription.billingPeriod()
- Invoice invoice = preview.invoice
- flow.invoiceItems = invoice.invoiceItemSummary
- flow.invoiceAmount = invoice.amount
- flow.invoiceAmountWithoutTax = invoice.amountWithoutTax
- flow.invoiceTaxAmount = invoice.taxAmount
- flow.period = account.subscription.billingPeriod()
- flow.purchaseError = ""
- }.to("confirmDetails")
- }
- finished {
- on("complete").to("complete")
- }
- complete {
- redirect(controller: "dashboard", action: "index")
- }
- }
- void changeCompanyDsRefreshRateToMax(Account account, Company company, User user) {
- if (account.exists()) {
- int fastestRefreshLimit = limitService.getCompanyLimit(company, "refresh_rate");
- List<RegisteredDatasource> datasourcesForUser = registeredDatasourcePersistenceService.findAllByCreatedByAndRefreshIntervalLessThan(user, fastestRefreshLimit)
- datasourcesForUser.each { datasource ->
- datasource.refreshInterval = fastestRefreshLimit
- registeredDatasourcePersistenceService.save(datasource, true)
- }
- }
- }
- private SubscribePreview getSubscribePreview(Account account, Map config)
- {
- account.name = config.companyInfo.name
- account.setBillTo(
- config.billing.firstName,
- config.billing.lastName,
- config.billing.street,
- null,
- config.billing.city,
- config.billing.state,
- config.billing.country,
- config.billing.postalCode,
- config.receipt.email
- )
- account.setSoldTo(
- config.billing.firstName,
- config.billing.lastName,
- config.address.street,
- null,
- config.address.city,
- config.address.state,
- config.address.country,
- config.address.postalCode,
- config.receipt.email
- )
- PricingModel pricingModel = account.subscription.pricingModel
- pricingModel.update(config.modifications)
- applyTrialDiscount(account, config.trialDiscount)
- applyOneTimeDiscount(account)
- SubscribePreview preview = account.previewCreateSubscription()
- return preview
- }
- private void applyTrialDiscount(Account account, BigDecimal trialDiscount) {
- if (trialDiscount)
- {
- for (RatePlan plan : account.subscription.ratePlans)
- {
- if (plan.product.isBaseProduct())
- {
- if (!plan.percentageDiscounts) // if this happens Zuora has been misconfigured, it can be fixed in Zuora
- throw new RuntimeException("Trial discount offered but no percentage discount charge available on selected product.")
- // we use BigDecimal operations directly here rather than just letting Groovy do it (Groovy uses
- // BigDecimal under the covers) because we want to specify the scale
- BigDecimal discountPercent = trialDiscount.multiply(new BigDecimal(100)).divide(plan.getAmount(), Zuora.MAX_SCALE, BigDecimal.ROUND_HALF_EVEN);
- DiscountPercentageCharge discountCharge = plan.percentageDiscounts[0]
- discountCharge.setDiscountPercentage(discountPercent)
- discountCharge.setUpToPeriods(1)
- }
- }
- }
- }
- private void applyOneTimeDiscount(Account account){
- if (!account.subscription.exists()) {
- for (RatePlanImpl plan : account.subscription.ratePlans) {
- if (plan.product.isBaseProduct()){
- plan.applyOneTimeDiscount()
- }
- }
- }
- }
- def stream_invoicePdf = {
- def user = userForSession(true)
- if (!roleService.isAdmin(user)) throw new AccessDeniedException()
- Account account = companyService.getPaymentAccount(user.company)
- Invoice invoice = account.invoices.find {it.id==params.invoice}
- byte[] invoicePdf = account.getInvoicePdf(invoice)
- // browser should cache for 1 hour..
- cache shared:true, validFor: 60*60*1
- response.setHeader("Pragma","cache")
- response.contentType = "application/pdf"
- // response.setHeader("Content-disposition", "attachment;filename=invoice${params.invoice}.pdf")
- response.contentLength = invoicePdf.length
- response.outputStream << invoicePdf
- response.outputStream.flush()
- }
- def ajax_cancelPayment = {
- def user = userForSession(true)
- if (!roleService.isAdmin(user)) throw new AccessDeniedException()
- def model = [:]
- try {
- Account account = companyService.getPaymentAccount(user.company)
- Subscription subscription = account.subscription
- Date subscriptionExpires = subscription.cancel();
- user.company.subscriptionExpires = subscriptionExpires
- user.company.save(flush:true)
- model.isPlanCancelled = true
- model.cancelledDate = g.formatDate(date: subscriptionExpires, format:'MMM dd, yyyy')
- } catch (Exception e) {
- model.isPlanCancelled = false
- }
- if(model.isPlanCancelled) {
- Map meta = [:]
- meta.isPartner = user.company.isPartner
- meta.companyName = user.company.name
- meta.userEmail = user.email
- if(user.company.partnerParent) {
- meta.partnerId = user.company.partnerParent.publicId
- }
- eventLogService.info("company", "company_plan_cancelled", "Plan cancelled", user.company, user, meta)
- }
- render model as JSON
- }
- def ajax_getPaymentHistory = {
- def user = userForSession()
- if (!roleService.isAdmin(user)) throw new AccessDeniedException()
- JSONElement props = JSON.parse(params.pagerQuery) // turn params into groovy usable JSON format
- def model = [:]
- Date zuoraStartDate = grailsApplication.config.zuora.history.startDate
- Account account = companyService.getPaymentAccount(user.company)
- List<Invoice> invoices = account.invoices.findAll { it.invoiceDate.after(zuoraStartDate) && it.amount != 0 }
- List historyList = invoices.collect {[date: it.invoiceDate, amount: it.amount, invoiceId: it.id] }
- if (props.sort) {
- historyList = historyList.sort(new DomainComparator(props.sort.name, props.sort.order))
- } else {
- historyList = historyList.sort(new DomainComparator('date', -1))
- }
- model.records = []
- model.count = historyList.size()
- def end = (props.page * props.limit) <= model.count ? props.page * props.limit : model.count
- def start = (props.page - 1) * props.limit
- DateTimeFormatter fmt = DateTimeFormat.forPattern("MMMM d, yyyy")
- for (int i = start; i < end; i++) {
- def payment = historyList.get(i)
- DateTime payDate = (payment.date == null ? null : new DateTime(payment.date))
- model.records << [
- date: (payDate != null ? fmt.print(payDate) : ""),
- amount: payment.amount,
- invoiceId: payment.invoiceId
- ]
- }
- render model as JSON
- }
- /** See {@link StaleAccountService#STALE_ACCOUNT_DELETE_KEY} for an explanation of why this is here. */
- def admin_deleteExpiredTrials = {
- try {
- def validator = new StaleAccountParamsValidator()
- validator.validatePassphrase(
- staleAccountService.getPassphrase(),
- params.get("superSecretKey")
- )
- String expiredForDays = params.get("expiredForDays")
- String incompleteForDays = params.get("incompleteForDays")
- String deleteLimit = params.get("deleteLimit")
- validator.validateLimits(
- ConfigurationHolder.config.deleteStaleAccount.expiredTrial.minimumGracePeriodDays,
- expiredForDays,
- deleteLimit,
- (null != incompleteForDays)
- )
- validator.validateLimits(
- ConfigurationHolder.config.deleteStaleAccount.expiredIncompleteTrial.minimumGracePeriodDays,
- incompleteForDays,
- deleteLimit,
- (null != expiredForDays)
- )
- } catch (StaleAccountServiceException e) {
- response.status = e.responseCode
- render([
- success: false,
- message: e.getMessage()
- ] as JSON)
- return;
- }
- int expiredForDays = params.int("expiredForDays")?:null
- int deleteLimit = params.int("deleteLimit") ?: 1;
- int incompleteForDays = params.int("incompleteForDays")?:null
- boolean preview = params.boolean("preview")?:false
- try {
- def result = [success: true]
- if (preview) {
- result.preview = true
- }
- if (expiredForDays) {
- List<Long> resultIds = staleAccountService.deleteExpiredTrials(expiredForDays, deleteLimit, preview)
- result.totalIds = resultIds.size()
- result.ids = resultIds
- }
- if (incompleteForDays) {
- List<Long> resultIds = staleAccountService.deleteIncompleteTrials(incompleteForDays, deleteLimit, preview)
- result.totalIncompleteIds = resultIds.size()
- result.incompleteIds = resultIds
- }
- render(result as JSON)
- } catch (StaleAccountServiceMutexException e) {
- response.status = 423
- render([
- success: false,
- message: e.getMessage()
- ] as JSON)
- }
- }
- /** See {@link StaleAccountService#STALE_ACCOUNT_DELETE_KEY} for an explanation of why this is here. */
- def admin_deleteDisabledAccounts = {
- try {
- def validator = new StaleAccountParamsValidator()
- validator.validatePassphrase(
- staleAccountService.getPassphrase(),
- params.get("superSecretKey")
- )
- validator.validateLimits(
- ConfigurationHolder.config.deleteStaleAccount.expiredTrial.minimumGracePeriodDays,
- params.get("lastLoginDaysAgo"),
- params.get("deleteLimit"),
- false
- )
- } catch (StaleAccountServiceException e) {
- response.status = e.responseCode
- render([
- success: false,
- message: e.getMessage()
- ] as JSON)
- return;
- }
- int lastLoginDaysAgo = params.int("lastLoginDaysAgo")
- int deleteLimit = params.int("deleteLimit") ?: 1
- boolean preview = params.boolean("preview")?:false
- try {
- def result = [success: true]
- if (preview) {
- result.preview = true
- }
- List<Long> resultIds = staleAccountService.deleteIncompleteTrials(incompleteForDays, deleteLimit, preview)
- result.totalIds = resultIds.size()
- result.ids = resultIds
- render(result as JSON)
- } catch (StaleAccountServiceMutexException e) {
- response.status = 423
- render([
- success: false,
- message: e.getMessage()
- ] as JSON)
- }
- }
- def ajax_getRefreshHistoryDatasources = {
- User user = userForSession()
- RegisteredDatasource rdi
- DatasourceInstance di
- int totalSumFromRegistered = 0
- int totalRecords = 0
- int remaining = 0
- Map model = [:]
- ArrayList keys = []
- List instances
- model.records = mongoPool.getDatabase(RefreshLogService.MONGO_DB_NAME).getCollection("records", BasicDBObject.class)
- .find(new BasicDBObject(["cid":user.companyId, "date":params.int("date")])).first()
- totalRecords = model.records.get("counts").size()
- model.dsRefreshes = [:]
- keys = dataRefreshService.parseKeysFromMongoRecords(model.records)
- if(keys) {
- instances = DatasourceInstance.createCriteria().list{"in"("id",keys)}
- }
- model.dsRefreshes.instances = [:]
- instances.each{a->
- rdi = a.datasource
- if (!model.dsRefreshes.instances[rdi.id]) model.dsRefreshes.instances[rdi.id] = [rdi.name, 0, rdi.publicId, 0] // [name, total refreshes, publicId, # instances]
- model.dsRefreshes.instances[rdi.id][1] += model.records.counts["${a.id}"]
- model.dsRefreshes.instances[rdi.id][3] += 1
- totalSumFromRegistered += model.records.counts["${a.id}"]
- }
- remaining = model.records.total - totalSumFromRegistered
- if (remaining > 0) {
- model.dsRefreshes.instances["del"] = ["Deleted", remaining, null, totalRecords - (instances ? instances.size() : 0) ]
- }
- model.dsRefreshes.instances = model.dsRefreshes.instances.values()
- model.dsRefreshes.totalInstances = totalRecords
- model.dsRefreshes.total = model.records.total
- render model.dsRefreshes as JSON
- }
- def ajax_setFirstRunProperty = {
- def user = userForSession();
- def company = user?.company
- if (company && params.name)
- {
- company.setSetting(params.name, params.value)
- session.firstrun[params.name] = params.value
- }
- render text: "OK", contentType: "text/plain"
- }
- /**
- * Ajax method to allow logging of client side events
- */
- def ajax_logEvent = {
- def user = userForSession()
- eventLogService.info(params.cat,params.event,params.desc,user.company,user)
- render text: "OK", contentType: "text/plain"
- }
- /**
- * Ajax method to collect event log info from mongodb so that an admin user may view it
- */
- def ajax_getEventLog = {
- JSONElement props = JSON.parse(params.pagerQuery)//turn params into groovy usable JSON format
- Company company =props.params.cid?Company.findByPublicId(props.params.cid): userForSession().company
- def results = [:]
- results.records = []
- results.count = 0
- String uids = ""
- int page = props.page
- int limit = props.limit
- def searchParams = [:]
- def sortParams = [:]
- searchParams['cpy'] = company.id
- try {
- //Filters
- if (!props.params.cid) searchParams['c'] = [$ne: 'exception']
- if (props.params?.user && props.params?.user != 'All') searchParams['usr'] = Integer.parseInt(props.params.user)
- if (props.params?.type && props.params.type != 'All') searchParams['c'] = props.params.type
- if (props.params?.from) {
- SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy")
- Date toDate = sdf.parse(props.params.to)
- Calendar cal = Calendar.getInstance()
- cal.setTime(toDate)
- cal.set(Calendar.HOUR, (cal.get(Calendar.HOUR) + 24))
- toDate = cal.getTime()
- searchParams['ts'] = [$gte: sdf.parse(props.params.from).time, $lte: toDate.time]
- }
- //Sorting - Mongo sort spec only accepts integer. toInteger makes sure it's integer. See: saas-4027
- if (props.sort) sortParams[props.sort.name] = props.sort.order.toInteger();
- //query database
- def cursor = eventLogService.report(searchParams, limit, page, sortParams).iterator()
- //collect results
- try {
- while (cursor.hasNext()) {
- def info = cursor.next()
- info.append("timestampDate", g.formatDate(date: new Date(info.ts), style: "MEDIUM"))
- info.cFormatted = eventLogService.prettyPrintEvents(info.c)
- info.eFormatted = eventLogService.prettyPrintEvents(info.e)
- if (info.usr) uids += info.usr + ","
- else {
- info.userId = "n/a"
- info.userName = "n/a"
- }
- results.records << info
- }
- } finally {
- cursor.close()
- }
- if (uids.length() > 0) {
- uids = uids.substring(0, uids.length() - 1)//remove trailing comma...
- def userData = User.executeQuery("select id,publicId,firstName,lastName from User where company =? and id in (" + uids + ")", [company])
- for (int $x = 0; $x < userData.size(); $x++) {
- def userResults = results.records.findAll { r -> r.usr == userData[$x][0] }
- for (int y = 0; y < userResults.size(); y++) {
- userResults[y].userId = userData[$x][1]
- userResults[y].userName = userData[$x][2] + " " + userData[$x][3]
- }
- }
- results.count = eventLogService.reportCount(searchParams)
- }
- }
- catch (Exception e) {
- log.error(LogBuilder.msg("Failed to get event log entries from mongo").exception(e))
- results.error = true
- }
- render results as JSON
- }
- private Map generatePricingFeatureJSONMap(Company company, PricingFeatureInfo featureInfo, PricingFeature feature)
- {
- Map featureMap = [
- sortKey: feature?.sortKey ?: featureInfo?.sortKey,
- id: feature?.id ?: featureInfo?.id,
- name: feature?.name ?: featureInfo?.name,
- help: feature?.help ?: featureInfo?.help,
- highlight: feature?.highlight ?: false,
- limit: feature ? (feature.unit ? feature.unit.current : 1) : 0,
- altLimit: [
- text: feature ? feature.valueText : featureInfo.missingValueText,
- image: feature ? feature.valueImage : featureInfo.missingValueImage
- ],
- finePrint: feature?.finePrint,
- ]
- if (company && feature?.limitName) {
- featureMap.limitName = feature.limitName
- featureMap.usage = limitService.getCurrentAggregateUsage(company, feature.limitName)
- }
- return featureMap
- }
- private String getHtmlValue(String text)
- {
- if (!text)
- return null;
- if (text.startsWith("http:") || text.startsWith("https:"))
- text = htmlCache.get(text)
- return text
- }
- private Map generateItemMap(PricingItem item)
- {
- Map itemMap = [
- sortKey: item.sortKey,
- id: item.id,
- productId: item.productId,
- name: item.name,
- billingPeriod: item.billingPeriod,
- description: [
- title: item.descriptionTitle,
- body: item.description
- ],
- help: item.help,
- banner: getHtmlValue(item.promotionBanner),
- topContent: getHtmlValue(item.topBanner),
- finePrint: item.finePrint,
- hidden: item.hidden,
- ]
- if (item.discountPercent) {
- itemMap.discount = [
- percentage: item.discountPercent,
- image: item.promotionBadge,
- text: item.promotionText
- ]
- }
- if (item.featureText) {
- itemMap.featured = [text: item.featureText]
- }
- return itemMap
- }
- private Map generateSelectableItemMap(SelectablePricingItem item)
- {
- Map itemMap = generateItemMap(item)
- itemMap.selected = item.selected
- if (itemMap.selected)
- itemMap.remove("discount")
- itemMap.amount = item.amount
- return itemMap
- }
- private Map generateTopUpMap(Company company, PricingTopUp topUp)
- {
- Map itemMap = generateItemMap(topUp)
- itemMap.putAll([
- group: topUp.group,
- picker: topUp.picker,
- slider: topUp.slider,
- limitName: topUp.unit.name,
- unit: [
- current: topUp.unit.current,
- price: topUp.unit.price,
- min: 0,
- max: topUp.maximum
- ]
- ])
- itemMap.description.short = getHtmlValue(topUp.htmlDescription)
- if (company)
- itemMap.unit.usage = limitService.getCurrentAggregateUsage(company, itemMap.limitName)
- if (topUp.unit.tiers && topUp.unit.tiers.size() > 1)
- {
- List tierBoundaries = new ArrayList<Integer>(topUp.unit.tiers.keySet())
- Collections.sort(tierBoundaries)
- List tiers = []
- for (int start : tierBoundaries)
- tiers << [start: start, price: topUp.unit.tiers[start]]
- itemMap.unit.tiers = tiers
- }
- return itemMap
- }
- private Map generateAddOnMap(PricingAddOn addOn, boolean current)
- {
- Map itemMap = generateSelectableItemMap(addOn)
- itemMap.includes = addOn.includes
- itemMap.current = current
- return itemMap
- }
- private Map generatePlanMap(Company company, PricingPlan plan, List<PricingFeatureInfo> featureInfos, boolean enabled, boolean current)
- {
- Map planMap = generateSelectableItemMap(plan)
- List topups = []
- for (PricingTopUp topUp : plan.topUps)
- topups << topUp.id
- planMap.topups = topups
- List addons = []
- for (PricingAddOn addon : plan.addOns)
- addons << addon.id
- planMap.addons = addons
- List features = []
- for (PricingFeatureInfo info : featureInfos)
- features << generatePricingFeatureJSONMap(company, info, plan.features[info.id])
- planMap.features = features
- // 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
- planMap.disabled = !enabled && company.type != Company.TYPE_TRIAL
- planMap.current = current
- return planMap
- }
- private Map generateFeatureInfo(PricingFeatureInfo featureInfo)
- {
- Map infoMap = [
- sortKey: featureInfo.sortKey,
- id: featureInfo.id,
- name: featureInfo.name,
- help: featureInfo.help,
- ]
- return infoMap
- }
- private Map generatePricingModelJSONMap(Company company, PricingModel model, String currentPlanId, List<String> currentAddonIds)
- {
- if (model.customPlanMessage)
- return [custom: model.customPlanMessage]
- if (model.ambiguous)
- return [ambiguous: true]
- List plans = []
- List addons = []
- List topups = []
- List featureInfo = []
- Set<String> validPlans = companyService.determineValidPlans(company, model)
- boolean selectedPlanIsHidden = false
- for (PricingPlan plan : model.plans)
- {
- plans << generatePlanMap(company, plan, model.featureList, (validPlans == null) || validPlans.contains(plan.id), currentPlanId == plan.id)
- if (plan.selected && plan.hidden)
- selectedPlanIsHidden = true
- }
- if (selectedPlanIsHidden)
- {
- boolean newPlanSelected = false
- for (Map plan : plans)
- {
- if (plan.selected)
- {
- plan.selected = false
- }
- else if (!newPlanSelected && !plan.hidden && !plan.disabled)
- {
- plan.selected = true
- newPlanSelected = true
- }
- }
- }
- for (PricingAddOn addon : model.addOns) {
- addons << generateAddOnMap(addon, currentAddonIds.find { it == addon.id } != null)
- }
- for (PricingTopUp topup : model.topUps)
- topups << generateTopUpMap(company, topup)
- for (PricingFeatureInfo info : model.featureList)
- featureInfo << generateFeatureInfo(info)
- Map modelMap = [
- ambiguous: false,
- plans: plans,
- featureList: featureInfo,
- planBottomText: model.bucketsTrailerHtml,
- showAnnual: model.showAnnual(),
- hideComparison: model.isComparisonHidden(),
- topups: topups,
- addons: addons,
- tag: model.tag,
- extraProducts: model.extraProducts,
- customizedProducts: model.modifiedProducts,
- ]
- if (modelMap.plans?.size() == 1)
- modelMap.plans[0].hidden = true
- return modelMap
- }
- def ajax_checkPlanUpgradeOptions = {
- roleService.checkSessionPermission(session.user, "account.settings")
- User user = userForSession()
- Company company = user.company
- if (companyService.ignorePayment(company)) {
- Map paymentIgnoreModel = [
- active: true,
- direct: true,
- changeRequired: false,
- ]
- render paymentIgnoreModel as JSON
- return
- }
- boolean active = companyService.checkSeatsActive(company)
- boolean direct = companyService.isDirectBilled(company)
- Map model = [
- active: active,
- direct: direct
- ]
- Account account = null
- try {
- account = companyService.getPaymentAccount(company)
- } catch (ZuoraSystemDisabledException e) {
- model.paymentSystemDisabled = true
- model.dynamicPaymentSystemInfo = e.info
- } catch (ZuoraException e) {
- model.paymentSystemError = true
- }
- if (!(model.paymentSystemDisabled || model.paymentSystemError) && (active && direct))
- {
- List<UpdateCompanyPreview> optionsPreview
- def updateClient = request.JSON.updateClient
- if (updateClient) {
- if (updateClient.isNewClient) {
- optionsPreview = companyService.previewNewClientRequirements(company, updateClient.clientState, request.JSON.features, request.JSON.limits)
- } else {
- def clientCompany = Company.findByPublicId(updateClient.clientId)
- optionsPreview = companyService.previewUpdateCompany(clientCompany, updateClient.clientState, request.JSON.features, request.JSON.limits)
- }
- } else {
- optionsPreview = companyService.previewUpdateCompany(company, company.state, request.JSON.features, request.JSON.limits)
- }
- if (optionsPreview != null) {
- model.isCancelled = account.subscription.cancelled
- if (model.isCancelled) {
- model.cancellationDate = company.subscriptionExpires
- } else {
- List options = []
- for (UpdateCompanyPreview preview : optionsPreview) {
- options << [
- optionToken: preview.token,
- newPlan: preview.newPlan.name,
- newAddOns: preview.newAddOns*.name,
- modifiedTopUps: preview.modifiedTopUps*.name,
- deltaAmount: preview.deltaAmount
- ]
- }
- model.options = options
- model.changeRequired = true
- }
- } else {
- model.changeRequired = false
- }
- }
- render model as JSON
- }
- def ajax_performPlanUpgradeOption = {
- roleService.checkSessionPermission(session.user, "account.settings")
- User user = userForSession()
- Company company = user.company
- def model = [:]
- boolean active = companyService.checkSeatsActive(company)
- boolean direct = companyService.isDirectBilled(company)
- if (active && direct) {
- String optionToken = request.JSON.optionToken;
- companyService.updateCompany(company, optionToken)
- session.jsEnv = userService.getJsEnvInfo(company, user)
- model.jsEnv = session.jsEnv
- model.success = true
- }
- render model as JSON
- }
- private void modifyAccountAndSubscription(Company company, Account account, AddressCommand addressCommand, BillingCommand billingCommand, ReceiptCommand receiptCommand, PricingModelModification pricingModelModification, BigDecimal trialDiscount)
- {
- // TODO: pull the preview values from config
- if (!account.soldTo && !addressCommand)
- addressCommand = new AddressCommand([country:"usa", state:"CA"])
- if (!account.billTo && !billingCommand)
- billingCommand = new BillingCommand([firstName: "preview", lastName: "preview", country:"usa", state:"CA"])
- if (!receiptCommand)
- receiptCommand = new ReceiptCommand([email: "preview@preview.invalid"])
- Subscription subscription = account.subscription
- account.name = company.name
- if (addressCommand && billingCommand && receiptCommand)
- {
- account.setSoldTo(
- billingCommand.firstName,
- billingCommand.lastName,
- addressCommand.street,
- null,
- addressCommand.city,
- addressCommand.state,
- addressCommand.country,
- addressCommand.postalCode,
- receiptCommand.email)
- }
- if (billingCommand && receiptCommand)
- {
- account.setBillTo(
- billingCommand.firstName,
- billingCommand.lastName,
- billingCommand.street,
- null,
- billingCommand.city,
- billingCommand.state,
- billingCommand.country,
- billingCommand.postalCode,
- receiptCommand.email)
- }
- if (billingCommand)
- {
- if (billingCommand.cardNumber)
- {
- account.setCreditCardPaymentMethod(
- "${billingCommand.firstName} ${billingCommand.lastName}",
- billingCommand.cardNumber,
- billingCommand.cardExpiryMonth,
- billingCommand.cardExpiryYear,
- billingCommand.cvv)
- }
- else
- {
- account.setChequePaymentMethod()
- }
- }
- PricingModel pricingModel = subscription.pricingModel
- pricingModel.update(pricingModelModification)
- applyTrialDiscount(account, trialDiscount)
- applyOneTimeDiscount(account)
- }
- private void updateAccountBillToAddress(Account account, Map json)
- {
- account.setBillTo(
- json.billTo.firstName,
- json.billTo.lastName,
- json.billTo.address1,
- json.billTo.address2,
- json.billTo.city,
- json.billTo.state,
- json.billTo.country,
- json.billTo.postalCode,
- json.billTo.workEmail
- )
- }
- private void updateAccountSoldToAddress(Account account, Map json)
- {
- account.setSoldTo(
- json.soldTo.firstName,
- json.soldTo.lastName,
- json.soldTo.address1,
- json.soldTo.address2,
- json.soldTo.city,
- json.soldTo.state,
- json.soldTo.country,
- json.soldTo.postalCode,
- json.soldTo.workEmail
- )
- }
- private void updateAccountPaymentInfo(Account account, Map json)
- {
- if (json.cardInfo)
- {
- account.setCreditCardPaymentMethod(
- json.cardInfo.cardHolderName,
- json.cardInfo.cardNumber,
- json.cardInfo.expirationMonth,
- json.cardInfo.expirationYear,
- json.cardInfo.securityCode
- )
- }
- else
- {
- account.setChequePaymentMethod()
- }
- }
- private void updateAccountPurchaseOrderNumber(Account account, Map json)
- {
- account.setPurchaseOrderNumber(json.purchaseOrderNumber)
- }
- private void updateAccountName(Account account, Map json)
- {
- account.name = json.companyName
- }
- private PricingModelModification getSanitizedPricingModelModifications(String planId, Map addOns, Map topUps)
- {
- String newPlanId = planId
- Map<String, Boolean> addOnChanges = new HashMap<String, Boolean>()
- /*
- newPlanId: savedPlan.planId,
- addOnChanges: savedPlan.addons,
- topUpChanges: savedPlan.topups
- */
- if (addOns instanceof Map)
- {
- for (Map.Entry<String, Boolean> addOnChange : addOns)
- {
- String addOnId = addOnChange.key
- boolean selected = addOnChange.value
- addOnChanges[addOnId] = selected
- }
- }
- Map<String, BigDecimal> topUpChanges = new HashMap<String, BigDecimal>()
- if (topUps instanceof Map)
- {
- for (Map.Entry<String, BigDecimal> topUpChange : topUps)
- {
- String topUpId = topUpChange.key
- BigDecimal quantity = topUpChange.value
- topUpChanges[topUpId] = quantity
- }
- }
- if (!newPlanId && !addOnChanges && !topUpChanges)
- return null
- else
- return new PricingModelModification(newPlanId, addOnChanges, topUpChanges)
- }
- static private Map getPeriodLabels()
- {
- return [
- 1: [ cycle: "monthly", "billed": "monthly",unit: "mo" ],
- 3: [ cycle: "quarterly", "billed": "quarterly", unit: "qr" ],
- 6: [ cycle: "semiannual", "billed": "semiannually", unit: "6m" ],
- 12: [ cycle: "annual", "billed": "annually", unit: "yr" ],
- 24: [ cycle: "biennial", "billed": "biennially", unit: "2y" ]
- ]
- }
- def ajax_setCompanyBillingPeriod = {
- roleService.checkSessionPermission(session.user, "account.settings")
- User user = userForSession(true)
- try {
- if (params.billingPeriod) {
- if (user?.company) {
- user.company.billingPeriod = Integer.valueOf(params.billingPeriod).intValue()
- // We want to make sure the new billing period is set before returning it to the user.
- if (user.company.save()){
- def model = [billingPeriod: user.company.billingPeriod]
- session.user.billingPeriod = user.company.billingPeriod;
- render model as JSON
- } else {
- throw new ApiBadRequestException("Error validating the new billing period set for this company.")
- }
- } else {
- throw new ApiBadRequestException("Unable to retrieve a company for the user.")
- }
- } else {
- throw new ApiBadRequestException("Parameter billingPeriod must be provided.")
- }
- } catch (ApiBadRequestException e) {
- def model = [
- sucess: false,
- status: HttpServletResponse.SC_BAD_REQUEST,
- message: e.getMessage()
- ]
- response.status = HttpServletResponse.SC_BAD_REQUEST
- render model as JSON
- }
- }
- def ajax_getPlanInfo = {
- Account account = null
- Subscription subscription = null
- User user = userForSession()
- Company company = null
- String currentPlanId = null
- List<String> currentAddonIds = []
- boolean isAnonymous = params.isAnonymous ? Boolean.valueOf((String) params.isAnonymous) : false
- PricingModel pricingModel
- if (user && !isAnonymous)
- {
- roleService.checkSessionPermission(session.user, "account.settings")
- company = user.company
- int billingPeriod = params.billingPeriod? Integer.valueOf(params.billingPeriod).intValue():company.billingPeriod
- zuora2Service.enableAccountCacheForThread(company.publicId, new ControllerPricingModelResolver(request, response, params.pricingPlan))
- account = companyService.getPaymentAccount(company, billingPeriod)
- subscription = account.subscription
- pricingModel = subscription.pricingModel
- if (pricingModel.customPlanMessage == null)
- {
- currentPlanId = pricingModel.plans.find {it.selected}?.id
- pricingModel.addOns.findAll {it.selected}.each {
- currentAddonIds << it.id
- }
- subscription = companyService.modifySubscriptionToMatchCompanyLimits(company, account)
- pricingModel = subscription.pricingModel
- }
- }
- else
- {
- int billingPeriod = params.billingPeriod? Integer.valueOf(params.billingPeriod).intValue():Company.DEFAULT_BILLING_PERIOD
- String defaultPlan = params.pricingPlan
- if (user)
- defaultPlan = user.company.getSettingValue(Company.PROPERTY_PRICING_MODEL, null)
- account = companyService.getAnonymousPaymentAccount(new ControllerPricingModelResolver(request, response, defaultPlan), billingPeriod)
- subscription = account.subscription
- pricingModel = subscription.pricingModel
- }
- if (params.savedPlan)
- {
- def savedPlan = new JSONDeserializer().deserialize(params.savedPlan)
- if (savedPlan instanceof Map)
- {
- if (savedPlan.modelTag == pricingModel.tag && pricingModel.plans.find {it.id == savedPlan.planId}) {
- try
- {
- PricingModelModification modification = getSanitizedPricingModelModifications(savedPlan.planId, savedPlan.addons, savedPlan.topups)
- if (modification)
- {
- // update the model
- pricingModel.update(modification)
- // update the subscription to ensure that the updated model isn't below the current limits
- subscription = companyService.modifySubscriptionToMatchCompanyLimits(company, account)
- // grab the updated model
- pricingModel = subscription.pricingModel
- }
- }
- catch (Exception e)
- {
- // if something bad happens here, just ignore the saved plan
- 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))
- }
- }
- }
- }
- Map pricingModelMap = generatePricingModelJSONMap(company, pricingModel, currentPlanId, currentAddonIds)
- def model = [
- isTrial: (!user || user.company.type == Company.TYPE_TRIAL),
- pricingModel: pricingModelMap,
- isCancelled: subscription.isCancelled(),
- cancellationDate: subscription.cancellationEffectiveDate,
- presentation: [
- ambiguousText: "Your plan cannot be displayed on this page.",
- periodLabels: getPeriodLabels(),
- plans: [
- display: [
- number: 4,
- selectedPositions: [ 2, 3, 1, 4 ]
- ],
- features: [
- altLimits: [
- 'altlimit.unlimited': [ text: "Unl." ],
- 'altlimit.month': [ suffix: "mth" ],
- 'altlimit.hour': [ divide: 3600, suffix: "hr" ],
- 'altlimit.minute': [ divide: 60, suffix: "m" ],
- 'altlimit.thousand': [ truncate: 3, suffix: "K" ],
- 'altlimit.checkmark': [ image: "https://static.klipfolio.com/static/pricing/images/feature-checkmark.png" ],
- 'altlimit.cross': [ image: "https://static.klipfolio.com/static/pricing/images/feature-cross.png" ]
- ]
- ]
- ],
- topups: [
- groups: [
- [ id: "topup-group-users", name: "Users" ],
- [ id: "topup-group-extras", name: "Extras" ]
- ]
- ]
- ]
- ]
- if (params.callback) {
- String dataString = new JSONSerializer().deepSerialize(model)
- String jsonpCallback = params.callback + "(" + dataString + ")"
- render text: jsonpCallback, contentType: "application/javascript"
- } else {
- render model as JSON
- }
- }
- def ajax_validatePlan = {
- def model = [
- valid: false,
- offerDiscount: false,
- ]
- if (params.planId)
- {
- User user = userForSession()
- Company company = user.company
- zuora2Service.enableAccountCacheForThread(company.publicId, new ControllerPricingModelResolver(request, response, null))
- Account account = companyService.getPaymentAccount(company)
- Subscription subscription = account.subscription
- PricingModel pricingModel = subscription.pricingModel
- if (pricingModel.customPlanMessage == null && !pricingModel.ambiguous)
- {
- pricingModel.update(new PricingModelModification(params.planId, [:], [:]))
- pricingModel = subscription.pricingModel
- Map selectedPlan = [
- amount: pricingModel.selectedPlan.amount,
- name: pricingModel.selectedPlan.name,
- planId: pricingModel.selectedPlan.id,
- billingPeriod: pricingModel.selectedPlan.billingPeriod
- ]
- // compute the delta between current usage and the selected plan
- Map<String, Map> limits = new HashMap<String, Map>()
- for (String limitName : pricingModel.allManagedLimitNames)
- {
- PricingEntitlementLimit limit = pricingModel.getLimit(limitName)
- if (limit.currentLimit == null) // unlimited
- continue
- List limitConfigs = grailsApplication.config.webui.limits
- Map limitConfig = limitConfigs.find {it.key == limitName}
- BigDecimal used = limitService.getCurrentAggregateUsage(company, limitName)
- if (used > limit.currentLimit || used == LimitService.UNLIMITED)
- {
- limits[limitName] = [
- name: limitConfig.name,
- usage: used,
- limit: limit.currentLimit
- ]
- }
- }
- subscription = companyService.modifySubscriptionToMatchCompanyLimits(company, account)
- pricingModel = subscription.pricingModel
- Map actualPlan = [
- amount: pricingModel.selectedPlan.amount,
- name: pricingModel.selectedPlan.name,
- planId: pricingModel.selectedPlan.id,
- billingPeriod: pricingModel.selectedPlan.billingPeriod
- ]
- Set<String> validPlans = companyService.determineValidPlans(company, pricingModel)
- model.valid = (validPlans == null || validPlans.contains(params.planId))
- if (!model.valid)
- {
- model.offerDiscount = company.type == Company.TYPE_TRIAL
- model.limits = limits
- model.actualPlan = actualPlan
- model.selectedPlan = selectedPlan
- model.periodLabels = getPeriodLabels()
- model.trialDiscount = actualPlan.amount - selectedPlan.amount
- }
- }
- }
- render model as JSON
- }
- def ajax_createPlanSubscription = {
- roleService.checkSessionPermission(session.user, "account.settings")
- User user = userForSession()
- Company company = user.company
- zuora2Service.enableAccountCacheForThread(company.publicId, new ControllerPricingModelResolver(request, response, params.pricingPlan))
- Account account = companyService.getPaymentAccount(company)
- Subscription subscription = account.subscription
- if (subscription.exists())
- {
- render ([success: false, error: "Subscription for company ${account.accountNumber} already exists."]) as JSON
- return
- }
- updateAccountName(account, request.JSON)
- updateAccountBillToAddress(account, request.JSON)
- updateAccountSoldToAddress(account, request.JSON)
- updateAccountPaymentInfo(account, request.JSON)
- PricingModel pricingModel = subscription.pricingModel
- println("${pricingModel.plans[0].name}: ${pricingModel.plans[0].id}")
- PricingModelModification modification = getSanitizedPricingModelModifications(request.JSON)
- pricingModel.update(modification)
- SubscribeResult result = account.createSubscription(true)
- zuora2Service.enableAccountCacheForThread(null, null) // reset the cache
- account = companyService.getPaymentAccount(company)
- companyService.modifyCompanyEntitlementsToMatchSubscription(company, account)
- render ([success: true, invoice: result.invoice]) as JSON
- }
- private PricingModelModification getSanitizedPricingModelModifications(Map json)
- {
- String newPlanId = json.newPlanId
- Map<String, Boolean> addOnChanges = new HashMap<String, Boolean>()
- if (json.addOnChanges)
- {
- for (Map.Entry<String, Boolean> addOnChange : json.addOnChanges.entrySet())
- {
- String addOnId = addOnChange.key
- boolean selected = addOnChange.value
- addOnChanges[addOnId] = selected
- }
- }
- Map<String, BigDecimal> topUpChanges = new HashMap<String, BigDecimal>()
- if (json.topUpChanges)
- {
- for (Map.Entry<String, BigDecimal> topUpChange : json.topUpChanges.entrySet())
- {
- String topUpId = topUpChange.key
- BigDecimal quantity = topUpChange.value
- addOnChanges[topUpId] = quantity
- }
- }
- return new PricingModelModification(newPlanId, addOnChanges, topUpChanges)
- }
- def ajax_previewUpdateCompany = {
- roleService.checkSessionPermission(session.user, "account.settings")
- User user = userForSession()
- Company company = user.company
- zuora2Service.enableAccountCacheForThread(company.publicId, new ControllerPricingModelResolver(request, response))
- Account account = getPaymentAccount(company, request, response)
- List<ReconcileLimitsPreview> preview = companyService.previewUpdateCompany(company, request.JSON.newState, request.JSON.features, request.JSON.limits)
- if (preview == null)
- render "null"
- else
- render preview as JSON
- }
- def ajax_previewNewClientRequirements = {
- roleService.checkSessionPermission(session.user, "account.settings")
- User user = userForSession()
- Company company = user.company
- zuora2Service.enableAccountCacheForThread(company.publicId, new ControllerPricingModelResolver(request, response))
- List<ReconcileLimitsPreview> preview = companyService.previewNewClientRequirements(company, request.JSON.newState, request.JSON.features, request.JSON.limits)
- render preview as JSON
- }
- def ajax_updateCompany = {
- roleService.checkSessionPermission(session.user, "account.settings")
- User user = userForSession()
- Company company = user.company
- companyService.updateCompany(company, request.JSON.previewToken)
- userService.updateUserSession(user, session)
- render ([success:true]) as JSON
- }
- def ajax_updateForNewClientRequirements = {
- roleService.checkSessionPermission(session.user, "account.settings")
- User user = userForSession()
- Company company = user.company
- companyService.updateForNewClientRequirements(company, request.JSON.newState, request.JSON.features, request.JSON.limits)
- render ([success:true]) as JSON
- }
- def ajax_confirmedPartner = {
- roleService.checkSessionPermission(session.user, "account.settings")
- def company = userForSession().company
- clearPartnerProperties(company)
- render text: "OK", contentType: "text/plain"
- }
- private void clearPartnerProperties(Company company) {
- // remove partner setup company properties
- company.setSetting(Company.PROPERTY_TEMPORARY_PARTNER, null)
- company.setSetting(Company.PROPERTY_CONFIRM_PARTNER, null)
- }
- private Map getCardWarning(Account account)
- {
- if (!account.exists() || !account.hasCreditCardPaymentMethod())
- return null
- def lastTransactionStatus = account.paymentMethod.lastTransactionStatus
- if (!lastTransactionStatus) return null
- if (lastTransactionStatus != "Approved") return [notProcessed:true]
- int year = account.paymentMethod.creditCardExpirationYear
- int month = account.paymentMethod.creditCardExpirationMonth
- String expiryString = "${year}-${month}-01"
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd")
- Date expiryDate = sdf.parse(expiryString)
- Date nowDate = new Date()
- if (nowDate < expiryDate)
- return null
- Calendar expiryCal = new GregorianCalendar()
- expiryCal.setTime(expiryDate)
- expiryCal.add(Calendar.MONTH, 1)
- if (nowDate < expiryCal.time)
- return [expiring:true]
- else
- return [expired:true]
- }
- }
- class CompanyInfoCommand implements Serializable {
- String name
- static constraints = {
- name(nullable: false, blank: false)
- }
- def bindFrom(Company c) {
- this.name = c.name
- }
- def applyTo(Company c) {
- c.name = this.name
- }
- }
- class AddressCommand implements Serializable {
- String street
- String city
- String state
- String postalCode
- String country
- static constraints = {
- street(blank:false)
- city(blank:false)
- state(validator: { state, obj ->
- if (state == "--") {
- return ['default.blank.message']
- }
- if (obj.country == "can" || obj.country == "usa") {
- if (state == null || state == "") {
- return ['default.blank.message']
- }
- }
- return true
- })
- postalCode(blank:false)
- country(blank:false, validator: { country, obj ->
- if (country == "---") {
- return ['default.blank.message']
- }
- if (!checkCountryStates(country, obj.state)) {
- return ['company.state.wrong_country']
- }
- return true
- })
- }
- static Map countryStates = [
- "usa": [ 'AA', 'AE', 'AP', 'AK', 'AL', 'AR', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL',
- 'GA', 'GU', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME',
- 'MI', 'MN', 'MO', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY',
- 'OH', 'OK', 'OR', 'PA', 'PR', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VI',
- 'VT', 'WA', 'WI', 'WV', 'WY'],
- "can": [ 'AB', 'BC', 'MB', 'NB', 'NL', 'NS', 'NT', 'NU', 'ON', 'PE', 'QC', 'SK', 'YT' ]
- ]
- static boolean checkCountryStates(String country, String state)
- {
- ArrayList states = (ArrayList) countryStates[country]
- if (states) {
- return (states.indexOf(state) != -1)
- } else {
- return (state == null || state == "")
- }
- }
- def bindFrom(def address) {
- this.street = address.address1
- this.city = address.city
- this.state = address.state
- this.postalCode = address.postalCode
- this.country = address.country
- }
- def applyTo(Contact address) {
- address.address1 = this.street
- address.city = this.city
- address.state = this.state
- address.postalCode = this.postalCode
- address.country = this.country
- }
- }
- class ReceiptCommand implements Serializable {
- String email
- static constraints = {
- email(nullable:false, blank:false, email:true)
- }
- def bindFrom(Map receiptInfo) {
- this.email = receiptInfo.email
- }
- def applyTo(Map receiptInfo) {
- receiptInfo.email = this.email
- }
- }
- class BillingCommand implements Serializable {
- String firstName
- String lastName
- String street
- String city
- String state
- String postalCode
- String country
- boolean hasInvoicing
- String cardType
- String cardNumber
- Integer cardExpiryMonth
- Integer cardExpiryYear
- String cvv
- String purchaseOrderNum
- static constraints = {
- firstName(blank:false)
- lastName(blank:false)
- street(blank:false)
- city(blank:false)
- state(validator: { state, obj ->
- if (state == "--") {
- return ['default.blank.message']
- }
- if (obj.country == "can" || obj.country == "usa") {
- if (state == null || state == "") {
- return ['default.blank.message']
- }
- }
- return true
- })
- postalCode(blank:false)
- country(blank:false, validator: { country, obj ->
- if (country == "---") {
- return ['default.blank.message']
- }
- if (!checkCountryStates(country, obj.state)) {
- return ['company.state.wrong_country']
- }
- return true
- })
- cardNumber(validator: { cardNumber, obj ->
- if (!obj.hasInvoicing){
- if(!cardNumber) {
- return ['default.blank.message']
- }
- if(cardNumber && (cardNumber.length() < 13 || cardNumber.length() > 20)) {
- return ['company.card.wrong_size']
- }
- if (cardNumber && !checkLuhn(cardNumber)) {
- return ['company.card.bad_checksum']
- }
- }
- return true
- })
- cardExpiryMonth(validator: { cardExpiryMonth, obj ->
- if (!obj.hasInvoicing) {
- if (!cardExpiryMonth) {
- return ['default.blank.message']
- }
- if (cardExpiryMonth < 1 || cardExpiryMonth > 12) {
- return ['company.card.bad_expiry_date']
- }
- Calendar cal = Calendar.getInstance()
- if (cardExpiryMonth && cardExpiryMonth < cal.get(Calendar.MONTH) + 1 && obj.cardExpiryYear == cal.get(Calendar.YEAR)) {
- return ['company.card.bad_expiry_date']
- }
- }
- return true
- })
- cardExpiryYear(validator: { cardExpiryYear, obj ->
- if (!obj.hasInvoicing) {
- if (!cardExpiryYear) {
- return ['default.blank.message']
- }
- Calendar cal = Calendar.getInstance()
- if (cardExpiryYear && cardExpiryYear < cal.get(Calendar.YEAR)) {
- return ['company.card.bad_expiry_date']
- }
- }
- return true
- })
- cvv(validator: { cvv, obj ->
- if (!obj.hasInvoicing) {
- if( !cvv) {
- return ['default.blank.message']
- }
- try {
- Integer.parseInt(cvv)
- } catch (Exception e) {
- return ['company.card.bad_cvv']
- }
- }
- return true
- })
- }
- static Map countryStates = [
- "usa": [ 'AA', 'AE', 'AP', 'AK', 'AL', 'AR', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL',
- 'GA', 'GU', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME',
- 'MI', 'MN', 'MO', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY',
- 'OH', 'OK', 'OR', 'PA', 'PR', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VI',
- 'VT', 'WA', 'WI', 'WV', 'WY'],
- "can": [ 'AB', 'BC', 'MB', 'NB', 'NL', 'NS', 'NT', 'NU', 'ON', 'PE', 'QC', 'SK', 'YT' ]
- ]
- static boolean checkCountryStates(String country, String state)
- {
- ArrayList states = (ArrayList) countryStates[country]
- if (states) {
- return (states.indexOf(state) != -1)
- } else {
- return (state == null || state == "")
- }
- }
- static boolean checkLuhn(String cardNumber)
- {
- int checksum = 0
- for (int i = 0; i < cardNumber.length(); ++i)
- {
- int digitNumber = cardNumber.length()-i-1
- try {
- int digit = Integer.parseInt(cardNumber.substring(digitNumber, digitNumber+1))
- if (i & 1) digit *= 2
- if (digit >= 10) digit = 1 + digit - 10
- checksum += digit
- } catch (Exception e) {
- return false
- }
- }
- return !(checksum % 10)
- }
- def bindFrom(Contact billTo, PaymentMethod paymentMethod, String purchaseOrderNumber) {
- this.firstName = billTo.firstName
- this.lastName = billTo.lastName
- this.street = billTo.address1
- this.city = billTo.city
- this.state = billTo.state
- this.postalCode = billTo.postalCode
- this.country = billTo.country
- this.cardNumber = paymentMethod.creditCardMaskNumber
- this.cardExpiryMonth = paymentMethod.creditCardExpirationMonth
- this.cardExpiryYear = paymentMethod.creditCardExpirationYear
- this.purchaseOrderNum = purchaseOrderNumber
- }
- }
Add Comment
Please, Sign In to add comment