mirror of
https://gitee.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git
synced 2025-09-19 18:22:24 +08:00
feat(wxapi): 新增视频号小店商品类目相关接口
This commit is contained in:
@@ -0,0 +1,48 @@
|
|||||||
|
namespace SKIT.FlurlHttpClient.Wechat.Api.Events
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <para>表示 EVENT.product_category_audit 事件的数据。</para>
|
||||||
|
/// <para>REF: https://developers.weixin.qq.com/doc/channels/API/category/callback/ProductCategoryAudit.html </para>
|
||||||
|
/// </summary>
|
||||||
|
public class ChannelsECCategoryAuditEvent : WechatApiEvent, WechatApiEvent.Serialization.IJsonSerializable, WechatApiEvent.Serialization.IXmlSerializable
|
||||||
|
{
|
||||||
|
public static class Types
|
||||||
|
{
|
||||||
|
public class EventData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置审核单 ID。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("audit_id")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("audit_id")]
|
||||||
|
[System.Text.Json.Serialization.JsonNumberHandling(System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString)]
|
||||||
|
[System.Xml.Serialization.XmlElement("audit_id")]
|
||||||
|
public long AuditId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置审核状态。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("status")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("status")]
|
||||||
|
[System.Xml.Serialization.XmlElement("status")]
|
||||||
|
public int Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置拒绝原因。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("reason")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("reason")]
|
||||||
|
[System.Xml.Serialization.XmlElement("reason", IsNullable = true)]
|
||||||
|
public string? RejectReason { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置事件数据。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("ProductCategoryAudit")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("ProductCategoryAudit")]
|
||||||
|
[System.Xml.Serialization.XmlElement("ProductCategoryAudit")]
|
||||||
|
public Types.EventData EventData { get; set; } = default!;
|
||||||
|
}
|
||||||
|
}
|
@@ -47,7 +47,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Events
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Newtonsoft.Json.JsonProperty("reject_reason")]
|
[Newtonsoft.Json.JsonProperty("reject_reason")]
|
||||||
[System.Text.Json.Serialization.JsonPropertyName("reject_reason")]
|
[System.Text.Json.Serialization.JsonPropertyName("reject_reason")]
|
||||||
[System.Xml.Serialization.XmlElement("reject_reason")]
|
[System.Xml.Serialization.XmlElement("reject_reason", IsNullable = true)]
|
||||||
public string? RejectReason { get; set; }
|
public string? RejectReason { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -39,7 +39,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Events
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Newtonsoft.Json.JsonProperty("reject_reason")]
|
[Newtonsoft.Json.JsonProperty("reject_reason")]
|
||||||
[System.Text.Json.Serialization.JsonPropertyName("reject_reason")]
|
[System.Text.Json.Serialization.JsonPropertyName("reject_reason")]
|
||||||
[System.Xml.Serialization.XmlElement("reject_reason")]
|
[System.Xml.Serialization.XmlElement("reject_reason", IsNullable = true)]
|
||||||
public string? RejectReason { get; set; }
|
public string? RejectReason { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -55,6 +55,68 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region ECCategory
|
||||||
|
/// <summary>
|
||||||
|
/// <para>异步调用 [POST] /channels/ec/category/availablesoncategories/get 接口。</para>
|
||||||
|
/// <para>REF: https://developers.weixin.qq.com/doc/channels/API/category/getavailablesoncategories.html </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static async Task<Models.ChannelsECCategoryAvailableSonCategoriesGetResponse> ExecuteChannelsECCategoryAvailableSonCategoriesGetAsync(this WechatApiClient client, Models.ChannelsECCategoryAvailableSonCategoriesGetRequest request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||||
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
|
IFlurlRequest flurlReq = client
|
||||||
|
.CreateRequest(request, HttpMethod.Post, "channels", "ec", "category", "availablesoncategories", "get")
|
||||||
|
.SetQueryParam("access_token", request.AccessToken);
|
||||||
|
|
||||||
|
return await client.SendRequestWithJsonAsync<Models.ChannelsECCategoryAvailableSonCategoriesGetResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>异步调用 [POST] /channels/ec/category/add 接口。</para>
|
||||||
|
/// <para>REF: https://developers.weixin.qq.com/doc/channels/API/category/add.html </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static async Task<Models.ChannelsECCategoryAddResponse> ExecuteChannelsECCategoryAddAsync(this WechatApiClient client, Models.ChannelsECCategoryAddRequest request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||||
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
|
IFlurlRequest flurlReq = client
|
||||||
|
.CreateRequest(request, HttpMethod.Post, "channels", "ec", "category", "add")
|
||||||
|
.SetQueryParam("access_token", request.AccessToken);
|
||||||
|
|
||||||
|
return await client.SendRequestWithJsonAsync<Models.ChannelsECCategoryAddResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>异步调用 [POST] /channels/ec/category/audit/get 接口。</para>
|
||||||
|
/// <para>REF: https://developers.weixin.qq.com/doc/channels/API/category/audit_get.html </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static async Task<Models.ChannelsECCategoryAuditGetResponse> ExecuteChannelsECCategoryAuditGetAsync(this WechatApiClient client, Models.ChannelsECCategoryAuditGetRequest request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||||
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
|
IFlurlRequest flurlReq = client
|
||||||
|
.CreateRequest(request, HttpMethod.Post, "channels", "ec", "category", "audit", "get")
|
||||||
|
.SetQueryParam("access_token", request.AccessToken);
|
||||||
|
|
||||||
|
return await client.SendRequestWithJsonAsync<Models.ChannelsECCategoryAuditGetResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region ECWindow
|
#region ECWindow
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <para>异步调用 [POST] /channels/ec/window/product/add 接口。</para>
|
/// <para>异步调用 [POST] /channels/ec/window/product/add 接口。</para>
|
||||||
|
@@ -0,0 +1,51 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace SKIT.FlurlHttpClient.Wechat.Api.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <para>表示 [POST] /channels/ec/category/add 接口的请求。</para>
|
||||||
|
/// </summary>
|
||||||
|
public class ChannelsECCategoryAddRequest : WechatApiRequest, IInferable<ChannelsECCategoryAddRequest, ChannelsECCategoryAddResponse>
|
||||||
|
{
|
||||||
|
public static class Types
|
||||||
|
{
|
||||||
|
public class Category
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置一级类目 ID。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("level1")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("level1")]
|
||||||
|
public int FirstCategoryId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置二级类目 ID。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("level2")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("level2")]
|
||||||
|
public int SecondCategoryId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置三级类目 ID。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("level3")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("level3")]
|
||||||
|
public int ThirdCategoryId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置资质材料图片 MediaId 列表。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("certificate")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("certificate")]
|
||||||
|
public IList<string>? CertificateMediaIdList { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置类目信息。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("category_info")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("category_info")]
|
||||||
|
public Types.Category Category { get; set; } = new Types.Category();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,16 @@
|
|||||||
|
namespace SKIT.FlurlHttpClient.Wechat.Api.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <para>表示 [POST] /channels/ec/category/add 接口的响应。</para>
|
||||||
|
/// </summary>
|
||||||
|
public class ChannelsECCategoryAddResponse : WechatApiResponse
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置审核单 ID。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("audit_id")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("audit_id")]
|
||||||
|
[System.Text.Json.Serialization.JsonNumberHandling(System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString)]
|
||||||
|
public long AuditId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
namespace SKIT.FlurlHttpClient.Wechat.Api.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <para>表示 [POST] /channels/ec/category/audit/get 接口的请求。</para>
|
||||||
|
/// </summary>
|
||||||
|
public class ChannelsECCategoryAuditGetRequest : WechatApiRequest, IInferable<ChannelsECCategoryAuditGetRequest, ChannelsECCategoryAuditGetResponse>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置审核单 ID。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("audit_id")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("audit_id")]
|
||||||
|
public long AuditId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,35 @@
|
|||||||
|
namespace SKIT.FlurlHttpClient.Wechat.Api.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <para>表示 [POST] /channels/ec/category/audit/get 接口的响应。</para>
|
||||||
|
/// </summary>
|
||||||
|
public class ChannelsECCategoryAuditGetResponse : WechatApiResponse
|
||||||
|
{
|
||||||
|
public static class Types
|
||||||
|
{
|
||||||
|
public class Data
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置审核状态。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("status")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("status")]
|
||||||
|
public int Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置审核拒绝原因。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("reject_reason")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("reject_reason")]
|
||||||
|
public string? RejectReason { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置返回数据。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("data")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("data")]
|
||||||
|
public Types.Data Data { get; set; } = default!;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
namespace SKIT.FlurlHttpClient.Wechat.Api.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <para>表示 [POST] /channels/ec/category/availablesoncategories/get 接口的请求。</para>
|
||||||
|
/// </summary>
|
||||||
|
public class ChannelsECCategoryAvailableSonCategoriesGetRequest : WechatApiRequest, IInferable<ChannelsECCategoryAvailableSonCategoriesGetRequest, ChannelsECCategoryAvailableSonCategoriesGetResponse>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置上级类目 ID。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("f_cat_id")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("f_cat_id")]
|
||||||
|
public int ParentCategoryId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,42 @@
|
|||||||
|
namespace SKIT.FlurlHttpClient.Wechat.Api.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <para>表示 [POST] /channels/ec/category/availablesoncategories/get 接口的响应。</para>
|
||||||
|
/// </summary>
|
||||||
|
public class ChannelsECCategoryAvailableSonCategoriesGetResponse : WechatApiResponse
|
||||||
|
{
|
||||||
|
public static class Types
|
||||||
|
{
|
||||||
|
public class Category
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置类目 ID。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("cat_id")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("cat_id")]
|
||||||
|
public int CategoryId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置类目名称。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("name")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置上级类目 ID。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("f_cat_id")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("f_cat_id")]
|
||||||
|
public int ParentCategoryId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置类目列表。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("cat_list")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("cat_list")]
|
||||||
|
public Types.Category[] CategoryList { get; set; } = default!;
|
||||||
|
}
|
||||||
|
}
|
@@ -41,7 +41,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Newtonsoft.Json.JsonProperty("certificate")]
|
[Newtonsoft.Json.JsonProperty("certificate")]
|
||||||
[System.Text.Json.Serialization.JsonPropertyName("certificate")]
|
[System.Text.Json.Serialization.JsonPropertyName("certificate")]
|
||||||
public IList<string>? QualificationUrlList { get; set; }
|
public IList<string>? CertificateUrlList { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +58,13 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Models
|
|||||||
[Newtonsoft.Json.JsonProperty("category_info")]
|
[Newtonsoft.Json.JsonProperty("category_info")]
|
||||||
[System.Text.Json.Serialization.JsonPropertyName("category_info")]
|
[System.Text.Json.Serialization.JsonPropertyName("category_info")]
|
||||||
public Types.Category Category { get; set; } = new Types.Category();
|
public Types.Category Category { get; set; } = new Types.Category();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置商品使用场景 ID 列表。
|
||||||
|
/// </summary>
|
||||||
|
[Newtonsoft.Json.JsonProperty("scene_group_list")]
|
||||||
|
[System.Text.Json.Serialization.JsonPropertyName("scene_group_list")]
|
||||||
|
public IList<int>? SceneGroupIdList { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
namespace SKIT.FlurlHttpClient.Wechat.Api.Models
|
namespace SKIT.FlurlHttpClient.Wechat.Api.Models
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <para>表示 [POST] /wx/component/fastregisterpersonalweapp?action=query 接口的请求。</para>
|
/// <para>表示 [POST] /wxa/component/fastregisterpersonalweapp?action=query 接口的请求。</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WxaComponentFastRegisterPersonalWeappQueryRequest : WechatApiRequest, IInferable<WxaComponentFastRegisterPersonalWeappQueryRequest, WxaComponentFastRegisterPersonalWeappCreateResponse>
|
public class WxaComponentFastRegisterPersonalWeappQueryRequest : WechatApiRequest, IInferable<WxaComponentFastRegisterPersonalWeappQueryRequest, WxaComponentFastRegisterPersonalWeappCreateResponse>
|
||||||
{
|
{
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"ToUserName": "gh_*",
|
||||||
|
"FromUserName": "OpenID",
|
||||||
|
"CreateTime": 1662480000,
|
||||||
|
"MsgType": "event",
|
||||||
|
"Event": "product_category_audit",
|
||||||
|
"ProductCategoryAudit": {
|
||||||
|
"audit_id": "123456",
|
||||||
|
"status": "1",
|
||||||
|
"reason": "xxx原因"
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"category_info": {
|
||||||
|
"level1": 7419,
|
||||||
|
"level2": 7439,
|
||||||
|
"level3": 7448,
|
||||||
|
"certificate": ["THE_MEDIA_ID_1", "THE_MEDIA_ID_2"]
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"errcode": 0,
|
||||||
|
"errmsg": "ok",
|
||||||
|
"audit_id": "123456"
|
||||||
|
}
|
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"audit_id": "123456"
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"errcode": 0,
|
||||||
|
"errmsg": "ok",
|
||||||
|
"data": {
|
||||||
|
"status": 9,
|
||||||
|
"reject_reason": "请重新提交审核"
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"f_cat_id": 0
|
||||||
|
}
|
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"errcode": 0,
|
||||||
|
"errmsg": "ok",
|
||||||
|
"cat_list": [
|
||||||
|
{
|
||||||
|
"cat_id": 1,
|
||||||
|
"f_cat_id": 0,
|
||||||
|
"name": "服饰"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cat_id": 2,
|
||||||
|
"f_cat_id": 0,
|
||||||
|
"name": "鞋帽"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Reference in New Issue
Block a user