feat: Complete overhaul of FourSat documentation structure and content
- Added FINAL-STATUS.md detailing project completion and key metrics - Created QUICK-REFERENCE.md for quick access to essential documents - Updated README.md with project overview and quick start guide - Established STRUCTURE.md outlining the final documentation structure - Organized and archived old files, ensuring a clean and efficient directory - Enhanced documentation quality with comprehensive metrics and checklists
This commit is contained in:
@@ -0,0 +1,410 @@
|
||||
# Payment Architecture with PYMS Microservice
|
||||
|
||||
**تاریخ**: 2024-12-02
|
||||
**وضعیت**: Architecture Document
|
||||
**اولویت**: 🔴 بالا (اطلاعات مهم برای Phase 9)
|
||||
|
||||
---
|
||||
|
||||
## 📋 خلاصه
|
||||
|
||||
**درگاه پرداخت** در این پروژه از طریق **مایکروسرویس PYMS** (`Afrino.PYMSMicroservice.Protobuf`) مدیریت میشود.
|
||||
|
||||
**CMS Microservice** فقط **نتیجه نهایی پرداخت** را ثبت میکند و خودش درگاه پرداخت را پیادهسازی نمیکند.
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ معماری کلی
|
||||
|
||||
```
|
||||
[User Frontend]
|
||||
↓
|
||||
[FrontOffice.BFF] ← درخواست خرید از اینجا شروع میشود
|
||||
↓
|
||||
[PYMS Microservice] ← مدیریت درگاه پرداخت (Afrino.PYMSMicroservice.Protobuf)
|
||||
↓
|
||||
[Payment Gateway: در PYMS/Gateway - نه CMS]
|
||||
↓ (Callback)
|
||||
[PYMS Microservice] ← تایید پرداخت
|
||||
↓
|
||||
[CMS Microservice] ← **فقط ثبت نتیجه** (Transaction با RefId)
|
||||
```
|
||||
|
||||
### توضیح جریان:
|
||||
|
||||
1. **کاربر** محصول را در Frontend انتخاب میکند
|
||||
2. **FrontOffice.BFF** درخواست خرید را به **PYMS Microservice** میفرستد
|
||||
3. **PYMS/Gateway** با درگاه پرداخت (بانک) ارتباط برقرار میکند و پرداخت را انجام میدهد
|
||||
4. **Gateway** نتیجه پرداخت را به **CMS Callback** میفرستد
|
||||
5. **CMS** تراکنش را تایید و عملیات بعدی (فعالسازی، اضافه PV، Wallet) را انجام میدهد
|
||||
4. **PYMS** URL درگاه را برمیگرداند
|
||||
5. کاربر به درگاه ریدایرکت میشود و پرداخت میکند
|
||||
6. بعد از پرداخت، **Callback** به **PYMS** برمیگردد
|
||||
7. **PYMS** پرداخت را Verify میکند
|
||||
8. **FrontOffice.BFF** نتیجه را به **CMS** میفرستد
|
||||
9. **CMS** Transaction را با RefId و وضعیت نهایی ثبت میکند
|
||||
|
||||
---
|
||||
|
||||
## 📦 Package: `Afrino.PYMSMicroservice.Protobuf`
|
||||
|
||||
**Version**: 0.0.11
|
||||
**Type**: gRPC Protobuf Client
|
||||
**Namespace**: `PYMSMicroservice.Protobuf.Protos.Transaction`
|
||||
|
||||
### Dependencies:
|
||||
- Google.Protobuf (3.23.3)
|
||||
- Grpc.Core.Api (2.54.0)
|
||||
- FluentValidation (11.2.2)
|
||||
- Google.Api.CommonProtos (2.10.0)
|
||||
|
||||
---
|
||||
|
||||
## 🔧 TransactionContract Service
|
||||
|
||||
### Client Class:
|
||||
```csharp
|
||||
using PYMSMicroservice.Protobuf.Protos.Transaction;
|
||||
using Grpc.Core;
|
||||
|
||||
var client = new TransactionContract.TransactionContractClient(channel);
|
||||
```
|
||||
|
||||
### Available Methods:
|
||||
|
||||
#### 1. **PaymentRequest** (شروع پرداخت)
|
||||
```csharp
|
||||
// Request
|
||||
var request = new PaymentRequestRequest
|
||||
{
|
||||
MerchantId = "YOUR_MERCHANT_ID", // شناسه فروشنده
|
||||
Amount = 100000, // مبلغ به ریال (یا تومان - بستگی به Currency)
|
||||
CallbackUrl = "https://yoursite.com/payment/callback",
|
||||
Description = "خرید بسته طلایی",
|
||||
Mobile = "09123456789", // اختیاری
|
||||
Email = "user@example.com", // اختیاری
|
||||
Currency = CurrencyEnum.Irt, // IRR (ریال) یا IRT (تومان)
|
||||
Type = TransactionTypeEnum.Real, // Real یا Sandbox
|
||||
OrderId = "ORDER_123456" // اختیاری - شناسه سفارش خودمان
|
||||
};
|
||||
|
||||
// Call
|
||||
var response = await client.PaymentRequestAsync(request);
|
||||
|
||||
// Response
|
||||
Console.WriteLine(response.PaymentGWUrl);
|
||||
// مثال: "https://bpm.shaparak.ir/pgwchannel/startpay.mellat?RefId=123456"
|
||||
```
|
||||
|
||||
**Response Fields**:
|
||||
- `PaymentGWUrl` (string): URL درگاه پرداخت که کاربر باید به آن ریدایرکت شود
|
||||
|
||||
#### 2. **PaymentVerification** (تایید پرداخت)
|
||||
```csharp
|
||||
// Request
|
||||
var request = new PaymentVerificationRequest
|
||||
{
|
||||
Authority = "AUTHORITY_FROM_CALLBACK", // Authority که از callback میآید
|
||||
Status = "OK" // Status که از callback میآید (OK/NOK)
|
||||
};
|
||||
|
||||
// Call
|
||||
var response = await client.PaymentVerificationAsync(request);
|
||||
|
||||
// Response
|
||||
if (response.PaymentStatus)
|
||||
{
|
||||
Console.WriteLine($"پرداخت موفق!");
|
||||
Console.WriteLine($"RefId: {response.RefId}");
|
||||
Console.WriteLine($"OrderId: {response.OrderId}");
|
||||
Console.WriteLine($"Message: {response.Message}");
|
||||
Console.WriteLine($"VerificationStatusCode: {response.VerificationStatusCode}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"پرداخت ناموفق: {response.Message}");
|
||||
}
|
||||
```
|
||||
|
||||
**Response Fields**:
|
||||
- `Id` (long): شناسه تراکنش در سیستم PYMS
|
||||
- `PaymentStatus` (bool): وضعیت پرداخت (true = موفق، false = ناموفق)
|
||||
- `Message` (string): پیام وضعیت
|
||||
- `RefId` (string): شناسه مرجع از درگاه پرداخت
|
||||
- `OrderId` (string): شناسه سفارش که در PaymentRequest ارسال شده
|
||||
- `VerificationStatusCode` (int): کد وضعیت تایید
|
||||
|
||||
#### 3. **CreateNewTransaction** (ثبت تراکنش جدید)
|
||||
```csharp
|
||||
var request = new CreateNewTransactionRequest
|
||||
{
|
||||
MerchantId = "...",
|
||||
Amount = 100000,
|
||||
CallbackUrl = "...",
|
||||
Description = "...",
|
||||
Currency = CurrencyEnum.Irt,
|
||||
PaymentStatus = false, // false در ابتدا
|
||||
Type = TransactionTypeEnum.Real
|
||||
};
|
||||
|
||||
var response = await client.CreateNewTransactionAsync(request);
|
||||
Console.WriteLine($"Transaction Id: {response.Id}");
|
||||
```
|
||||
|
||||
#### 4. **UpdateTransaction** (بهروزرسانی تراکنش)
|
||||
```csharp
|
||||
var request = new UpdateTransactionRequest
|
||||
{
|
||||
Id = transactionId,
|
||||
PaymentStatus = true, // بعد از verify
|
||||
RefId = "...",
|
||||
VerificationStatusCode = 100,
|
||||
VerificationStatusMessage = "تراکنش موفق"
|
||||
};
|
||||
|
||||
await client.UpdateTransactionAsync(request);
|
||||
```
|
||||
|
||||
#### 5. **GetTransaction** (دریافت تراکنش)
|
||||
```csharp
|
||||
var request = new GetTransactionRequest
|
||||
{
|
||||
Id = transactionId,
|
||||
// یا
|
||||
Authority = "AUTHORITY_FROM_CALLBACK"
|
||||
};
|
||||
|
||||
var response = await client.GetTransactionAsync(request);
|
||||
```
|
||||
|
||||
#### 6. **GetAllTransactionByFilter** (لیست تراکنشها)
|
||||
```csharp
|
||||
var request = new GetAllTransactionByFilterRequest
|
||||
{
|
||||
PaginationState = new PaginationState { PageNumber = 1, PageSize = 10 },
|
||||
Filter = new GetAllTransactionByFilterFilter
|
||||
{
|
||||
MerchantId = "...",
|
||||
PaymentStatus = true
|
||||
}
|
||||
};
|
||||
|
||||
var response = await client.GetAllTransactionByFilterAsync(request);
|
||||
// response.Models: لیست تراکنشها
|
||||
// response.MetaData: اطلاعات صفحهبندی
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Enums
|
||||
|
||||
### CurrencyEnum
|
||||
```csharp
|
||||
public enum CurrencyEnum
|
||||
{
|
||||
Irr = 0, // ریال
|
||||
Irt = 1 // تومان
|
||||
}
|
||||
```
|
||||
|
||||
### TransactionTypeEnum
|
||||
```csharp
|
||||
public enum TransactionTypeEnum
|
||||
{
|
||||
Real = 0, // تراکنش واقعی
|
||||
Sandbox = 1 // تراکنش تستی
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 نکات مهم برای CMS
|
||||
|
||||
### 1. **CMS فقط نتیجه را ثبت میکند**
|
||||
CMS نباید خودش با درگاه پرداخت ارتباط برقرار کند. این کار توسط **PYMS Microservice** انجام میشود.
|
||||
|
||||
### 2. **Flow پیشنهادی برای Phase 9 (Club Discount Shop)**:
|
||||
|
||||
#### در FrontOffice.BFF:
|
||||
```csharp
|
||||
// 1. کاربر محصول را انتخاب میکند
|
||||
var product = await cmsClient.GetProductAsync(productId);
|
||||
|
||||
// 2. محاسبه تخفیف
|
||||
var userWallet = await cmsClient.GetUserWalletAsync(userId);
|
||||
var maxDiscountAmount = product.Price * (product.MaxDiscountPercent / 100);
|
||||
var actualDiscountAmount = Math.Min(userWallet.DiscountBalance, maxDiscountAmount);
|
||||
var gatewayAmount = product.Price - actualDiscountAmount;
|
||||
|
||||
// 3. ثبت Order در CMS با وضعیت Pending
|
||||
var order = await cmsClient.CreateDiscountOrderAsync(new CreateDiscountOrderRequest
|
||||
{
|
||||
UserId = userId,
|
||||
ProductId = productId,
|
||||
TotalAmount = product.Price,
|
||||
DiscountAmount = actualDiscountAmount,
|
||||
GatewayAmount = gatewayAmount,
|
||||
Status = OrderStatus.Pending
|
||||
});
|
||||
|
||||
// 4. درخواست پرداخت از PYMS
|
||||
var paymentResponse = await pymsClient.PaymentRequestAsync(new PaymentRequestRequest
|
||||
{
|
||||
MerchantId = "YOUR_MERCHANT_ID",
|
||||
Amount = (long)gatewayAmount, // مبلغی که باید از درگاه پرداخت شود
|
||||
CallbackUrl = $"https://yoursite.com/payment/verify?orderId={order.Id}",
|
||||
Description = $"خرید {product.Title}",
|
||||
Currency = CurrencyEnum.Irt,
|
||||
Type = TransactionTypeEnum.Real,
|
||||
OrderId = order.Id.ToString()
|
||||
});
|
||||
|
||||
// 5. ریدایرکت به درگاه
|
||||
return Redirect(paymentResponse.PaymentGWUrl);
|
||||
```
|
||||
|
||||
#### در Callback (بعد از بازگشت از درگاه):
|
||||
```csharp
|
||||
// 1. دریافت Authority و Status از Query String
|
||||
var authority = Request.Query["Authority"];
|
||||
var status = Request.Query["Status"];
|
||||
var orderId = Request.Query["orderId"];
|
||||
|
||||
// 2. تایید پرداخت از PYMS
|
||||
var verifyResponse = await pymsClient.PaymentVerificationAsync(new PaymentVerificationRequest
|
||||
{
|
||||
Authority = authority,
|
||||
Status = status
|
||||
});
|
||||
|
||||
// 3. ثبت نتیجه در CMS
|
||||
if (verifyResponse.PaymentStatus)
|
||||
{
|
||||
// 3.1. کسر DiscountBalance
|
||||
await cmsClient.DeductDiscountBalanceAsync(new DeductDiscountBalanceRequest
|
||||
{
|
||||
UserId = userId,
|
||||
Amount = order.DiscountAmount,
|
||||
Description = $"خرید محصول {product.Title}",
|
||||
RefId = verifyResponse.RefId
|
||||
});
|
||||
|
||||
// 3.2. ثبت Transaction در CMS
|
||||
await cmsClient.CreateTransactionAsync(new CreateTransactionRequest
|
||||
{
|
||||
UserId = userId,
|
||||
Type = TransactionType.DiscountPurchase,
|
||||
Amount = order.TotalAmount,
|
||||
DiscountAmount = order.DiscountAmount,
|
||||
GatewayAmount = order.GatewayAmount,
|
||||
RefId = verifyResponse.RefId,
|
||||
Status = TransactionStatus.Completed,
|
||||
Description = $"خرید {product.Title}"
|
||||
});
|
||||
|
||||
// 3.3. تغییر وضعیت Order به Completed
|
||||
await cmsClient.CompleteDiscountOrderAsync(new CompleteDiscountOrderRequest
|
||||
{
|
||||
OrderId = orderId,
|
||||
RefId = verifyResponse.RefId
|
||||
});
|
||||
|
||||
return View("PaymentSuccess");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 3.4. تغییر وضعیت Order به Failed
|
||||
await cmsClient.FailDiscountOrderAsync(new FailDiscountOrderRequest
|
||||
{
|
||||
OrderId = orderId,
|
||||
ErrorMessage = verifyResponse.Message
|
||||
});
|
||||
|
||||
return View("PaymentFailed", verifyResponse.Message);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. **Entity های مورد نیاز در CMS**:
|
||||
|
||||
```csharp
|
||||
// Domain/Entities/DiscountOrder.cs
|
||||
public class DiscountOrder
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long UserId { get; set; }
|
||||
public long ProductId { get; set; }
|
||||
public decimal TotalAmount { get; set; }
|
||||
public decimal DiscountAmount { get; set; } // مبلغ از DiscountBalance
|
||||
public decimal GatewayAmount { get; set; } // مبلغ از درگاه
|
||||
public OrderStatus Status { get; set; } // Pending/Completed/Failed
|
||||
public string? RefId { get; set; } // RefId از PYMS
|
||||
public string? ErrorMessage { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
|
||||
// Navigation
|
||||
public User User { get; set; }
|
||||
public Product Product { get; set; }
|
||||
}
|
||||
|
||||
// Domain/Enums/OrderStatus.cs
|
||||
public enum OrderStatus
|
||||
{
|
||||
Pending = 0, // در انتظار پرداخت
|
||||
Completed = 1, // پرداخت موفق
|
||||
Failed = 2 // پرداخت ناموفق
|
||||
}
|
||||
```
|
||||
|
||||
### 4. **Commands مورد نیاز در CMS**:
|
||||
|
||||
- `CreateDiscountOrderCommand`: ثبت سفارش اولیه
|
||||
- `CompleteDiscountOrderCommand`: تکمیل سفارش بعد از پرداخت موفق
|
||||
- `FailDiscountOrderCommand`: شکست سفارش
|
||||
- `DeductDiscountBalanceCommand`: کسر از DiscountBalance
|
||||
|
||||
---
|
||||
|
||||
## ✅ مزایای این معماری
|
||||
|
||||
1. ✅ **Separation of Concerns**: CMS فقط روی business logic خودش تمرکز دارد
|
||||
2. ✅ **Single Responsibility**: PYMS مسئول پرداخت است، CMS مسئول ثبت نتیجه
|
||||
3. ✅ **Easy Testing**: میتوان PYMS را با Mock جایگزین کرد
|
||||
4. ✅ **Scalability**: هر microservice بهصورت مستقل scale میشود
|
||||
5. ✅ **Maintainability**: تغییرات در درگاه پرداخت فقط در PYMS انجام میشود
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ نکات امنیتی
|
||||
|
||||
1. **همیشه Verify کنید**: حتی اگر Status=OK باشد، حتماً PaymentVerification را صدا بزنید
|
||||
2. **Callback را Validate کنید**: مطمئن شوید request واقعاً از درگاه آمده (IP whitelisting)
|
||||
3. **OrderId را Validate کنید**: مطمئن شوید OrderId متعلق به همان کاربری است که لاگین کرده
|
||||
4. **مبلغ را چک کنید**: مبلغ پرداخت شده با مبلغ سفارش مطابقت داشته باشد
|
||||
5. **Idempotency**: از ثبت تکراری تراکنش جلوگیری کنید (با RefId)
|
||||
|
||||
---
|
||||
|
||||
## 📚 مثال کامل برای Phase 9
|
||||
|
||||
در فاز 9، باید:
|
||||
1. ✅ **FrontOffice.BFF** درخواست پرداخت را به **PYMS** بفرستد
|
||||
2. ✅ **PYMS** URL درگاه را برگرداند
|
||||
3. ✅ بعد از بازگشت، **FrontOffice.BFF** verify کند
|
||||
4. ✅ نتیجه را به **CMS** بفرستد تا:
|
||||
- DiscountBalance کسر شود
|
||||
- Transaction ثبت شود
|
||||
- Order تکمیل شود
|
||||
|
||||
---
|
||||
|
||||
**نتیجهگیری**:
|
||||
- ✅ **Payment Gateway Service** (فقط DayaPaymentService برای Payout) **فقط برای پرداخت به کاربران است**
|
||||
- ✅ **Transaction System در CMS** برای دریافت نتیجه پرداخت از Gateway و ادامه عملیات:
|
||||
- Entity: `Transaction` (ReferenceId, Amount, Status, Gateway)
|
||||
- Commands: `CreateTransaction`, `VerifyTransaction` (Callback), `RefundTransaction`
|
||||
- Queries: `GetTransactions`, `GetUserTransactions`
|
||||
- جریان: User → Gateway (پرداخت) → Callback به CMS → CMS (فعالسازی)
|
||||
- ✅ این سرویسها فقط برای **مستندسازی** و **درک معماری** نوشته شدند
|
||||
- ✅ در عمل، **PYMS Microservice** مسئول ارتباط با درگاه است
|
||||
- ✅ **CMS فقط نتیجه را ثبت میکند**
|
||||
Reference in New Issue
Block a user