# 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 فقط نتیجه را ثبت می‌کند**
