421a651975
- All 14 totalDoc files updated with Magic Wallet additions - MAGIC-WALLET-PLAN.md: Phase 1-6 checklist fully marked complete - Business docs: Magic Wallet section, commission filter, new entities - Payment docs: VAT 9%→10%, TransactionType 14+15, ZarinPal 4th usage - Technical docs: UserWallet fields, ClubMembershipCycle, gRPC RPCs - Overview docs: Magic flowchart, ER diagram, changelog, glossary, roadmap
330 lines
12 KiB
Markdown
330 lines
12 KiB
Markdown
# 🔌 API، Protobuf و یکپارچهسازی خارجی
|
||
|
||
> **منابع ادغامشده:** `FRONTOFFICE-CMS-API-COMPATIBILITY.md`, `REMAINING-TASKS.md`, `chatika-integration.md`, `payment-gateway.md`, `club-feature-management-services.md`
|
||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: Magic Wallet gRPC RPCs)
|
||
|
||
---
|
||
|
||
## ۱. معماری ارتباطات
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ External Services │
|
||
│ ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌─────────┐ │
|
||
│ │ ZarinPal│ │ Kavenegar│ │ DayaLoan│ │ Chatika │ │
|
||
│ │ (IPG) │ │ (SMS) │ │ (Loan) │ │ (AI) │ │
|
||
│ └────┬────┘ └────┬─────┘ └────┬────┘ └────┬────┘ │
|
||
│ │ │ │ │ │
|
||
│ ┌────▼───────────▼────────────▼────────────▼────┐ │
|
||
│ │ CMS Microservice │ │
|
||
│ │ (gRPC Server + Hangfire + EF Core) │ │
|
||
│ └────────────────┬───────────────────────────────┘ │
|
||
│ │ gRPC (Protobuf v3) │
|
||
│ ┌───────────┼───────────┐ │
|
||
│ ┌────▼────┐ ┌────▼────┐ │
|
||
│ │BackOffice│ │FrontOffice│ │
|
||
│ │(Blazor │ │(Blazor │ │
|
||
│ │ WASM) │ │ Server) │ │
|
||
│ └─────────┘ └──────────┘ │
|
||
└──────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## ۲. gRPC Proto Definitions
|
||
|
||
### ۲.۱ لیست کامل سرویسها
|
||
|
||
```protobuf
|
||
// ===== product.proto =====
|
||
service ProductService {
|
||
rpc GetProducts (GetProductsRequest) returns (GetProductsResponse);
|
||
rpc GetProductById (GetProductByIdRequest) returns (ProductMessage);
|
||
rpc CreateProduct (CreateProductRequest) returns (CreateProductResponse);
|
||
rpc UpdateProduct (UpdateProductRequest) returns (UpdateProductResponse);
|
||
rpc DeleteProduct (DeleteProductRequest) returns (Empty);
|
||
rpc GetProductsPaged (GetProductsPagedRequest) returns (GetProductsPagedResponse);
|
||
}
|
||
|
||
// ===== order.proto =====
|
||
service OrderService {
|
||
rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse);
|
||
rpc GetOrders (GetOrdersRequest) returns (GetOrdersResponse);
|
||
rpc GetOrderById (GetOrderByIdRequest) returns (OrderMessage);
|
||
rpc UpdateOrderStatus (UpdateOrderStatusRequest) returns (Empty);
|
||
}
|
||
|
||
// ===== user.proto =====
|
||
service UserService {
|
||
rpc Register (RegisterRequest) returns (AuthResponse);
|
||
rpc Login (LoginRequest) returns (AuthResponse);
|
||
rpc GetProfile (GetProfileRequest) returns (UserProfileMessage);
|
||
rpc UpdateProfile (UpdateProfileRequest) returns (Empty);
|
||
rpc SendOtp (SendOtpRequest) returns (SendOtpResponse);
|
||
rpc VerifyOtp (VerifyOtpRequest) returns (VerifyOtpResponse);
|
||
}
|
||
|
||
// ===== club.proto =====
|
||
service ClubService {
|
||
rpc GetNetworkTree (GetNetworkTreeRequest) returns (NetworkTreeResponse);
|
||
rpc GetBalance (GetBalanceRequest) returns (BalanceResponse);
|
||
rpc ReadContract (ReadContractRequest) returns (ContractResponse);
|
||
rpc RequestContractOtp (RequestOtpRequest) returns (OtpResponse);
|
||
rpc VerifyContractOtp (VerifyOtpRequest) returns (VerifyOtpResponse);
|
||
rpc AcceptContract (AcceptContractRequest) returns (AcceptContractResponse);
|
||
rpc GetClubFeatures (GetFeaturesRequest) returns (FeaturesResponse);
|
||
}
|
||
|
||
// ===== payment.proto =====
|
||
service PaymentService {
|
||
rpc CreatePayment (CreatePaymentRequest) returns (CreatePaymentResponse);
|
||
rpc VerifyPayment (VerifyPaymentRequest) returns (VerifyPaymentResponse);
|
||
rpc GetPaymentStatus (PaymentStatusRequest) returns (PaymentStatusResponse);
|
||
}
|
||
|
||
// ===== blog.proto =====
|
||
service BlogService {
|
||
rpc GetPosts (GetPostsRequest) returns (GetPostsResponse);
|
||
rpc GetPostBySlug (GetPostBySlugRequest) returns (BlogPostMessage);
|
||
rpc CreatePost (CreatePostRequest) returns (CreatePostResponse);
|
||
rpc UpdatePost (UpdatePostRequest) returns (Empty);
|
||
rpc DeletePost (DeletePostRequest) returns (Empty);
|
||
}
|
||
|
||
// ===== inventory.proto =====
|
||
service InventoryService {
|
||
rpc GetInventory (GetInventoryRequest) returns (InventoryMessage);
|
||
rpc UpdateStock (UpdateStockRequest) returns (Empty);
|
||
rpc GetAllInventories (GetAllRequest) returns (InventoryListResponse);
|
||
}
|
||
|
||
// ===== sitepage.proto =====
|
||
service SitePageService {
|
||
rpc GetPage (GetPageRequest) returns (SitePageMessage);
|
||
rpc SaveSettings (SaveSettingsRequest) returns (Empty);
|
||
rpc GetAllPages (Empty) returns (PageListResponse);
|
||
}
|
||
|
||
// ===== file.proto =====
|
||
service FileService {
|
||
rpc Upload (stream UploadRequest) returns (UploadResponse);
|
||
rpc Download (DownloadRequest) returns (stream DownloadResponse);
|
||
rpc Delete (DeleteFileRequest) returns (Empty);
|
||
}
|
||
|
||
// ===== category.proto =====
|
||
service CategoryService {
|
||
rpc GetCategories (GetCategoriesRequest) returns (CategoryListResponse);
|
||
rpc CreateCategory (CreateCategoryRequest) returns (CreateCategoryResponse);
|
||
rpc UpdateCategory (UpdateCategoryRequest) returns (Empty);
|
||
}
|
||
|
||
// ===== config.proto =====
|
||
service SystemConfigService {
|
||
rpc GetConfig (GetConfigRequest) returns (ConfigResponse);
|
||
rpc UpdateConfig (UpdateConfigRequest) returns (Empty);
|
||
rpc GetAllConfigs (Empty) returns (ConfigListResponse);
|
||
}
|
||
|
||
// ===== userwallet.proto ===== (NEW — Magic Wallet)
|
||
service UserWalletService {
|
||
rpc GetCustomerWallet (GetCustomerWalletRequest) returns (GetCustomerWalletResponse);
|
||
rpc InitiateMagicCharge (InitiateMagicChargeRequest) returns (InitiateMagicChargeResponse);
|
||
rpc GetMagicWalletStatus (GetMagicWalletStatusRequest) returns (MagicWalletStatusResponse);
|
||
}
|
||
```
|
||
|
||
### ۲.۲ Shared Messages
|
||
|
||
```protobuf
|
||
// ===== common.proto =====
|
||
message PaginationState {
|
||
int32 skip = 1;
|
||
int32 take = 2;
|
||
}
|
||
|
||
message PaginatedResponse {
|
||
int32 totalCount = 1;
|
||
int32 pageSize = 2;
|
||
int32 currentPage = 3;
|
||
}
|
||
|
||
message Empty {}
|
||
```
|
||
|
||
---
|
||
|
||
## ۳. External Service Integration
|
||
|
||
### ۳.۱ ZarinPal (پرداخت)
|
||
|
||
```csharp
|
||
public class ZarinPalService : IPaymentGateway
|
||
{
|
||
// Config
|
||
private readonly string _merchantId;
|
||
private readonly bool _isSandbox;
|
||
|
||
// Endpoints
|
||
const string PAYMENT_URL = "https://api.zarinpal.com/pg/v4/payment/request.json";
|
||
const string VERIFY_URL = "https://api.zarinpal.com/pg/v4/payment/verify.json";
|
||
const string SANDBOX_URL = "https://sandbox.zarinpal.com/pg/v4/payment/request.json";
|
||
|
||
// Flow
|
||
// 1. CreatePayment → Authority token
|
||
// 2. Redirect → https://www.zarinpal.com/pg/StartPay/{Authority}
|
||
// 3. Callback → VerifyPayment(Authority, Amount)
|
||
// 4. Result → RefID (reference number)
|
||
}
|
||
```
|
||
|
||
### ۳.۲ Kavenegar (SMS)
|
||
|
||
```csharp
|
||
public class KavenegarService : ISmsService
|
||
{
|
||
// Templates — فقط یک تمپلیت در کد موجود است
|
||
const string OTP_TEMPLATE = "Afrino"; // تنها تمپلیت استفادهشده
|
||
// Sender: "1000001110100"
|
||
|
||
// Rate Limiting
|
||
// ۱ SMS per phone per 60 seconds
|
||
// ۵ SMS per phone per hour
|
||
// ۲۰ SMS per phone per day
|
||
|
||
public async Task SendOtpAsync(string phone, string code)
|
||
{
|
||
await _api.VerifyLookup(phone, code, OTP_TEMPLATE);
|
||
}
|
||
}
|
||
```
|
||
|
||
### ۳.۳ Daya Loan (وام)
|
||
|
||
```csharp
|
||
public class DayaLoanService : ILoanService
|
||
{
|
||
// Hangfire job — هر ۲۰ دقیقه (*/20 * * * *)
|
||
// Polly retry: 3 attempts, exponential backoff (2s, 4s, 8s)
|
||
// Mock mode for staging (auto-approve)
|
||
|
||
public async Task<LoanResult> RequestLoanAsync(Guid userId, decimal amount)
|
||
{
|
||
if (_options.UseMock)
|
||
return LoanResult.Approved(amount);
|
||
|
||
var response = await _httpClient.PostAsync(
|
||
$"{_baseUrl}/api/loans/request",
|
||
new { UserId = userId, Amount = amount });
|
||
|
||
return MapResponse(response);
|
||
}
|
||
}
|
||
```
|
||
|
||
### ۳.۴ Chatika (AI)
|
||
|
||
```csharp
|
||
public class ChatikaService : IAiChatService
|
||
{
|
||
// Hangfire job — هر ۵ دقیقه
|
||
// Polly retry: 3 attempts
|
||
// Only for active club members
|
||
|
||
public async Task<string> GetResponseAsync(string userMessage)
|
||
{
|
||
var response = await _httpClient.PostAsync(
|
||
$"{_baseUrl}/api/chat",
|
||
new { Message = userMessage });
|
||
|
||
return response.Content.ReadAsStringAsync();
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## ۴. API Compatibility Layer
|
||
|
||
### ۴.۱ FrontOffice Service Pattern
|
||
|
||
```csharp
|
||
// هر سرویس در FrontOffice یک wrapper بر gRPC client است
|
||
public class ProductService : IProductService
|
||
{
|
||
private readonly ProductServiceClient _client;
|
||
|
||
public ProductService(ProductServiceClient client)
|
||
{
|
||
_client = client;
|
||
}
|
||
|
||
public async Task<ProductListResult> GetProductsPagedAsync(
|
||
int skip, int take, Guid? categoryId = null, string? search = null)
|
||
{
|
||
try
|
||
{
|
||
var request = new GetProductsPagedRequest {
|
||
Pagination = new PaginationState { Skip = skip, Take = take },
|
||
CategoryId = categoryId?.ToString() ?? "",
|
||
SearchTerm = search ?? ""
|
||
};
|
||
|
||
var response = await _client.GetProductsPagedAsync(request);
|
||
|
||
return new ProductListResult(
|
||
response.Products.Select(MapToDto).ToList(),
|
||
response.TotalCount);
|
||
}
|
||
catch (RpcException ex) when (ex.StatusCode == StatusCode.Unavailable)
|
||
{
|
||
// CMS is down — show cached data or error
|
||
throw new ServiceUnavailableException("CMS service unavailable");
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### ۴.۲ Error Handling
|
||
|
||
| gRPC Status | HTTP Equivalent | Handling |
|
||
|------------|-----------------|----------|
|
||
| `OK` | 200 | Return data |
|
||
| `NotFound` | 404 | Show "not found" message |
|
||
| `InvalidArgument` | 400 | Show validation errors |
|
||
| `Unauthenticated` | 401 | Redirect to login |
|
||
| `PermissionDenied` | 403 | Show "access denied" |
|
||
| `Unavailable` | 503 | Show "service down" |
|
||
| `Internal` | 500 | Show generic error |
|
||
|
||
---
|
||
|
||
## ۵. Proto Package Distribution
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A["CMS/src/Protos/*.proto"] --> B["pack-protos.sh"]
|
||
B --> C["Foursat.CMSMicroservice.Protobuf.nupkg\nv1.0.x"]
|
||
C --> D["Push to BaGet / Nexus"]
|
||
D --> E["BackOffice\nPackageReference"]
|
||
D --> F["FrontOffice\nProjectReference ✅"]
|
||
```
|
||
|
||
> ⚠️ FrontOffice از NuGet package به **ProjectReference** مستقیم سوییچ شده (برای دسترسی به پروتوهای جدید Magic Wallet)
|
||
|
||
---
|
||
|
||
## ۶. Remaining Tasks / Integration Gaps
|
||
|
||
| آیتم | اولویت | وضعیت |
|
||
|------|---------|--------|
|
||
| Product Bundle API | Medium | ⬜ Proto + Handler needed |
|
||
| Manual Payment API | Low | ⬜ Design only |
|
||
| SignalR for Chatika | Low | ⬜ Replace polling |
|
||
| File upload streaming | Done | ✅ |
|
||
| Blog search | Done | ✅ |
|
||
| Inventory autocomplete | Done | ✅ |
|
||
| Lazy load pagination | Done | ✅ |
|
||
| Rate limiting (API level) | Medium | ⬜ |
|
||
| API versioning | Low | ⬜ |
|