コード例 #1
0
ファイル: LcLogger.cs プロジェクト: loconomics/loconomics
    public static void LogAspnetError(Exception ex)
    {
        var Request = HttpContext.Current.Request;

        using (var logger = new LcLogger("aspnet-errors"))
        {
            try
            {
                logger.Log("Page error, unhandled exception caugth at Global.asax, context:");
                if (WebMatrix.WebData.WebSecurity.IsAuthenticated)
                {
                    logger.Log("User:: {0}:{1}", WebMatrix.WebData.WebSecurity.CurrentUserId, WebMatrix.WebData.WebSecurity.CurrentUserName);
                }
                else
                {
                    logger.Log("User:: Anonymous");
                }
                logger.Log("Request:: {0} {1}", Request.HttpMethod, Request.RawUrl);
                logger.Log("User-Agent:: {0}", Request.UserAgent);
                try
                {
                    logger.Log("Form Data::");
                    logger.LogData(ASP.LcHelpers.NameValueToString(Request.Form));
                }
                catch { }
                logger.LogEx("Page error details", ex);

                // Send Email too
                LcMessaging.NotifyError("Catch at logger", Request.RawUrl, logger.ToString());
            }
            catch { }
            logger.Save();
        }
    }
コード例 #2
0
    public static void checkAccountIsConfirmed(string username)
    {
        // #454: User is auto-logged on registering, allowing it to do the Onboarding,
        // But next times, it is required to confirm email before logged.
        // Since we set IsConfirmed as true on database to let 'auto-logging on register',
        // we must check for the existance of a confirmation token:
        var userId = WebSecurity.GetUserId(username);

        using (var db = new LcDatabase())
        {
            string token = LcAuth.GetConfirmationToken(userId);

            if (userId > -1 && !string.IsNullOrWhiteSpace(token))
            {
                // Resend confirmation mail
                var confirmationUrl = LcUrl.LangUrl + "Account/Confirm/?confirmationCode=" + Uri.EscapeDataString(token ?? "");

                var isProvider = (bool)(db.QueryValue("SELECT IsProvider FROM users WHERE UserID=@0", userId) ?? false);

                if (isProvider)
                {
                    LcMessaging.SendWelcomeProvider(userId, username);
                }
                else
                {
                    LcMessaging.SendWelcomeCustomer(userId, username);
                }

                /// http 409:Conflict
                throw new HttpException(409, "Your account has not yet been confirmed. Please check your inbox and spam folders and click on the e-mail sent.");
            }
        }
    }
コード例 #3
0
    public static bool Login(string email, string password, bool persistCookie = false)
    {
        if (IsAccountLockedOut(email))
        {
            throw new ConstraintException(AccountLockedErrorMessage);
        }
        // Navigate back to the homepage and exit
        var logged = WebSecurity.Login(email, password, persistCookie);

        if (logged)
        {
            LcData.UserInfo.RegisterLastLoginTime(0, email);

            // mark the user as logged in via a normal account,
            // as opposed to via an OAuth or OpenID provider.
            System.Web.HttpContext.Current.Session["OAuthLoggedIn"] = false;
        }
        else
        {
            // Per issue #982, HIPAA rules:
            // Check if, by failling this login attempt, the user gets it's account locked-out
            if (IsAccountLockedOut(email))
            {
                // then, notify us
                LcMessaging.NotifyLockedAccount(email, WebSecurity.GetUserId(email), DateTime.Now);
                // Rather than communicate a 'invalid user password' let the user know that now it's user
                // is locked out due to many unsuccessful attempts (preventing from try again something that, by sure, will be locked,
                // and avoiding misperception of 6 allowed attempts).
                throw new ConstraintException(AccountLockedErrorMessage);
            }
        }

        return(logged);
    }
コード例 #4
0
    public static void RequestBackgroundCheck(int userId, int backgroundCheckId)
    {
        using (var db = Database.Open("sqlloco")) {
            // Save request in database as 'pending'
            db.Execute(@"
                INSERT INTO UserBackgroundCheck (
                    UserID,
                    BackgroundCheckID,
                    StatusID,
                    LastVerifiedDate,
                    CreatedDate,
                    ModifiedDate,
                    ModifiedBy
                ) VALUES (
                    @0, @1,
                    1, --status: pending
                    getdate(),
                    getdate(), getdate(), 'sys'
                )

                -- Check Alert
                EXEC TestAlertBackgroundCheck @0
            ", userId, backgroundCheckId);

            // Send email to loconomics
            LcMessaging.SendMail("*****@*****.**",
                                 "[Action Required] Background check request",
                                 LcMessaging.ApplyTemplate(LcUrl.LangPath + "Email/EmailBackgroundCheckRequest/",
                                                           new Dictionary <string, object> {
                { "ProviderUserID", userId },
                { "BackgroundCheckID", backgroundCheckId }
            }));
        }
    }
コード例 #5
0
        public static void Set(UserPaymentPlan data, LcDatabase sharedDb = null)
        {
            using (var db = new LcDatabase(sharedDb))
            {
                db.Execute(sqlSet,
                           data.userPaymentPlanID,
                           data.userID,
                           data.subscriptionID,
                           data.paymentPlan.ToString(),
                           data.paymentMethod,
                           data.paymentPlanLastChangedDate,
                           data.nextPaymentDueDate,
                           data.nextPaymentAmount,
                           data.firstBillingDate,
                           data.subscriptionEndDate,
                           data.paymentMethodToken,
                           data.paymentExpiryDate,
                           data.planStatus,
                           data.daysPastDue
                           );

                try
                {
                    // Set OwnerStatus
                    if (IsPartnershipPlan(data.paymentPlan))
                    {
                        var ow = new Owner();
                        ow.userID   = data.userID;
                        ow.statusID = (int)OwnerStatus.notYetAnOwner;
                        Owner.Set(ow);
                    }
                    else
                    {
                        // Run Membership Checks to enable/disable member (OwnerStatus update)
                        UserProfile.CheckAndSaveOwnerStatus(data.userID);
                    }
                }
                catch (Exception ex)
                {
                    // An error checking status must NOT prevent us from saving/creating
                    // the payment-plan, but we must notify staff so we can take manual action
                    // to fix the error and run the check again for this user
                    try
                    {
                        LcLogger.LogAspnetError(ex);
                        LcMessaging.NotifyError("UserPaymentPlan.Set->UserProfile.CheckAndSaveOwnerStatus::userID=" + data.userID,
                                                System.Web.HttpContext.Current.Request.RawUrl,
                                                ex.ToString());
                    }
                    catch
                    {
                        // Prevent cancel paymentplan creation/update because of email or log failing. Really strange
                        // and webhook-scheduleTask for subscriptions would attempt again this.
                    }
                }
            }
        }
コード例 #6
0
    /// <summary>
    /// Check:: Release Payment for Service Complete: 1 hour 15 min after service is performed
    /// (before #844 was 1 day after the service is performed)
    /// If:: Provider has already completed bookings (is not a new provider)
    /// If:: Performed bookings only, without pricing adjustment
    /// If:: Current time is 1 hour 15 min after Confirmed Service EndTime (before #844 was 1 day)
    /// Action:: set booking status as 'completed',
    /// send a messages.
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="db"></param>
    /// <returns></returns>
    private static TaskResult RunBookingReleasePayment(LcLogger logger, Database db)
    {
        var messages = 0L;
        var items    = 0L;

        // NOTE: Changed to ALL providers at 2016-01-26 as of #844
        foreach (var b in LcRest.Booking.QueryPendingOfPaymentReleaseBookings(null, db))
        {
            try
            {
                // Release the payment
                try
                {
                    if (b.ReleasePayment())
                    {
                        items++;

                        // Send messages

                        // Notify customer and provider with an updated booking details:
                        LcMessaging.SendBooking.For(b.bookingID).BookingCompleted();

                        // Update MessagingLog for the booking
                        db.Execute(sqlAddBookingMessagingLog, b.bookingID, "[Release Payment 1H]");

                        messages += 2;
                    }
                }
                catch (Exception ex)
                {
                    var errTitle = "Booking Release Payment after 1H to providers";
                    var errDesc  = String.Format(
                        "BookingID: {0}, TransactionID: {1}. Not payed on [Release Payment 1H], error on Braintree 'release transaction from escrow': {2}",
                        b.bookingID,
                        b.paymentTransactionID,
                        ex.Message
                        );

                    LcMessaging.NotifyError(errTitle, "/ScheduleTask", errDesc);

                    logger.Log("Error on: " + errTitle + "; " + errDesc);
                }
            }
            catch (Exception ex)
            {
                logger.LogEx("Booking Release Payment 1H", ex);
            }
        }
        logger.Log("Total of Booking Release Payment after 1H: {0}, messages sent: {1}", items, messages);

        return(new TaskResult
        {
            ItemsProcessed = items,
            MessagesSent = messages
        });
    }
コード例 #7
0
ファイル: Thread.cs プロジェクト: loconomicsau/loconomics
        /// <summary>
        /// Creat a thread with an initial message.
        /// Send email to the recipient, but without copy to sender.
        /// </summary>
        /// <param name="CustomerUserID"></param>
        /// <param name="FreelancerUserID"></param>
        /// <param name="JobTitleID"></param>
        /// <param name="Subject"></param>
        /// <param name="BodyText"></param>
        /// <param name="SentByFreelancer"></param>
        public static int PostInquiry(int CustomerUserID, int FreelancerUserID, int JobTitleID, string Subject, string BodyText, int SentByUserID)
        {
            // Validate user can send it (its in the thread)
            if (SentByUserID != CustomerUserID &&
                SentByUserID != FreelancerUserID)
            {
                // Not allowed, quick return, nothing done:
                return(0);
            }

            var clientEmail = UserProfile.GetEmail(CustomerUserID);
            var serviceProfessionalEmail = UserProfile.GetEmail(FreelancerUserID);

            if (clientEmail != null && serviceProfessionalEmail != null)
            {
                bool SentByFreelancer = SentByUserID == FreelancerUserID;

                int typeID   = SentByFreelancer ? 22 : 1;
                int threadID = LcMessaging.CreateThread(CustomerUserID, FreelancerUserID, JobTitleID, Subject, typeID, BodyText, SentByUserID);

                // From a REST API, a copy is Not send to the original user, as in the general API, so just
                // send an email to the recipient
                // NOTE: the Kind possible values are in the template.
                if (SentByFreelancer)
                {
                    // TODO: i18n
                    LcMessaging.SendMail(clientEmail, "A Message From a Loconomics Freelancer",
                                         LcMessaging.ApplyTemplate(LcUrl.LangPath + "Email/EmailInquiry/",
                                                                   new Dictionary <string, object> {
                        { "ThreadID", threadID },
                        { "Kind", 4 },
                        { "RequestKey", LcMessaging.SecurityRequestKey },
                        { "EmailTo", clientEmail }
                    }), replyTo: serviceProfessionalEmail
                                         );
                }
                else
                {
                    LcMessaging.SendMail(serviceProfessionalEmail, "A Message From a Loconomics Client",
                                         LcMessaging.ApplyTemplate(LcUrl.LangPath + "Email/EmailInquiry/",
                                                                   new Dictionary <string, object> {
                        { "ThreadID", threadID },
                        { "Kind", 1 },
                        { "RequestKey", LcMessaging.SecurityRequestKey },
                        { "EmailTo", serviceProfessionalEmail }
                    }), replyTo: clientEmail
                                         );
                }

                return(threadID);
            }

            return(0);
        }
コード例 #8
0
 public static void SendRegisterUserEmail(RegisteredUser user)
 {
     // Sent welcome email (if there is a confirmationUrl and token values, the email will contain it to perform the required confirmation)
     if (user.IsProvider)
     {
         LcMessaging.SendWelcomeProvider(user.UserID, user.Email);
     }
     else
     {
         LcMessaging.SendWelcomeCustomer(user.UserID, user.Email);
     }
 }
コード例 #9
0
    /// <summary>
    /// Check:: Setting No-Payment Bookings as Complete: 1 hour 15 min after service is performed
    /// If::Performed bookings only
    /// If:: Current time is 1 hour 15 min after Confirmed Service EndTime
    /// Action:: set booking status as 'completed',
    /// send messages.
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="db"></param>
    /// <returns></returns>
    private static TaskResult RunBookingNoPaymentComplete(LcLogger logger, Database db)
    {
        var messages = 0L;
        var items    = 0L;

        foreach (var b in LcRest.Booking.QueryPendingOfCompleteWithoutPaymentBookings(db))
        {
            try
            {
                // Release the payment
                try
                {
                    b.SetBookingAsCompleted();
                    items++;

                    // Send messages

                    // Notify customer and provider with an updated booking details:
                    LcMessaging.SendBooking.For(b.bookingID).BookingCompleted();

                    // Update MessagingLog for the booking
                    db.Execute(sqlAddBookingMessagingLog, b.bookingID, "[Complete - no payment]");

                    messages += 2;
                }
                catch (Exception ex)
                {
                    var errTitle = "Setting No-Payment Bookings as Complete after 1H15M to providers";
                    var errDesc  = String.Format(
                        "BookingID: {0}, Error: {1}",
                        b.bookingID,
                        ex.Message
                        );

                    LcMessaging.NotifyError(errTitle, "/ScheduleTask", errDesc);

                    logger.Log("Error on: " + errTitle + "; " + errDesc);
                }
            }
            catch (Exception ex)
            {
                logger.LogEx("Setting No-Payment Bookings as Complete 1H15M", ex);
            }
        }
        logger.Log("Total of No-Payment Bookings set as Complete after 1H15M: {0}, messages sent: {1}", items, messages);

        return(new TaskResult
        {
            ItemsProcessed = items,
            MessagesSent = messages
        });
    }
コード例 #10
0
 private bool ProcessUser(int userID)
 {
     ItemsReviewed += 1;
     try
     {
         LcMessaging.SendEarningsEntryReminder(userID, LcRest.UserProfile.GetEmail(userID));
         ItemsProcessed += 1;
     }
     catch (Exception ex)
     {
         logger.LogEx("Earnings Entry Reminder for userID:" + userID, ex);
     }
     return(true);
 }
コード例 #11
0
        private void UpdateFromGateway(IEnumerable <LcRest.UserPaymentPlan> list, LcLogger logger)
        {
            long reviewed  = 0;
            long processed = 0;

            foreach (var userPlan in list)
            {
                try
                {
                    // Query Braintree subscription
                    var m    = new LcPayment.Membership();
                    var subs = m.GetSubscription(userPlan.subscriptionID);
                    reviewed++;

                    if (subs == null)
                    {
                        // Problem: saved ID didn't found at Braintree, corrupted data
                        var err = String.Format("Database subscriptionID '{0}' not found at Braintree, corrupted data." +
                                                " Please review user {1} subscription", userPlan.subscriptionID, userPlan.userID);
                        LcMessaging.NotifyError("UserPaymentPlanSubscriptionUpdatesTask", "ScheduledTask", err);
                        logger.Log(err);
                    }
                    else
                    {
                        // If status changed, update record
                        if (userPlan.planStatus != subs.Status.ToString())
                        {
                            userPlan.UpdatedAtGateway(subs);
                            LcRest.UserPaymentPlan.Set(userPlan);
                            // Update items count
                            processed++;

                            // Payments
                            foreach (var transaction in subs.Transactions)
                            {
                                var payment = LcRest.UserFeePayment.FromSubscriptionTransaction(userPlan, transaction);
                                LcRest.UserFeePayment.Set(payment);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    logger.LogEx("Pending and Past Due subscriptions", ex);
                }
            }
            logger.Log("Subscriptions updated from gateway: {0} from total reviewed {1}", processed, reviewed);
            ItemsReviewed  += reviewed;
            ItemsProcessed += processed;
        }
コード例 #12
0
    /// <summary>
    /// Check:: Authorize postponed transactions 24hours previous to service start-time
    /// If::Confirmed or performed bookings only, not cancelled or in dispute or completed(completed may be
    /// and old booking already paid
    /// If::Current time is 24 hours before Confirmed Service StartTime
    /// If:: BookingRequest PaymentTransactionID is a Card token rather than an actual TransactionID
    /// If::Customer was still not charged / transaction was not submitted for settlement ([ClientPayment] is null)
    /// Action::authorize booking transaction
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="db"></param>
    /// <returns></returns>
    private static TaskResult RunBookingAuthorizePostponedTransactions(LcLogger logger, Database db)
    {
        var messages = 0L;
        var items    = 0L;

        foreach (var b in LcRest.Booking.QueryPostponedPaymentAuthorizations(db))
        {
            try
            {
                // Create transaction authorizing charge (but actually not charging still)
                // for saved customer credit card and update DB.
                try
                {
                    if (b.AuthorizeTransaction())
                    {
                        items++;
                    }
                }
                catch (Exception ex)
                {
                    var errTitle = "Booking Authorize Postponed Transactions, 24h before Service Starts";
                    var errDesc  = String.Format(
                        "BookingID: {0}, TransactionID: {1} Payment not allowed, error on Braintree 'sale transaction, authorizing only': {2}",
                        b.bookingID,
                        b.paymentTransactionID,
                        ex.Message
                        );

                    LcMessaging.NotifyError(errTitle, "/ScheduleTask", errDesc);

                    logger.Log("Error on: " + errTitle + "; " + errDesc);

                    // DOUBT: Notify providers on failed authorization/receive-payment?
                }
            }
            catch (Exception ex)
            {
                logger.LogEx("Booking Authorize Postponed Transactions, 24h before Service Starts", ex);
            }
        }
        logger.Log("Total of Booking Authorize Postponed Transactions, 24h before Service Starts: {0}", items);

        return(new TaskResult
        {
            ItemsProcessed = items,
            MessagesSent = messages
        });
    }
コード例 #13
0
    /// <summary>
    /// Check:: Charge Customer the day of the service
    /// If::Confirmated or performed bookings only, not cancelled or in dispute or completed(completed may be
    /// and old booking already paid
    /// If::Current time is the 1 hour after the End Service, or later
    /// If::Customer was still not charged / transaction was not submitted for settlement ([TotalPricePaidByCustomer] is null)
    /// Action::settle booking transaction
    /// set[TotalPricePaidByCustomer] and[TotalServiceFeesPaidByCustomer] values
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="db"></param>
    /// <returns></returns>
    private static TaskResult RunBookingChargeCustomer(LcLogger logger, Database db)
    {
        var messages = 0L;
        var items    = 0L;

        // Get bookings affected by conditions
        foreach (var b in LcRest.Booking.QueryPendingOfClientChargeBookings(db))
        {
            try
            {
                // Charge customer and update DB
                try
                {
                    if (b.SettleTransaction())
                    {
                        items++;
                    }
                }
                catch (Exception ex)
                {
                    var errTitle = "Booking Charge Customer, Receive Payment";
                    var errDesc  = String.Format(
                        "BookingID: {0}, TransactionID: {1} Payment not received, error on Braintree 'settle transaction': {2}",
                        b.bookingID,
                        b.paymentTransactionID,
                        ex.Message
                        );

                    LcMessaging.NotifyError(errTitle, "/ScheduleTask", errDesc);

                    logger.Log("Error on: " + errTitle + "; " + errDesc);
                }
            }
            catch (Exception ex)
            {
                logger.LogEx("Booking Charge Customer, Receive Payment", ex);
            }
        }
        logger.Log("Total of Booking Charge Customer, Receive Payment: {0}", items);

        return(new TaskResult
        {
            ItemsProcessed = items,
            MessagesSent = messages
        });
    }
コード例 #14
0
        public static int SubmitPhoto(UserLicenseCertification item, string originalFileName, Stream photo)
        {
            // For updates, needs to remove previous file
            if (item.userLicenseCertificationID > 0)
            {
                var oldItem = Get(item.userID, item.jobTitleID, item.languageID, item.userLicenseCertificationID);
                if (oldItem == null)
                {
                    // Not found:
                    return(0);
                }
                if (!String.IsNullOrEmpty(oldItem.submittedImageLocalURL))
                {
                    var localPath = oldItem.submittedImageLocalURL.Replace(LcUrl.AppUrl, "");
                    localPath = HttpContext.Current.Server.MapPath(LcUrl.RenderAppPath + localPath);
                    File.Delete(localPath);
                }
            }

            // File name with special prefix
            var    autofn      = Guid.NewGuid().ToString().Replace("-", "");
            string fileName    = photoPrefix + autofn + (System.IO.Path.GetExtension(originalFileName) ?? ".jpg");
            string virtualPath = LcUrl.RenderAppPath + LcData.Photo.GetUserPhotoFolder(item.userID);
            var    path        = HttpContext.Current.Server.MapPath(virtualPath);

            item.submittedImageLocalURL = LcUrl.AppUrl + LcData.Photo.GetUserPhotoFolder(item.userID) + fileName;
            // Check folder or create
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            using (var file = File.Create(path + fileName))
            {
                photo.CopyTo(file);
            }
            // TODO: i18n
            var msg = "UserID: " + item.userID + " submitted a photo of their License/Certification to being verified and added. It can be found in the FTP an folder: " + virtualPath;
            // TODO create config value
            var email = "*****@*****.**";

            LcMessaging.SendMail(email, "License/Certification Verification Request", msg);

            return(Set(item));
        }
コード例 #15
0
        static public void Set(UserJobTitleServiceAttributes serviceAttributes)
        {
            // Validate
            // Get all attributes that applies (we avoid save selected attributes that does not apply
            // to the job title).
            var validAttributes        = ServiceAttribute.GetGroupedJobTitleAttributes(serviceAttributes.jobTitleID, serviceAttributes.languageID, serviceAttributes.countryID);
            var indexedValidAttributes = new Dictionary <int, HashSet <int> >();

            // Check that there is almost one value for required categories, or show error
            foreach (var attCat in validAttributes)
            {
                if (attCat.requiredInput && (
                        !serviceAttributes.serviceAttributes.ContainsKey(attCat.serviceAttributeCategoryID) ||
                        serviceAttributes.serviceAttributes[attCat.serviceAttributeCategoryID].Count == 0))
                {
                    throw new ValidationException(String.Format(requiredAttCatError, attCat.name), attCat.serviceAttributeCategoryID.ToString(), "serviceAttributes");
                }
                indexedValidAttributes.Add(attCat.serviceAttributeCategoryID, new HashSet <int>(attCat.serviceAttributes.Select(x => x.serviceAttributeID)));
            }

            // Save data
            using (var db = new LcDatabase())
            {
                // Transaction
                db.Execute("BEGIN TRANSACTION");

                // First, remove all current attributes, replaced by the new set
                db.Execute(sqlDelAllAttributes, serviceAttributes.userID, serviceAttributes.jobTitleID, serviceAttributes.languageID, serviceAttributes.countryID);

                // Add new ones, if they are valid
                foreach (var cat in serviceAttributes.serviceAttributes)
                {
                    if (indexedValidAttributes.ContainsKey(cat.Key))
                    {
                        foreach (var att in cat.Value)
                        {
                            if (indexedValidAttributes[cat.Key].Contains(att))
                            {
                                // Add to database
                                db.Execute(sqlInsertAttribute, serviceAttributes.userID, serviceAttributes.jobTitleID, cat.Key, att, serviceAttributes.languageID, serviceAttributes.countryID);
                            }
                            // else JUST DISCARD SILENTLY INVALID ATTID
                        }
                    }
                    // else JUST DISCARD SILENTLY INVALID CATID
                }

                // Register user proposed new attributes:
                foreach (var cat in serviceAttributes.proposedServiceAttributes)
                {
                    // Category must exists, even if the attribute is new.
                    if (indexedValidAttributes.ContainsKey(cat.Key))
                    {
                        foreach (var attName in cat.Value)
                        {
                            if (String.IsNullOrWhiteSpace(attName))
                            {
                                continue;
                            }

                            // Clean-up, preparation of the new name
                            var newAttName = attName.Capitalize().Replace(",", "");

                            // Register new attribute
                            int serviceAttributeID = db.QueryValue(sqlRegisterNewAttribute,
                                                                   serviceAttributes.languageID,
                                                                   serviceAttributes.countryID,
                                                                   null, // sourceID
                                                                   newAttName,
                                                                   null, // description
                                                                   serviceAttributes.jobTitleID,
                                                                   serviceAttributes.userID,
                                                                   false,  // Initially not approved
                                                                   cat.Key // categoryID
                                                                   );
                            // Set for the user:
                            db.Execute(sqlInsertAttribute, serviceAttributes.userID, serviceAttributes.jobTitleID,
                                       cat.Key, serviceAttributeID, serviceAttributes.languageID, serviceAttributes.countryID);
                        }
                    }
                }

                if (serviceAttributes.proposedServiceAttributes.Count > 0)
                {
                    LcMessaging.NotifyNewServiceAttributes(serviceAttributes.userID, serviceAttributes.jobTitleID, serviceAttributes.proposedServiceAttributes);
                }

                // Since ExperienceLevel is not a service category anymore else an independent table, we need
                // specific code to save its data.
                if (serviceAttributes.experienceLevelID > 0)
                {
                    db.Execute(sqlSetExpLevel, serviceAttributes.userID, serviceAttributes.jobTitleID, serviceAttributes.languageID, serviceAttributes.countryID, serviceAttributes.experienceLevelID);
                }
                else
                {
                    db.Execute(sqlDelExpLevel, serviceAttributes.userID, serviceAttributes.jobTitleID, serviceAttributes.languageID, serviceAttributes.countryID);
                }

                // Check alert
                db.Execute("EXEC TestAlertPositionServices @0, @1", serviceAttributes.userID, serviceAttributes.jobTitleID);

                // Ends transaction (very important for the delete-insert attributes part, but it guarantees that all or nothing):
                db.Execute("COMMIT TRANSACTION");
            }
        }
コード例 #16
0
    /// <summary>
    /// Convert a user record with 'Not Enabled Account' into a standard enabled account. See IsUserButNotEnabledAccount
    /// for more info, and check that value before call this to prevent an error when user has an enabled account.
    /// This supports the cases
    /// - User with status of 'serviceProfessionalClient': the user has an account created as client by a service professional.
    /// - User with status of 'subscriber': the user submitted it's email through a Lead Generation API to get in touch with newsletters or
    ///   to reference a service professional.
    ///
    /// For the conversion, we need support next actions/requests:
    /// - A: We need to communicate that specific situation (error message), generate a confirmation code
    ///   for the existent user, send email to let him to confirm that he/she owns the given e-mail.
    /// - B: On returning here after request/response A, a confirmation code is being provided and we must proceed
    ///   by checking the confirmation code and, on success, enable account (change status), update the membership password and
    ///   continue with a valid set of LoginResult. External code should allow user to update any additional account data.
    /// </summary>
    /// <param name="userID"></param>
    /// <param name="email"></param>
    /// <param name="password"></param>
    /// <param name="returnProfile"></param>
    /// <returns></returns>
    private static LoginResult SignupANotEnabledAccount(int userID, string email, string password, bool returnProfile, int accountStatusID, bool isOrganization)
    {
        // Get confirmation code, if any
        var confirmationCode = Request["confirmationCode"];
        // Prepare error message
        var errTpl = "";

        if (accountStatusID == (int)LcEnum.AccountStatus.serviceProfessionalClient)
        {
            errTpl = UserIsServiceProfessionalClientMessage;
        }
        else if (accountStatusID == (int)LcEnum.AccountStatus.subscriber)
        {
            errTpl = UserIsSubscriberMessage;
        }
        else
        {
            throw new Exception("[[[Not allowed]]]");
        }
        var errMsg = String.Format(errTpl, email);

        // Action/Request A: Create confirmation code
        if (String.IsNullOrEmpty(confirmationCode))
        {
            // To generate a confirmation code (creates the Membership record, that does not exists still)
            // this needs a random password (we still didn't verified the user, so do NOT trust on the given password).
            // NOTE: since this can be attempted several time by the user, and next attempts will fail because the Membership
            // record will exists already, just double check and try creation only if record don't exists:
            if (!LcAuth.HasMembershipRecord(userID))
            {
                WebSecurity.CreateAccount(email, LcAuth.GeneratePassword(), true);
            }
            StartOnboardingForUser(userID);
            // send email to let him to confirm it owns the given e-mail
            LcMessaging.SendWelcomeCustomer(userID, email);
            // Not valid after all, just communicate was was done and needs to do to active its account:
            throw new HttpException(409, errMsg);
        }
        // Action/Request B: confirm confirmation code
        else
        {
            // If confirmation token is valid, enable account and reset password
            if (LcAuth.GetConfirmationToken(userID) == confirmationCode)
            {
                // We know is valid, we can update the accountStatus to be an standard/enabled account
                // and that will allow to set the account as confirmed
                using (var db = new LcDatabase())
                {
                    db.Execute("UPDATE users SET accountStatusID = @1 WHERE UserID = @0", userID, LcEnum.AccountStatus.active);
                }
                // now we can confirm (we already know the code is valid, it will just double check and update database)
                LcAuth.ConfirmAccount(confirmationCode);
                // set the password provided by the user. Trick: we need to generate a reset token in order to set the password.
                var token = WebSecurity.GeneratePasswordResetToken(email);
                LcAuth.ResetPassword(token, password);
                // Left continue with profile data update..
            }
            else
            {
                // RE-send email to let him to confirm it owns the given e-mail
                LcMessaging.SendWelcomeCustomer(userID, email);
                throw new HttpException(409, errMsg);
            }
        }

        // We need a logged object, and additionally a double check is performed (so we ensure setting the password process worked).
        return(Login(email, password, false, returnProfile, false));
    }
コード例 #17
0
    /// <summary>
    /// Runs all tasks if the conditions for each are met.
    /// </summary>
    /// <returns>Logged string</returns>
    public static string Run()
    {
        var logger      = new LcLogger("ScheduledTask");
        var elapsedTime = DateTime.Now;

        /*
         * Bookings
         */
        var        totalmessages = 0L;
        var        totalitems    = 0L;
        TaskResult result;
        var        taskRunners = new List <BookingTaskRunner>
        {
            RunBookingTimedOut,
            RunBookingRequestExpiration,
            RunBooking48HReminder,
            RunBookingAuthorizePostponedTransactions,
            RunBookingChargeCustomer,
            RunBookingServicePerformed,
            RunBookingReleasePayment,
            RunBookingNoPaymentComplete,
            RunBookingReviewReminder1W
        };

        using (var db = Database.Open("sqlloco"))
        {
            foreach (var runner in taskRunners)
            {
                try
                {
                    result         = runner(logger, db);
                    totalitems    += result.ItemsProcessed;
                    totalmessages += result.MessagesSent;
                }
                catch (Exception ex)
                {
                    logger.LogEx(runner.Method.Name, ex);
                }
            }
            // Ending work with database
        }

        logger.Log("Elapsed time {0}, for {1} bookings affected and {2} messages sent", DateTime.Now - elapsedTime, totalitems, totalmessages);


        /*
         * iCalendar
         */
        try
        {
            RunIcalendar(logger);
        }
        catch (Exception err)
        {
            logger.LogEx("Import Calendar", err);
        }

        /*
         * Membership tasks
         */
        // Note: the whole ScheduleTask is set-up to run every hour,
        // since the subscriptions status is keep up to date by Webhooks already,
        // and this task is just a fallback in case of communication error and can
        // perform lot of request to Braintree, we force to reduce execution to once
        // a week.
        // Easily, we just check if we are at Midnight of Monday
        if (DateTime.Now.Hour == 0 && DateTime.Now.DayOfWeek == DayOfWeek.Monday)
        {
            try
            {
                Tasks.UserPaymentPlanSubscriptionUpdatesTask.Run(logger);
            }
            catch (Exception err)
            {
                logger.LogEx("UserPaymentPlanSubscriptionUpdatesTask", err);
            }
        }

        // Earnings reminder are sent only two times a month, so we check the day and the hour
        // (the hour to make it just once at the matched day, since the task executes every hour)
        if ((DateTime.Now.Day == 1 || DateTime.Now.Day == 15) && DateTime.Now.Hour == 7)
        {
            try
            {
                Tasks.EarningsEntryReminderTask.Run(logger);
            }
            catch (Exception err)
            {
                logger.LogEx("EarningsEntryReminderTask", err);
            }
        }

        /*
         * Task Ended
         */
        logger.Log("Total Elapsed time {0}", DateTime.Now - elapsedTime);

        string logresult = logger.ToString();

        // Finishing: save log on disk, per month rotation
        //try {
        logger.Save();
        // Send Email too if exceptions found
        if (logger.HasExceptions)
        {
            LcMessaging.NotifyError("ScheduleTask throw Exceptions", "/ScheduledTask", logresult);
        }
        //}catch { }

        return(logresult);
    }
コード例 #18
0
    /// <summary>
    /// Performs a transaction to authorize the payment on the client payment method, but
    /// not charging still, using the data from the given booking and the saved paymentMethodID.
    /// Booking is NOT checked before perform the task, use the LcRest.Booking API to securely run pre-condition
    /// checks before authorize transaction. The booking must have the data loaded for the pricingSummary.
    ///
    /// REVIEWED #771
    /// </summary>
    /// <param name="booking"></param>
    /// <param name="paymentMethodID">AKA creditCardToken</param>
    /// <returns>It returns the transactionID generated, original booking object is not updated.
    /// Errors in the process are throwed.</returns>
    public static string AuthorizeBookingTransaction(LcRest.Booking booking)
    {
        if (booking.pricingSummary == null ||
            !booking.pricingSummary.totalPrice.HasValue ||
            booking.pricingSummary.totalPrice.Value <= 0)
        {
            throw new ConstraintException("To authorize a booking payment is required a price to charge.");
        }

        var gateway = NewBraintreeGateway();

        TransactionRequest request = new TransactionRequest
        {
            Amount = booking.pricingSummary.totalPrice.Value,
            // Marketplace #408: since provider receive the money directly, Braintree must discount
            // the next amount in concept of fees and pay that to the Marketplace Owner (us, Loconomics ;-)
            ServiceFeeAmount   = booking.pricingSummary.serviceFeeAmount,
            CustomerId         = GetCustomerId(booking.clientUserID),
            PaymentMethodToken = booking.paymentMethodID,
            // Now, with Marketplace #408, the receiver of the money for each transaction is
            // the provider through account at Braintree, and not the Loconomics account:
            //MerchantAccountId = LcPayment.BraintreeMerchantAccountId,
            MerchantAccountId = GetProviderPaymentAccountId(booking.serviceProfessionalUserID),
            Options           = new TransactionOptionsRequest
            {
                // Marketplace #408: don't pay provider still, wait for the final confirmation 'release scrow'
                HoldInEscrow = true,
                // Do not submit, just authorize:
                SubmitForSettlement = false
            }
        };

        var r = gateway.Transaction.Sale(request);

        // Everything goes fine
        if (r.IsSuccess())
        {
            // Get the transactionID
            if (r.Target != null &&
                !String.IsNullOrEmpty(r.Target.Id))
            {
                // If the card is a TEMPorarly card (just to perform this transaction)
                // it must be removed now since was successful used
                // IMPORTANT: Since an error on this subtask is not important to the
                // user and will break a success process creating a new problem if throwed (because transactionID
                // gets lost),
                // is catched and managed internally by Loconomics stuff that can check and fix transparentely
                // this minor error.
                try
                {
                    if (booking.paymentMethodID.StartsWith(TempSavedCardPrefix))
                    {
                        gateway.CreditCard.Delete(booking.paymentMethodID);
                    }
                }
                catch (Exception ex)
                {
                    try
                    {
                        LcMessaging.NotifyError(String.Format("LcPayment.AuthorizeBookingTransaction..DeleteBraintreeTempCard({0});bookingID={1}", booking.paymentMethodID, booking.bookingID), "", ex.Message);
                        LcLogger.LogAspnetError(ex);
                    }
                    catch { }
                }

                // r.Target.Id => transactionID
                return(r.Target.Id);
            }
            else
            {
                // Transaction worked but impossible to know the transactionID (weird, is even possible?),
                // notify error
                throw new Exception("Impossible to know transaction details, please contact support. BookingID #" + booking.bookingID.ToString());
            }
        }
        else
        {
            throw new Exception(r.Message);
        }
    }
コード例 #19
0
    /// <summary>
    /// Signup with fields:
    /// - email [required]
    /// - password [required when no facebookUserID is given]
    /// - facebookUserID [optional]
    /// - countryID [optional defaults to COUNTRY_CODE_USA]
    /// - profileType [optional defaults to client]
    /// - utm [optional, not a named form parameter but the whole query string]
    /// - firstName [optional except atBooking]
    /// - lastName [optional except atBooking]
    /// - phone [optional except atBooking]
    /// - returnProfile [optional defaults to false] Returns the user profile in a property of the result
    /// - atBooking [optional]
    /// - isOrganization [optional] Default false
    /// </summary>
    /// <param name="page"></param>
    /// <returns></returns>
    public static LoginResult Signup(WebPage page)
    {
        page.Validation.RequireField("email", "[[[You must specify an email.]]]");
        // Username is an email currently, so need to be restricted
        page.Validation.Add("email",
                            Validator.Regex(LcValidators.EmailAddressRegexPattern, "[[[The email is not valid.]]]"));

        // First data
        var profileTypeStr        = Request.Form["profileType"] ?? "";
        var isServiceProfessional = SERVICE_PROFESSIONAL_TYPE == profileTypeStr.ToUpper();
        var isClient            = !isServiceProfessional;
        var facebookUserID      = Request.Form["facebookUserID"].AsLong(0);
        var facebookAccessToken = Request.Form["facebookAccessToken"];
        var email          = Request.Form["email"];
        var atBooking      = Request.Form["atBooking"].AsBool();
        var isOrganization = Request.Form["isOrganization"].AsBool();

        //
        // Conditional validations
        // Facebook
        var useFacebookConnect = facebookUserID > 0 && !String.IsNullOrEmpty(facebookAccessToken);

        if (!useFacebookConnect)
        {
            page.Validation.RequireField("password", "[[[You must specify a password.]]]");
            // We manually validate if a password was given, in order to prevent
            // showing up the validation format message additionally to the 'required password' message
            if (!String.IsNullOrWhiteSpace(Request.Form["password"]))
            {
                page.Validation.Add("password", new PasswordValidator());
            }
        }
        else
        {
            var prevFbUser = LcAuth.GetFacebookUser(facebookUserID);
            if (prevFbUser != null)
            {
                throw new HttpException(409, "[[[Facebook account already connected. Sign in.]]]");
            }
        }

        // For a signup at a client booking, we require more fields
        if (atBooking)
        {
            page.Validation.RequireField("phone", "[[[You must specify your mobile phone number.]]]");
            page.Validation.RequireField("firstName", "[[[You must specify your first name.]]]");
            page.Validation.RequireField("lastName", "[[[You must specify your last name.]]]");
        }

        if (page.Validation.IsValid())
        {
            // TODO To use countryCode for a more 'open' public REST API, where 'code' is a well know ISO 2-letters CODE
            //var countryCode = Request.Form["countryCode"] ?? "US";
            var countryID = Request.Form["countryID"].AsInt(COUNTRY_CODE_AU);

            // Autogenerated password (we need to save one) on facebook connect:
            var password      = useFacebookConnect ? LcAuth.GeneratePassword() : Request.Form["password"];
            var firstName     = Request.Form["firstName"];
            var lastName      = Request.Form["lastName"];
            var phone         = Request.Form["phone"];
            var returnProfile = Request.Form["returnProfile"].AsBool();

            var         utm    = Request.Url.Query;
            LoginResult logged = null;

            // If the user exists, try to log-in with the given password,
            // becoming a provider if that was the requested profileType and follow as
            // a normal login.
            // If the password didn't match, throw a sign-up specific error (email in use)
            // Otherwise, just register the user.
            if (LcAuth.ExistsEmail(email))
            {
                // We query the user with that email
                var userID = WebSecurity.GetUserId(email);
                var user   = LcRest.UserProfile.Get(userID);
                // There are special cases when a user is registered, but never has accepted TOU or created a password (Not Enabled Account),
                // and is possible for that user to become an regular/enabled account.
                if (IsUserButNotEnabledAccount(user))
                {
                    logged = SignupANotEnabledAccount(userID, email, password, returnProfile, user.accountStatusID, isOrganization);
                }
                else
                {
                    // If the email exists, we try to log-in using the provided password, to don't bother with "e-mail in use" error
                    // if the user provides the correct credentials (maybe just don't remember he/she has already an account; make it easy for them
                    // to return).
                    // Try Login
                    try
                    {
                        logged = Login(email, password, false, returnProfile, true);
                        userID = logged.userID;
                        // Ensure we set-up
                        // as a professional if requested
                        // Next code will throw exception on error
                        if (isServiceProfessional)
                        {
                            LcAuth.BecomeProvider(userID);
                        }
                    }
                    catch (HttpException)
                    {
                        // Not valid log-in, throw a 'email exists' error with Conflict http code
                        throw new HttpException(409, "[[[E-mail address is already in use.]]]");
                    }
                }

                // Update account data with the extra information.
                using (var db = new LcDatabase())
                {
                    db.Execute(@"
                        UPDATE users SET
                            firstName = coalesce(@1, firstName),
                            lastName = coalesce(@2, lastName),
                            mobilePhone = coalesce(@3, mobilePhone),
                            isOrganization = @4
                        WHERE userID = @0
                    ", userID, firstName, lastName, phone, isOrganization);
                    // Create a home address record almost with the country
                    var home = LcRest.Address.GetHomeAddress(userID);
                    home.countryCode = LcRest.Locale.GetCountryCodeByID(countryID);
                    home.countryID   = countryID;
                    LcRest.Address.SetAddress(home);

                    StartOnboardingForUser(userID);
                }

                // SIGNUP
                LcMessaging.SendMail("*****@*****.**", "Sign-up", String.Format(@"
                    <html><body><h3>Sign-up.</h3>
                    <strong>This user was already in the database, is re-registering itself again!</strong><br/>
                    <dl>
                    <dt>Profile:</dt><dd>{0}</dd>
                    <dt>First Name:</dt><dd>{1}</dd>
                    <dt>Last Name:</dt><dd>{2}</dd>
                    <dt>Country:</dt><dd>{5}</dd>
                    <dt>Email:</dt><dd>{3}</dd>
                    <dt>UserID:</dt><dd>{4}</dd>
                    <dt>Phone:</dt><dd>{6}</dd>
                    </dl>
                    </body></html>
                ", profileTypeStr, firstName, lastName, email, logged.userID, countryID, phone));

                return(logged);
            }
            else
            {
                if (useFacebookConnect)
                {
                    // Verify Facebook ID and accessToken contacting to Facebook Servers
                    if (LcFacebook.GetUserFromAccessToken(facebookUserID.ToString(), facebookAccessToken) == null)
                    {
                        throw new HttpException(400, "[[[Facebook account does not exists.]]]");
                    }
                }

                var registered = LcAuth.RegisterUser(email, firstName, lastName, password,
                                                     isServiceProfessional, utm, -1, null, phone, null, countryID, isOrganization);

                // Create a home address record almost with the country
                var home = LcRest.Address.GetHomeAddress(registered.UserID);
                home.countryCode = LcRest.Locale.GetCountryCodeByID(countryID);
                home.countryID   = countryID;
                LcRest.Address.SetAddress(home);

                if (useFacebookConnect)
                {
                    // Register connection between the new account and the Facebook account
                    LcAuth.ConnectWithFacebookAccount(registered.UserID, facebookUserID);
                }

                // Welcome and confirmation e-mail
                LcAuth.SendRegisterUserEmail(registered);

                // SIGNUP
                LcMessaging.SendMail("*****@*****.**", "Sign-up", String.Format(@"
                    <html><body><h3>Sign-up.</h3>
                    <dl>
                    <dt>Profile:</dt><dd>{0}</dd>
                    <dt>First Name:</dt><dd>{1}</dd>
                    <dt>Last Name:</dt><dd>{2}</dd>
                    <dt>Country:</dt><dd>{5}</dd>
                    <dt>Email:</dt><dd>{3}</dd>
                    <dt>UserID:</dt><dd>{4}</dd>
                    <dt>Phone:</dt><dd>{6}</dd>
                    </dl>
                    </body></html>
                ", profileTypeStr, firstName, lastName, email, registered.UserID, countryID, phone));

                // Auto login:
                return(Login(email, password, false, returnProfile, true));
            }
        }
        else
        {
            // Bad request, input data incorrect because of validation rules
            throw new HttpException(400, LcRessources.ValidationSummaryTitle);
        }
    }
コード例 #20
0
    /// <summary>
    /// Signup with fields:
    /// - email [required]
    /// - password [required when no facebookUserID is given]
    /// - facebookUserID [optional]
    /// - countryID [optional defaults to COUNTRY_CODE_USA]
    /// - profileType [optional defaults to client]
    /// - utm [optional, not a named form parameter but the whole query string]
    /// - firstName [optional for professionals, required for clients]
    /// - lastName [optional for professionals, required for clients]
    /// - postalCode [optional]
    /// - referralCode [optional]
    /// - device [optional]
    /// - phone [optional for professionals, required for clients]
    /// - returnProfile [optional defaults to false] Returns the user profile in a property of the result
    /// </summary>
    /// <param name="page"></param>
    /// <returns></returns>
    public static LoginResult Signup(WebPage page)
    {
        page.Validation.RequireField("email", "You must specify an email.");
        // Username is an email currently, so need to be restricted
        page.Validation.Add("email",
                            Validator.Regex(LcValidators.EmailAddressRegexPattern, "The email is not valid."));

        // First data
        var profileTypeStr        = Request.Form["profileType"] ?? "";
        var isServiceProfessional = SERVICE_PROFESSIONAL_TYPE == profileTypeStr.ToUpper();
        var isClient            = !isServiceProfessional;
        var facebookUserID      = Request.Form["facebookUserID"].AsLong(0);
        var facebookAccessToken = Request.Form["facebookAccessToken"];
        var email = Request.Form["email"];

        //
        // Conditional validations
        // Facebook
        var useFacebookConnect = facebookUserID > 0 && !String.IsNullOrEmpty(facebookAccessToken);

        if (!useFacebookConnect)
        {
            page.Validation.RequireField("password", "You must specify a password.");
            // We manually validate if a password was given, in order to prevent
            // showing up the validation format message additionally to the 'required password' message
            if (!String.IsNullOrWhiteSpace(Request.Form["password"]))
            {
                page.Validation.Add("password", new PasswordValidator());
            }
        }
        else
        {
            var prevFbUser = LcAuth.GetFacebookUser(facebookUserID);
            if (prevFbUser != null)
            {
                throw new HttpException(409, "Facebook account already connected. Sign in.");
            }
        }
        // Profile Type
        if (isClient)
        {
            page.Validation.RequireField("phone", "You must specify your mobile phone number.");
            page.Validation.RequireField("firstName", "You must specify your first name.");
            page.Validation.RequireField("lastName", "You must specify your last name.");
        }

        if (page.Validation.IsValid())
        {
            var postalCode = Request.Form["postalCode"];
            // TODO To use countryCode for a more 'open' public REST API, where 'code' is a well know ISO 2-letters CODE
            //var countryCode = Request.Form["countryCode"] ?? "US";
            var countryID = Request.Form["countryID"].AsInt(COUNTRY_CODE_USA);

            // Postal code is Optional
            if (!String.IsNullOrEmpty(postalCode))
            {
                // Validate postal code before continue
                var add = new LcRest.Address
                {
                    postalCode = postalCode,
                    //countryCode = countryCode
                    countryID = countryID
                };
                if (!LcRest.Address.AutosetByCountryPostalCode(add))
                {
                    // bad postal code
                    page.ModelState.AddError("postalCode", "Invalid postal code");
                    throw new HttpException(400, LcRessources.ValidationSummaryTitle);
                }
            }

            // Autogenerated password (we need to save one) on facebook connect:
            var password      = useFacebookConnect ? LcAuth.GeneratePassword() : Request.Form["password"];
            var firstName     = Request.Form["firstName"];
            var lastName      = Request.Form["lastName"];
            var referralCode  = Request.Form["referralCode"];
            var device        = Request.Form["device"];
            var phone         = Request.Form["phone"];
            var returnProfile = Request.Form["returnProfile"].AsBool();

            var         utm    = Request.Url.Query;
            LoginResult logged = null;

            // If the user exists, try to log-in with the given password,
            // becoming a provider if that was the requested profileType and follow as
            // a normal login.
            // If the password didn't match, throw a sign-up specific error (email in use)
            // Otherwise, just register the user.
            if (LcAuth.ExistsEmail(email))
            {
                // If the email exists, we try to log-in using the provided password, to don't bother with "e-mail in use" error
                // if the user provides the correct credentials (maybe just don't remember he/she has already an account; make it easy for them
                // to return).
                // BUT we have a special situation that needs extra checks:
                // CLIENT--CONFIRMATION LOGIC
                // The email can exists because the user has an account created as client by a service professional:
                // - A: On that cases, we need to communicate that specific situation (error message), generate a confirmation code
                // for the existent user, send email to let him to confirm it owns the given e-mail.
                // - B: On returning here after point A, a confirmation code is provided and we must proceed
                // by checking the confirmation code and, on success, unlock and update the membership password and
                // continue updating any given data.
                var userID = WebSecurity.GetUserId(email);
                var user   = LcRest.UserProfile.Get(userID);
                if (user.accountStatusID != (int)LcEnum.AccountStatus.serviceProfessionalClient)
                {
                    // NOT a client, just standard sign-up that requires verify the email/password or fail
                    // Try Login
                    try
                    {
                        logged = Login(email, password, false, returnProfile, true);
                        userID = logged.userID;
                        // throw exception on error
                        if (isServiceProfessional)
                        {
                            LcAuth.BecomeProvider(userID);
                        }
                    }
                    catch (HttpException)
                    {
                        // Not valid log-in, throw a 'email exists' error with Conflict http code
                        throw new HttpException(409, "E-mail address is already in use.");
                    }
                }
                else
                {
                    // CLIENT--CONFIRMATION LOGIC
                    // The email can exists because the user has an account created as client by a service professional:
                    // - A: On that cases, we need to communicate that specific situation (error message), generate a confirmation code
                    // for the existent user, send email to let him to confirm it owns the given e-mail.
                    // - B: On returning here after point A, a confirmation code is provided and we must proceed
                    // by checking the confirmation code and, on success, unlock and update the membership password and
                    // continue updating any given data.
                    var confirmationCode = Request["confirmationCode"];
                    var errMsg           = String.Format(@"We see one of our service professionals has already scheduled services for you in the past.
                        We've just sent an invitation to create your account to {0}.
                        Please follow its instructions. We can't wait to get you on board!", email
                                                         );
                    if (String.IsNullOrEmpty(confirmationCode))
                    {
                        // Point A: create confirmation code
                        // generate a confirmation code (creates the Membership record, that does not exists still since is as just a client)
                        // this needs a random password (we still didn't verified the user, so do NOT trust on the given password).
                        // NOTE: since this can be attempted several time by the user, and next attempts will fail because the Membership
                        // record will exists already, just double check and try creation only if record don't exists:
                        if (!LcAuth.HasMembershipRecord(userID))
                        {
                            WebSecurity.CreateAccount(email, LcAuth.GeneratePassword(), true);
                        }
                        // send email to let him to confirm it owns the given e-mail
                        LcMessaging.SendWelcomeCustomer(userID, email);
                        // Not valid after all, just communicate was was done and needs to do to active its account:
                        throw new HttpException(409, errMsg);
                    }
                    else
                    {
                        // Point B: confirm confirmation code
                        if (LcAuth.GetConfirmationToken(userID) == confirmationCode)
                        {
                            // We know is valid, we can update the accountStatus to not be any more a "service professional's client"
                            // and that will allow to set the account as confirmed
                            using (var db = new LcDatabase())
                            {
                                db.Execute("UPDATE users SET accountStatusID = @1 WHERE UserID = @0", userID, LcEnum.AccountStatus.active);
                            }
                            // now we can confirm (we already know the code is valid, it will just double check and update database)
                            LcAuth.ConfirmAccount(confirmationCode);
                            // set the password provided by the user. Trick: we need to generate a reset token in order to set the password.
                            var token = WebSecurity.GeneratePasswordResetToken(email);
                            LcAuth.ResetPassword(token, password);
                            // Left continue with profile data update..
                        }
                        else
                        {
                            // RE-send email to let him to confirm it owns the given e-mail
                            LcMessaging.SendWelcomeCustomer(userID, email);
                            throw new HttpException(409, errMsg);
                        }
                    }

                    // We need a logged object, and additionally a double check is performed (so we ensure setting the password process worked).
                    logged = Login(email, password, false, returnProfile, false);
                }

                // Update account data with the extra information.
                using (var db = new LcDatabase())
                {
                    db.Execute(@"
                        UPDATE users SET
                            firstName = coalesce(@1, firstName),
                            lastName = coalesce(@2, lastName),
                            mobilePhone = coalesce(@3, mobilePhone),
                            signupDevice = coalesce(@4, signupDevice)
                        WHERE userID = @0
                    ", userID, firstName, lastName, phone, device);

                    if (!String.IsNullOrEmpty(postalCode))
                    {
                        var address = LcRest.Address.GetHomeAddress(userID);
                        if (address.postalCode != postalCode)
                        {
                            address.postalCode = postalCode;
                            //address.countryCode = countryCode;
                            address.countryCode = LcRest.Locale.GetCountryCodeByID(countryID);
                            address.countryID   = countryID;
                            LcRest.Address.SetAddress(address);
                        }
                    }
                }

                // SIGNUP
                LcMessaging.SendMail("*****@*****.**", "Sign-up", String.Format(@"
                    <html><body><h3>Sign-up.</h3>
                    <strong>This user was already in the database, is re-registering itself again!</strong><br/>
                    <dl>
                    <dt>Profile:</dt><dd>{0}</dd>
                    <dt>First Name:</dt><dd>{1}</dd>
                    <dt>Last Name:</dt><dd>{2}</dd>
                    <dt>Postal code:</dt><dd>{3}</dd>
                    <dt>Country:</dt><dd>{9}</dd>
                    <dt>Referral code:</dt><dd>{4}</dd>
                    <dt>Device:</dt><dd>{5}</dd>
                    <dt>Phone:</dt><dd>{6}</dd>
                    <dt>Email:</dt><dd>{7}</dd>
                    <dt>UserID:</dt><dd>{8}</dd>
                    </dl>
                    </body></html>
                ", profileTypeStr, firstName, lastName, postalCode, referralCode, device, phone, email, logged.userID, countryID));

                return(logged);
            }
            else
            {
                if (useFacebookConnect)
                {
                    // Verify Facebook ID and accessToken contacting to Facebook Servers
                    if (LcFacebook.GetUserFromAccessToken(facebookUserID.ToString(), facebookAccessToken) == null)
                    {
                        throw new HttpException(400, "Facebook account does not exists.");
                    }
                }

                var registered = LcAuth.RegisterUser(email, firstName, lastName, password, isServiceProfessional, utm, -1, null, phone, device);
                if (!String.IsNullOrEmpty(postalCode))
                {
                    // Set address
                    var address = LcRest.Address.GetHomeAddress(registered.UserID);
                    address.postalCode = postalCode;
                    //address.countryCode = countryCode;
                    address.countryCode = LcRest.Locale.GetCountryCodeByID(countryID);
                    address.countryID   = countryID;
                    LcRest.Address.SetAddress(address);
                }

                if (useFacebookConnect)
                {
                    // Register connection between the new account and the Facebook account
                    LcAuth.ConnectWithFacebookAccount(registered.UserID, facebookUserID);
                }

                // Welcome and confirmation e-mail
                LcAuth.SendRegisterUserEmail(registered);

                // SIGNUP
                LcMessaging.SendMail("*****@*****.**", "Sign-up", String.Format(@"
                    <html><body><h3>Sign-up.</h3>
                    <dl>
                    <dt>Profile:</dt><dd>{0}</dd>
                    <dt>First Name:</dt><dd>{1}</dd>
                    <dt>Last Name:</dt><dd>{2}</dd>
                    <dt>Postal code:</dt><dd>{3}</dd>
                    <dt>Country:</dt><dd>{9}</dd>
                    <dt>Referral code:</dt><dd>{4}</dd>
                    <dt>Device:</dt><dd>{5}</dd>
                    <dt>Phone:</dt><dd>{6}</dd>
                    <dt>Email:</dt><dd>{7}</dd>
                    <dt>UserID:</dt><dd>{8}</dd>
                    </dl>
                    </body></html>
                ", profileTypeStr, firstName, lastName, postalCode, referralCode, device, phone, email, registered.UserID, countryID));

                // Auto login:
                return(Login(email, password, false, returnProfile, true));
            }
        }
        else
        {
            // Bad request, input data incorrect because of validation rules
            throw new HttpException(400, LcRessources.ValidationSummaryTitle);
        }
    }
コード例 #21
0
    /// <summary>
    /// Runs all tasks if the conditions for each are met.
    /// </summary>
    /// <returns>Logged string</returns>
    public static string Run()
    {
        var logger      = new LcLogger("ScheduledTask");
        var elapsedTime = DateTime.Now;

        /*
         * Bookings
         */

        int messages = 0, items = 0;
        int totalmessages = 0, totalitems = 0;

        var sqlAddBookingMessagingLog = "UPDATE Booking SET MessagingLog = coalesce(MessagingLog, '') + @1 WHERE BookingID=@0";

        using (var db = Database.Open("sqlloco"))
        {
            /*
             * Check:: Booking timed out
             * If:: A not complete booking request exist without changes from more than 1 day
             * Action:: Invalidate the booking tentative events
             */
            messages = 0;
            items    = 0;
            foreach (var b in LcRest.Booking.QueryIncomplete2TimedoutBookings(db))
            {
                try
                {
                    LcRest.Booking.SetAsTimedout(b, db);
                    items++;
                }
                catch (Exception ex)
                {
                    logger.LogEx("Booking Timed-out", ex);
                }
            }
            logger.Log("Total invalidated as TimedOut Booking: {0}, messages sent: {1}", items, messages);
            totalitems    += items;
            totalmessages += messages;

            /*
             * Check:: Booking Request expiration
             * If:: Provider didn't reply
             * If:: Request not updated/changed
             * Action:: Set as expired, un-authorize/return money to customer, notify
             */
            messages = 0;
            items    = 0;
            foreach (var b in LcRest.Booking.QueryRequest2ExpiredBookings(db))
            {
                try
                {
                    // RequestStatusID:6:expired
                    b.ExpireBooking();
                    // Send message
                    LcMessaging.SendBooking.For(b.bookingID).BookingRequestExpired();
                    // Update MessagingLog for the booking
                    db.Execute(sqlAddBookingMessagingLog, b.bookingID, "[Booking Request Expiration]");

                    items++;
                    messages += 2;
                }
                catch (Exception ex)
                {
                    logger.LogEx("Booking Request Expired", ex);
                }
            }
            logger.Log("Total invalidated as Expired Booking Requests: {0}, messages sent: {1}", items, messages);
            totalitems    += items;
            totalmessages += messages;

            /*
             * Check:: [48H Service Reminder] Booking will be on 48Hours
             * If:: Confirmated bookings not cancelled
             * If:: Current time is 48 hours before Confirmed Service StarTime
             * Action:: send a booking reminder email
             */
            messages = 0;
            items    = 0;
            foreach (var b in db.Query(@"
                SELECT  BookingID
                FROM    Booking As B
                         INNER JOIN
                        CalendarEvents As E
                          ON B.ServiceDateID = E.Id
                WHERE   BookingStatusID = @0
                         AND
                        -- at 48 hours before service starts (between 49 and 48 hours)
                        getdate() > dateadd(hh, -49, E.StartTime)
                         AND
                        getdate() <= dateadd(hh, -48, E.StartTime)
                         AND
                        B.MessagingLog not like '%[48H Service Reminder]%'
            ", (int)LcEnum.BookingStatus.confirmed))
            {
                try
                {
                    // Send message
                    LcMessaging.SendBooking.For(b.BookingID).BookingReminder();

                    // Update MessagingLog for the booking
                    db.Execute(sqlAddBookingMessagingLog, b.BookingID, "[48H Service Reminder]");

                    items++;
                    messages += 2;
                }
                catch (Exception ex)
                {
                    logger.LogEx("Booking 48H Reminders", ex);
                }
            }
            logger.Log("Total of Booking 48H Reminders: {0}, messages sent: {1}", items, messages);
            totalitems    += items;
            totalmessages += messages;

            /*
             * Check:: Authorize postponed transactions 24hours previous to service start-time
             * If:: Confirmed or performed bookings only, not cancelled or in dispute or completed (completed may be
             * and old booking already paid
             * If:: Current time is 24 hours before Confirmed Service StartTime
             * If:: BookingRequest PaymentTransactionID is a Card token rather than an actual TransactionID
             * If:: Customer was still not charged / transaction was not submitted for settlement ([ClientPayment] is null)
             * Action:: authorize booking transaction
             */
            items = 0;
            {
                foreach (var b in LcRest.Booking.QueryPostponedPaymentAuthorizations(db))
                {
                    try
                    {
                        // Create transaction authorizing charge (but actually not charging still)
                        // for saved customer credit card and update DB.
                        try
                        {
                            if (b.AuthorizeTransaction())
                            {
                                items++;
                            }
                        }
                        catch (Exception ex)
                        {
                            var errTitle = "Booking Authorize Postponed Transactions, 24h before Service Starts";
                            var errDesc  = String.Format(
                                "BookingID: {0}, TransactionID: {1} Payment not allowed, error on Braintree 'sale transaction, authorizing only': {2}",
                                b.bookingID,
                                b.paymentTransactionID,
                                ex.Message
                                );

                            LcMessaging.NotifyError(errTitle, "/ScheduleTask", errDesc);

                            logger.Log("Error on: " + errTitle + "; " + errDesc);

                            // DOUBT: Notify providers on failed authorization/receive-payment?
                        }
                    }
                    catch (Exception ex)
                    {
                        logger.LogEx("Booking Authorize Postponed Transactions, 24h before Service Starts", ex);
                    }
                }
            }
            logger.Log("Total of Booking Authorize Postponed Transactions, 24h before Service Starts: {0}", items);
            totalitems += items;

            /*
             * Check:: Charge Customer the day of the service
             * If:: Confirmated or performed bookings only, not cancelled or in dispute or completed (completed may be
             * and old booking already paid
             * If:: Current time is the 1 hour after the End Service, or later
             * If:: Customer was still not charged / transaction was not submitted for settlement ([TotalPricePaidByCustomer] is null)
             * Action:: settle booking transaction
             *          set [TotalPricePaidByCustomer] and [TotalServiceFeesPaidByCustomer] values
             */
            items = 0;
            {
                // Get bookings affected by conditions

                foreach (var b in LcRest.Booking.QueryPendingOfClientChargeBookings(db))
                {
                    try
                    {
                        // Charge customer and update DB
                        try
                        {
                            if (b.SettleTransaction())
                            {
                                items++;
                            }
                        }
                        catch (Exception ex)
                        {
                            var errTitle = "Booking Charge Customer, Receive Payment";
                            var errDesc  = String.Format(
                                "BookingID: {0}, TransactionID: {1} Payment not received, error on Braintree 'settle transaction': {2}",
                                b.bookingID,
                                b.paymentTransactionID,
                                ex.Message
                                );

                            LcMessaging.NotifyError(errTitle, "/ScheduleTask", errDesc);

                            logger.Log("Error on: " + errTitle + "; " + errDesc);
                        }
                    }
                    catch (Exception ex)
                    {
                        logger.LogEx("Booking Charge Customer, Receive Payment", ex);
                    }
                }
            }
            logger.Log("Total of Booking Charge Customer, Receive Payment: {0}", items);
            totalitems += items;

            /*
             * Check:: Service Performed: The end of the service (before #844, was at 48H passed from Service)
             * If:: Confirmated bookings only, not cancelled, not set as performed, complete or dispute
             * If:: Current time is Confirmed Service EndTime
             * Action:: set booking status as 'service-performed'
             */
            messages = 0;
            items    = 0;
            {
                foreach (var b in LcRest.Booking.QueryConfirmed2ServicePerformedBookings(db))
                {
                    try
                    {
                        // Set as servicePerformed
                        b.bookingStatusID = (int)LcEnum.BookingStatus.servicePerformed;
                        LcRest.Booking.SetStatus(b, db);

                        // Send messages

                        // Notify customer and provider with an updated booking details:
                        LcMessaging.SendBooking.For(b.bookingID).ServicePerformed();

                        // Update MessagingLog for the booking
                        db.Execute(sqlAddBookingMessagingLog, b.bookingID, "[Service Performed]");

                        items++;
                        // Before Marketplace: messages += 3;
                        messages += 2;
                    }
                    catch (Exception ex)
                    {
                        logger.LogEx("Booking Service Performed", ex);
                    }
                }
            }
            logger.Log("Total of Booking Service Performed: {0}, messages sent: {1}", items, messages);
            totalitems    += items;
            totalmessages += messages;

            /*
             * Check:: Release Payment for New Providers: 5 full days after the service is performed
             * If:: If provider is a new provider (it has not previous completed bookings)
             * If:: Performed bookings only, without pricing adjustment
             * If:: Current time is 5 days after Confirmed Service EndTime
             * Action:: set booking status as 'completed',
             *          send a message to the provider notifying that payment is released.
             */
            /* REMOVED AS OF #844, 2016-01-26
             * messages = 0;
             * items = 0;
             * {
             *  foreach (var b in LcRest.Booking.QueryPendingOfPaymentReleaseBookings(true, db))
             *  {
             *      try
             *      {
             *          // Release the payment
             *          try
             *          {
             *              if (b.ReleasePayment())
             *              {
             *                  items++;
             *
             *                  // Send messages
             *
             *                  // Notify customer and provider with an updated booking details:
             *                  LcMessaging.SendBooking.For(b.bookingID).BookingCompleted();
             *
             *                  // Update MessagingLog for the booking
             *                  db.Execute(sqlAddBookingMessagingLog, b.bookingID, "[Release Payment 120H New Provider]");
             *
             *                  messages += 2;
             *              }
             *          }
             *          catch (Exception ex)
             *          {
             *
             *              var errTitle = "Booking Release Payment after 120H for new providers";
             *              var errDesc = String.Format(
             *                  "BookingID: {0}, TransactionID: {1}. Not payed on [Release Payment 120H New Provider], error on Braintree 'release transaction from escrow': {2}",
             *                  b.bookingID,
             *                  b.paymentTransactionID,
             *                  ex.Message
             *              );
             *
             *              LcMessaging.NotifyError(errTitle, "/ScheduleTask", errDesc);
             *
             *              logger.Log("Error on: " + errTitle + "; " + errDesc);
             *          }
             *      }
             *      catch (Exception ex)
             *      {
             *          logger.LogEx("Booking Release Payment after 120H for new providers", ex);
             *      }
             *  }
             * }
             * logger.Log("Total of Booking Release Payment after 120H for new providers: {0}, messages sent: {1}", items, messages);
             * totalitems += items;
             * totalmessages += messages;
             */

            /*
             * Check:: Release Payment for Service Complete: 1 hour 15 min after service is performed
             * (before #844 was 1 day after the service is performed)
             * //If:: Provider has already completed bookings (is not a new provider)
             * If:: Performed bookings only, without pricing adjustment
             * If:: Current time is 1 hour 15 min after Confirmed Service EndTime (before #844 was 1 day)
             * Action:: set booking status as 'completed',
             *          send a messages.
             */
            messages = 0;
            items    = 0;
            {
                // NOTE: Changed to ALL providers at 2016-01-26 as of #844
                foreach (var b in LcRest.Booking.QueryPendingOfPaymentReleaseBookings(null, db))
                {
                    try
                    {
                        // Release the payment
                        try
                        {
                            if (b.ReleasePayment())
                            {
                                items++;

                                // Send messages

                                // Notify customer and provider with an updated booking details:
                                LcMessaging.SendBooking.For(b.bookingID).BookingCompleted();

                                // Update MessagingLog for the booking
                                db.Execute(sqlAddBookingMessagingLog, b.bookingID, "[Release Payment 1H]");

                                messages += 2;
                            }
                        }
                        catch (Exception ex)
                        {
                            var errTitle = "Booking Release Payment after 1H to providers";
                            var errDesc  = String.Format(
                                "BookingID: {0}, TransactionID: {1}. Not payed on [Release Payment 1H], error on Braintree 'release transaction from escrow': {2}",
                                b.bookingID,
                                b.paymentTransactionID,
                                ex.Message
                                );

                            LcMessaging.NotifyError(errTitle, "/ScheduleTask", errDesc);

                            logger.Log("Error on: " + errTitle + "; " + errDesc);
                        }
                    }
                    catch (Exception ex)
                    {
                        logger.LogEx("Booking Release Payment 1H", ex);
                    }
                }
            }
            logger.Log("Total of Booking Release Payment after 1H: {0}, messages sent: {1}", items, messages);
            totalitems    += items;
            totalmessages += messages;

            /*
             * Check:: Setting No-Payment Bookings as Complete: 1 hour 15 min after service is performed
             * If:: Performed bookings only
             * If:: Current time is 1 hour 15 min after Confirmed Service EndTime
             * Action:: set booking status as 'completed',
             *          send messages.
             */
            messages = 0;
            items    = 0;
            {
                foreach (var b in LcRest.Booking.QueryPendingOfCompleteWithoutPaymentBookings(db))
                {
                    try
                    {
                        // Release the payment
                        try
                        {
                            b.SetBookingAsCompleted();
                            items++;

                            // Send messages

                            // Notify customer and provider with an updated booking details:
                            LcMessaging.SendBooking.For(b.bookingID).BookingCompleted();

                            // Update MessagingLog for the booking
                            db.Execute(sqlAddBookingMessagingLog, b.bookingID, "[Complete - no payment]");

                            messages += 2;
                        }
                        catch (Exception ex)
                        {
                            var errTitle = "Setting No-Payment Bookings as Complete after 1H15M to providers";
                            var errDesc  = String.Format(
                                "BookingID: {0}, Error: {1}",
                                b.bookingID,
                                ex.Message
                                );

                            LcMessaging.NotifyError(errTitle, "/ScheduleTask", errDesc);

                            logger.Log("Error on: " + errTitle + "; " + errDesc);
                        }
                    }
                    catch (Exception ex)
                    {
                        logger.LogEx("Setting No-Payment Bookings as Complete 1H15M", ex);
                    }
                }
            }
            logger.Log("Total of No-Payment Bookings set as Complete after 1H15M: {0}, messages sent: {1}", items, messages);
            totalitems    += items;
            totalmessages += messages;


            /*
             * Check:: [8AM Review Reminder] Booking Review Reminder Next day after service at 8AM
             * If:: Confirmed bookings not cancelled
             * If:: User did not the review still
             * If:: Current time is 8AM on the day after the Confirmed Service EndTime
             * Action:: send a booking review reminder email
             */
            /* DISABLED AS OF #844, 2016-01-26. Reminder information goes into the 'completed' email that happens sooner than before
             * messages = 0;
             * items = 0;
             * var confirmedPerformedCompletedStatuses = String.Join(",", new List<int> { (int)LcEnum.BookingStatus.confirmed, (int)LcEnum.BookingStatus.servicePerformed, (int)LcEnum.BookingStatus.completed });
             * foreach (var b in db.Query(@"
             *  SELECT  B.BookingID,
             *          CAST(CASE WHEN (SELECT count(*) FROM UserReviews As URP
             *              WHERE URP.BookingID = B.BookingID
             *                  AND
             *                  URP.ProviderUserID = B.ServiceProfessionalUserID
             *                  AND
             *                  URP.PositionID = 0
             *          ) = 0 THEN 0 ELSE 1 END As bit) As ReviewedByProvider,
             *          CAST(CASE WHEN (SELECT count(*) FROM UserReviews As URC
             *              WHERE URC.BookingID = B.BookingID
             *                  AND
             *                  URC.CustomerUserID = B.ClientUserID
             *                  AND
             *                  URC.PositionID = B.JobTitleID
             *          ) = 0 THEN 0 ELSE 1 END As bit) As ReviewedByCustomer
             *  FROM    Booking As B
             *           INNER JOIN
             *          CalendarEvents As E
             *            ON B.ServiceDateID = E.Id
             *  WHERE   B.BookingStatusID  IN (" + String.Join(",", new List<int> { (int)LcEnum.BookingStatus.confirmed, (int)LcEnum.BookingStatus.servicePerformed, (int)LcEnum.BookingStatus.completed }) + @")
             *           AND
             *          -- at 8AM hours
             *          datepart(hh, getdate()) = 8
             *           AND
             *          -- of the day after the service
             *          Cast(dateadd(d, -1, getdate()) As Date) = Cast(E.EndTime As Date)
             *           AND
             *          B.MessagingLog not like '%[8AM Review Reminder]%'
             * "))
             * {
             *  try
             *  {
             *      // We need check that there was not reviews already (why send a reminder for something
             *      // already done? just we avoid that!).
             *      // If both users did its reviews, nothing to send
             *      if (b.ReviewedByProvider && b.ReviewedByCustomer)
             *      {
             *          // Next booking
             *          continue;
             *      }
             *      char messageFor =
             *          b.ReviewedByProvider ? 'c' :
             *          b.ReviewedByCustomer ? 'p' :
             *          'b';
             *
             *      // Send message
             *      LcMessaging.SendBooking.For((int)b.BookingID).RequestToReview();
             *
             *      // Update MessagingLog for the booking
             *      db.Execute(sqlAddBookingMessagingLog, b.BookingID, "[8AM Review Reminder]");
             *
             *      items++;
             *      if (messageFor == 'c' || messageFor == 'p')
             *      {
             *          messages++;
             *      }
             *      else
             *      {
             *          messages += 2;
             *      }
             *  }
             *  catch (Exception ex)
             *  {
             *      logger.LogEx("Booking Review Reminders Next 8AM", ex);
             *  }
             * }
             * logger.Log("Total of Booking Review Reminders Next 8AM: {0}, messages sent: {1}", items, messages);
             * totalitems += items;
             * totalmessages += messages;
             */

            /*
             * Check:: [1W Review Reminder] Booking Review Reminder 1Week after service
             * If:: Confirmed bookings not cancelled, non stoped manully, maybe is set as performed already
             * If:: User did not the review still
             * If:: Past 1 Week from service
             * Action:: send a booking review reminder email
             */
            messages = 0;
            items    = 0;
            foreach (var b in db.Query(@"
                SELECT  B.BookingID,
                        CAST(CASE WHEN (SELECT count(*) FROM UserReviews As URP
                            WHERE URP.BookingID = B.BookingID
                                AND
                                URP.ProviderUserID = B.ServiceProfessionalUserID
                                AND 
                                URP.PositionID = 0
                        ) = 0 THEN 0 ELSE 1 END As bit) As ReviewedByProvider,
                        CAST(CASE WHEN (SELECT count(*) FROM UserReviews As URC
                            WHERE URC.BookingID = B.BookingID
                                AND
                                URC.CustomerUserID = B.ClientUserID
                                AND 
                                URC.PositionID = B.JobTitleID
                        ) = 0 THEN 0 ELSE 1 END As bit) As ReviewedByCustomer
                FROM    Booking As B
                         INNER JOIN
                        CalendarEvents As E
                          ON B.ServiceDateID = E.Id
                WHERE   B.BookingStatusID IN (" +
                                       String.Join(",", new List <int> {
                (int)LcEnum.BookingStatus.confirmed, (int)LcEnum.BookingStatus.servicePerformed, (int)LcEnum.BookingStatus.completed
            }) + @")
                         AND
                        -- at 1 Week=168 hours, after service ended (between 168 and 175 hours -6 hours of margin-)
                        getdate() >= dateadd(hh, 168, E.EndTime)
                         AND
                        getdate() < dateadd(hh, 175, E.EndTime)
                         AND
                        B.MessagingLog not like '%[1W Review Reminder]%'
            "))
            {
                try
                {
                    // We need check that there was not reviews already (why send a reminder for something
                    // already done? just we avoid that!).
                    // If both users did its reviews, nothing to send
                    if (b.ReviewedByProvider && b.ReviewedByCustomer)
                    {
                        // Next booking
                        continue;
                    }
                    char messageFor =
                        b.ReviewedByProvider ? 'c' :
                        b.ReviewedByCustomer ? 'p' :
                        'b';

                    // Send message
                    LcMessaging.SendBooking.For((int)b.BookingID).RequestToReviewReminder();

                    // Update MessagingLog for the booking
                    db.Execute(sqlAddBookingMessagingLog, b.BookingID, "[1W Review Reminder]");

                    items++;
                    if (messageFor == 'c' || messageFor == 'p')
                    {
                        messages++;
                    }
                    else
                    {
                        messages += 2;
                    }
                }
                catch (Exception ex)
                {
                    logger.LogEx("Booking Review Reminders 1W", ex);
                }
            }
            logger.Log("Total of Booking Review Reminders 1W: {0}, messages sent: {1}", items, messages);
            totalitems    += items;
            totalmessages += messages;

            // Ending work with database
        }

        logger.Log("Elapsed time {0}, for {1} bookings affected and {2} messages sent", DateTime.Now - elapsedTime, totalitems, totalmessages);


        /*
         * iCalendar
         */
        DateTime partialElapsedTime = DateTime.Now;

        int successCalendars = 0,
            failedCalendars  = 0;

        foreach (var err in LcCalendar.BulkImport())
        {
            if (err == null)
            {
                successCalendars++;
            }
            else
            {
                failedCalendars++;
                logger.LogEx("Import Calendar", err);
            }
        }

        logger.Log("Elapsed time {0}, for {1} user calendars imported, {2} failed", DateTime.Now - partialElapsedTime, successCalendars, failedCalendars);


        /*
         * Task Ended
         */
        logger.Log("Total Elapsed time {0}", DateTime.Now - elapsedTime);

        string logresult = logger.ToString();

        // Finishing: save log on disk, per month rotation
        //try {
        logger.Save();
        //}catch { }

        return(logresult);
    }
コード例 #22
0
        public static int PostInquiry(int ThreadID, string BodyText, int SentByUserID)
        {
            // Get Thread info
            var thread = Thread.Get(ThreadID);

            // Validate user can send it (its in the thread)
            if (thread == null || (
                    SentByUserID != thread.clientUserID &&
                    SentByUserID != thread.serviceProfessionalUserID
                    ))
            {
                // Not allowed, quick return, nothing done:
                return(0);
            }

            var clientEmail = UserProfile.GetEmail(thread.clientUserID);
            var serviceProfessionalEmail = UserProfile.GetEmail(thread.serviceProfessionalUserID);

            if (clientEmail != null && serviceProfessionalEmail != null)
            {
                bool SentByFreelancer      = SentByUserID == thread.serviceProfessionalUserID;
                var  firstMessage          = GetFirstThreadMessage(ThreadID);
                var  firstSentByFreelancer = firstMessage.sentByUserID == thread.serviceProfessionalUserID;

                // ThreadStatus: 1=respond, 2=responded
                // MessageType: 1=customer inquiry, 3=provider answer, 22=provider inquiry, 23=customer answer
                var statusID = 0;
                var typeID   = 0;
                if (firstSentByFreelancer)
                {
                    if (SentByFreelancer)
                    {
                        // Freelancer is asking again
                        statusID = (int)LcMessaging.MessageThreadStatus.Respond;
                        typeID   = (int)LcMessaging.MessageType.ProfessionalInquiry;
                    }
                    else
                    {
                        // Client answered
                        statusID = (int)LcMessaging.MessageThreadStatus.Responded;
                        typeID   = (int)LcMessaging.MessageType.ClientResponseToInquiry;
                    }
                }
                else
                {
                    if (SentByFreelancer)
                    {
                        // Freelancer answered
                        statusID = (int)LcMessaging.MessageThreadStatus.Responded;
                        typeID   = (int)LcMessaging.MessageType.ProfessionalResponseToInquiry;
                    }
                    else
                    {
                        // Client is asking again
                        statusID = (int)LcMessaging.MessageThreadStatus.Respond;
                        typeID   = (int)LcMessaging.MessageType.ClientInquiry;
                    }
                }

                int messageID = LcMessaging.CreateMessage(ThreadID, statusID, typeID, BodyText, SentByUserID);

                // From a REST API, a copy is Not send to the original user, as in the general API, so just
                // send an email to the recipient
                // NOTE: the Kind possible values are in the template.
                if (SentByFreelancer)
                {
                    // NOTE: Message from freelancer to client, answering an inquiry started by the client.
                    // TODO: i18n
                    LcMessaging.SendMail(clientEmail, "A Message From a Loconomics Freelancer",
                                         LcMessaging.ApplyTemplate(LcUrl.LangPath + "Email/EmailInquiry/",
                                                                   new Dictionary <string, object> {
                        { "ThreadID", ThreadID },
                        { "MessageID", messageID },
                        { "Kind", firstSentByFreelancer ? 6 : 2 },
                        { "RequestKey", LcMessaging.SecurityRequestKey },
                        { "EmailTo", clientEmail }
                    }), replyTo: serviceProfessionalEmail
                                         );
                }
                else
                {
                    // NOTE: Copy to the author. The author is a freelancer, answering to a client that started the inquiry.
                    // TODO: i18n
                    LcMessaging.SendMail(serviceProfessionalEmail, "A Message From a Loconomics Client",
                                         LcMessaging.ApplyTemplate(LcUrl.LangPath + "Email/EmailInquiry/",
                                                                   new Dictionary <string, object> {
                        { "ThreadID", ThreadID },
                        { "MessageID", messageID },
                        { "Kind", firstSentByFreelancer ? 5 : 3 },
                        { "RequestKey", LcMessaging.SecurityRequestKey },
                        { "EmailTo", serviceProfessionalEmail }
                    }), replyTo: clientEmail
                                         );
                }

                return(messageID);
            }
            // no emails, users don't exists or inactive
            return(0);
        }
コード例 #23
0
    /// <summary>
    /// Process a request to create a user job title given a jobTitleID or as
    /// fallback a validated and sanitized jobTitleName (pass in GetValidatedJobTitleName result)
    /// </summary>
    /// <param name="userID"></param>
    /// <param name="jobTitleID"></param>
    /// <param name="jobTitleName"></param>
    /// <returns></returns>
    public dynamic Create(int userID, int jobTitleID, string jobTitleName)
    {
        var jobTitleExists = false;

        if (jobTitleID == 0)
        {
            // Look-up/new-job-title version: it's possible that the user wrotes a
            // job title name without pick-up one from the list, we look-up for that in database
            // for a jobTitleID,
            // and may not exists, so we try to create a new one with a template.

            // Name for the job title is required
            if (String.IsNullOrEmpty(jobTitleName))
            {
                throw new HttpException(400, "A Job Title is required");
            }

            // Search
            var jobTitle = LcRest.PublicJobTitle.AutocompleteSearch(jobTitleName, LcRest.Locale.Current).FirstOrDefault();
            if (jobTitle != null)
            {
                // Use the first one
                jobTitleID     = jobTitle.value;
                jobTitleExists = true;
            }
            else
            {
                //  Create a new job-title based on the given name #650
                jobTitleID = LcData.JobTitle.CreateJobTitleByName(jobTitleName, LcRest.Locale.Current.languageID, LcRest.Locale.Current.countryID, userID);
                // Check well know custom error codes
                if (jobTitleID == -1)
                {
                    throw new HttpException(400, String.Format("The Job Title '{0}' is not allowed.", jobTitleName));
                }
                LcMessaging.NotifyNewJobTitle(jobTitleName, jobTitleID);
                jobTitleExists = true;
            }
        }
        else
        {
            // Double check that the job title exists
            jobTitleExists = LcRest.PublicJobTitle.Get(jobTitleID, LcRest.Locale.Current) != null;
        }

        if (jobTitleID > 0 && jobTitleExists)
        {
            // Read data; It stops on not valid:
            var data = GetValidatedItemBodyInput();

            LcData.JobTitle.InsertUserJobTitle(
                userID,
                jobTitleID,
                data.policyID,
                data.intro,
                data.instantBooking,
                data.collectPaymentAtBookMeButton,
                LcRest.Locale.Current.languageID,
                LcRest.Locale.Current.countryID
                );

            // If user is just a client, needs to become a professional
            var user = LcRest.UserProfile.Get(userID);
            if (!user.isServiceProfessional)
            {
                LcAuth.BecomeProvider(userID);
                // Set onboarding step as done for 'add job titles' to avoid display that screen again to the user:
                LcData.UserInfo.SetOnboardingStep(userID, "addJobTitles");
                // Send email as provider
                LcMessaging.SendWelcomeProvider(userID, WebSecurity.CurrentUserName);
            }
        }
        else
        {
            throw new HttpException(404, "Job Title not found or disapproved");
        }

        return(GetItem(userID, jobTitleID));
    }
コード例 #24
0
    /// <summary>
    /// Process a request to create a user job title given a jobTitleID with
    /// a validated and sanitized jobTitleName (pass in GetValidatedJobTitleName result)
    /// as a custom listing title.
    /// </summary>
    /// <param name="userID"></param>
    /// <param name="jobTitleID"></param>
    /// <param name="jobTitleName"></param>
    /// <returns></returns>
    public dynamic Create(int userID, int jobTitleID, string jobTitleName)
    {
        if (jobTitleID == 0 || jobTitleID == LcRest.UserJobTitle.UserGeneratedJobTitleID)
        {
            // new-job-title version: it's possible that the user wrotes a
            // job title name without pick-up one from the list, on that case
            // the user generated job title is assigned and the given title name is
            // used as listing title

            // Name for the job title is required
            if (String.IsNullOrWhiteSpace(jobTitleName))
            {
                throw new HttpException(400, "A Job Title is required");
            }

            // Search: we try an exact match, just in case we have already the job title (singular or plural) and
            // user didn't select it from the list
            var locale   = LcRest.Locale.Current;
            var jobTitle = LcRest.JobTitle.FindExactName(jobTitleName, locale.languageID, locale.countryID);
            if (jobTitle.HasValue)
            {
                // Use the first one
                jobTitleID = jobTitle.Value;
            }
            else
            {
                //  Create a new job-title based on the given name #650
                jobTitleID = LcRest.UserJobTitle.UserGeneratedJobTitleID;
            }
        }
        // Double check that the job title exists
        else
        {
            var existentTitle = LcRest.PublicJobTitle.Get(jobTitleID, LcRest.Locale.Current);
            if (existentTitle == null)
            {
                throw new HttpException(404, "Job Title not found or disapproved");
            }
            // If exists, we use the user given title, with fallback to the one we have for the given jobTitleID
            else if (String.IsNullOrWhiteSpace(jobTitleName))
            {
                jobTitleName = existentTitle.singularName;
            }
        }

        // Read data; It stops on not valid:
        var data = GetValidatedItemBodyInput();

        LcRest.UserJobTitle.Create(new LcRest.UserJobTitle
        {
            userID                       = userID,
            jobTitleID                   = jobTitleID,
            title                        = jobTitleName,
            intro                        = data.intro,
            cancellationPolicyID         = data.policyID,
            collectPaymentAtBookMeButton = data.collectPaymentAtBookMeButton,
            instantBooking               = data.instantBooking
        });

        // If user is just a client, needs to become a professional
        var user = LcRest.UserProfile.Get(userID);

        if (!user.isServiceProfessional)
        {
            LcAuth.BecomeProvider(userID);
            // Set onboarding step as done for 'add job title' to avoid display that screen again to the user:
            LcData.UserInfo.SetOnboardingStep(userID, "addJobTitle");
            // Send email as provider
            LcMessaging.SendWelcomeProvider(userID, WebSecurity.CurrentUserName);
        }

        return(LcRest.UserJobTitle.GetItem(userID, jobTitleID));
    }