/// <summary> /// Adds a redemption reward for the transaction in the context. /// </summary> internal void AddRedemptionRewards() { RedeemedDealInfo redeemedDealInfo = (RedeemedDealInfo)Context[Key.RedeemedDealInfo]; if (Context.Config.EnableRedemptionRewards == true && (ReimbursementTender)redeemedDealInfo.ReimbursementTenderId == ReimbursementTender.MicrosoftEarn) { IRewardOperations rewardOperations = CommerceOperationsFactory.RewardOperations(Context); Context[Key.RewardId] = Context.Config.FirstEarnRewardId; Context[Key.FirstEarnRewardAmount] = Context.Config.FirstEarnRewardAmount; Context[Key.FirstEarnRewardExplanation] = Context.Config.FirstEarnRewardExplanation; ConcurrentDictionary <string, string> payload = new ConcurrentDictionary <string, string>(); IScheduler scheduler = PartnerFactory.Scheduler(Context.Config.SchedulerQueueName, Context.Config.SchedulerTableName, Context.Config); if (rewardOperations.AddRedemptionReward() == ResultCode.Success) { // Add a job to potentially reward user for their first Earn. payload[Key.RewardPayoutId.ToString()] = ((Guid)Context[Key.RewardPayoutId]).ToString(); payload[Key.PartnerCardId.ToString()] = (string)Context[Key.PartnerCardId]; payload[Key.PartnerRedeemedDealId.ToString()] = redeemedDealInfo.PartnerRedeemedDealId; payload[Key.RewardId.ToString()] = Context.Config.FirstEarnRewardId.ToString(); ScheduledJobDetails scheduledJobDetails = new ScheduledJobDetails { JobId = Guid.NewGuid(), JobType = ScheduledJobType.ApplyRedemptionReward, JobDescription = redeemedDealInfo.GlobalUserId.ToString(), Orchestrated = true, Payload = payload }; scheduler.ScheduleJobAsync(scheduledJobDetails).Wait(); } // Add a job to potentially reward the person who referred this user for this user's first Earn. Context[Key.RedeemedDealId] = ((RedeemedDeal)Context[Key.RedeemedDeal]).Id; Guid globalUserId = ((RedeemedDealInfo)Context[Key.RedeemedDealInfo]).GlobalUserId; Context[Key.GlobalUserId] = globalUserId; string userId = globalUserId.ToString(); if (rewardOperations.AddReferredRedemptionReward() == ResultCode.Success) { payload[Key.GlobalUserId.ToString()] = userId; payload[Key.ReferralEvent.ToString()] = ReferralEvent.Signup.ToString(); ScheduledJobDetails scheduledJobDetails = new ScheduledJobDetails { JobId = Guid.NewGuid(), JobType = ScheduledJobType.ApplyReferralReward, JobDescription = userId, Orchestrated = true, StartTime = DateTime.UtcNow, Payload = payload }; scheduler.ScheduleJobAsync(scheduledJobDetails).Wait(); } } }
/// <summary> /// Updates analytics with information about the transaction in the context. /// </summary> private void UpdateAnalytics() { RedeemedDealInfo redeemedDealInfo = (RedeemedDealInfo)Context[Key.RedeemedDealInfo]; SharedUserLogic sharedUserLogic = new SharedUserLogic(Context, CommerceOperationsFactory.UserOperations(Context)); Context[Key.GlobalUserId] = redeemedDealInfo.GlobalUserId; User user = sharedUserLogic.RetrieveUser(); RedeemedDeal redeemedDeal = (RedeemedDeal)Context[Key.RedeemedDeal]; Analytics.AddRedemptionEvent(redeemedDealInfo.GlobalUserId, redeemedDeal.AnalyticsEventId, user.AnalyticsEventId, redeemedDealInfo.ParentDealId, redeemedDealInfo.Currency, redeemedDeal.AuthorizationAmount, redeemedDealInfo.DiscountAmount, redeemedDealInfo.GlobalId, (string)Context[Key.PartnerMerchantId], CommerceWorkerConfig.Instance); }
/// <summary> /// Checks if this transaction has earned any discount amount in order to send a notification /// </summary> /// <returns>If we have to send a notification for the transaction or not</returns> private bool TransactionMetNotificationThreshold() { Context.Log.Verbose("Checking if the discount amount for the transaction is good enough for a notification"); //By default make it true. It's better to send a notification wrongly than not to send one. bool transactionMetNotificationThreshold = true; const int thresholdAmount = 0; //No point in sending a notification where the customer did not save anything RedeemedDealInfo redeemedDealInfo = (RedeemedDealInfo)Context[Key.RedeemedDealInfo]; if (redeemedDealInfo.DiscountAmount <= thresholdAmount) { Context.Log.Verbose("Discount amount {0) is too small/has not met the threshold limit of {1} to send notification", redeemedDealInfo.DiscountAmount, thresholdAmount); //Do not send a notification if the discount has not met the threshold transactionMetNotificationThreshold = false; } return(transactionMetNotificationThreshold); }
/// <summary> /// Adds the Authorization to the data store and logs accordingly. /// </summary> /// <returns> /// The ResultCode corresponding to the result of the operation. /// </returns> public ResultCode AddAuthorization() { ResultCode result; // Add the redemption event info to the data store. Context.Log.Verbose("Attempting to add the authorization to the data store."); result = AuthorizationOperations.AddAuthorization(); Context.Log.Verbose("ResultCode after adding the authorization to the data store: {0}", result); //TODO: Shouldn't this be in the CardLink layer? // If the authorization was successfully created, complete populating redeemed deal into fields. if (result == ResultCode.Created) { RedeemedDealInfo redeeemedDealInfo = (RedeemedDealInfo)Context[Key.RedeemedDealInfo]; int redemptionAmt = redeeemedDealInfo.DiscountAmount; double actualSavings = (double)redemptionAmt / 100; redeeemedDealInfo.DiscountText = String.Format("${0:F2}", actualSavings); } return(result); }
/// <summary> /// Executes processing of the request. /// </summary> public ResultCode Execute() { ResultCode result = ResultCode.None; EndPointMessageRequest request = (EndPointMessageRequest)Context[Key.Request]; Dictionary <String, String> messageElementCollectionDictionary = new Dictionary <string, string>(); foreach (MessageElementsCollection c in request.MessageElementsCollection) { messageElementCollectionDictionary.Add(c.Key, c.Value); } String requestType = messageElementCollectionDictionary[VisaEPMConstants.EventEventType]; if (string.Equals(requestType, VisaEPMConstants.OnClearEventTypeValue, StringComparison.OrdinalIgnoreCase)) { Dictionary <String, String> userDefinedFieldsCollectionDictionary = new Dictionary <string, string>(); foreach (UserDefinedFieldsCollection c in request.UserDefinedFieldsCollection) { userDefinedFieldsCollectionDictionary.Add(c.Key, c.Value); } String cardId = request.CardId; String merchantId = messageElementCollectionDictionary[VisaEPMConstants.TransactionVisaMerchantId]; String storeId = messageElementCollectionDictionary[VisaEPMConstants.TransactionVisaStoreId]; // Marshal the redeem deal request into a RedeemedDeal object. RedeemedDeal redeemedDeal = new RedeemedDeal() { AnalyticsEventId = Guid.NewGuid() }; Context[Key.RedeemedDeal] = redeemedDeal; redeemedDeal.CallbackEvent = RedemptionEvent.Settlement; redeemedDeal.PartnerRedeemedDealScopeId = messageElementCollectionDictionary[VisaEPMConstants.TransactionVipTransactionId]; redeemedDeal.PartnerRedeemedDealId = messageElementCollectionDictionary[VisaEPMConstants.TransactionTransactionID]; String time = messageElementCollectionDictionary[VisaEPMConstants.TransactionTimeStampYYMMDD]; // UTC Time: 2013-12-05T07:25:06 redeemedDeal.PurchaseDateTime = DateTime.Parse(time); redeemedDeal.PurchaseDateTime = DateTime.SpecifyKind(redeemedDeal.PurchaseDateTime, DateTimeKind.Utc); String amount = messageElementCollectionDictionary[VisaEPMConstants.TransactionClearingAmount]; redeemedDeal.AuthorizationAmount = AmexUtilities.ParseAuthAmount(amount); redeemedDeal.Currency = VisaConstants.CurrencyUSD; Context[Key.PartnerCardId] = cardId; Context[Key.PartnerClaimedDealId] = null; // could be the BingOfferDealId Context[Key.PartnerMerchantId] = string.Format("{0};{1}", merchantId, storeId); Context[Key.OutletPartnerMerchantId] = null; // storedId; Context[Key.CreditStatus] = CreditStatus.ClearingReceived; string merchantCity = messageElementCollectionDictionary.NullIfNotExist(VisaEPMConstants.MerchantCityString); string merchantState = messageElementCollectionDictionary.NullIfNotExist(VisaEPMConstants.MerchantStateString); var merchantPostalCode = messageElementCollectionDictionary.NullIfNotExist(VisaEPMConstants.MerchantPostalCodeString); KeyTransactionData keyTransactionData = new KeyTransactionData { MerchantCity = merchantCity, MerchantState = merchantState, MerchantPostalCode = merchantPostalCode }; Context[Key.PartnerData] = keyTransactionData.XmlSerialize(); LogRedeedmedDealRequestParameters(redeemedDeal); result = AddRedeemedDeal(); Context[Key.ResultCode] = result; // If the deal was successfully redeemed, apply any applicable rewards. if (result == ResultCode.Created) { RedeemedDealInfo redeemedDealInfo = (RedeemedDealInfo)Context[Key.RedeemedDealInfo]; if (redeemedDealInfo != null) { // Update analytics. SharedUserLogic sharedUserLogic = new SharedUserLogic(Context, CommerceOperationsFactory.UserOperations( Context)); Context[Key.GlobalUserId] = redeemedDealInfo.GlobalUserId; User user = sharedUserLogic.RetrieveUser(); if (user != null) { Analytics.AddRedemptionEvent(redeemedDealInfo.GlobalUserId, redeemedDeal.AnalyticsEventId, user.AnalyticsEventId, redeemedDealInfo.ParentDealId, redeemedDealInfo.Currency, redeemedDeal.AuthorizationAmount, redeemedDealInfo.DiscountAmount, redeemedDealInfo.GlobalId, (string)Context[Key.PartnerMerchantId]); } // Add rewards for any active rewards program. AddRedemptionRewards(); } } } return(result); }
/// <summary> /// Send email and sms /// </summary> /// <param name="userId"> /// User Id to send notification to /// </param> /// <param name="environment"> /// Environment for which notification needs to be sent /// </param> internal void Send(Guid userId, string environment) { try { Users.Dal.DataModel.User user = RetrieveUser(userId); //By default, enable both email and phone notification TransactionNotificationPreference transactionNotificationPreference = TransactionNotificationPreference.Email | TransactionNotificationPreference.Phone; if (user.Info != null && user.Info.Preferences != null) { transactionNotificationPreference = user.Info.Preferences.TransactionNotificationMedium; } if (transactionNotificationPreference == TransactionNotificationPreference.None) { Context.Log.Verbose("User has turned off both SMS & Email auth notification"); return; } INotificationContentCreator creator = NotificationContentCreatorFactory.NotificationContentCreator(Context); RedeemedDealInfo redeemedDealInfo = (RedeemedDealInfo)Context[Key.RedeemedDealInfo]; Context.Log.Verbose("Credit Amount : {0}", redeemedDealInfo.DiscountText); Context.Log.Verbose("DiscountSummary : {0}", redeemedDealInfo.DiscountSummary); Context.Log.Verbose("LastFourDigits : {0}", redeemedDealInfo.LastFourDigits); Context.Log.Verbose("MerchantName : {0}", redeemedDealInfo.MerchantName); Context.Log.Verbose("UserName : {0}", SalutationName); Context.Log.Verbose("ReimbursementTender : {0}", redeemedDealInfo.ReimbursementTenderId); AuthEmailNotificationData authEmailNotificationData = new AuthEmailNotificationData() { CreditAmount = redeemedDealInfo.DiscountText, DiscountSummary = redeemedDealInfo.DiscountSummary, LastFourDigits = redeemedDealInfo.LastFourDigits, MerchantName = redeemedDealInfo.MerchantName, UserName = SalutationName, UserId = userId.ToString(), DealId = redeemedDealInfo.ParentDealId.ToString(), DiscountId = redeemedDealInfo.GlobalId.ToString(), TransactionDate = redeemedDealInfo.TransactionDate.ToLongDateString(), TransactionId = redeemedDealInfo.TransactionId, PartnerId = redeemedDealInfo.PartnerId.ToString(CultureInfo.InvariantCulture), PartnerMerchantId = redeemedDealInfo.PartnerMerchantId, Percent = (float)Math.Round(redeemedDealInfo.Percent, 2) }; NotificationContent content; if ((transactionNotificationPreference & TransactionNotificationPreference.Email) == TransactionNotificationPreference.Email) { EnvironmentType environmentType; if (Enum.TryParse(environment, true, out environmentType)) { authEmailNotificationData.PopulateAuthStatusAndEmailLink(user, UsersDal, environmentType); } bool isEarn = true; if (redeemedDealInfo.ReimbursementTenderId == (int)ReimbursementTender.MicrosoftEarn) { content = creator.CreateAuthEmailContentAsync(authEmailNotificationData, authEmailSubjectEarn, authEmailTemplatePathEarn).Result; } else if (redeemedDealInfo.ReimbursementTenderId == (int)ReimbursementTender.MicrosoftBurn) { content = creator.CreateAuthEmailContentAsync(authEmailNotificationData, authEmailSubjectBurn, authEmailTemplatePathBurn).Result; } else { isEarn = false; content = creator.CreateAuthEmailContentAsync(authEmailNotificationData, authEmailSubjectClo).Result; } Context.Log.Verbose("About to send Email Auth notification"); SendEmailNotification(user.Email, content, isEarn); Context.Log.Verbose("Email notification sent"); } else { Context.Log.Verbose("User has turned off Email Auth notification"); } if ((transactionNotificationPreference & TransactionNotificationPreference.Phone) == TransactionNotificationPreference.Phone) { AuthSmsNotificationData authSmsNotificationData = new AuthSmsNotificationData() { DiscountSummary = redeemedDealInfo.DiscountSummary, MerchantName = redeemedDealInfo.MerchantName, Percent = (float)Math.Round(redeemedDealInfo.Percent, 2), CreditAmount = redeemedDealInfo.DiscountText }; if (redeemedDealInfo.ReimbursementTenderId == (int)ReimbursementTender.MicrosoftEarn) { content = creator.CreateAuthSmsContentAsync(authSmsNotificationData, authSmsTemplatePathEarn).Result; } else if (redeemedDealInfo.ReimbursementTenderId == (int)ReimbursementTender.MicrosoftBurn) { content = creator.CreateAuthSmsContentAsync(authSmsNotificationData, authSmsTemplatePathBurn).Result; } else { content = creator.CreateAuthSmsContentAsync(authSmsNotificationData).Result; } Context.Log.Verbose("About to send SMS Auth notification"); SendSmsNotification(userId, content.TextBody); Context.Log.Verbose("SMS Notification sent"); } else { Context.Log.Verbose("User has turned off SMS Auth notification"); } } catch (Exception exception) { // catch all exception, log them as warning. // but continue sending other notifications if needed Context.Log.Warning("Sending notification resulted in error. User Id : {0}", exception, userId); } }
/// <summary> /// Executes processing of the deal redemption request. /// </summary> public ResultCode Execute() { ResultCode result; // Marshal the First Data redeem deal request into a RedeemedDeal object. RedeemedDeal redeemedDeal = new RedeemedDeal() { AnalyticsEventId = Guid.NewGuid() }; Context[Key.RedeemedDeal] = redeemedDeal; FirstData firstData = new FirstData(Context); firstData.MarshalRedeemDeal(); // If the purchase date and time was valid, Add the RedeemedDeal to the data store. if (redeemedDeal.PurchaseDateTime != DateTime.MinValue) { string disallowedReason = Context[Key.DisallowedReason] as string; if (String.IsNullOrWhiteSpace(disallowedReason) == true) { result = AddRedeemedDeal(); } else { Context.Log.Warning("Transaction is disallowed because tender type was: {0}.", disallowedReason); Context[Key.RedeemedDealInfo] = new RedeemedDealInfo { PartnerDealId = (string)Context[Key.PartnerDealId], PartnerClaimedDealId = (string)Context[Key.PartnerClaimedDealId] }; result = ResultCode.TransactionDisallowed; } } else { result = ResultCode.InvalidPurchaseDateTime; } // Build the response to send back to First Data based on the result of adding the RedeemedDeal. Context[Key.ResultCode] = result; firstData.BuildRedeemedDealResponse(); // If the deal was successfully redeemed, send user notification and update analytics. if (result == ResultCode.Created) { RedeemedDealInfo redeemedDealInfo = (RedeemedDealInfo)Context[Key.RedeemedDealInfo]; // Send notification. NotifyAuthorization notifyAuthorization = new NotifyAuthorization(Context); Task.Run(new Action(notifyAuthorization.SendNotification)); // Update analytics. SharedUserLogic sharedUserLogic = new SharedUserLogic(Context, CommerceOperationsFactory.UserOperations(Context)); Context[Key.GlobalUserId] = redeemedDealInfo.GlobalUserId; User user = sharedUserLogic.RetrieveUser(); Analytics.AddRedemptionEvent(redeemedDealInfo.GlobalUserId, redeemedDeal.AnalyticsEventId, user.AnalyticsEventId, redeemedDealInfo.ParentDealId, redeemedDealInfo.Currency, redeemedDeal.AuthorizationAmount, redeemedDealInfo.DiscountAmount, redeemedDealInfo.GlobalId, (string)Context[Key.PartnerMerchantId]); } return(result); }
/// <summary> /// Adds the authorization in the context to the data store. /// </summary> /// <returns> /// The ResultCode corresponding to the result of the operation. /// </returns> public ResultCode AddAuthorization() { int?offset = GetPartnerMerchantTimeZoneOffset(); ResultCode result; Authorization authorization = (Authorization)Context[Key.Authorization]; // TEMPORARY: Determine if the allow list forces transaction rejection. if (PhysStoreFilter() == true) { //Context.Log.Critical("AuthorizationId: {0}\r\nPartnerId: {1}\r\nPartnerDealId: {2}\r\nPartnerCardId: {3}\r\nPartnerMerchantId: {4}\r\nPurchaseDateTime: {5}\r\nAuthorizationAmount: {6}\r\nTransactionScopeId: {7}" + // "TransactionId: {8}\r\nCurrency: {9}\r\nPartnerData: {10}\r\noffset: {11}", null, // authorization.Id, (int)Context[Key.Partner], Context[Key.PartnerDealId], Context[Key.PartnerCardId], Context[Key.PartnerMerchantId], authorization.PurchaseDateTime, authorization.AuthorizationAmount, authorization.TransactionScopeId, // authorization.TransactionId, authorization.Currency, Context[Key.PartnerData], offset); result = SqlProcedure("AddAuthorization", new Dictionary <string, object> { { "@authorizationId", authorization.Id }, { "@partnerId", (int)Context[Key.Partner] }, { "@recommendedPartnerDealId", Context[Key.PartnerDealId] }, { "@partnerCardId", Context[Key.PartnerCardId] }, { "@partnerMerchantId", Context[Key.PartnerMerchantId] }, { "@purchaseDateTime", authorization.PurchaseDateTime }, { "@authAmount", authorization.AuthorizationAmount }, { "@transactionScopeId", authorization.TransactionScopeId }, { "@transactionId", authorization.TransactionId }, { "@currency", authorization.Currency }, { "@partnerData", Context[Key.PartnerData] }, { "@timeZoneOffset", offset } }, (sqlDataReader) => { if (sqlDataReader.Read() == true) { RedeemedDealInfo redeemedDealInfo = new RedeemedDealInfo { GlobalId = sqlDataReader.GetGuid(sqlDataReader.GetOrdinal("GlobalDealId")), Currency = sqlDataReader.GetString(sqlDataReader.GetOrdinal("Currency")), Amount = sqlDataReader.GetInt32(sqlDataReader.GetOrdinal("Amount")), Percent = sqlDataReader.GetDecimal(sqlDataReader.GetOrdinal("Percent")), MinimumPurchase = sqlDataReader.GetInt32(sqlDataReader.GetOrdinal("MinimumPurchase")), GlobalUserId = sqlDataReader.GetGuid(sqlDataReader.GetOrdinal("GlobalUserId")), PartnerDealId = sqlDataReader.GetString(sqlDataReader.GetOrdinal("PartnerDealId")), PartnerClaimedDealId = sqlDataReader.IsDBNull(sqlDataReader.GetOrdinal("PartnerClaimedDealId")) ? null : sqlDataReader.GetString(sqlDataReader.GetOrdinal("PartnerClaimedDealId")), PartnerRedeemedDealId = authorization.PartnerRedeemedDealId, DiscountSummary = sqlDataReader.GetString(sqlDataReader.GetOrdinal("DiscountSummary")), LastFourDigits = sqlDataReader.GetString(sqlDataReader.GetOrdinal("LastFourDigits")), DiscountAmount = sqlDataReader.GetInt32(sqlDataReader.GetOrdinal("DiscountAmount")), PartnerMerchantId = (string)Context[Key.PartnerMerchantId], TransactionId = authorization.TransactionId, TransactionDate = authorization.PurchaseDateTime, PartnerId = (int)Context[Key.Partner], ReimbursementTenderId = sqlDataReader.GetInt32(sqlDataReader.GetOrdinal("ReimbursementTenderId")) }; // Function based partner claimed deal id is not stored (comes as null or empty string). Generate it. if (string.IsNullOrEmpty(redeemedDealInfo.PartnerClaimedDealId)) { int cardId = sqlDataReader.GetInt32(sqlDataReader.GetOrdinal("CardId")); int dealId = sqlDataReader.GetInt32(sqlDataReader.GetOrdinal("DealId")); redeemedDealInfo.PartnerClaimedDealId = General.TwoIntegersToHexString(cardId, dealId); } // Add nullable items. int merchantNameColumnId = sqlDataReader.GetOrdinal("MerchantName"); if (sqlDataReader.IsDBNull(merchantNameColumnId) == false) { redeemedDealInfo.MerchantName = sqlDataReader.GetString(merchantNameColumnId); } int parentDealIdColumnId = sqlDataReader.GetOrdinal("ParentDealId"); if (sqlDataReader.IsDBNull(parentDealIdColumnId) == false) { redeemedDealInfo.ParentDealId = sqlDataReader.GetGuid(parentDealIdColumnId); } // Populate the context. Context[Key.RedeemedDealInfo] = redeemedDealInfo; } }); } // TEMPORARY: Log the disallowed transaction. else { result = ResultCode.NoApplicableDealFound; Context.Log.Warning("Transaction disallowed. Card / Allow list mismatch."); } if (result == ResultCode.Success) { result = ResultCode.Created; } return(result); }
/// <summary> /// Process the transaction log file /// </summary> /// <returns> /// Async Task Wrapper /// </returns> public async Task Process() { TransactionLogParser transactionLogParser = new TransactionLogParser(Context.Log); TransactionLogFile transactionLogFile = transactionLogParser.Parse(TransactionLogFileName, TransactionLogFileStream); if (transactionLogFile != null) { foreach (TransactionLogDetail detail in transactionLogFile.TransactionLogRecords) { // block the reversed transactions if (TransactionIdSet.Contains(detail.TransactionId) || detail.TransactionAmount <= 0) { continue; } TransactionIdSet.Add(detail.TransactionId); // 1. process the detail record here -> Insert as redeemed deal RedeemedDeal redeemedDeal = new RedeemedDeal() { AnalyticsEventId = Guid.NewGuid() }; Context[Key.RedeemedDeal] = redeemedDeal; MarshalRedeemDeal(detail); ResultCode result = RedeemedDealOperations.AddRedeemedDeal(); //2. If the record was processed successfully, attempt to add a redemption reward if applicable and analytics if (result == ResultCode.Created) { RedeemedDealInfo redemptionInfo = (RedeemedDealInfo)Context[Key.RedeemedDealInfo]; // First add a redemption reward to the redeeming user if they're enabled. if (EnableRedemptionRewards) { if (WorkerActions.RewardRedemption(RewardOperations, Context) == ResultCode.Success) { // Add job to process the reward payout. ConcurrentDictionary <string, string> payload = new ConcurrentDictionary <string, string>(); payload[Key.RewardPayoutId.ToString()] = ((Guid)Context[Key.RewardPayoutId]).ToString(); payload[Key.PartnerCardId.ToString()] = (string)Context[Key.PartnerCardId]; payload[Key.PartnerRedeemedDealId.ToString()] = redemptionInfo.PartnerRedeemedDealId; IScheduler scheduler = PartnerFactory.Scheduler(CommerceWorkerConfig.Instance.SchedulerQueueName, CommerceWorkerConfig.Instance.SchedulerTableName, CommerceWorkerConfig.Instance); ScheduledJobDetails scheduledJobDetails = new ScheduledJobDetails { JobId = Guid.NewGuid(), JobType = ScheduledJobType.ApplyRedemptionReward, JobDescription = redemptionInfo.GlobalUserId.ToString(), Orchestrated = true, Payload = payload }; await scheduler.ScheduleJobAsync(scheduledJobDetails).ConfigureAwait(false); } } // Then add a referred redemption reward to the user who referred the redeeming user. Context[Key.RedeemedDealId] = ((RedeemedDeal)Context[Key.RedeemedDeal]).Id; Context[Key.GlobalUserId] = ((RedeemedDealInfo)Context[Key.RedeemedDealInfo]).GlobalUserId; WorkerActions.RewardReferredRedemption(RewardOperations, Context); // Update analytics. // For FDC this happens at AUTH time // Butfor Amex flow, we put analytics at the time of Transaction File Processing SharedUserLogic sharedUserLogic = new SharedUserLogic(Context, CommerceOperationsFactory.UserOperations(Context)); Context[Key.GlobalUserId] = redemptionInfo.GlobalUserId; User user = sharedUserLogic.RetrieveUser(); Analytics.AddRedemptionEvent(redemptionInfo.GlobalUserId, redeemedDeal.AnalyticsEventId, user.AnalyticsEventId, redemptionInfo.ParentDealId, redemptionInfo.Currency, redeemedDeal.AuthorizationAmount, redemptionInfo.DiscountAmount, redemptionInfo.GlobalId, (string)Context[Key.PartnerMerchantId], CommerceWorkerConfig.Instance); } } } }