Przeglądaj źródła

小程序授权

Your Name 4 miesięcy temu
rodzic
commit
6951a20522
44 zmienionych plików z 1326 dodań i 45 usunięć
  1. 2 1
      FuelCloud/ElectronicPayment/Models/Models.csproj
  2. 9 3
      FuelCloud/ElectronicPayment/Models/Models/ElectronicOrderModel.cs
  3. 115 0
      FuelCloud/ElectronicPayment/Models/Models/SignUtility.cs
  4. 10 1
      FuelCloud/ElectronicPayment/Models/Models/WxPayConfig.cs
  5. 0 1
      FuelCloud/ElectronicPayment/WeChatSdk/Business/MicroPay.cs
  6. 1 0
      FuelCloud/Fuel.Application/Fuel.Application.csproj
  7. 16 0
      FuelCloud/Fuel.Application/MqttService/Class1.cs
  8. 16 0
      FuelCloud/Fuel.Application/MqttService/IMqttClientService.cs
  9. 105 0
      FuelCloud/Fuel.Application/MqttService/MqttClientService .cs
  10. 17 0
      FuelCloud/Fuel.Application/MqttService/MqttOptions.cs
  11. 6 0
      FuelCloud/Fuel.Application/Service/INozzleService.cs
  12. 5 0
      FuelCloud/Fuel.Application/Service/ISiteService.cs
  13. 1 1
      FuelCloud/Fuel.Application/Service/ITransactionsService.cs
  14. 110 2
      FuelCloud/Fuel.Application/Service/NozzleService.cs
  15. 58 4
      FuelCloud/Fuel.Application/Service/SiteService.cs
  16. 47 11
      FuelCloud/Fuel.Application/Service/TransactionsService.cs
  17. 15 0
      FuelCloud/Fuel.Application/Service/UserService.cs
  18. 1 1
      FuelCloud/Fuel.Infrastructure/Fuel.Infrastructure.csproj
  19. 8 0
      FuelCloud/src/Fuel.Payment.Core/Models/ElectronicOrderModel.cs
  20. 107 0
      FuelCloud/src/Fuel.Payment.Core/Models/ModelMapper.cs
  21. 1 1
      FuelCloud/src/Fuel.Payment.Server/Controllers/AuthController.cs
  22. 56 0
      FuelCloud/src/Fuel.Payment.Server/Controllers/NozzleController.cs
  23. 20 0
      FuelCloud/src/Fuel.Payment.Server/Controllers/SiteController.cs
  24. 2 2
      FuelCloud/src/Fuel.Payment.Server/Controllers/TransactionsController.cs
  25. BIN
      FuelCloud/src/Fuel.Payment.Server/File/Certificate/12345678-9abc-def0-1234-56789abcdef0/apiclient_cert.p12
  26. 1 0
      FuelCloud/src/Fuel.Payment.Server/Fuel.PaymentServer.csproj
  27. 2 1
      FuelCloud/src/Fuel.Payment.Server/MicServer/Middlewares/SignatureValidationMiddleware.cs
  28. 1 1
      FuelCloud/src/Fuel.Payment.Server/MicServer/Middlewares/SignatureValidator.cs
  29. 9 2
      FuelCloud/src/Fuel.Payment.Server/Program.cs
  30. 6 0
      FuelCloud/src/Fuel.Payment.Server/appsettings.json
  31. 1 0
      FuelCloud/src/Fuel.Payment.Service/AllInPayProcessor/AllInPay/AllInPayProcessor.cs
  32. 2 0
      FuelCloud/src/Fuel.Payment.Service/Fuel.Payment.Service.csproj
  33. 1 0
      FuelCloud/src/Fuel.Payment.Service/Pay/IPayService.cs
  34. 25 3
      FuelCloud/src/Fuel.Payment.Service/Pay/PayService.cs
  35. 185 0
      FuelCloud/src/Fuel.Payment.Service/WeChatPaymentProcessor/Wechat/WechatPaymentProcessor.cs
  36. 16 9
      FuelCloud/src/FuelServer.Core/Entity/transactions.cs
  37. 6 0
      FuelCloud/src/FuelServer.Core/Fuel.Core.csproj
  38. 125 0
      FuelCloud/src/FuelServer.Core/Models/FileHandler.cs
  39. 106 0
      FuelCloud/src/FuelServer.Core/Models/KeyValueCache.cs
  40. 39 0
      FuelCloud/src/FuelServer.Core/Nozzle/Dto/UploadNozzle.cs
  41. 1 1
      FuelCloud/src/FuelServer.Core/Transactions/Dto/UploadTransactions.cs
  42. 60 0
      FuelCloud/src/FuelServer.Core/WechatServer/AccessTokenManager.cs
  43. 4 0
      FuelCloud/src/FuelServer.Core/WechatServer/WechatConstants.cs
  44. 8 0
      FuelCloud/src/FuelServer.Core/WechatServer/WechatUserSessionResponse.cs

+ 2 - 1
FuelCloud/ElectronicPayment/Models/Models.csproj

@@ -64,8 +64,9 @@
     <Compile Include="Models\AliPayConfig.cs" />
     <Compile Include="Models\Class1.cs" />
     <Compile Include="Models\ConfigurationServiceModels.cs" />
-    <Compile Include="Models\ElectronicOrderModels.cs" />
     <Compile Include="ExtentionMethod.cs" />
+    <Compile Include="Models\ElectronicOrderModel.cs" />
+    <Compile Include="Models\SignUtility.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Models\WxPayConfig.cs" />
   </ItemGroup>

+ 9 - 3
FuelCloud/ElectronicPayment/Models/Models/ElectronicOrderModels.cs → FuelCloud/ElectronicPayment/Models/Models/ElectronicOrderModel.cs

@@ -1,9 +1,11 @@
 using System;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
-using System.Security.Cryptography.X509Certificates;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
 
-namespace WayneCloud.Models
+namespace WayneCloud.Models.Models
 {
     public enum TradeStatus
     {
@@ -154,6 +156,10 @@ namespace WayneCloud.Models
         /// Gets or sets the process result which from the communication result with the 3rd party payment server
         /// </summary>
         public List<ElectronicOrderProcessResultModel> ProcessResults { get; set; }
+        /// <summary>
+        /// 设备公网IP
+        /// </summary>
+        public string IpAddress { get; set; }
 
         public string ToSimpleLogString()
         {
@@ -200,4 +206,4 @@ namespace WayneCloud.Models
         public string key15 { get; set; }
         public string value15 { get; set; }
     }
-}
+}

+ 115 - 0
FuelCloud/ElectronicPayment/Models/Models/SignUtility.cs

@@ -0,0 +1,115 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WayneCloud.Models.Models
+{
+    public class SignUtility
+    {
+        /// <summary>
+        /// Generate a random string with specified length.
+        /// </summary>
+        /// <param name="random">Random integer generator</param>
+        /// <param name="length">Length of the target string</param>
+        /// <returns></returns>
+        public static string GenerateRondomString(Random random, int length)
+        {
+            const string charSet =
+                "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#@$^*()";
+
+            int setLength = charSet.Length;
+            char[] chars = new char[length];
+
+            for (int i = 0; i < length; i++)
+                chars[i] = charSet[random.Next(setLength)];
+
+            return new string(chars);
+        }
+
+        /// <summary>
+        /// Generate the signature to be validated by WX server.
+        /// </summary>
+        /// <param name="requestData">Request data in string, fields sorted in ASCII alphabet order</param>
+        /// <param name="merchantKey">Merchant key</param>
+        /// <returns></returns>
+        public static string GenerateSignature(string requestData, string merchantKey)
+        {
+            var input = string.Concat(requestData, "&key=", merchantKey);
+
+            var md5 = MD5.Create();
+            var bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(input)); //Must use UTF-8, since CHS involved.
+            return ByteArrayToString(bytes);
+        }
+
+        /// <summary>
+        /// Converts a byte array to HEX string.
+        /// </summary>
+        /// <param name="ba">input byte array</param>
+        /// <returns></returns>
+        public static string ByteArrayToString(byte[] ba)
+        {
+            return BitConverter.ToString(ba).Replace("-", "");
+        }
+
+        /// <summary>
+        /// Builds the partial sign data, since sign field is excluded.
+        /// </summary>
+        /// <param name="request">The generic request object.</param>
+        /// <returns></returns>
+        public static string BuildSignData<T>(T request, bool includingOpenId = true)
+        {
+            PropertyInfo[] allProps = typeof(T).GetProperties();
+            PropertyInfo[] targetProps;
+            if (includingOpenId)
+            {
+                targetProps = allProps.Where(p => p.Name != "sign" && p.Name != "paySign" && p.Name != "billNumber").ToArray();
+            }
+            else
+            {
+                targetProps = allProps.Where(p => p.Name != "sign" && p.Name != "paySign" && p.Name != "openid" && p.Name != "billNumber").ToArray();
+            }
+
+            Array.Sort(targetProps,
+                delegate (PropertyInfo propertyInfo1, PropertyInfo propertyInfo2)
+                {
+                    return propertyInfo1.Name.CompareTo(propertyInfo2.Name);
+                });
+
+            StringBuilder sb = new StringBuilder();
+            foreach (PropertyInfo p in targetProps)
+            {
+                sb.Append(p.Name);
+                sb.Append("=");
+                sb.Append(GetPropValue(request, p.Name));
+                sb.Append("&");
+            }
+            var signData = sb.ToString().TrimEnd('&');
+            return signData;
+        }
+
+        /// <summary>
+        /// Gets all the public properties of the target object.
+        /// </summary>
+        /// <param name="obj">The object</param>
+        /// <param name="propName">The property name</param>
+        /// <returns></returns>
+        private static string GetPropValue(object obj, string propName)
+        {
+            var objValue = obj.GetType().GetProperty(propName).GetValue(obj, null);
+            if (objValue != null)
+                return objValue.ToString();
+            return string.Empty;
+        }
+
+        public static string GetSecondsSince1970()
+        {
+            TimeSpan span = DateTime.Now.Subtract(new DateTime(1970, 1, 1, 0, 0, 0));
+            return span.TotalSeconds.ToString();
+        }
+
+    }
+}

+ 10 - 1
FuelCloud/ElectronicPayment/Models/Models/WxPayConfig.cs

@@ -1,9 +1,11 @@
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Security.Cryptography.X509Certificates;
 using System.Text;
 using System.Threading.Tasks;
+using System.Xml.Serialization;
 
 namespace WayneCloud.Models.Models
 {
@@ -51,7 +53,14 @@ namespace WayneCloud.Models.Models
         */
         public int REPORT_LEVENL { get; set; }
         public string MINI_PROGRAM_APPID { get; set; }
-
+        public static WxPayConfig DeserializeFromXmlString(string xmlString)
+        {
+            using (StringReader reader = new StringReader(xmlString))
+            {
+                XmlSerializer serializer = new XmlSerializer(typeof(WxPayConfig));
+                return (WxPayConfig)serializer.Deserialize(reader);
+            }
+        }
     }
 
     public class WxPayConfigInfo

+ 0 - 1
FuelCloud/ElectronicPayment/WeChatSdk/Business/MicroPay.cs

@@ -5,7 +5,6 @@ using System.Threading.Tasks;
 using WayneCloud.Models.Models;
 using WayneCloud.Models;
 
-
 namespace Wechat.PayAPI
 {
     public class MicroPay

+ 1 - 0
FuelCloud/Fuel.Application/Fuel.Application.csproj

@@ -9,6 +9,7 @@
   <ItemGroup>
     <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.6" />
     <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
+    <PackageReference Include="MQTTnet" Version="4.3.5.1141" />
   </ItemGroup>
 
   <ItemGroup>

+ 16 - 0
FuelCloud/Fuel.Application/MqttService/Class1.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Fuel.Application.MqttService
+{
+    public class Class1
+    {
+        public string type { get; set; }
+        public object data { get; set; }
+    }
+
+   
+}

+ 16 - 0
FuelCloud/Fuel.Application/MqttService/IMqttClientService.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Fuel.Application.MqttService
+{
+    public interface IMqttClientService
+    {
+        Task ConnectAsync();
+        Task PublishAsync(string topic, string payload);
+        Task SubscribeAsync(string topic);
+        Task DisconnectAsync();
+    }
+}

+ 105 - 0
FuelCloud/Fuel.Application/MqttService/MqttClientService .cs

@@ -0,0 +1,105 @@
+using MQTTnet;
+using MQTTnet.Client;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Logging;
+using System.Text;
+using MQTTnet.Server;
+
+
+namespace Fuel.Application.MqttService
+{
+    public class MqttClientService : IMqttClientService, IDisposable
+    {
+        private readonly IMqttClient _mqttClient;
+        private readonly MqttOptions _options;
+        private readonly ILogger<MqttClientService> _logger;
+
+        public MqttClientService(
+            IOptions<MqttOptions> options,
+            ILogger<MqttClientService> logger)
+        {
+            _options = options.Value;
+            _logger = logger;
+
+            var factory = new MqttFactory();
+            _mqttClient = factory.CreateMqttClient();
+
+            // 配置事件处理
+            _mqttClient.ConnectedAsync += HandleConnectedAsync;
+            _mqttClient.DisconnectedAsync += HandleDisconnectedAsync;
+            _mqttClient.ApplicationMessageReceivedAsync += HandleMessageReceivedAsync;
+        }
+
+        private async Task HandleConnectedAsync(MqttClientConnectedEventArgs e)
+        {
+            _logger.LogInformation("MQTT connected");
+            await Task.CompletedTask;
+        }
+
+        private async Task HandleDisconnectedAsync(MqttClientDisconnectedEventArgs e)
+        {
+            _logger.LogWarning("MQTT disconnected. Attempting to reconnect...");
+            await Task.Delay(TimeSpan.FromSeconds(5));
+            await ConnectAsync(); // 自动重连
+        }
+
+        private async Task HandleMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs e)
+        {
+            var payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
+            _logger.LogInformation($"Received message: {payload} [Topic: {e.ApplicationMessage.Topic}]");
+            await Task.CompletedTask;
+        }
+
+        public async Task ConnectAsync()
+        {
+            var options = new MqttClientOptionsBuilder()
+                .WithTcpServer(_options.Server, _options.Port)
+                .WithClientId(_options.ClientId)
+                .WithCredentials(_options.Username, _options.Password)
+                .WithCleanSession()
+                .Build();
+
+            await _mqttClient.ConnectAsync(options);
+        }
+
+        public async Task PublishAsync(string topic, string payload)
+        {
+            if (!_mqttClient.IsConnected)
+            {
+                await ConnectAsync();
+            }
+
+            var message = new MqttApplicationMessageBuilder()
+                .WithTopic(topic)
+                .WithPayload(payload)
+                .WithQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce)
+                .Build();
+
+            await _mqttClient.PublishAsync(message);
+        }
+
+        public async Task SubscribeAsync(string topic)
+        {
+            if (!_mqttClient.IsConnected)
+            {
+                await ConnectAsync();
+            }
+
+            var topicFilter = new MqttTopicFilterBuilder()
+                .WithTopic(topic)
+                .Build();
+
+            await _mqttClient.SubscribeAsync(topicFilter);
+        }
+
+        public async Task DisconnectAsync()
+        {
+            await _mqttClient.DisconnectAsync();
+        }
+
+        public void Dispose()
+        {
+            _mqttClient?.Dispose();
+        }
+    }
+}

+ 17 - 0
FuelCloud/Fuel.Application/MqttService/MqttOptions.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Fuel.Application.MqttService
+{
+    public class MqttOptions
+    {
+        public string Server { get; set; } = "broker.emqx.io";
+        public int Port { get; set; } = 1883;
+        public string ClientId { get; set; } = $"client_{Guid.NewGuid()}";
+        public string Username { get; set; }
+        public string Password { get; set; }
+    }
+}

+ 6 - 0
FuelCloud/Fuel.Application/Service/INozzleService.cs

@@ -21,5 +21,11 @@ namespace Fuel.Application.Service
         Task<ServiceResponse> UpdateTanks(UploadTanks uploadTanks);
         Task<ServiceResponse> DeleteTanks(UploadTanks uploadTanks);
         Task<ServiceResponse> UpdateNozzleStatus(List<UploadNozzleStatus> uploadNozzleStatuses);
+        Task<ServiceResponse> NozzleAuthorizationAsync(int trxId);
+        Task<ServiceResponse> UpdateNozzleAuthorization(NozzleAuthorization nozzleAuthorization);
+
+        Task<ServiceResponse> CancelNozzleAuthorizationAsync(int trxId);
+
+        Task<ServiceResponse> UpdateCancelNozzleAuthorization(NozzleAuthorization nozzleAuthorization);
     }
 }

+ 5 - 0
FuelCloud/Fuel.Application/Service/ISiteService.cs

@@ -11,5 +11,10 @@ namespace Fuel.Application.Service
     {
         Task<ServiceResponse> GetSiteInfo();
         Task<ServiceResponse> GetAppidSecret(string Code);
+        Task<ServiceResponse> AddMiniprogramUser(string type,
+           string UserName,
+           string UserAvatarUrl,
+           string UserPhoneNumber,
+           string Address);
     }
 }

+ 1 - 1
FuelCloud/Fuel.Application/Service/ITransactionsService.cs

@@ -16,7 +16,7 @@ namespace Fuel.Application.Service
         Task<ServiceResponse> GetTransactionsAsync(TransactionsInput input);
         Task<ServiceResponse> CommitPayment(int trxId, string AuthCode);
         Task<ServiceResponse> GetMiniProgramTransactionsUnpaidAsync(TransactionsInput input);
-        Task<ServiceResponse> GetMiniProgramTransactionsUnpaidNozzleAsync(TransactionsInput input);
+        Task<ServiceResponse> GetMiniProgramTransactionsUnpaidNozzleAsync(long NozzleId);
         Task<ServiceResponse> GetMiniProgramTransactionsPaidAsync(TransactionsInput input);
         Task<ServiceResponse> RefundTrx(int trxId,
             double longitude,

+ 110 - 2
FuelCloud/Fuel.Application/Service/NozzleService.cs

@@ -1,11 +1,15 @@
-using Fuel.Core;
+using DFS.Infrastructure;
+using Fuel.Application.MqttService;
+using Fuel.Core;
 using Fuel.Core.Models;
 using Fuel.Core.Nozzle.Dto;
 using FuelServer.Core.Entity;
+using Newtonsoft.Json;
 using Org.BouncyCastle.Ocsp;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Net;
 using System.Text;
 using System.Threading.Tasks;
 using System.Transactions;
@@ -17,10 +21,13 @@ namespace Fuel.Application.Service
     {
         private readonly EntityHelper _entityHelper;
         public readonly IFreeSql _fsql;
-        public NozzleService(EntityHelper entityHelper, IFreeSql fsql)
+        private readonly IMqttClientService _mqttService;
+
+        public NozzleService(EntityHelper entityHelper, IFreeSql fsql, IMqttClientService mqttService)
         {
             _entityHelper = entityHelper;
             _fsql = fsql;
+            _mqttService = mqttService;
         }
         #region 油品
         /// <summary>
@@ -30,6 +37,7 @@ namespace Fuel.Application.Service
         /// <returns></returns>
         public async Task<ServiceResponse> UploadProduct(UploadProduct uploadProduct)
         {
+
             Guid guid = HttpRequestReader.GetCurrentBuId(); //站点id
             var _product = _fsql.Select<product>().Where(_ => _.Buid == guid && _.ProductId == uploadProduct.ProductId).First();
             if (_product != null)
@@ -190,6 +198,9 @@ namespace Fuel.Application.Service
         /// <returns></returns>
         public async Task<ServiceResponse> UploadNozzle(UploadNozzle uploadNozzle)
         {
+            await _mqttService.SubscribeAsync("fromClound/12345678-9abc-def0-1234-56789abcdef0");
+            await Task.Delay(2000);
+            await _mqttService.PublishAsync("fromClound/12345678-9abc-def0-1234-56789abcdef0", "测试");
             //RedisHelper.HSetAsync("Transaction", "11:22:33:44", "3232");
             //RedisHelper.SetAsync("33:22:33:44", "qweqweqwe", 3600);
             // var fsdds = RedisHelper.GetAsync("33:22:33:44");
@@ -302,5 +313,102 @@ namespace Fuel.Application.Service
                     });
         }
         #endregion
+
+        /// <summary>
+        ///  更新授权状态
+        /// </summary>
+        /// <param name="nozzleAuthorization"></param>
+        /// <returns></returns>
+        public async Task<ServiceResponse> UpdateNozzleAuthorization(NozzleAuthorization nozzleAuthorization)
+        {
+            Guid guid = HttpRequestReader.GetCurrentBuId(); //站点id
+            string key = guid + "_" + nozzleAuthorization.NozzleId;//授权结果的key
+            KeyValueCache.AddOrUpdate("authGun", key, nozzleAuthorization);//更新授权结果
+            return ServiceResponse.Ok();
+        }
+        /// <summary>
+        /// 向fcc发起油枪授权
+        /// </summary>
+        /// <param name="nozzleAuthorization"></param>
+        /// <returns></returns>
+        public async Task<ServiceResponse> NozzleAuthorizationAsync(int trxId)
+        {
+            Guid guid = HttpRequestReader.GetCurrentBuId(); //站点id
+            var trx = _entityHelper.GetEntitiesAsync<transactions>(_ => _.Id == trxId).Result.FirstOrDefault();
+            if (trx == null)
+            {
+                return ServiceResponse.Error(HttpStatusCode.NotAcceptable, "未查询到订单!");
+            }
+            string key = guid + "_" + trx.NozzleId;//授权结果的key
+            string jsonString = JsonConvert.SerializeObject(trx);
+            await _mqttService.SubscribeAsync("fromClound/" + guid);
+            await Task.Delay(2000);
+            var sendjson = new { type= 1,data = jsonString };
+            await _mqttService.PublishAsync("fromClound/" + guid, JsonConvert.SerializeObject(sendjson));
+            KeyValueCache.AddOrUpdate("authGun", key, AuthorizationStatus.WaitAuthorization);//添加字典,用于监听授权结果
+            bool changed = await KeyValueCache.MonitorDictionaryChanges("authGun", key, AuthorizationStatus.WaitAuthorization);
+            if (!changed)
+            {
+                return ServiceResponse.Error(HttpStatusCode.NotAcceptable, "授权失败");
+            }
+
+            var auth = KeyValueCache.GetValueOrDefault<NozzleAuthorization>("authGun", key);
+            if (auth == null || auth.OilMachineStatus != OilMachineStatus.Success)
+            {
+                return ServiceResponse.Error(HttpStatusCode.NotAcceptable, "授权失败");
+            }
+            trx.TransactionNumber = auth.TransactionNumber;
+            trx.authorizationStatus = AuthorizationStatus.Authorized;//将订单授权状态更改成已授权
+            _entityHelper.UpdateAsync(trx);
+            return ServiceResponse.Ok("授权成功");
+        }
+        /// <summary>
+        /// 向fcc发起取消油枪授权
+        /// </summary>
+        /// <param name="nozzleAuthorization"></param>
+        /// <returns></returns>
+        public async Task<ServiceResponse> CancelNozzleAuthorizationAsync(int trxId)
+        {
+            Guid guid = HttpRequestReader.GetCurrentBuId(); //站点id
+            var trx = _entityHelper.GetEntitiesAsync<transactions>(_ => _.Id == trxId).Result.FirstOrDefault();
+            if (trx == null)
+            {
+                return ServiceResponse.Error(HttpStatusCode.NotAcceptable, "未查询到订单!");
+            }
+            string key = guid + "_" + trx.NozzleId;//授权结果的key
+            string jsonString = JsonConvert.SerializeObject(trx);
+            await _mqttService.SubscribeAsync("fromClound/" + guid);
+            await Task.Delay(2000);
+            var sendjson = new { type = 2, data = jsonString };
+            await _mqttService.PublishAsync("fromClound/" + guid, JsonConvert.SerializeObject(sendjson));
+            KeyValueCache.AddOrUpdate("cancelAuth", key, AuthorizationStatus.WaitAuthorization);//添加字典,用于监听授权结果
+            bool changed = await KeyValueCache.MonitorDictionaryChanges("cancelAuth", key, AuthorizationStatus.WaitAuthorization);
+            if (!changed)
+            {
+                return ServiceResponse.Error(HttpStatusCode.NotAcceptable, "取消授权失败");
+            }
+
+            var auth = KeyValueCache.GetValueOrDefault<NozzleAuthorization>("cancelAuth", key);
+            if (auth == null || auth.OilMachineStatus != OilMachineStatus.Success)
+            {
+                return ServiceResponse.Error(HttpStatusCode.NotAcceptable, "取消授权失败");
+            }
+            trx.TransactionNumber = auth.TransactionNumber;
+            trx.authorizationStatus = AuthorizationStatus.Unauthorized;//将订单授权状态更改成已授权
+            _entityHelper.UpdateAsync(trx);
+            return ServiceResponse.Ok("取消授权成功");
+        }
+        /// <summary>
+        ///  更新取消授权状态
+        /// </summary>
+        /// <param name="nozzleAuthorization"></param>
+        /// <returns></returns>
+        public async Task<ServiceResponse> UpdateCancelNozzleAuthorization(NozzleAuthorization nozzleAuthorization)
+        {
+            Guid guid = HttpRequestReader.GetCurrentBuId(); //站点id
+            string key = guid + "_" + nozzleAuthorization.NozzleId;//授权结果的key
+            KeyValueCache.AddOrUpdate("cancelAuth", key, nozzleAuthorization);//更新授权结果
+            return ServiceResponse.Ok();
+        }
     }
 }

+ 58 - 4
FuelCloud/Fuel.Application/Service/SiteService.cs

@@ -8,6 +8,7 @@ using Newtonsoft.Json;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Net;
 using System.Net.Http.Headers;
 using System.Text;
 using System.Threading.Tasks;
@@ -24,6 +25,13 @@ namespace Fuel.Application.Service
         public async Task<ServiceResponse> GetSiteInfo()
         {
             Guid Buid = HttpRequestReader.GetCurrentBuId(); //站点id
+            Guid WachatID = HttpRequestReader.GetWachatID(); //用户
+            var userSession = WechatUserSessionRepo.GetUserSession(WachatID.ToString());
+            if (userSession == null)
+            {
+                return ServiceResponse.Error(HttpStatusCode.NotAcceptable, "未找到用户!");
+            }
+            var user = _fsql.Select<miniprogramusers>().Where(_ => _.UnionId == userSession.unionid).First();
             var site = _fsql.Select<businessunitinfo>().Where(_ => _.Buid == Buid ).First();
             var SiteInfo = new {
                 site = new { 
@@ -33,10 +41,10 @@ namespace Fuel.Application.Service
                 },
                 userInfo = new
                 {
-                    UserName = "",//用户名称
-                    UserAvatarUrl = "",//用户头像地址
-                    UserAddress= "",//用户地址
-                    UserPhoneNumber = ""//用户手机号
+                    UserName = user.UserName,//用户名称
+                    UserAvatarUrl = user.UserAvatarUrl,//用户头像地址
+                    UserAddress = user.Address,//用户地址
+                    UserPhoneNumber = user.UserPhoneNumber//用户手机号
                 }
             };
             return ServiceResponse.Ok(SiteInfo);
@@ -51,7 +59,9 @@ namespace Fuel.Application.Service
             }
             WechatUserSessionResponse userSession =
                await WechatLoginCodeToSessionKeyNOpenId(Code, site.Appid,site.Secret);
+            AccessTokenManager.GetAccessTokenAsync(site.Appid, site.Secret);
             var thirdSessionKey = GenerateThirdSessionKey();
+            userSession.unionid = userSession.openid;
             WechatUserSessionRepo.AddUserSession(thirdSessionKey, userSession);
             return ServiceResponse.Ok(thirdSessionKey);
         }
@@ -85,6 +95,50 @@ namespace Fuel.Application.Service
             }
 
         }
+
+        /// <summary>
+        /// 添加小程序用户
+        /// </summary>
+        /// <param name="type">1:新增,2:更新</param>
+        /// <param name="UserName"></param>
+        /// <param name="UserAvatarUrl"></param>
+        /// <param name="UserPhoneNumber"></param>
+        /// <param name="Address"></param>
+        /// <returns></returns>
+        public async Task<ServiceResponse> AddMiniprogramUser(string type,
+            string UserName,
+            string UserAvatarUrl,
+            string UserPhoneNumber,
+            string Address)
+        {
+            Guid Buid = HttpRequestReader.GetCurrentBuId(); //站点id
+            Guid WachatID = HttpRequestReader.GetWachatID(); //用户
+            var userSession = WechatUserSessionRepo.GetUserSession(WachatID.ToString());
+            if (userSession == null)
+            {
+                return ServiceResponse.Error(HttpStatusCode.NotAcceptable, "未找到用户!");
+            }
+            var miniprogramusers = _fsql.Select<miniprogramusers>().Where(_ => _.UnionId == userSession.unionid).First();
+            if (miniprogramusers != null)
+            {
+                return ServiceResponse.Ok(miniprogramusers);
+            }
+            miniprogramusers user = new miniprogramusers();
+            user.Buid = Buid;
+            user.UserName = UserName;
+            user.UserAvatarUrl = UserAvatarUrl;
+            user.UserPhoneNumber = UserPhoneNumber;
+            user.Address = Address;
+            user.OpenId = userSession.openid;
+            user.UnionId = userSession.unionid;
+            int affectedRows = _fsql.Insert<miniprogramusers>().AppendData(user).ExecuteAffrows();
+            if (affectedRows <= 0)
+            {
+                return ServiceResponse.Error("用户信息插入失败");
+            }
+            return ServiceResponse.Ok(user);
+        }
+
         private string GenerateThirdSessionKey()
         {
             var thirdSessionKey = Guid.NewGuid();

+ 47 - 11
FuelCloud/Fuel.Application/Service/TransactionsService.cs

@@ -10,6 +10,11 @@ using DFS.Core.Abstractions.View;
 using Fuel.Core;
 using System;
 using System.Transactions;
+using Fuel.Core.WechatServer;
+using Fuel.Core.Nozzle.Dto;
+using Fuel.Application.MqttService;
+using Org.BouncyCastle.Asn1.X509;
+using Jayrock.Json;
 
 
 namespace Fuel.Application.Service
@@ -20,12 +25,14 @@ namespace Fuel.Application.Service
         private readonly IHttpContextAccessor _httpContextAccessor;
         private readonly IPayService _payService;
         public readonly IFreeSql _fsql;
-        public TransactionsService(EntityHelper entityHelper, IHttpContextAccessor httpContextAccessor, IPayService payService, IFreeSql fsql)
+        private readonly IMqttClientService _mqttService;
+        public TransactionsService(EntityHelper entityHelper, IHttpContextAccessor httpContextAccessor, IPayService payService, IFreeSql fsql, IMqttClientService mqttService)
         {
             _entityHelper = entityHelper;
             _httpContextAccessor = httpContextAccessor;
             _payService = payService;
             _fsql = fsql;
+            _mqttService = mqttService;
         }
 
         /// <summary>
@@ -145,16 +152,17 @@ namespace Fuel.Application.Service
         /// 小程序用户根据抢号查询未支付订单
         /// </summary>
         /// <returns></returns>
-        public async Task<ServiceResponse> GetMiniProgramTransactionsUnpaidNozzleAsync(TransactionsInput input)
+        public async Task<ServiceResponse> GetMiniProgramTransactionsUnpaidNozzleAsync(long NozzleId)
         {
-            string Buid = _httpContextAccessor.HttpContext.Request.Headers["Buid"].FirstOrDefault();
-            Guid guid = Guid.Parse(Buid);
-            Expression<Func<transactions, bool>> where = p => p.Buid == guid;
-            if (input.MiniProgramID == null)
+            Guid Buid = HttpRequestReader.GetCurrentBuId(); //站点id
+            Expression<Func<transactions, bool>> where = p => p.Buid == Buid;
+            Guid WachatID = HttpRequestReader.GetWachatID(); //用户
+            var userSession = WechatUserSessionRepo.GetUserSession(WachatID.ToString());
+            if (userSession == null)
             {
-                return ServiceResponse.Error("用户id为空");
+                return ServiceResponse.Error(HttpStatusCode.NotAcceptable, "未找到用户!");
             }
-            where = CombineExpressions(where, p => p.NozzleId == input.NozzleId && p.OrderStatus == transactionsORDERSTATUS.Unpaid);
+            where = CombineExpressions(where, p => p.NozzleId == NozzleId && p.OrderStatus == transactionsORDERSTATUS.Unpaid);
             var result = await _entityHelper.GetEntitiesAsync<transactions>(where);
             return ServiceResponse.Ok(result);
         }
@@ -308,17 +316,45 @@ namespace Fuel.Application.Service
         }
         public async Task<ServiceResponse> UnifiedOrder(int trxId)
         {
+            Guid Buid = HttpRequestReader.GetCurrentBuId(); //站点id
+            Guid WachatID = HttpRequestReader.GetWachatID(); //用户
+            var userSession = WechatUserSessionRepo.GetUserSession(WachatID.ToString());
+            if (userSession == null)
+            {
+                return ServiceResponse.Error(HttpStatusCode.NotAcceptable, "未找到用户!");
+            }
             var trx = _entityHelper.GetEntitiesAsync<transactions>(_ => _.Id == trxId).Result.FirstOrDefault();
             if (trx == null)
             {
                 return ServiceResponse.Error(HttpStatusCode.NotAcceptable, "未查询到订单!");
             }
-            var serviceResult = await _payService.UnifiedOrder((decimal)trx.OriginalAmount, "ALL_IN_SCAN");
-            if (!serviceResult.IsSuccessful())
+            var serviceResult = await _payService.UnifiedOrder((decimal)trx.OriginalAmount, "WX_SCAN", userSession.unionid);
+            var dataProperties = serviceResult.Data.GetType().GetProperty("UnifiedOrderResult");
+            if (!serviceResult.IsSuccessful() || dataProperties == null)
             {
                 return ServiceResponse.Error(HttpStatusCode.NotAcceptable, "统一下单失败");
             }
-            return ServiceResponse.Ok(serviceResult.Data);
+
+            //小程序支付完后,云端需要通过订单编号去支付平台查询支付状态,以便更新订单信息
+            _ = Task.Delay(5000).ContinueWith(async _ =>
+            {
+                var dataProperties = serviceResult.Data.GetType().GetProperty("eOrder");
+                var orderModel = (Fuel.Payment.Core.Models.ElectronicOrderModel)dataProperties.GetValue(serviceResult.Data);
+                var genericResponse = await _payService.QueryOrder(orderModel);
+                if (genericResponse.IsSuccessful())
+                {
+                    trx.OrderStatus = transactionsORDERSTATUS.Paid;//将订单状态更改成已支付
+                    await _mqttService.SubscribeAsync("fromClound/" + Buid);
+                    string jsonString = JsonConvert.SerializeObject(trx);
+                    await Task.Delay(2000);
+                    var sendjson = new { type = 3, data = jsonString };
+                    ///支付完成将订单信息推送到fcc
+                    await _mqttService.PublishAsync("fromClound/" + Buid, JsonConvert.SerializeObject(sendjson));
+                    _entityHelper.UpdateAsync(trx);
+                }
+            });
+            var unifiedOrderResult = dataProperties.GetValue(serviceResult.Data);
+            return ServiceResponse.Ok(unifiedOrderResult);
         }
         public async Task<ServiceResponse> Redeem(int trxId, decimal OriginalQty)
         {

+ 15 - 0
FuelCloud/Fuel.Application/Service/UserService.cs

@@ -51,9 +51,17 @@ namespace Fuel.Application.Service
             permissionList.Add("Nozzle:uploadTanks:POST");
             permissionList.Add("Nozzle:UpdateTanks:Put");
             permissionList.Add("Nozzle:DeleteTanks:Delete");
+            permissionList.Add("Nozzle:NozzleAuthorization:GET");
+            permissionList.Add("Nozzle:UpdateNozzleAuthorization:PUT");
+            permissionList.Add("Nozzle:UpdateCancelNozzleAuthorization:PUT");
+            permissionList.Add("Nozzle:CancelNozzleAuthorization:GET");
 
             permissionList.Add("Transactions:CreateTransactions:POST");
             permissionList.Add("Transactions:UnifiedOrder:GET");
+            permissionList.Add("Transactions:NozzleAuthorization:GET");
+
+            permissionList.Add("Site:AddMiniprogramUser:POST");
+            permissionList.Add("Site:GetSiteInfo:GET");
             return permissionList;
 
         }
@@ -85,9 +93,16 @@ namespace Fuel.Application.Service
             permissionList.Add("Nozzle:uploadTanks:POST");
             permissionList.Add("Nozzle:UpdateTanks:Put");
             permissionList.Add("Nozzle:DeleteTanks:Delete");
+            permissionList.Add("Nozzle:NozzleAuthorization:GET");
+            permissionList.Add("Nozzle:UpdateNozzleAuthorization:PUT");
+            permissionList.Add("Nozzle:UpdateCancelNozzleAuthorization:PUT");
+            permissionList.Add("Nozzle:CancelNozzleAuthorization:GET");
 
             permissionList.Add("Transactions:CreateTransactions:POST");
             permissionList.Add("Transactions:UnifiedOrder:GET");
+
+            permissionList.Add("Site:AddMiniprogramUser:POST");
+            permissionList.Add("Site:GetSiteInfo:GET");
             return permissionList;
         }
         public Task<users> GetUsers()

+ 1 - 1
FuelCloud/Fuel.Infrastructure/Fuel.Infrastructure.csproj

@@ -11,7 +11,7 @@
     <PackageReference Include="FreeSql.Provider.MySql" Version="3.2.833" />
     <PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.0" />
     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
-    <PackageReference Include="Microsoft.Extensions.Primitives" Version="9.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Primitives" Version="9.0.2" />
   </ItemGroup>
 
   <ItemGroup>

+ 8 - 0
FuelCloud/src/Fuel.Payment.Core/Models/ElectronicOrderModel.cs

@@ -7,6 +7,9 @@ using System.Threading.Tasks;
 
 namespace Fuel.Payment.Core.Models
 {
+    /// <summary>
+    /// Host the phase 1 supported transaction info.
+    /// </summary>
     public class ElectronicOrderModel
     {
         [Key]
@@ -102,6 +105,11 @@ namespace Fuel.Payment.Core.Models
         /// Gets or sets the process result which from the communication result with the 3rd party payment server
         /// </summary>
         public List<ElectronicOrderProcessResultModel> ProcessResults { get; set; }
+        /// <summary>
+        /// 设备公网IP
+        /// </summary>
+        public string IpAddress { get; set; }
+
         public object UnifiedOrderResult { get; set; }
 
         public string ToSimpleLogString()

+ 107 - 0
FuelCloud/src/Fuel.Payment.Core/Models/ModelMapper.cs

@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Fuel.Payment.Core.Models
+{
+    public static class ModelMapper
+    {
+        /// <summary>
+        /// 将 Fuel.Payment.Core.Models.ElectronicOrderModel 转换为 WayneCloud.Models.Models.ElectronicOrderModel
+        /// </summary>
+        /// <param name="source">源对象</param>
+        /// <returns>目标对象</returns>
+        public static WayneCloud.Models.Models.ElectronicOrderModel ConvertToWayneCloudModel(Fuel.Payment.Core.Models.ElectronicOrderModel source)
+        {
+            if (source == null) return null;
+
+            var target = new WayneCloud.Models.Models.ElectronicOrderModel
+            {
+                Id = source.Id,
+                SiteId = source.SiteId,
+                Config = source.Config,
+                Certification = source.Certification,
+                CreationTime = source.CreationTime,
+                Channel = source.Channel,
+                IsRefund = source.IsRefund,
+                TradeStatus = ConvertToWayneCloudModel(source.TradeStatus),
+                NetAmount = source.NetAmount,
+                GrossAmount = source.GrossAmount,
+                TotalAmount = source.TotalAmount,
+                BillNumber = source.BillNumber,
+                Title = source.Title,
+                AuthCode = source.AuthCode,
+                OperatorId = source.OperatorId,
+                TerminalId = source.TerminalId,
+                ReceivedTime = source.ReceivedTime,
+                Optional = new Dictionary<string, object>(source.Optional),
+                FuelOrderDetails = ConvertToWayneCloudModelList(source.FuelOrderDetails),
+                ProcessResults = ConvertToWayneCloudModelList(source.ProcessResults),
+                IpAddress = source.IpAddress
+            };
+
+            return target;
+        }
+        public static WayneCloud.Models.Models.TradeStatus ConvertToWayneCloudModel(Fuel.Payment.Core.Models.TradeStatus source)
+        {
+            return (WayneCloud.Models.Models.TradeStatus)(int)source;
+        }
+        public static WayneCloud.Models.Models.FuelOrderDetailModel ConvertToWayneCloudModel(Fuel.Payment.Core.Models.FuelOrderDetailModel source)
+        {
+            if (source == null) return null;
+
+            return new WayneCloud.Models.Models.FuelOrderDetailModel
+            {
+                Id = source.Id,
+                PumpNumber = source.PumpNumber,
+                NozzleNumber = source.NozzleNumber,
+                FuelProductName = source.FuelProductName,
+                FuelProductId = source.FuelProductId,
+                Qualtity = source.Qualtity,
+                Price = source.Price,
+                Amount = source.Amount,
+                Category = source.Category
+            };
+        }
+
+        public static List<WayneCloud.Models.Models.FuelOrderDetailModel> ConvertToWayneCloudModelList(List<Fuel.Payment.Core.Models.FuelOrderDetailModel> sourceList)
+        {
+            if (sourceList == null) return null;
+
+            var targetList = new List<WayneCloud.Models.Models.FuelOrderDetailModel>();
+            foreach (var item in sourceList)
+            {
+                targetList.Add(ConvertToWayneCloudModel(item));
+            }
+            return targetList;
+        }
+
+        public static WayneCloud.Models.Models.ElectronicOrderProcessResultModel ConvertToWayneCloudModel(Fuel.Payment.Core.Models.ElectronicOrderProcessResultModel source)
+        {
+            if (source == null) return null;
+
+            return new WayneCloud.Models.Models.ElectronicOrderProcessResultModel
+            {
+                BillNumber = source.BillNumber,
+                ResultCode = source.ResultCode,
+                ResultMessage = source.ResultMessage,
+                ErrorDetail = source.ErrorDetail,
+                RawResult = source.RawResult
+            };
+        }
+
+        public static List<WayneCloud.Models.Models.ElectronicOrderProcessResultModel> ConvertToWayneCloudModelList(List<Fuel.Payment.Core.Models.ElectronicOrderProcessResultModel> sourceList)
+        {
+            if (sourceList == null) return null;
+
+            var targetList = new List<WayneCloud.Models.Models.ElectronicOrderProcessResultModel>();
+            foreach (var item in sourceList)
+            {
+                targetList.Add(ConvertToWayneCloudModel(item));
+            }
+            return targetList;
+        }
+    }
+}

+ 1 - 1
FuelCloud/src/Fuel.Payment.Server/Controllers/AuthController.cs

@@ -53,7 +53,7 @@ namespace Fuel.PaymentServer.Controllers
         public async Task<IActionResult> Wechatlogin(string code)
         {
             Guid Buid = HttpRequestReader.GetCurrentBuId(); //站点id
-            Guid WachatID = HttpRequestReader.GetWachatID(); //小程序用户id
+            //Guid WachatID = HttpRequestReader.GetWachatID(); //小程序用户id
             var appidResponse = await _siteService.GetAppidSecret(code);
             if (!appidResponse.IsSuccessful())
             {

+ 56 - 0
FuelCloud/src/Fuel.Payment.Server/Controllers/NozzleController.cs

@@ -5,6 +5,7 @@ using Fuel.Core.Transactions.Dto;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
+using Org.BouncyCastle.Asn1.Ocsp;
 
 namespace Fuel.PaymentServer.Controllers
 {
@@ -156,5 +157,60 @@ namespace Fuel.PaymentServer.Controllers
             var serviceResult = await InozzleService.DeleteTanks(uploadTanks);
             return Ok(serviceResult);
         }
+        /// <summary>
+        /// 更新授权状态
+        /// </summary>
+        /// <param name="uploadNozzle"></param>
+        /// <returns></returns>
+        [Permission("Nozzle:UpdateNozzleAuthorization:PUT")]
+        [Route("UpdateNozzleAuthorization")]
+        [HttpPut]
+        public async Task<IActionResult> UpdateNozzleAuthorizationAsync(NozzleAuthorization nozzleAuthorization)
+        {
+            var serviceResult = await InozzleService.UpdateNozzleAuthorization(nozzleAuthorization);
+            return Ok(serviceResult);
+        }
+
+        /// <summary>
+        /// 向fcc发起油枪授权
+        /// </summary>
+        /// <returns></returns>
+        [Permission("Nozzle:NozzleAuthorization:GET")]
+        [Route("NozzleAuthorization")]
+        [HttpGet]
+        public async Task<IActionResult> NozzleAuthorizationAsync(int trxid)
+        {
+            var serviceResult = await InozzleService.NozzleAuthorizationAsync(trxid);
+            return Ok(serviceResult);
+        }
+
+        /// <summary>
+        /// 向fcc发起取消油枪授权
+        /// </summary>
+        /// <returns></returns>
+        [Permission("Nozzle:CancelNozzleAuthorization:GET")]
+        [Route("CancelNozzleAuthorization")]
+        [HttpGet]
+        public async Task<IActionResult> CancelNozzleAuthorizationAsync(int trxid)
+        {
+            var serviceResult = await InozzleService.CancelNozzleAuthorizationAsync(trxid);
+            return Ok(serviceResult);
+        }
+
+        /// <summary>
+        /// 更新取消授权状态
+        /// </summary>
+        /// <param name="uploadNozzle"></param>
+        /// <returns></returns>
+        [Permission("Nozzle:UpdateCancelNozzleAuthorization:PUT")]
+        [Route("UpdateCancelNozzleAuthorization")]
+        [HttpPut]
+        public async Task<IActionResult> UpdateCancelNozzleAuthorizationAsync(NozzleAuthorization nozzleAuthorization)
+        {
+            var serviceResult = await InozzleService.UpdateCancelNozzleAuthorization(nozzleAuthorization);
+            return Ok(serviceResult);
+        }
+
+
     }
 }

+ 20 - 0
FuelCloud/src/Fuel.Payment.Server/Controllers/SiteController.cs

@@ -1,6 +1,7 @@
 using Fuel.Application.Authorization;
 using Fuel.Application.Service;
 using Fuel.Core.Transactions.Dto;
+using Fuel.Core.WechatServer;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
 
@@ -28,5 +29,24 @@ namespace Fuel.PaymentServer.Controllers
             var serviceResult = await _siteService.GetSiteInfo();
             return Ok(serviceResult);
         }
+
+        /// <summary>
+        /// 小程序用户登录时添加用户信息
+        /// </summary>
+        /// <returns></returns>
+        [Permission("Site:AddMiniprogramUser:POST")]
+        [Route("AddMiniprogramUser")]
+        [HttpPost]
+        public async Task<IActionResult> AddMiniprogramUser(AddWechatUser user)
+        {
+            var serviceResult = await _siteService.AddMiniprogramUser("1", user.UserName, user.UserAvatarUrl, user.UserPhoneNumber, user.Address);
+            return Ok(serviceResult);
+
+        }
+   //     public async Task<IActionResult> AddMiniprogramUser(
+   //string UserName,
+   //string UserAvatarUrl,
+   //string UserPhoneNumber,
+   //string Address)
     }
 }

+ 2 - 2
FuelCloud/src/Fuel.Payment.Server/Controllers/TransactionsController.cs

@@ -66,9 +66,9 @@ namespace Fuel.PaymentServer.Controllers
         [Permission("Transactions:GetMiniProgramTransactionsUnpaidNozzle:GET")]
         [Route("GetMiniProgramTransactionsUnpaidNozzle")]
         [HttpGet]
-        public async Task<IActionResult> GetMiniProgramTransactionsUnpaidNozzleAsync(TransactionsInput input)
+        public async Task<IActionResult> GetMiniProgramTransactionsUnpaidNozzleAsync(long NozzleId)
         {
-            var serviceResult = await _transactionsService.GetMiniProgramTransactionsUnpaidNozzleAsync(input);
+            var serviceResult = await _transactionsService.GetMiniProgramTransactionsUnpaidNozzleAsync(NozzleId);
             return Ok(serviceResult);
         }
 

BIN
FuelCloud/src/Fuel.Payment.Server/File/Certificate/12345678-9abc-def0-1234-56789abcdef0/apiclient_cert.p12


+ 1 - 0
FuelCloud/src/Fuel.Payment.Server/Fuel.PaymentServer.csproj

@@ -12,6 +12,7 @@
     <PackageReference Include="JWT" Version="10.1.1" />
     <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" />
     <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.5" />
+    <PackageReference Include="MQTTnet.Extensions.ManagedClient" Version="4.3.7.1207" />
     <PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
   </ItemGroup>
 

+ 2 - 1
FuelCloud/src/Fuel.Payment.Server/MicServer/Middlewares/SignatureValidationMiddleware.cs

@@ -29,7 +29,8 @@ namespace DFS.Core.Mvc.Middlewares
         {
             // 跳过登录接口
             var path = context.Request.Path.Value;
-            if (path.StartsWith("/api/Auth/login", StringComparison.OrdinalIgnoreCase))
+            if (path.StartsWith("/api/Auth/login", StringComparison.OrdinalIgnoreCase) 
+                || path.StartsWith("/api/Auth/Wechatlogin", StringComparison.OrdinalIgnoreCase))
             {
                 await _next(context);
                 return;

+ 1 - 1
FuelCloud/src/Fuel.Payment.Server/MicServer/Middlewares/SignatureValidator.cs

@@ -66,7 +66,7 @@ namespace DFS.Core.Mvc.Middlewares
                 var stringToSign = $"sign_method={signMethod}&secret_id={secretId}&nonce={nonce}&timestamp={timestamp}";
                 if (!string.IsNullOrEmpty(bodyContent))
                 {
-                    stringToSign += bodyContent;
+                    stringToSign += "&" + bodyContent;
                 }
               //var sfd =  Sm4Encryptor.Encrypt(stringToSign, Secret);
                 //stringToSign = Sm4Encryptor.Encrypt(stringToSign, Secret);

+ 9 - 2
FuelCloud/src/Fuel.Payment.Server/Program.cs

@@ -24,11 +24,13 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
 using Microsoft.IdentityModel.Tokens;
 using System.Text;
 using DFS.Core.Mvc.Middlewares;
+using Fuel.Payment.Service.WeChatPaymentProcessor.Wechat;
+using Fuel.Application.MqttService;
 
 var builder = WebApplication.CreateBuilder(args);
 builder.Services.AddScoped<IPayService, PayService>();
 AsyncPaymentProcessorFactory.Default.Regist(o => o.Channel == "ALI_SCAN", new AlipayPaymentProcessor());
-//AsyncPaymentProcessorFactory.Default.Regist(o => o.Channel == "WX_SCAN", new WechatPaymentProcessor());
+AsyncPaymentProcessorFactory.Default.Regist(o => o.Channel == "WX_SCAN", new WechatPaymentProcessor());
 AsyncPaymentProcessorFactory.Default.Regist(o => o.Channel == "ALL_IN_SCAN", new AllInPayProcessor());
 //AsyncPaymentProcessorFactory.Default.Regist(o => o.Channel == "ALL_IN_SCAN_V2", new AllInPayProcessorV2());
 //AsyncPaymentProcessorFactory.Default.Regist(o => o.Channel == "WX_ORDER_SCAN", new WechatPaymentProcessor());
@@ -105,6 +107,11 @@ builder.Services.AddAuthorization(options =>
     AddPermissionPolicies(options);
 });
 
+//mqtt×¢²á
+builder.Services.Configure<MqttOptions>(builder.Configuration.GetSection("Mqtt"));
+builder.Services.AddScoped<IMqttClientService, MqttClientService>();
+
+
 var app = builder.Build();
 
 app.UseRouting();
@@ -137,5 +144,5 @@ app.UseAuthentication();
 app.UseAuthorization();
 
 app.MapControllers();
-app.Urls.Add("http://192.168.0.202:5006");
+app.Urls.Add("http://192.168.88.140:5006");
 app.Run();

+ 6 - 0
FuelCloud/src/Fuel.Payment.Server/appsettings.json

@@ -14,5 +14,11 @@
     "PrefixKey": "TransServer_",
     "KeyName": "Trans"
   },
+  "Mqtt": {
+    "Server": "10.153.148.121",
+    "Port": 1883,
+    "Username": "",
+    "Password": ""
+  },
   "AllowedHosts": "*"
 }

+ 1 - 0
FuelCloud/src/Fuel.Payment.Service/AllInPayProcessor/AllInPay/AllInPayProcessor.cs

@@ -472,6 +472,7 @@ namespace Fuel.Payment.Service.AllInPayProcessor.AllInPay
         {
             throw new NotImplementedException();
         }
+
     }
 }
 public class PeriodicTask

+ 2 - 0
FuelCloud/src/Fuel.Payment.Service/Fuel.Payment.Service.csproj

@@ -8,6 +8,8 @@
 
   <ItemGroup>
     <Folder Include="Impl\" />
+    <Folder Include="WeChatPaymentProcessor\Wechat\Cert\" />
+    <Folder Include="WeChatPaymentProcessor\Wechat\Config\" />
   </ItemGroup>
 
   <ItemGroup>

+ 1 - 0
FuelCloud/src/Fuel.Payment.Service/Pay/IPayService.cs

@@ -13,5 +13,6 @@ namespace Fuel.Payment.Service.Pay
         Task<ServiceResponse> PerformElectronicProcess(string AuthCode, decimal payDueAmount, string Channel = "ALL_IN_SCAN", string preCreatedBillNumber = "");
         Task<ServiceResponse> ReturnProcess(decimal payDueAmount, string Channel = "ALL_IN_SCAN", string preCreatedBillNumber = "");
         Task<ServiceResponse> UnifiedOrder(decimal payDueAmount, string Channel = "ALL_IN_SCAN", string userId = "001");
+        Task<ServiceResponse> QueryOrder(ElectronicOrderModel eOrder);
     }
 }

+ 25 - 3
FuelCloud/src/Fuel.Payment.Service/Pay/PayService.cs

@@ -101,7 +101,7 @@ namespace Fuel.Payment.Service.Pay
             var optionalParam = new Dictionary<string, object>();
             if (!string.IsNullOrEmpty(userId))
             {
-                optionalParam.Add("mobilePayId", "o7yXJ5NCQDKeBTiRhGjZ0QW5X5qI");
+                optionalParam.Add("mobilePayId", userId);
             }
             ElectronicOrderModel eOrder = new ElectronicOrderModel
             {
@@ -121,9 +121,31 @@ namespace Fuel.Payment.Service.Pay
             //var payConfig = await eProcessor.Initialize(eOrder).ConfigureAwait(false);
             //eOrder.Config = payConfig.electronicOrderModel?.Config;
             var genericResponseAlipay = await eProcessor.UnifiedOrder(eOrder).ConfigureAwait(false);
-            return new ServiceResponse { StatusCode = HttpStatusCode.OK, Data = genericResponseAlipay.UnifiedOrderResult };
-        }
+            _ = Task.Delay(5000).ContinueWith(async _ =>
+            {
+                var genericResponse = await eProcessor.Query(eOrder, 40, 2000).ConfigureAwait(false);
+                if (eOrder.TradeStatus == TradeStatus.SUCCESS)
+                { 
 
+                }
+            });
+            return new ServiceResponse { StatusCode = HttpStatusCode.OK, Data = new { UnifiedOrderResult = genericResponseAlipay.UnifiedOrderResult, eOrder = eOrder } };
+        }
+        /// <summary>
+        /// 查询订单
+        /// </summary>
+        /// <param name="eOrder"></param>
+        /// <returns></returns>
+        public async Task<ServiceResponse> QueryOrder(ElectronicOrderModel eOrder)
+        {
+            var eProcessor = AsyncPaymentProcessorFactory.Default.Get(eOrder);
+            var genericResponse = await eProcessor.Query(eOrder, 40, 2000).ConfigureAwait(false);
+            if (eOrder.TradeStatus == TradeStatus.SUCCESS)
+            {
+                return ServiceResponse.Ok(true);
+            }
+            return ServiceResponse.Error();
+        }
 
         private string GetPaymentChannelByPaymentId(PaymentID paymentID)
         {

+ 185 - 0
FuelCloud/src/Fuel.Payment.Service/WeChatPaymentProcessor/Wechat/WechatPaymentProcessor.cs

@@ -0,0 +1,185 @@
+using Fuel.Core.Models;
+using Fuel.Payment.Core;
+using Fuel.Payment.Core.Enum;
+using Fuel.Payment.Core.Models;
+using Fuel.Payment.Service.Factory;
+using Newtonsoft.Json;
+using Org.BouncyCastle.Asn1.X509;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Reflection.PortableExecutable;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading.Tasks;
+using Wechat.PayAPI;
+
+
+
+namespace Fuel.Payment.Service.WeChatPaymentProcessor.Wechat
+{
+    public class WechatPaymentProcessor : AsyncPaymentProcessor
+    {
+        private NLog.Logger logger = NLog.LogManager.GetLogger("Main");
+        public override async Task<GenericProcessResponse> Cancel(ElectronicOrderModel order)
+        {
+            Log.Info("MicroPay", "Micropay failure, reverse order " + order.BillNumber);
+
+            order.TradeStatus = TradeStatus.CANCELLING;
+
+            var result = await MicroPay.Cancel(order.BillNumber, (WayneCloud.Models.Models.WxPayConfig)order.Config, (X509Certificate2)order.Certification);
+
+            if (result)
+            {
+                Log.Info("MicroPay", "Micropay Cancel Order successed, order " + order.BillNumber + " has been closed.");
+                order.TradeStatus = TradeStatus.CLOSED;
+            }
+            return new GenericProcessResponse();
+        }
+
+        public override async Task<GenericProcessResponse> Process(ElectronicOrderModel order)
+        {
+            order.Config = Initialize(order).Result?.electronicOrderModel?.Config;
+            order.Certification = Initialize(order).Result?.electronicOrderModel?.Certification;
+            TradeStatus tradeStatus = TradeStatus.PAYERROR;
+            var response = new GenericProcessResponse()
+            {
+                WeChatResponse = new WxPayData()
+            };
+            if (order.Optional.ContainsKey("preCreatedBillNumber"))
+            {
+                response = await Query(order, 40, 2000);
+                if (response.WeChatResponse.IsSet("trade_state") &&
+                    response.WeChatResponse.GetValue("trade_state") as string == "SUCCESS")
+                {
+                    tradeStatus = TradeStatus.SUCCESS;
+                    logger.Info("MicroPay TradeStatus.SUCCESS", $"The final MicroPay response is: {response.WeChatResponse.GetValue("trade_state")}");
+                }
+                else
+                {
+                    if (response.WeChatResponse.IsSet("trade_state"))
+                    {
+                        logger.Info("MicroPay", $"The final MicroPay response is: {response.WeChatResponse.GetValue("trade_state")}");
+                    }
+                    else
+                    {
+                        logger.Info("MicroPay", $"The final MicroPay response is: {response.WeChatResponse.GetValue("trade_state")}");
+                    }
+                }
+            }
+            else
+            {
+                response.WeChatResponse = await MicroPay.Run(ModelMapper.ConvertToWayneCloudModel(order));
+            }
+
+            logger.Info("MicroPay", "The final MicroPay response is: \r\n" + response.WeChatResponse.ToXml());
+            return response;
+        }
+
+        public override async Task<GenericProcessResponse> Query(ElectronicOrderModel order)
+        {
+            return await Query(order, 1, 2000);
+        }
+
+        public override async Task<GenericProcessResponse> Query(ElectronicOrderModel order, int count, int interval)
+        {
+            Log.Info("MicroPay", "Wechat OrderQuery is processing order: " + order.BillNumber);
+            var result = await MicroPay.RunQuery(ModelMapper.ConvertToWayneCloudModel(order), count, interval);
+
+            Log.Info("MicroPay", "Wechat OrderQuery process complete, result : \r\n" + result.ToXml());
+
+            return new GenericProcessResponse()
+            {
+                WeChatResponse = result
+            };
+        }
+
+        public override async Task<GenericProcessResponse> Return(ElectronicOrderModel order)
+        {
+            Log.Info("MicroPay", "MicroPay is processing refund, BillNumber = " + order.BillNumber);
+
+            TradeStatus tradeStatus;
+            //var result = Refund.Run("", order.BillNumber, 
+            //    order.TotalAmount.ToString(), order.NetAmount.ToString(), out tradeStatus);
+            var result = await Refund.Run("", ModelMapper.ConvertToWayneCloudModel(order));
+
+            Log.Info("MicroPay", "The final MicroPay response is: \r\n" + result.ToXml());
+            return new GenericProcessResponse()
+            {
+                WeChatResponse = result
+            };
+        }
+
+        public override async Task<GenericProcessResponse> UnifiedOrder(ElectronicOrderModel order)
+        {
+            order.Config = Initialize(order).Result?.electronicOrderModel?.Config;
+            order.Certification = Initialize(order).Result?.electronicOrderModel?.Certification;
+            Log.Info("UnifiedOrder", "UnifiedOrder is processing order " + order.BillNumber);
+            var result = await MicroPay.UnifiedOrder(ModelMapper.ConvertToWayneCloudModel(order));
+
+            Log.Info("UnifiedOrder", "The final MicroPay UnifiedOrder is: \r\n" + result.ToXml());
+            var Generic = new GenericProcessResponse()
+            {
+                WeChatResponse = result
+            };
+            var ProcessResults = await UnifiedOrderResult(Generic, order);
+            Generic.UnifiedOrderResult = ProcessResults;
+            return Generic;
+        }
+
+        protected override async Task<GenericProcessResponse> Initialize(ElectronicOrderModel order)
+        {
+            string sourceFilePath = @"C:\cw\Code\smartfuel_lite\FuelCloud\src\Fuel.Payment.Server\File\Certificate\12345678-9abc-def0-1234-56789abcdef0\apiclient_cert.p12";
+            string config = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<WxPayConfig>  \r\n  <APPID>wxb198dafff060e651</APPID>  \r\n  <SUBAPPID></SUBAPPID>\r\n  <MINI_PROGRAM_APPID>wxb198dafff060e651</MINI_PROGRAM_APPID>\r\n  <MCHID>1617253894</MCHID>\r\n  <SUBMCHID></SUBMCHID>\r\n  <KEY>Kguangzhouhengshangongsi20250211</KEY>\r\n  <APPSECRET>2e6ee037b95c8fb90eb415bb559f8259</APPSECRET>\r\n  <SSLCERT>apiclient_cert.p12</SSLCERT>\r\n  <SSLCERT_PASSWORD>1617253894</SSLCERT_PASSWORD>\r\n  <NOTIFY_URL>http://paysdk.weixin.qq.com/example/ResultNotifyPage.aspx</NOTIFY_URL>\r\n  <IP>8.8.8.8</IP>\r\n  <PROXY_URL>http://10.152.18.220:8080</PROXY_URL>\r\n  <REPORT_LEVENL>1</REPORT_LEVENL>\r\n</WxPayConfig>";
+            FileHandler fileHandler = new FileHandler();
+            byte[] fileBytes = fileHandler.ReadFileToByteArray(sourceFilePath);
+            order.Certification = fileHandler.GetWxPayCertificationInfo(fileBytes, "1617253894");
+            WayneCloud.Models.Models.WxPayConfig wxPayConfig = WayneCloud.Models.Models.WxPayConfig.DeserializeFromXmlString(config);
+            order.Config = wxPayConfig;
+            return new GenericProcessResponse()
+            {
+                electronicOrderModel = order
+            };
+        }
+
+        protected override async Task<ElectronicOrderModel> PaymentResult(GenericProcessResponse order, ElectronicOrderModel electronicOrderModel)
+        {
+            throw new NotImplementedException();
+        }
+
+
+        /// <summary>
+        /// 统一下单结果
+        /// </summary>
+        /// <param name="processResponse"></param>
+        /// <param name="eOrder"></param>
+        /// <returns></returns>
+
+        protected override async Task<object> UnifiedOrderResult(GenericProcessResponse order, ElectronicOrderModel electronicOrderModel)
+        {
+            var returnCode = ReturnCode.PAY_ERROR;
+            object response = null;
+            var wechatResponse = order.WeChatResponse;
+            if (electronicOrderModel.TradeStatus == TradeStatus.SUCCESS && wechatResponse.IsSet("prepay_id"))
+            {
+                var prepayId = wechatResponse.GetValue("prepay_id");
+                var payInfo = new PayInfo();
+
+                var config = electronicOrderModel.Config as WayneCloud.Models.Models.WxPayConfig;
+                payInfo.appId = config.MINI_PROGRAM_APPID;
+                payInfo.package = "prepay_id=" + prepayId;
+                payInfo.signType = "MD5";
+                payInfo.timeStamp = WayneCloud.Models.Models.SignUtility.GetSecondsSince1970();
+                payInfo.nonceStr = WayneCloud.Models.Models.SignUtility.GenerateRondomString(new Random(), 32);
+                payInfo.paySign = WayneCloud.Models.Models.SignUtility.GenerateSignature(WayneCloud.Models.Models.SignUtility.BuildSignData(payInfo), config.KEY);
+                payInfo.billNumber = electronicOrderModel.BillNumber;
+                response = payInfo;
+            }
+            electronicOrderModel.UnifiedOrderResult = response;
+            return electronicOrderModel;
+        }
+
+
+    }
+}

+ 16 - 9
FuelCloud/src/FuelServer.Core/Entity/transactions.cs

@@ -155,14 +155,16 @@ namespace FuelServer.Core.Entity
         /// </summary>
         [JsonProperty, Column(StringLength = 20, IsNullable = false)]
         public string ResultCode { get; set; }
-		/// <summary>
-		/// 是否授权
-		/// </summary>
-		public AuthorizationStatus authorizationStatus { get; set; }
-		/// <summary>
-		/// 单价
-		/// </summary>
-		public decimal? Price { get; set; }
+        /// <summary>
+        /// 是否授权
+        /// </summary>
+        [JsonProperty]
+        public AuthorizationStatus authorizationStatus { get; set; }
+        /// <summary>
+        /// 单价
+        /// </summary>
+        [JsonProperty]
+        public decimal? Price { get; set; }
     }
     public enum AuthorizationStatus
     {
@@ -174,7 +176,12 @@ namespace FuelServer.Core.Entity
         /// <summary>
         /// 授权
         /// </summary>
-        Authorized
+        Authorized,
+
+        /// <summary>
+        /// 等待授权
+        /// </summary>
+        WaitAuthorization
     }
     public enum transactionsORDERSTATUS
     {

+ 6 - 0
FuelCloud/src/FuelServer.Core/Fuel.Core.csproj

@@ -10,7 +10,13 @@
     <PackageReference Include="DFS.Infrastructure.Redis" Version="8.0.0" />
     <PackageReference Include="FreeSql" Version="3.2.833" />
     <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
+    <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.2" />
+    <PackageReference Include="MQTTnet" Version="4.1.4.563" />
     <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
   </ItemGroup>
 
+  <ItemGroup>
+    <ProjectReference Include="..\..\ElectronicPayment\Models\Models.csproj" />
+  </ItemGroup>
+
 </Project>

+ 125 - 0
FuelCloud/src/FuelServer.Core/Models/FileHandler.cs

@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading.Tasks;
+using WayneCloud.Models.Models;
+
+namespace Fuel.Core.Models
+{
+    public class FileHandler
+    {
+        /// <summary>
+        /// 读取文件内容到字节数组
+        /// </summary>
+        /// <param name="filePath">文件路径</param>
+        /// <returns>文件内容的字节数组</returns>
+        public byte[] ReadFileToByteArray(string filePath)
+        {
+            try
+            {
+                // 使用 File.ReadAllBytes 方法读取文件内容
+                return File.ReadAllBytes(filePath);
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Error reading file: {ex.Message}");
+                throw;
+            }
+        }
+
+        /// <summary>
+        /// 将字节数组写入文件
+        /// </summary>
+        /// <param name="filePath">目标文件路径</param>
+        /// <param name="fileBytes">要写入的字节数组</param>
+        public void WriteByteArrayToFile(string filePath, byte[] fileBytes)
+        {
+            try
+            {
+                // 确保目录存在
+                string directory = Path.GetDirectoryName(filePath);
+                if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
+                {
+                    Directory.CreateDirectory(directory);
+                }
+
+                // 使用 File.WriteAllBytes 方法写入字节数组
+                File.WriteAllBytes(filePath, fileBytes);
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Error writing file: {ex.Message}");
+                throw;
+            }
+        }
+
+        /// <summary>
+        /// 获取文件的元数据
+        /// </summary>
+        /// <param name="filePath">文件路径</param>
+        /// <returns>包含文件元数据的对象</returns>
+        public FileMetadata GetFileMetadata(string filePath)
+        {
+            try
+            {
+                var fileInfo = new FileInfo(filePath);
+                return new FileMetadata
+                {
+                    Name = fileInfo.Name,
+                    FullName = fileInfo.FullName,
+                    Length = fileInfo.Length,
+                    CreatedTime = fileInfo.CreationTimeUtc,
+                    LastModifiedTime = fileInfo.LastWriteTimeUtc,
+                    ContentHash = ComputeFileContentHash(filePath)
+                };
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Error getting file metadata: {ex.Message}");
+                throw;
+            }
+        }
+
+        /// <summary>
+        /// 计算文件内容的哈希值
+        /// </summary>
+        /// <param name="filePath">文件路径</param>
+        /// <returns>文件内容的哈希值</returns>
+        private string ComputeFileContentHash(string filePath)
+        {
+            using (var hashAlgorithm = System.Security.Cryptography.SHA256.Create())
+            {
+                using (var stream = File.OpenRead(filePath))
+                {
+                    var hashBytes = hashAlgorithm.ComputeHash(stream);
+                    return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
+                }
+            }
+        }
+
+        public  async Task<WxPayCertificationInfo> GetWxPayCertificationInfo(byte[] Value, string SSLCERT_PASSWORD)
+        {
+            X509Certificate2 cert = new X509Certificate2(Value, SSLCERT_PASSWORD);
+            var certificationInfo = new WxPayCertificationInfo
+            {
+                Certification = cert
+            };
+            return certificationInfo;
+        }
+    }
+
+    /// <summary>
+    /// 文件元数据类
+    /// </summary>
+    public class FileMetadata
+    {
+        public string Name { get; set; }
+        public string FullName { get; set; }
+        public long Length { get; set; }
+        public DateTime CreatedTime { get; set; }
+        public DateTime LastModifiedTime { get; set; }
+        public string ContentHash { get; set; }
+    }
+}

+ 106 - 0
FuelCloud/src/FuelServer.Core/Models/KeyValueCache.cs

@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Fuel.Core.Models
+{
+    public static class KeyValueCache
+    {
+        private static Dictionary<string, object> authGun = new Dictionary<string, object>(); // 云端向FCC发送油枪授权
+        private static Dictionary<string, object> cancelAuth = new Dictionary<string, object>(); // 云端向FCC发送取消授权
+        private static Dictionary<string, object> payComplete = new Dictionary<string, object>(); // 云端支付完成向FCC发送订单支付完成信息
+        private static Dictionary<string, object> refundOrder = new Dictionary<string, object>(); // 云端退款向FCC发送订单信息
+
+        // 根据字典名称选择对应的字典
+        private static Dictionary<string, object> SelectDictionary(string dictName)
+        {
+            return dictName switch
+            {
+                "authGun" => authGun,
+                "cancelAuth" => cancelAuth,
+                "payComplete" => payComplete,
+                "refundOrder" => refundOrder,
+                _ => throw new ArgumentException("Invalid dictionary name.")
+            };
+        }
+
+        // 添加或更新键值对
+        public static void AddOrUpdate(string dictName, string key, object value)
+        {
+            var dictionary = SelectDictionary(dictName);
+            if (dictionary.ContainsKey(key))
+            {
+                dictionary[key] = value; // 更新现有键的值
+            }
+            else
+            {
+                dictionary.Add(key, value); // 添加新的键值对
+            }
+        }
+
+        // 删除键值对
+        public static bool Remove(string dictName, string key)
+        {
+            var dictionary = SelectDictionary(dictName);
+            return dictionary.Remove(key);
+        }
+
+        // 查找键值对
+        public static bool TryGetValue(string dictName, string key, out object value)
+        {
+            var dictionary = SelectDictionary(dictName);
+            return dictionary.TryGetValue(key, out value);
+        }
+
+        // 获取所有键值对
+        public static Dictionary<string, object>.KeyCollection GetAllKeys(string dictName)
+        {
+            var dictionary = SelectDictionary(dictName);
+            return dictionary.Keys;
+        }
+
+        // 根据键获取值(如果没有找到则返回null)
+        public static T GetValueOrDefault<T>(string dictName, string key)
+        {
+            var dictionary = SelectDictionary(dictName);
+            if (dictionary.TryGetValue(key, out var value))
+            {
+                try
+                {
+                    return (T)value;
+                }
+                catch (InvalidCastException)
+                {
+                    return default(T); // 返回默认值
+                }
+            }
+            return default(T); // 如果键不存在,返回默认值
+        }
+
+        /// <summary>
+        /// 用于云端向fcc发送mqtt指令,监听返回结果
+        /// </summary>
+        /// <param name="dictName"></param>
+        /// <param name="key"></param>
+        /// <param name="initialValue"></param>
+        /// <param name="durationSeconds"></param>
+        /// <param name="checkIntervalSeconds"></param>
+        /// <returns></returns>
+        public static async Task<bool> MonitorDictionaryChanges(string dictName, string key, object initialValue, int durationSeconds = 10, int checkIntervalSeconds = 1)
+        {
+            var dictionary = SelectDictionary(dictName);
+
+            for (int i = 0; i < durationSeconds; i++)
+            {
+                if (dictionary.TryGetValue(key, out var currentValue) && !object.Equals(currentValue, initialValue))
+                {
+                    return true; 
+                }
+                await Task.Delay(checkIntervalSeconds * 1000); // 等待指定的间隔时间
+            }
+            return false; 
+        }
+    }
+}

+ 39 - 0
FuelCloud/src/FuelServer.Core/Nozzle/Dto/UploadNozzle.cs

@@ -81,4 +81,43 @@ namespace Fuel.Core.Nozzle.Dto
         /// </summary>
         public long Status { get; set; }
     }
+    public class NozzleAuthorization
+    {
+        /// <summary>
+        /// 油枪ID
+        /// </summary>
+        public string NozzleId { get; set; }
+        /// <summary>
+        /// 交易流水
+        /// </summary>
+        public string TransactionNumber { get; set; }
+        public OilMachineStatus OilMachineStatus { get; set; }
+
+    }
+    public enum OilMachineStatus
+    {
+        /// <summary>
+        /// FCC与油机断开连接。
+        /// </summary>
+        Disconnected = 1,
+
+        /// <summary>
+        /// FCC向油机发送授权,油机超时未回复。
+        /// </summary>
+        AuthorizationTimeout = 2,
+
+        /// <summary>
+        /// 操作失败。
+        /// </summary>
+        Failed = 3,
+
+        /// <summary>
+        /// 操作成功。
+        /// </summary>
+        Success = 4,
+        /// <summary>
+        /// 未找到交易流水号。
+        /// </summary>
+        TransactionNumberNotFound = 5
+    }
 }

+ 1 - 1
FuelCloud/src/FuelServer.Core/Transactions/Dto/UploadTransactions.cs

@@ -60,7 +60,7 @@ namespace Fuel.Core.Transactions.Dto
             return new transactions()
             {
                 Buid = Buid,
-                NozzleId = nozzle.NozzleId,
+                NozzleId = nozzle.ExternalGunNumber,
                 ProductId = product.Id,
                 ActualPaymentAmount = upload.ActualPaymentAmount,
                 FuelItemTransactionEndTime = upload.FuelItemTransactionEndTime,

+ 60 - 0
FuelCloud/src/FuelServer.Core/WechatServer/AccessTokenManager.cs

@@ -0,0 +1,60 @@
+using Microsoft.Extensions.Caching.Memory;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Fuel.Core.WechatServer
+{
+    public static class AccessTokenManager
+    {
+        private static readonly HttpClient client = new HttpClient();
+        private static readonly MemoryCache cache = new MemoryCache(new MemoryCacheOptions());
+        private const string TokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=YOUR_APPID&secret=YOUR_SECRET";
+
+        public static async Task<string> GetAccessTokenAsync(string AppId, string AppSecret)
+        {
+            var cacheKey = $"AccessToken_{AppId}";
+            if (cache.TryGetValue(cacheKey, out AccessTokenCache cachedToken) && cachedToken.ExpireTime > DateTime.Now)
+            {
+                return cachedToken.AccessToken;
+            }
+            string url = string.Format(WechatConstants.token,
+            AppId, AppSecret);
+            // 获取新的access_token
+            var response = await client.GetStringAsync(url);
+            var tokenInfo = JsonConvert.DeserializeObject<AccessTokenResponse>(response);
+
+            // 更新缓存
+            var cacheEntryOptions = new MemoryCacheEntryOptions()
+                .SetAbsoluteExpiration(TimeSpan.FromSeconds(tokenInfo.ExpiresIn - 60)); // 提前1分钟刷新
+
+            var cacheInfo = new AccessTokenCache
+            {
+                AccessToken = tokenInfo.AccessToken,
+                ExpireTime = DateTime.Now.AddSeconds(tokenInfo.ExpiresIn - 60) // 提前1分钟刷新
+            };
+
+            cache.Set(cacheKey, cacheInfo, cacheEntryOptions);
+
+            return tokenInfo.AccessToken;
+        }
+
+        private class AccessTokenResponse
+        {
+            [JsonProperty("access_token")]
+            public string AccessToken { get; set; }
+
+            [JsonProperty("expires_in")]
+            public int ExpiresIn { get; set; }
+        }
+
+        private class AccessTokenCache
+        {
+            public string AccessToken { get; set; }
+            public DateTime ExpireTime { get; set; }
+        }
+    }
+}

+ 4 - 0
FuelCloud/src/FuelServer.Core/WechatServer/WechatConstants.cs

@@ -10,5 +10,9 @@ namespace Fuel.Core.WechatServer
     {
         public const string Jscode2sessionUrl =
            "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code";
+
+        public const string token =
+         "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}";
+
     }
 }

+ 8 - 0
FuelCloud/src/FuelServer.Core/WechatServer/WechatUserSessionResponse.cs

@@ -13,4 +13,12 @@ namespace Fuel.Core.WechatServer
         public string unionid { get; set; }
         public string buId { get; set; }
     }
+    public class AddWechatUser
+    {
+        public string Address { get; set; }
+     
+        public string UserAvatarUrl { get; set; }
+        public string UserName { get; set; }
+        public string UserPhoneNumber { get; set; }
+    }
 }