Exemplo em C#
Este documento contém um exemplo completo de implementação da API Flowbiz em C# utilizando ASP.NET Core.
Código Completo
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace FlowbizApi
{
// Modelos de dados
public class Brand
{
public string Id { get; set; }
public string Name { get; set; }
}
public class ProductCategory
{
public string Id { get; set; }
public string Name { get; set; }
}
public class Variant
{
public string Sku { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public double? PriceFrom { get; set; }
public int? Stock { get; set; }
public string ImageUrl { get; set; }
public string Url { get; set; }
public bool? Available { get; set; }
}
public class Product
{
public string ProductId { get; set; }
public string Url { get; set; }
public Brand Brand { get; set; }
public List<ProductCategory> Category { get; set; }
public List<Variant> Variants { get; set; }
}
public class OrderItem
{
public string ProductId { get; set; }
public List<ProductCategory> Categories { get; set; }
public string Sku { get; set; }
public string Name { get; set; }
public string Brand { get; set; }
public double Price { get; set; }
public int Quantity { get; set; }
public string Url { get; set; }
public string ImageUrl { get; set; }
}
public class PaymentMethod
{
public string Type { get; set; }
public double Amount { get; set; }
}
public class DeliveryMethod
{
public string Type { get; set; }
public double Amount { get; set; }
}
public class DeliveryAddress
{
public string City { get; set; }
public string AddressLine2 { get; set; }
public string Neighborhood { get; set; }
public string AddressNumber { get; set; }
public string State { get; set; }
public string AddressLine1 { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
}
public class Order
{
public string Platform { get; set; }
public string OrderId { get; set; }
public DateTime Date { get; set; }
public double Subtotal { get; set; }
public double Freight { get; set; }
public double Discounts { get; set; }
public double Total { get; set; }
public string Currency { get; set; }
public string RawPaymentStatus { get; set; }
public bool IsPaid { get; set; }
public string CustomerId { get; set; }
public string CustomerEmail { get; set; }
public bool? OptIn { get; set; }
public List<OrderItem> Items { get; set; }
public List<PaymentMethod> PaymentMethods { get; set; }
public List<DeliveryMethod> DeliveryMethods { get; set; }
public DeliveryAddress DeliveryAddress { get; set; }
}
// Middleware para validar a API Key
public class ApiKeyMiddleware
{
private readonly RequestDelegate _next;
private const string API_KEY = "sua_api_key_aqui"; // Em produção, use configurações do aplicativo
public ApiKeyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Bypass para healthz
if (string.Equals(context.Request.Path, "/api/v2/healthz", StringComparison.OrdinalIgnoreCase))
{
await _next(context);
return;
}
if (!context.Request.Headers.TryGetValue("X-Impulse-Key", out var extractedApiKey))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsJsonAsync(new { error = "unauthorized", message = "X-Impulse-Key is missing or invalid" });
return;
}
if (!API_KEY.Equals(extractedApiKey))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsJsonAsync(new { error = "unauthorized", message = "X-Impulse-Key is missing or invalid" });
return;
}
await _next(context);
}
}
// Classe principal
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
// Aplicar middleware de validação da API Key (exceto /healthz)
app.UseMiddleware<ApiKeyMiddleware>();
app.UseEndpoints(endpoints =>
{
// Healthz (sem autenticação)
endpoints.MapGet("/api/v2/healthz", async context =>
{
await context.Response.WriteAsJsonAsync(new { status = "ok" });
});
// Endpoint de categorias
endpoints.MapGet("/api/v2/categories", async context =>
{
// Paginação: offset/limit (padrão 50, máx 100)
int offset = 0;
int limit = 50;
int.TryParse(context.Request.Query["offset"], out offset);
int.TryParse(context.Request.Query["limit"], out limit);
if (limit <= 0) limit = 50;
if (limit > 100) limit = 100;
if (offset < 0) offset = 0;
// Simulação de dados
var allCategories = new List<ProductCategory>
{
new ProductCategory { Id = "1", Name = "Eletrônicos" },
new ProductCategory { Id = "2", Name = "Roupas" },
new ProductCategory { Id = "3", Name = "Acessórios" }
};
// Aplicar paginação e envelope
var data = allCategories.Skip(offset).Take(limit).ToList();
var count = data.Count;
var total = allCategories.Count;
var hasNext = (offset + count) < total;
await context.Response.WriteAsJsonAsync(new
{
data,
total,
offset,
limit,
count,
hasNext,
nextOffset = hasNext ? offset + count : (int?)null
});
});
// Endpoint de marcas
endpoints.MapGet("/api/v2/brands", async context =>
{
int offset = 0;
int limit = 50;
int.TryParse(context.Request.Query["offset"], out offset);
int.TryParse(context.Request.Query["limit"], out limit);
if (limit <= 0) limit = 50;
if (limit > 100) limit = 100;
if (offset < 0) offset = 0;
// Simulação de dados
var allBrands = new List<Brand>
{
new Brand { Id = "1", Name = "Samsung" },
new Brand { Id = "2", Name = "Apple" },
new Brand { Id = "3", Name = "Nike" }
};
var data = allBrands.Skip(offset).Take(limit).ToList();
var count = data.Count;
var total = allBrands.Count;
var hasNext = (offset + count) < total;
await context.Response.WriteAsJsonAsync(new
{
data,
total,
offset,
limit,
count,
hasNext,
nextOffset = hasNext ? offset + count : (int?)null
});
});
// Endpoint de produtos
endpoints.MapGet("/api/v2/products", async context =>
{
int offset = 0;
int limit = 50;
int.TryParse(context.Request.Query["offset"], out offset);
int.TryParse(context.Request.Query["limit"], out limit);
if (limit <= 0) limit = 50;
if (limit > 100) limit = 100;
if (offset < 0) offset = 0;
// Simulação de dados
var allProducts = new List<Product>
{
new Product
{
ProductId = "1",
Url = "https://exemplo.com/produto/1",
Brand = new Brand { Id = "1", Name = "Samsung" },
Category = new List<ProductCategory> { new ProductCategory { Id = "1", Name = "Eletrônicos" } },
Variants = new List<Variant> { new Variant { Sku = "SKU-1A", Name = "Variante A", Price = 50.0, Stock = 10, Available = true } }
},
new Product
{
ProductId = "2",
Url = "https://exemplo.com/produto/2",
Brand = new Brand { Id = "2", Name = "Apple" },
Category = new List<ProductCategory> { new ProductCategory { Id = "1", Name = "Eletrônicos" } },
Variants = new List<Variant> { new Variant { Sku = "SKU-2A", Name = "Variante A", Price = 75.0, Stock = 5, Available = true } }
}
};
var data = allProducts.Skip(offset).Take(limit).ToList();
var count = data.Count;
var total = allProducts.Count;
var hasNext = (offset + count) < total;
await context.Response.WriteAsJsonAsync(new
{
data,
total,
offset,
limit,
count,
hasNext,
nextOffset = hasNext ? offset + count : (int?)null
});
});
// Endpoint de pedidos
endpoints.MapGet("/api/v2/orders", async context =>
{
int offset = 0;
int limit = 50;
int.TryParse(context.Request.Query["offset"], out offset);
int.TryParse(context.Request.Query["limit"], out limit);
if (limit <= 0) limit = 50;
if (limit > 100) limit = 100;
if (offset < 0) offset = 0;
// Filtro obrigatório de data: start_date/end_date (YYYY-MM-DD)
string start = context.Request.Query["start_date"];
string end = context.Request.Query["end_date"];
if (string.IsNullOrEmpty(start) || string.IsNullOrEmpty(end))
{
context.Response.StatusCode = 400;
await context.Response.WriteAsJsonAsync(new { error = "bad_request", message = "start_date and end_date are required in YYYY-MM-DD format" });
return;
}
if (!DateTime.TryParse(start + "T00:00:00Z", out DateTime startDate) || !DateTime.TryParse(end + "T23:59:59Z", out DateTime endDate))
{
context.Response.StatusCode = 400;
await context.Response.WriteAsJsonAsync(new { error = "bad_request", message = "start_date and end_date must be in YYYY-MM-DD format" });
return;
}
// Simulação de dados
var allOrders = new List<Order>
{
new Order
{
Platform = "SuaPlataforma",
OrderId = "12345",
Date = new DateTime(2023, 6, 1, 10, 0, 0, DateTimeKind.Utc),
Subtotal = 100.0,
Freight = 10.0,
Discounts = 5.0,
Total = 105.0,
Currency = "BRL",
RawPaymentStatus = "Pago",
IsPaid = true,
CustomerId = "1",
CustomerEmail = "[email protected]",
Items = new List<OrderItem>
{
new OrderItem
{
ProductId = "1",
Categories = new List<ProductCategory> { new ProductCategory { Id = "1", Name = "Eletrônicos" } },
Sku = "SKU-1A",
Name = "Produto 1",
Brand = "Samsung",
Price = 50.0,
Quantity = 2,
Url = "https://exemplo.com/produto/1",
ImageUrl = "https://exemplo.com/img/1.jpg"
}
},
PaymentMethods = new List<PaymentMethod>
{
new PaymentMethod { Type = "credit_card", Amount = 105.0 }
},
DeliveryMethods = new List<DeliveryMethod>
{
new DeliveryMethod { Type = "standard", Amount = 10.0 }
},
DeliveryAddress = new DeliveryAddress
{
City = "São Paulo",
AddressLine2 = "Apto 101",
Neighborhood = "Centro",
AddressNumber = "123",
State = "SP",
AddressLine1 = "Rua Principal",
PostalCode = "01000-000",
Country = "BR"
}
}
};
// Filtrar por data (inclusivo)
allOrders = allOrders.Where(o => o.Date >= startDate && o.Date <= endDate).ToList();
// Aplicar paginação e envelope
var data = allOrders.Skip(offset).Take(limit).ToList();
var count = data.Count;
var total = allOrders.Count;
var hasNext = (offset + count) < total;
await context.Response.WriteAsJsonAsync(new
{
data,
total,
offset,
limit,
count,
hasNext,
nextOffset = hasNext ? offset + count : (int?)null
});
});
});
}
}
// Programa principal
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}Explicação
Este exemplo implementa uma API REST em C# com ASP.NET Core que atende aos requisitos do padrão Flowbiz. A implementação inclui:
- Modelos de dados para representar os objetos do domínio (Order, Product, etc.)
- Middleware de autenticação para validar a API Key
- Endpoints para listar categorias, marcas, produtos e pedidos
- Suporte à paginação em todos os endpoints
- Filtro de data no endpoint de pedidos
Para usar este exemplo, você precisará:
- Substituir
sua_api_key_aquipela sua chave de API real - Implementar a lógica para buscar dados do seu banco de dados ou sistema de armazenamento
- Adaptar as estruturas de dados conforme necessário para seu caso de uso específico
Configuração do Projeto
Para criar um novo projeto ASP.NET Core e implementar este exemplo:
dotnet new web -n FlowbizApi
cd FlowbizApiSubstitua o conteúdo do arquivo Program.cs e Startup.cs pelo código fornecido acima.
Executando o Exemplo
Para executar este exemplo:
dotnet runO servidor será iniciado na porta 5000 (HTTP) e 5001 (HTTPS) e estará pronto para receber requisições.
Testando a API
Você pode testar a API usando ferramentas como Postman, cURL ou qualquer cliente HTTP. Lembre-se de incluir o header X-Impulse-Key com o valor correto em todas as requisições.
Exemplo de requisição:
curl -X GET "http://localhost:5000/api/v2/categories" \
-H "X-Impulse-Key: sua_api_key_aqui"Updated 3 months ago
