public async Task <Data.WebhookDeliveryData> GetWebhookDelivery(string invoiceId, string deliveryId) { using var ctx = _applicationDbContextFactory.CreateContext(); return(await ctx.InvoiceWebhookDeliveries .Where(d => d.InvoiceId == invoiceId && d.DeliveryId == deliveryId) .Select(d => d.Delivery) .FirstOrDefaultAsync()); }
private async Task MigratedInvoiceTextSearchToDb(int startFromPage) { // deleting legacy DBriize database if present var dbpath = Path.Combine(_datadirs.Value.DataDir, "InvoiceDB"); if (Directory.Exists(dbpath)) { Directory.Delete(dbpath, true); } var invoiceQuery = new InvoiceQuery { IncludeArchived = true }; var totalCount = await _invoiceRepository.GetInvoicesTotal(invoiceQuery); const int PAGE_SIZE = 1000; var totalPages = Math.Ceiling(totalCount * 1.0m / PAGE_SIZE); Logs.PayServer.LogInformation($"Importing {totalCount} invoices into the search table in {totalPages - startFromPage} pages"); for (int i = startFromPage; i < totalPages && !CancellationToken.IsCancellationRequested; i++) { Logs.PayServer.LogInformation($"Import to search table progress: {i + 1}/{totalPages} pages"); // migrate data to new table using invoices from database using var ctx = _dbContextFactory.CreateContext(); invoiceQuery.Skip = i * PAGE_SIZE; invoiceQuery.Take = PAGE_SIZE; var invoices = await _invoiceRepository.GetInvoices(invoiceQuery); foreach (var invoice in invoices) { var textSearch = new List <string>(); // recreating different textSearch.Adds that were previously in DBriize foreach (var paymentMethod in invoice.GetPaymentMethods()) { if (paymentMethod.Network != null) { var paymentDestination = paymentMethod.GetPaymentMethodDetails().GetPaymentDestination(); textSearch.Add(paymentDestination); textSearch.Add(paymentMethod.Calculate().TotalDue.ToString()); } } // textSearch.Add(invoice.Id); textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture)); textSearch.Add(invoice.Price.ToString(CultureInfo.InvariantCulture)); textSearch.Add(invoice.Metadata.OrderId); textSearch.Add(invoice.StoreId); textSearch.Add(invoice.Metadata.BuyerEmail); // textSearch.Add(invoice.RefundMail); // TODO: Are there more things to cache? PaymentData? InvoiceRepository.AddToTextSearch(ctx, new InvoiceData { Id = invoice.Id, InvoiceSearchData = new List <InvoiceSearchData>() }, textSearch.ToArray()); } var settings = await _settingsRepository.GetSettingAsync <MigrationSettings>(); if (i + 1 < totalPages) { settings.MigratedInvoiceTextSearchPages = i; } else { // during final pass we set int.MaxValue so migration doesn't run again settings.MigratedInvoiceTextSearchPages = int.MaxValue; } // this call triggers update; we're sure that MigrationSettings is already initialized in db // because of logic executed in MigrationStartupTask.cs _settingsRepository.UpdateSettingInContext(ctx, settings); await ctx.SaveChangesAsync(); } Logs.PayServer.LogInformation($"Full invoice search import successful"); }
public ApplicationDbContext CreateDbContext() { return(_ContextFactory.CreateContext()); }
public async Task <BitTokenEntity[]> GetTokens(string sin) { if (sin == null) { return(Array.Empty <BitTokenEntity>()); } using (var ctx = _Factory.CreateContext()) { return((await ctx.PairedSINData.Where(p => p.SIN == sin) .ToArrayAsync()) .Select(p => CreateTokenEntity(p)) .ToArray()); } }
public async Task <IActionResult> PullPayments( string storeId, PullPaymentState pullPaymentState, int skip = 0, int count = 50, string sortOrder = "desc" ) { await using var ctx = _dbContextFactory.CreateContext(); var now = DateTimeOffset.UtcNow; var ppsQuery = ctx.PullPayments .Include(data => data.Payouts) .Where(p => p.StoreId == storeId); if (sortOrder != null) { switch (sortOrder) { case "desc": ViewData["NextStartSortOrder"] = "asc"; ppsQuery = ppsQuery.OrderByDescending(p => p.StartDate); break; case "asc": ppsQuery = ppsQuery.OrderBy(p => p.StartDate); ViewData["NextStartSortOrder"] = "desc"; break; } } var paymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData()); if (!paymentMethods.Any()) { TempData.SetStatusMessageModel(new StatusMessageModel { Message = "You must enable at least one payment method before creating a pull payment.", Severity = StatusMessageModel.StatusSeverity.Error }); return(RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new { storeId })); } var vm = this.ParseListQuery(new PullPaymentsModel { Skip = skip, Count = count, ActiveState = pullPaymentState }); switch (pullPaymentState) { case PullPaymentState.Active: ppsQuery = ppsQuery .Where( p => !p.Archived && (p.EndDate != null ? p.EndDate > DateTimeOffset.UtcNow : true) && p.StartDate <= DateTimeOffset.UtcNow ); break; case PullPaymentState.Archived: ppsQuery = ppsQuery.Where(p => p.Archived); break; case PullPaymentState.Expired: ppsQuery = ppsQuery.Where(p => DateTimeOffset.UtcNow > p.EndDate); break; case PullPaymentState.Future: ppsQuery = ppsQuery.Where(p => p.StartDate > DateTimeOffset.UtcNow); break; } var pps = await ppsQuery .Skip(vm.Skip) .Take(vm.Count) .ToListAsync(); vm.PullPayments.AddRange(pps.Select(pp => { var blob = pp.GetBlob(); return(new PullPaymentsModel.PullPaymentModel() { StartDate = pp.StartDate, EndDate = pp.EndDate, Id = pp.Id, Name = blob.Name, AutoApproveClaims = blob.AutoApproveClaims, Progress = _pullPaymentService.CalculatePullPaymentProgress(pp, now), Archived = pp.Archived }); })); return(View(vm)); }
public async Task <IActionResult> PullPayments(string storeId, int skip = 0, int count = 50, string sortOrder = "desc") { await using var ctx = _dbContextFactory.CreateContext(); var now = DateTimeOffset.UtcNow; var ppsQuery = ctx.PullPayments .Include(data => data.Payouts) .Where(p => p.StoreId == storeId && !p.Archived); if (sortOrder != null) { switch (sortOrder) { case "desc": ViewData["NextStartSortOrder"] = "asc"; ppsQuery = ppsQuery.OrderByDescending(p => p.StartDate); break; case "asc": ppsQuery = ppsQuery.OrderBy(p => p.StartDate); ViewData["NextStartSortOrder"] = "desc"; break; } } var vm = this.ParseListQuery(new PullPaymentsModel() { Skip = skip, Count = count, Total = await ppsQuery.CountAsync() }); var pps = (await ppsQuery .Skip(vm.Skip) .Take(vm.Count) .ToListAsync() ); foreach (var pp in pps) { var totalCompleted = pp.Payouts.Where(p => (p.State == PayoutState.Completed || p.State == PayoutState.InProgress) && p.IsInPeriod(pp, now)) .Select(o => o.GetBlob(_jsonSerializerSettings).Amount).Sum(); var totalAwaiting = pp.Payouts.Where(p => (p.State == PayoutState.AwaitingPayment || p.State == PayoutState.AwaitingApproval) && p.IsInPeriod(pp, now)).Select(o => o.GetBlob(_jsonSerializerSettings).Amount).Sum(); ; var ppBlob = pp.GetBlob(); var ni = _currencyNameTable.GetCurrencyData(ppBlob.Currency, true); var nfi = _currencyNameTable.GetNumberFormatInfo(ppBlob.Currency, true); var period = pp.GetPeriod(now); vm.PullPayments.Add(new PullPaymentsModel.PullPaymentModel() { StartDate = pp.StartDate, EndDate = pp.EndDate, Id = pp.Id, Name = ppBlob.Name, Progress = new PullPaymentsModel.PullPaymentModel.ProgressModel() { CompletedPercent = (int)(totalCompleted / ppBlob.Limit * 100m), AwaitingPercent = (int)(totalAwaiting / ppBlob.Limit * 100m), Awaiting = totalAwaiting.RoundToSignificant(ni.Divisibility).ToString("C", nfi), Completed = totalCompleted.RoundToSignificant(ni.Divisibility).ToString("C", nfi), Limit = _currencyNameTable.DisplayFormatCurrency(ppBlob.Limit, ppBlob.Currency), ResetIn = period?.End is DateTimeOffset nr ? ZeroIfNegative(nr - now).TimeString() : null, EndIn = pp.EndDate is DateTimeOffset end ? ZeroIfNegative(end - now).TimeString() : null } });
public async Task <IActionResult> CreateApp(CreateAppViewModel vm) { var stores = await _AppService.GetOwnedStores(GetUserId()); if (stores.Length == 0) { StatusMessage = new StatusMessageModel() { Html = $"Error: You need to create at least one store. <a href='{(Url.Action("CreateStore", "UserStores"))}'>Create store</a>", Severity = StatusMessageModel.StatusSeverity.Error }.ToString(); return(RedirectToAction(nameof(ListApps))); } var selectedStore = vm.SelectedStore; vm.SetStores(stores); vm.SelectedStore = selectedStore; if (!Enum.TryParse <AppType>(vm.SelectedAppType, out AppType appType)) { ModelState.AddModelError(nameof(vm.SelectedAppType), "Invalid App Type"); } if (!ModelState.IsValid) { return(View(vm)); } if (!stores.Any(s => s.Id == selectedStore)) { StatusMessage = "Error: You are not owner of this store"; return(RedirectToAction(nameof(ListApps))); } var id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(20)); using (var ctx = _ContextFactory.CreateContext()) { var appData = new AppData() { Id = id }; appData.StoreDataId = selectedStore; appData.Name = vm.Name; appData.AppType = appType.ToString(); ctx.Apps.Add(appData); await ctx.SaveChangesAsync(); } StatusMessage = "App successfully created"; CreatedAppId = id; switch (appType) { case AppType.PointOfSale: return(RedirectToAction(nameof(UpdatePointOfSale), new { appId = id })); case AppType.Crowdfund: return(RedirectToAction(nameof(UpdateCrowdfund), new { appId = id })); default: return(RedirectToAction(nameof(ListApps))); } }
private async Task MigrateLighingAddressDatabaseMigration() { await using var ctx = _DBContextFactory.CreateContext(); var lightningAddressSettings = await _Settings.GetSettingAsync <UILNURLController.LightningAddressSettings>( nameof(UILNURLController.LightningAddressSettings)); if (lightningAddressSettings is null) { return; } var storeids = lightningAddressSettings.StoreToItemMap.Keys.ToArray(); var existingStores = (await ctx.Stores.Where(data => storeids.Contains(data.Id)).Select(data => data.Id).ToArrayAsync()).ToHashSet(); foreach (var storeMap in lightningAddressSettings.StoreToItemMap) { if (!existingStores.Contains(storeMap.Key)) { continue; } foreach (var storeitem in storeMap.Value) { if (lightningAddressSettings.Items.TryGetValue(storeitem, out var val)) { await _lightningAddressService.Set( new LightningAddressData() { StoreDataId = storeMap.Key, Username = storeitem, Blob = new LightningAddressDataBlob() { Max = val.Max, Min = val.Min, CurrencyCode = val.CurrencyCode }.SerializeBlob() }, ctx); } } } await ctx.SaveChangesAsync(); await _Settings.UpdateSetting <object>(null, nameof(UILNURLController.LightningAddressSettings)); }
public async Task <List <ApplicationUserData> > GetUsersWithRoles() { await using var context = _applicationDbContextFactory.CreateContext(); return(await(context.Users.Select(p => FromModel(p, p.UserRoles.Join(context.Roles, userRole => userRole.RoleId, role => role.Id, (userRole, role) => role.Name).ToArray()))).ToListAsync()); }
public async Task <List <LightningAddressData> > Get(LightningAddressQuery query) { await using var context = _applicationDbContextFactory.CreateContext(); return(await GetCore(context, query)); }
public async Task <IActionResult> PullPayments( string storeId, PullPaymentState pullPaymentState, int skip = 0, int count = 50, string sortOrder = "desc" ) { await using var ctx = _dbContextFactory.CreateContext(); var now = DateTimeOffset.UtcNow; var ppsQuery = ctx.PullPayments .Include(data => data.Payouts) .Where(p => p.StoreId == storeId); if (sortOrder != null) { switch (sortOrder) { case "desc": ViewData["NextStartSortOrder"] = "asc"; ppsQuery = ppsQuery.OrderByDescending(p => p.StartDate); break; case "asc": ppsQuery = ppsQuery.OrderBy(p => p.StartDate); ViewData["NextStartSortOrder"] = "desc"; break; } } var paymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData()); if (!paymentMethods.Any()) { TempData.SetStatusMessageModel(new StatusMessageModel { Message = "You must enable at least one payment method before creating a pull payment.", Severity = StatusMessageModel.StatusSeverity.Error }); return(RedirectToAction(nameof(UIStoresController.GeneralSettings), "UIStores", new { storeId })); } var vm = this.ParseListQuery(new PullPaymentsModel { Skip = skip, Count = count, Total = await ppsQuery.CountAsync(), ActiveState = pullPaymentState }); switch (pullPaymentState) { case PullPaymentState.Active: ppsQuery = ppsQuery .Where( p => !p.Archived && (p.EndDate != null ? p.EndDate > DateTimeOffset.UtcNow : true) && p.StartDate <= DateTimeOffset.UtcNow ); break; case PullPaymentState.Archived: ppsQuery = ppsQuery.Where(p => p.Archived); break; case PullPaymentState.Expired: ppsQuery = ppsQuery.Where(p => DateTimeOffset.UtcNow > p.EndDate); break; case PullPaymentState.Future: ppsQuery = ppsQuery.Where(p => p.StartDate > DateTimeOffset.UtcNow); break; } var pps = (await ppsQuery .Skip(vm.Skip) .Take(vm.Count) .ToListAsync() ); foreach (var pp in pps) { var totalCompleted = pp.Payouts.Where(p => (p.State == PayoutState.Completed || p.State == PayoutState.InProgress) && p.IsInPeriod(pp, now)) .Select(o => o.GetBlob(_jsonSerializerSettings).Amount).Sum(); var totalAwaiting = pp.Payouts.Where(p => (p.State == PayoutState.AwaitingPayment || p.State == PayoutState.AwaitingApproval) && p.IsInPeriod(pp, now)).Select(o => o.GetBlob(_jsonSerializerSettings).Amount).Sum(); ; var ppBlob = pp.GetBlob(); var ni = _currencyNameTable.GetCurrencyData(ppBlob.Currency, true); var nfi = _currencyNameTable.GetNumberFormatInfo(ppBlob.Currency, true); var period = pp.GetPeriod(now); vm.PullPayments.Add(new PullPaymentsModel.PullPaymentModel() { StartDate = pp.StartDate, EndDate = pp.EndDate, Id = pp.Id, Name = ppBlob.Name, Progress = new PullPaymentsModel.PullPaymentModel.ProgressModel() { CompletedPercent = (int)(totalCompleted / ppBlob.Limit * 100m), AwaitingPercent = (int)(totalAwaiting / ppBlob.Limit * 100m), Awaiting = totalAwaiting.RoundToSignificant(ni.Divisibility).ToString("C", nfi), Completed = totalCompleted.RoundToSignificant(ni.Divisibility).ToString("C", nfi), Limit = _currencyNameTable.DisplayFormatCurrency(ppBlob.Limit, ppBlob.Currency), ResetIn = period?.End is DateTimeOffset nr ? ZeroIfNegative(nr - now).TimeString() : null, EndIn = pp.EndDate is DateTimeOffset end ? ZeroIfNegative(end - now).TimeString() : null, },
private async Task AddInitialUserBlob() { await using var ctx = _DBContextFactory.CreateContext(); foreach (var user in await ctx.Users.AsQueryable().ToArrayAsync()) { user.SetBlob(new UserBlob() { ShowInvoiceStatusChangeHint = true }); } await ctx.SaveChangesAsync(); }
public async Task <StatusMessageModel> DoSpecificAction(string action, string[] payoutIds, string storeId) { switch (action) { case "mark-paid": await using (var context = _dbContextFactory.CreateContext()) { var payouts = (await context.Payouts .Include(p => p.PullPaymentData) .Include(p => p.PullPaymentData.StoreData) .Where(p => payoutIds.Contains(p.Id)) .Where(p => p.PullPaymentData.StoreId == storeId && !p.PullPaymentData.Archived && p.State == PayoutState.AwaitingPayment) .ToListAsync()).Where(data => PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) && CanHandle(paymentMethodId)) .Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == false); foreach (var valueTuple in payouts) { valueTuple.Item2.Accounted = true; valueTuple.data.State = PayoutState.InProgress; SetProofBlob(valueTuple.data, valueTuple.Item2); } await context.SaveChangesAsync(); } return(new StatusMessageModel() { Message = "Payout payments have been marked confirmed", Severity = StatusMessageModel.StatusSeverity.Success }); case "reject-payment": await using (var context = _dbContextFactory.CreateContext()) { var payouts = (await context.Payouts .Include(p => p.PullPaymentData) .Include(p => p.PullPaymentData.StoreData) .Where(p => payoutIds.Contains(p.Id)) .Where(p => p.PullPaymentData.StoreId == storeId && !p.PullPaymentData.Archived && p.State == PayoutState.AwaitingPayment) .ToListAsync()).Where(data => PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) && CanHandle(paymentMethodId)) .Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == true); foreach (var valueTuple in payouts) { valueTuple.Item2.TransactionId = null; SetProofBlob(valueTuple.data, valueTuple.Item2); } await context.SaveChangesAsync(); } return(new StatusMessageModel() { Message = "Payout payments have been unmarked", Severity = StatusMessageModel.StatusSeverity.Success }); } return(null); }
private async Task UpdatePayoutsInProgress() { try { using var ctx = _dbContextFactory.CreateContext(); var payouts = await ctx.Payouts .Include(p => p.PullPaymentData) .Where(p => p.State == PayoutState.InProgress) .ToListAsync(); foreach (var payout in payouts) { var proof = ParseProof(payout) as PayoutTransactionOnChainBlob; var payoutBlob = payout.GetBlob(this._jsonSerializerSettings); if (proof is null || proof.Accounted is false) { continue; } foreach (var txid in proof.Candidates.ToList()) { var explorer = _explorerClientProvider.GetExplorerClient(payout.GetPaymentMethodId().CryptoCode); var tx = await explorer.GetTransactionAsync(txid); if (tx is null) { proof.Candidates.Remove(txid); } else if (tx.Confirmations >= payoutBlob.MinimumConfirmation) { payout.State = PayoutState.Completed; proof.TransactionId = tx.TransactionHash; payout.Destination = null; break; } else { var rebroadcasted = await explorer.BroadcastAsync(tx.Transaction); if (rebroadcasted.RPCCode == RPCErrorCode.RPC_TRANSACTION_ERROR || rebroadcasted.RPCCode == RPCErrorCode.RPC_TRANSACTION_REJECTED) { proof.Candidates.Remove(txid); } else { payout.State = PayoutState.InProgress; proof.TransactionId = tx.TransactionHash; continue; } } } if (proof.TransactionId is null && !proof.Candidates.Contains(proof.TransactionId)) { proof.TransactionId = null; } if (proof.Candidates.Count == 0) { payout.State = PayoutState.AwaitingPayment; } else if (proof.TransactionId is null) { proof.TransactionId = proof.Candidates.First(); } if (payout.State == PayoutState.Completed) { proof.Candidates = null; } SetProofBlob(payout, proof); } await ctx.SaveChangesAsync(); } catch (Exception ex) { Logs.PayServer.LogWarning(ex, "Error while processing an update in the pull payment hosted service"); } }
public async Task <StoreData> FindStore(string storeId) { if (storeId == null) { return(null); } using (var ctx = _ContextFactory.CreateContext()) { var result = await ctx.FindAsync <StoreData>(storeId).ConfigureAwait(false); return(result); } }
/// <summary> /// Add a payment to an invoice /// </summary> /// <param name="invoiceId"></param> /// <param name="date"></param> /// <param name="paymentData"></param> /// <param name="cryptoCode"></param> /// <param name="accounted"></param> /// <returns>The PaymentEntity or null if already added</returns> public async Task <PaymentEntity> AddPayment(string invoiceId, DateTimeOffset date, CryptoPaymentData paymentData, BTCPayNetworkBase network, bool accounted = false) { await using var context = _applicationDbContextFactory.CreateContext(); var invoice = await context.Invoices.FindAsync(invoiceId); if (invoice == null) { return(null); } InvoiceEntity invoiceEntity = invoice.GetBlob(_btcPayNetworkProvider); PaymentMethod paymentMethod = invoiceEntity.GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentData.GetPaymentType())); IPaymentMethodDetails paymentMethodDetails = paymentMethod.GetPaymentMethodDetails(); PaymentEntity entity = new PaymentEntity { Version = 1, #pragma warning disable CS0618 CryptoCode = network.CryptoCode, #pragma warning restore CS0618 ReceivedTime = date.UtcDateTime, Accounted = accounted, NetworkFee = paymentMethodDetails.GetNextNetworkFee(), Network = network }; entity.SetCryptoPaymentData(paymentData); //TODO: abstract if (paymentMethodDetails is Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod bitcoinPaymentMethod && bitcoinPaymentMethod.NetworkFeeMode == NetworkFeeMode.MultiplePaymentsOnly && bitcoinPaymentMethod.NextNetworkFee == Money.Zero) { bitcoinPaymentMethod.NextNetworkFee = bitcoinPaymentMethod.NetworkFeeRate.GetFee(100); // assume price for 100 bytes paymentMethod.SetPaymentMethodDetails(bitcoinPaymentMethod); invoiceEntity.SetPaymentMethod(paymentMethod); invoice.Blob = InvoiceRepository.ToBytes(invoiceEntity, network); } PaymentData data = new PaymentData { Id = paymentData.GetPaymentId(), Blob = InvoiceRepository.ToBytes(entity, entity.Network), InvoiceDataId = invoiceId, Accounted = accounted }; await context.Payments.AddAsync(data); InvoiceRepository.AddToTextSearch(context, invoice, paymentData.GetSearchTerms()); var alreadyExists = false; try { await context.SaveChangesAsync().ConfigureAwait(false); } catch (DbUpdateException) { alreadyExists = true; } if (alreadyExists) { return(null); } if (paymentData.PaymentConfirmed(entity, invoiceEntity.SpeedPolicy)) { _eventAggregator.Publish(new InvoiceEvent(invoiceEntity, InvoiceEvent.PaymentSettled) { Payment = entity }); } return(entity); }
public async Task <bool> RemovePendingInvoice(string invoiceId) { Logs.PayServer.LogInformation($"Remove pending invoice {invoiceId}"); using (var ctx = _ContextFactory.CreateContext()) { ctx.PendingInvoices.Remove(new PendingInvoiceData() { Id = invoiceId }); try { await ctx.SaveChangesAsync(); return(true); } catch (DbUpdateException) { return(false); } } }
// In the past, if a server was considered local network, then we would disable TLS checks. // Now we don't do it anymore, as we have an explicit flag (DisableCertificateCheck) to control the behavior. // But we need to migrate old users that relied on the behavior before. private async Task MigrateEmailServerDisableTLSCerts() { await using var ctx = _DBContextFactory.CreateContext(); var serverEmailSettings = await _Settings.GetSettingAsync <Services.Mails.EmailSettings>(); if (serverEmailSettings?.Server is String server) { serverEmailSettings.DisableCertificateCheck = Extensions.IsLocalNetwork(server); if (serverEmailSettings.DisableCertificateCheck) { await _Settings.UpdateSetting(serverEmailSettings); } } var stores = await ctx.Stores.ToArrayAsync(); foreach (var store in stores) { var storeBlob = store.GetStoreBlob(); if (storeBlob.EmailSettings?.Server is String storeServer) { storeBlob.EmailSettings.DisableCertificateCheck = Extensions.IsLocalNetwork(storeServer); if (storeBlob.EmailSettings.DisableCertificateCheck) { store.SetStoreBlob(storeBlob); } } } await ctx.SaveChangesAsync(); }