diff options
Diffstat (limited to 'trunk/etherpad/src/etherpad/control/pro/admin/team_billing_control.js')
-rw-r--r-- | trunk/etherpad/src/etherpad/control/pro/admin/team_billing_control.js | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/trunk/etherpad/src/etherpad/control/pro/admin/team_billing_control.js b/trunk/etherpad/src/etherpad/control/pro/admin/team_billing_control.js new file mode 100644 index 0000000..5be6a0e --- /dev/null +++ b/trunk/etherpad/src/etherpad/control/pro/admin/team_billing_control.js @@ -0,0 +1,447 @@ +/** + * Copyright 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import("dateutils"); +import("email.sendEmail"); +import("fastJSON"); +import("funhtml.*"); +import("jsutils.*"); +import("sqlbase.sqlcommon.inTransaction"); +import("stringutils.*"); + +import("etherpad.billing.billing"); +import("etherpad.billing.fields"); +import("etherpad.billing.team_billing"); +import("etherpad.control.pro.admin.pro_admin_control"); +import("etherpad.globals"); +import("etherpad.helpers"); +import("etherpad.pro.domains"); +import("etherpad.pro.pro_accounts"); +import("etherpad.pro.pro_utils"); +import("etherpad.sessions"); +import("etherpad.store.checkout"); +import("etherpad.utils.*"); + +import("static.js.billing_shared.{billing=>billingJS}"); + +var billingButtonName = "Confirm" + +function _cart() { + var s = sessions.getSession(); + if (! s.proBillingCart) { + s.proBillingCart = {}; + } + return s.proBillingCart; +} + +function _billingForm() { + return renderTemplateAsString('store/eepnet-checkout/billing-info.ejs', { + cart: _cart(), + billingButtonName: billingButtonName, + billingFinalPhrase: "", + helpers: helpers, + errorIfInvalid: _errorIfInvalid, + billing: billingJS, + obfuscateCC: checkout.obfuscateCC, + dollars: checkout.dollars, + countryList: fields.countryList, + usaStateList: fields.usaStateList, + getFullSuperdomainHost: pro_utils.getFullSuperdomainHost, + showCouponCode: true, + }); +} + +function _plural(num) { + return (num == 1 ? "" : "s"); +} + +function _billingSummary(domainId, subscription) { + var paymentInfo = team_billing.getRecurringBillingInfo(domainId); + if (! paymentInfo) { + return; + } + var latestInvoice = team_billing.getLatestPaidInvoice(subscription.id); + var usersSoFar = team_billing.getMaxUsers(domainId); + var costSoFar = team_billing.calculateSubscriptionCost(usersSoFar, subscription.coupon); + + var lastPaymentString = + (latestInvoice ? + "US $"+checkout.dollars(billing.centsToDollars(latestInvoice.amt))+ + " ("+latestInvoice.users+" account"+_plural(latestInvoice.users)+")"+ + ", on "+checkout.formatDate(latestInvoice.time) : + "None"); + + var coupon = false; + if (subscription.coupon) { + println("has a coupon: "+subscription.coupon); + var cval = team_billing.getCouponValue(subscription.coupon); + coupon = []; + if (cval.freeUsers) { + coupon.push(cval.freeUsers+" free user"+(cval.freeUsers == 1 ? "" : "s")); + } + if (cval.pctDiscount) { + coupon.push(cval.pctDiscount+"% savings"); + } + coupon = coupon.join(", "); + } + + return { + fullName: paymentInfo.fullname, + paymentSummary: + paymentInfo.paymentsummary + + (paymentInfo.expiration ? + ", expires "+checkout.formatExpiration(paymentInfo.expiration) : + ""), + lastPayment: lastPaymentString, + nextPayment: checkout.formatDate(subscription.paidThrough), + maxUsers: usersSoFar, + estimatedPayment: "US $"+checkout.dollars(costSoFar), + coupon: coupon + } +} + +function _statusMessage() { + if (_cart().statusMessage) { + return toHTML(P({style: "color: green;"}, _cart().statusMessage)); + } else { + return ''; + } +} + +function renderMainPage(doEdit) { + var cart = _cart(); + var domainId = domains.getRequestDomainId(); + var subscription = team_billing.getSubscriptionForCustomer(domainId); + var pendingInvoice = team_billing.getLatestPendingInvoice(domainId) + var usersSoFar = team_billing.getMaxUsers(domainId); + var costSoFar = team_billing.calculateSubscriptionCost(usersSoFar, subscription && subscription.coupon); + + checkout.guessBillingNames(cart, pro_accounts.getSessionProAccount().fullName); + if (! cart.billingReferralCode) { + if (subscription && subscription.coupon) { + cart.billingReferralCode = subscription.coupon; + } + } + + var summary = _billingSummary(domainId, subscription); + if (! summary) { + doEdit = true; + } + + pro_admin_control.renderAdminPage('manage-billing', { + billingForm: _billingForm, + doEdit: doEdit, + paymentInfo: summary, + getFullSuperdomainHost: pro_utils.getFullSuperdomainHost, + firstCharge: checkout.formatDate(subscription ? subscription.paidThrough : dateutils.nextMonth(new Date)), + billingButtonName: billingButtonName, + errorDiv: _errorDiv, + showBackButton: (summary != undefined), + statusMessage: _statusMessage, + isBehind: (subscription ? subscription.paidThrough < Date.now() - 86400*1000 : false), + amountDue: "US $"+checkout.dollars(billing.centsToDollars(pendingInvoice ? pendingInvoice.amt : costSoFar*100)), + cart: _cart() + }); + + delete _cart().errorId; + delete _cart().errorMsg; + delete _cart().statusMessage; +} + +function render_main() { + renderMainPage(false); +} + +function render_edit() { + renderMainPage(true); +} + +function _errorDiv() { + var m = _cart().errorMsg; + if (m) { + return DIV({className: 'errormsg', id: 'errormsg'}, m); + } else { + return ''; + } +} + +function _validationError(id, errorMessage) { + var cart = _cart(); + cart.errorMsg = errorMessage; + cart.errorId = {}; + if (id instanceof Array) { + id.forEach(function(k) { + cart.errorId[k] = true; + }); + } else { + cart.errorId[id] = true; + } + response.redirect('/ep/admin/billing/edit'); +} + +function _errorIfInvalid(id) { + var cart = _cart(); + if (cart.errorId && cart.errorId[id]) { + return 'error'; + } else { + return ''; + } +} + +function paypalNotifyUrl() { + return request.scheme+"://"+pro_utils.getFullSuperdomainHost()+"/ep/store/paypalnotify"; +} + +function _paymentSummary(payInfo) { + return payInfo.cardType + " ending in " + payInfo.cardNumber.substr(-4); +} + +function _expiration(payInfo) { + return payInfo.cardExpiration; +} + +function _attemptAuthorization(success_f) { + var cart = _cart(); + var domain = domains.getRequestDomainRecord(); + var domainId = domain.id; + var domainName = domain.subDomain; + var payInfo = checkout.generatePayInfo(cart); + var proAccount = pro_accounts.getSessionProAccount(); + var fullName = cart.billingFirstName+" "+cart.billingLastName; + var email = proAccount.email; + + // PCI rules require that we not store the CVV longer than necessary to complete the transaction + var savedCvv = payInfo.cardCvv; + delete payInfo.cardCvv; + checkout.writeToEncryptedLog(fastJSON.stringify({date: String(new Date()), domain: domain, payInfo: payInfo})); + payInfo.cardCvv = savedCvv; + + var result = billing.authorizePurchase(payInfo, paypalNotifyUrl()); + if (result.status == 'success') { + billing.log({type: 'new-subscription', + name: fullName, + domainId: domainId, + domainName: domainName}); + success_f(result); + } else if (result.status == 'pending') { + _validationError('', "Your authorization is pending. When it clears, your account will be activated. "+ + "You may choose to pay by different means now, or wait until your authorization clears."); + } else if (result.status == 'failure') { + var paypalResult = result.debug; + billing.log({'type': 'FATAL', value: "Direct purchase failed on paypal.", cart: cart, paypal: paypalResult}); + checkout.validateErrorFields(_validationError, "There seems to be an error in your billing information."+ + " Please verify and correct your ", + result.errorField.userErrors); + checkout.validateErrorFields(_validationError, "The bank declined your billing information. Please try a different ", + result.errorField.permanentErrors); + _validationError('', "A temporary error has prevented processing of your payment. Please try again later."); + } else { + billing.log({'type': 'FATAL', value: "Unknown error: "+result.status+" - debug: "+result.debug}); + sendEmail('support@pad.spline.inf.fu-berlin.de', 'urgent@pad.spline.inf.fu-berlin.de', 'UNKNOWN ERROR WARNING!', {}, + "Hey,\n\nThis is a billing system error. Some unknown error occurred. "+ + "This shouldn't ever happen. Probably good to let J.D. know. <grin>\n\n"+ + fastJSON.stringify(cart)); + _validationError('', "An unknown error occurred. We're looking into it!") + } +} + +function _processNewSubscription() { + _attemptAuthorization(function(result) { + var domain = domains.getRequestDomainRecord(); + var domainId = domain.id; + var domainName = domain.subDomain; + + var cart = _cart(); + var payInfo = checkout.generatePayInfo(cart); + var proAccount = pro_accounts.getSessionProAccount(); + var fullName = cart.billingFirstName+" "+cart.billingLastName; + var email = proAccount.email; + + inTransaction(function() { + + var subscriptionId = team_billing.createSubscription(domainId, cart.billingReferralCode); + + team_billing.setRecurringBillingInfo( + domainId, + fullName, + email, + _paymentSummary(payInfo), + _expiration(payInfo), + result.purchaseInfo.paypalId); + }); + + if (globals.isProduction()) { + sendEmail('sales@pad.spline.inf.fu-berlin.de', 'sales@pad.spline.inf.fu-berlin.de', "EtherPad: New paid pro account for "+fullName, {}, + "This is an automatic notification.\n\n"+fullName+" ("+email+") successfully set up "+ + "a billing profile for domain: "+domainName+"."); + } + }); +} + +function _updateExistingSubscription(subscription) { + var cart = _cart(); + + _attemptAuthorization(function(result) { + inTransaction(function() { + var cart = _cart(); + var domain = domains.getRequestDomainId(); + var payInfo = checkout.generatePayInfo(cart); + var proAccount = pro_accounts.getSessionProAccount(); + var fullName = cart.billingFirstName+" "+cart.billingLastName; + var email = proAccount.email; + + var subscriptionId = subscription.id; + + team_billing.setRecurringBillingInfo( + domain, + fullName, + email, + _paymentSummary(payInfo), + _expiration(payInfo), + result.purchaseInfo.paypalId); + }); + }); + + if (subscription.paidThrough < new Date) { + // if they're behind, do the purchase! + if (team_billing.processSubscription(subscription)) { + cart.statusMessage = "Your payment was successful, and your account is now up to date! You will receive a receipt by email." + } else { + cart.statusMessage = "Your payment failed; you will receive further instructions by email."; + } + } +} + +function _processBillingInfo() { + var cart = _cart(); + var domain = domains.getRequestDomainId(); + + var subscription = team_billing.getSubscriptionForCustomer(domain); + if (! subscription) { + _processNewSubscription(); + response.redirect('/ep/admin/billing/'); + } else { + team_billing.updateSubscriptionCouponCode(subscription.id, cart.billingReferralCode); + if (cart.billingCCNumber.length > 0) { + _updateExistingSubscription(subscription); + } + response.redirect('/ep/admin/billing') + } +} + +function _processPaypalPurchase() { + var domain = domains.getRequestDomainId(); + billing.log({type: "paypal-attempt", + domain: domain, + message: "Someone tried to use paypal to pay for on-demand."+ + " They got an error message. If this happens a lot, we should implement paypal."}) + java.lang.Thread.sleep(5000); + _validationError('billingPurchaseType', "There was an error contacting PayPal. Please try another payment type.") +} + +function _processInvoicePurchase() { + var output = [ + "Name: "+cart.billingFirstName+" "+cart.billingLastName, + "\nAddress: ", + cart.billingAddressLine1+(cart.billingAddressLine2.length > 0 ? "\n"+cart.billingAddressLine2 : ""), + cart.billingCity + ", " + (cart.billingState.length > 0 ? cart.billingState : cart.billingProvince), + cart.billingZipCode.length > 0 ? cart.billingZipCode : cart.billingPostalCode, + cart.billingCountry, + "\nEmail: ", + pro_accounts.getSessionProAccount().email + ].join("\n"); + var recipient = (globals.isProduction() ? 'sales@pad.spline.inf.fu-berlin.de' : 'jd@appjet.com'); + sendEmail( + recipient, + 'sales@pad.spline.inf.fu-berlin.de', + 'Invoice payment request - '+pro_utils.getProRequestSubdomain(), + {}, + "Hi there,\n\nA pro user tried to pay by invoice. Their information follows."+ + "\n\nThanks!\n\n"+output); + _validationError('', "Your information has been sent to our sales department; a salesperson will contact you shortly regarding your invoice request.") +} + +function render_apply() { + var cart = _cart(); + eachProperty(request.params, function(k, v) { + if (startsWith(k, "billing")) { + if (k == "billingCCNumber" && v.charAt(0) == 'X') { return; } + cart[k] = toHTML(v); + } + }); + + if (! request.params.backbutton) { + var allPaymentFields = ["billingCCNumber", "billingExpirationMonth", "billingExpirationYear", "billingCSC", "billingAddressLine1", "billingAddressLine2", "billingCity", "billingState", "billingZipCode", "billingProvince", "billingPostalCode"]; + var allBlank = true; + allPaymentFields.forEach(function(field) { if (cart[field].length > 0) { allBlank = false; }}); + if (! allBlank) { + checkout.validateBillingCart(_validationError, cart); + } + } else { + response.redirect("/ep/admin/billing/"); + } + + var couponCode = cart.billingReferralCode; + + if (couponCode.length != 0 && (couponCode.length != 8 || ! team_billing.getCouponValue(couponCode))) { + _validationError('billingReferralCode', 'Invalid referral code entered. Please verify your code and try again.'); + } + + if (cart.billingPurchaseType == 'paypal') { + _processPaypalPurchase(); + } else if (cart.billingPurchaseType == 'invoice') { + _processInvoicePurchase(); + } + + _processBillingInfo(); +} + +function handlePaypalNotify() { + // XXX: handle delayed paypal authorization +} + +function render_invoices() { + if (request.params.id) { + var purchaseId = team_billing.getSubscriptionForCustomer(domains.getRequestDomainId()).id; + var invoice = billing.getInvoice(request.params.id); + if (invoice.purchase != purchaseId) { + response.redirect(request.path); + } + + var transaction; + var adjustments = billing.getAdjustments(invoice.id); + if (adjustments.length == 1) { + transaction = billing.getTransaction(adjustments[0].transaction); + } + + pro_admin_control.renderAdminPage('single-invoice', { + formatDate: checkout.formatDate, + dollars: checkout.dollars, + centsToDollars: billing.centsToDollars, + invoice: invoice, + transaction: transaction + }); + } else { + var invoices = team_billing.getAllInvoices(domains.getRequestDomainId()); + + pro_admin_control.renderAdminPage('billing-invoices', { + invoices: invoices, + formatDate: checkout.formatDate, + dollars: checkout.dollars, + centsToDollars: billing.centsToDollars + }); + } +}
\ No newline at end of file |