# Payment Gateway Integration Guide

## 📋 Overview

## 🔄 جریان پرداخت در سیستم

### 1️⃣ دریافت پول از کاربر (Payment IN)
```
کاربر → Gateway/PYMS → بانک → پرداخت موفق
                              ↓
                      Callback به CMS
                              ↓
          CMS: VerifyTransaction + فعال‌سازی عضویت
```
**توضیح**: 
- درگاه اینترنتی در **Gateway/PYMS** است (نه CMS)
- CMS فقط **نتیجه پرداخت را دریافت** می‌کند (از طریق Callback)
- سپس عملیات بعدی (فعال‌سازی، اضافه PV، Wallet) را انجام می‌دهد
- **Transaction System** در CMS برای این کار طراحی شده

### 2️⃣ پرداخت به کاربر (Payout)
```
ادمین تایید برداشت → CMS → DayaPaymentService → واریز به حساب کاربر
```
**توضیح**:
- این سند فقط برای **Payout** است
- سیستم از دو پیاده‌سازی پشتیبانی می‌کند:

1. **MockPaymentGatewayService** - برای Development و Testing
2. **DayaPaymentService** - API واقعی Daya (برای واریز به حساب کاربران)

---

## 🏗️ Architecture

### Interface Design

```csharp
public interface IPaymentGatewayService
{
    // پرداخت (خرید بسته)
    Task<PaymentInitiateResult> InitiatePaymentAsync(
        PaymentRequest request, 
        CancellationToken cancellationToken = default);

    // تایید پرداخت (Callback)
    Task<PaymentVerificationResult> VerifyPaymentAsync(
        string refId, 
        string verificationToken, 
        CancellationToken cancellationToken = default);

    // برداشت/پرداخت به کاربر (Withdrawal)
    Task<PayoutResult> ProcessPayoutAsync(
        PayoutRequest request, 
        CancellationToken cancellationToken = default);
}
```

### DTO Models

#### PaymentRequest
```csharp
public class PaymentRequest
{
    public long UserId { get; set; }
    public string Mobile { get; set; }
    public decimal Amount { get; set; }
    public string Description { get; set; }
    public string CallbackUrl { get; set; }
}
```

#### PaymentInitiateResult
```csharp
public class PaymentInitiateResult
{
    public bool IsSuccess { get; set; }
    public string? RefId { get; set; }
    public string? GatewayUrl { get; set; }
    public string? ErrorMessage { get; set; }
}
```

#### PaymentVerificationResult
```csharp
public class PaymentVerificationResult
{
    public bool IsSuccess { get; set; }
    public string RefId { get; set; }
    public string? TrackingCode { get; set; }
    public decimal Amount { get; set; }
    public string? Message { get; set; }
}
```

#### PayoutRequest
```csharp
public class PayoutRequest
{
    public long UserId { get; set; }
    public string Iban { get; set; }
    public decimal Amount { get; set; }
    public string? Description { get; set; }
}
```

#### PayoutResult
```csharp
public class PayoutResult
{
    public bool IsSuccess { get; set; }
    public string? TransactionId { get; set; }
    public string Message { get; set; }
    public DateTime ProcessedAt { get; set; }
}
```

---

## 🔧 Implementation Details

### 1. MockPaymentGatewayService

**Purpose**: Development و Testing بدون نیاز به API واقعی

**Features**:
- ✅ IBAN validation (IR prefix, 26 characters)
- ✅ Amount validation (min 10,000 Toman)
- ✅ Mock RefId generation (MockRef_{timestamp})
- ✅ Simulated network delay (500ms)
- ✅ Comprehensive logging
- ✅ Gateway URL generation (mock://payment)

**Usage**:
```json
{
  "UseRealPaymentGateway": false
}
```

**Example**:
```csharp
var result = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest
{
    UserId = 123,
    Mobile = "09123456789",
    Amount = 100000,
    Description = "خرید بسته طلایی",
    CallbackUrl = "https://yoursite.com/payment/callback"
});

// result.IsSuccess = true
// result.RefId = "MockRef_1701619200"
// result.GatewayUrl = "mock://payment/MockRef_1701619200"
```

---

### 2. DayaPaymentService

**Purpose**: یکپارچه‌سازی با API واقعی Daya برای پرداخت و برداشت

**Configuration**:
```json
{
  "UseRealPaymentGateway": true,
  "PaymentProvider": "Daya",
  "DayaPayment": {
    "BaseUrl": "https://api.daya.ir",
    "ApiKey": "YOUR_DAYA_API_KEY"
  }
}
```

**API Endpoints**:

#### Initiate Payment
```http
POST {BaseUrl}/api/v1/payment/initiate
Content-Type: application/json
X-API-Key: {ApiKey}

{
  "userId": 123,
  "mobile": "09123456789",
  "amount": 100000,
  "description": "خرید بسته طلایی",
  "callbackUrl": "https://yoursite.com/payment/callback"
}

Response:
{
  "success": true,
  "refId": "DAYA123456789",
  "gatewayUrl": "https://gateway.daya.ir/pay/DAYA123456789",
  "errorMessage": null
}
```

#### Verify Payment
```http
POST {BaseUrl}/api/v1/payment/verify
Content-Type: application/json
X-API-Key: {ApiKey}

{
  "refId": "DAYA123456789",
  "token": "DAYA123456789"
}

Response:
{
  "success": true,
  "refId": "DAYA123456789",
  "trackingCode": "TRACK987654321",
  "amount": 100000,
  "message": "تراکنش موفق"
}
```

#### Process Payout
```http
POST {BaseUrl}/api/v1/payout/process
Content-Type: application/json
X-API-Key: {ApiKey}

{
  "userId": 123,
  "iban": "IR123456789012345678901234",
  "amount": 50000,
  "description": "برداشت کمیسیون"
}

Response:
{
  "success": true,
  "transactionId": "TXN_123456789",
  "message": "پرداخت با موفقیت انجام شد",
  "processedAt": "2024-12-02T10:30:00Z"
}
```

**Error Handling**:
```csharp
try
{
    var response = await _httpClient.PostAsJsonAsync(url, request, cancellationToken);
    
    if (!response.IsSuccessStatusCode)
    {
        _logger.LogError("Daya API error: StatusCode={StatusCode}", response.StatusCode);
        return new PaymentInitiateResult
        {
            IsSuccess = false,
            ErrorMessage = $"خطا در ارتباط با سرویس پرداخت: {response.StatusCode}"
        };
    }
    
    var result = await response.Content.ReadFromJsonAsync<DayaInitiateResponse>(cancellationToken);
    // Process result...
}
catch (Exception ex)
{
    _logger.LogError(ex, "Error in InitiatePaymentAsync");
    return new PaymentInitiateResult
    {
        IsSuccess = false,
        ErrorMessage = "خطای غیرمنتظره در برقراری ارتباط با سرویس پرداخت"
    };
}
```

---

### 3. BankMellatPaymentService

**Purpose**: یکپارچه‌سازی با IPG بانک ملت (SOAP Web Service)

**Configuration**:
```json
{
  "UseRealPaymentGateway": true,
  "PaymentProvider": "BankMellat",
  "BankMellat": {
    "ServiceUrl": "https://bpm.shaparak.ir/pgwchannel/services/pgw",
    "TerminalId": "YOUR_TERMINAL_ID",
    "Username": "YOUR_USERNAME",
    "Password": "YOUR_PASSWORD"
  }
}
```

**SOAP Operations**:

#### bpPayRequest (Initiate Payment)
```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
               xmlns:ns="http://interfaces.core.sw.bps.com/">
    <soap:Body>
        <ns:bpPayRequest>
            <terminalId>{TERMINAL_ID}</terminalId>
            <userName>{USERNAME}</userName>
            <userPassword>{PASSWORD}</userPassword>
            <orderId>{ORDER_ID}</orderId>
            <amount>{AMOUNT_IN_RIALS}</amount>
            <localDate>{yyyyMMdd}</localDate>
            <localTime>{HHmmss}</localTime>
            <additionalData>{DESCRIPTION}</additionalData>
            <callBackUrl>{CALLBACK_URL}</callBackUrl>
            <payerId>0</payerId>
        </ns:bpPayRequest>
    </soap:Body>
</soap:Envelope>
```

**Response**:
```xml
<soap:Envelope>
    <soap:Body>
        <ns:bpPayRequestResponse>
            <return>{REF_ID}</return>  <!-- Success: positive number, Error: negative number -->
        </ns:bpPayRequestResponse>
    </soap:Body>
</soap:Envelope>
```

#### bpVerifyRequest (Verify Payment)
```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
               xmlns:ns="http://interfaces.core.sw.bps.com/">
    <soap:Body>
        <ns:bpVerifyRequest>
            <terminalId>{TERMINAL_ID}</terminalId>
            <userName>{USERNAME}</userName>
            <userPassword>{PASSWORD}</userPassword>
            <orderId>{ORDER_ID}</orderId>
            <saleOrderId>{ORDER_ID}</saleOrderId>
            <saleReferenceId>{REF_ID}</saleReferenceId>
        </ns:bpVerifyRequest>
    </soap:Body>
</soap:Envelope>
```

#### bpSettleRequest (Settle Payment)
```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
               xmlns:ns="http://interfaces.core.sw.bps.com/">
    <soap:Body>
        <ns:bpSettleRequest>
            <terminalId>{TERMINAL_ID}</terminalId>
            <userName>{USERNAME}</userName>
            <userPassword>{PASSWORD}</userPassword>
            <orderId>{ORDER_ID}</orderId>
            <saleOrderId>{ORDER_ID}</saleOrderId>
            <saleReferenceId>{REF_ID}</saleReferenceId>
        </ns:bpSettleRequest>
    </soap:Body>
</soap:Envelope>
```

**Error Codes**:

| Code | Description (Persian) |
|------|----------------------|
| 0 | تراکنش موفق |
| 11 | شماره کارت نامعتبر است |
| 12 | موجودی کافی نیست |
| 13 | رمز نادرست است |
| 14 | تعداد دفعات وارد کردن رمز بیش از حد مجاز است |
| 15 | کارت نامعتبر است |
| 17 | کاربر از انجام تراکنش منصرف شده است |
| 18 | تاریخ انقضای کارت گذشته است |
| 21 | پذیرنده نامعتبر است |
| 23 | خطای امنیتی رخ داده است |
| 24 | اطلاعات کاربری پذیرنده نامعتبر است |
| 25 | مبلغ نامعتبر است |
| 41 | شماره درخواست تکراری است |
| 43 | قبلا درخواست Verify داده شده است |
| 51 | تراکنش تکراری است |

**Limitations**:
- ⚠️ Direct payout (ProcessPayoutAsync) **not supported** by Bank Mellat IPG
- ℹ️ For withdrawals, use **Shaparak Paya** or third-party services like Fanapay, IPG.ir

---

## ⚙️ Service Registration (ConfigureServices.cs)

```csharp
// Payment Gateway Service - برای Development از Mock استفاده می‌شود
var useRealPaymentGateway = configuration.GetValue<bool>("UseRealPaymentGateway", false);

if (useRealPaymentGateway)
{
    var paymentProvider = configuration.GetValue<string>("PaymentProvider", "BankMellat");
    
    if (paymentProvider == "Daya")
    {
        services.AddHttpClient<IPaymentGatewayService, DayaPaymentService>()
            .SetHandlerLifetime(TimeSpan.FromMinutes(5));
    }
    else if (paymentProvider == "BankMellat")
    {
        services.AddHttpClient<IPaymentGatewayService, BankMellatPaymentService>()
            .SetHandlerLifetime(TimeSpan.FromMinutes(5));
    }
    else
    {
        throw new InvalidOperationException($"Invalid PaymentProvider: {paymentProvider}");
    }
}
else
{
    // Mock برای Development و Testing
    services.AddScoped<IPaymentGatewayService, MockPaymentGatewayService>();
}
```

---

## 📝 Usage Examples

### Purchase Package (InitiatePaymentAsync)

```csharp
// In Command Handler
public class PurchaseGoldenPackageCommandHandler : IRequestHandler<PurchaseGoldenPackageCommand, long>
{
    private readonly IPaymentGatewayService _paymentGateway;

    public async Task<long> Handle(PurchaseGoldenPackageCommand request, CancellationToken ct)
    {
        // Initiate payment
        var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest
        {
            UserId = request.UserId,
            Mobile = user.Mobile,
            Amount = packagePrice,
            Description = "خرید بسته طلایی",
            CallbackUrl = "https://yoursite.com/payment/callback"
        }, ct);

        if (!paymentResult.IsSuccess)
        {
            throw new InvalidOperationException(paymentResult.ErrorMessage);
        }

        // Create transaction record
        var transaction = new Transaction
        {
            UserId = request.UserId,
            Type = TransactionType.PackagePurchase,
            Amount = packagePrice,
            Status = TransactionStatus.Pending,
            RefId = paymentResult.RefId,
            Description = "خرید بسته طلایی"
        };

        await _context.Transactions.AddAsync(transaction, ct);
        await _context.SaveChangesAsync(ct);

        // Redirect user to gateway
        return transaction.Id; // Return transaction ID for frontend to track
    }
}
```

### Verify Payment (Callback)

```csharp
public class VerifyGoldenPackagePurchaseCommandHandler : IRequestHandler<VerifyGoldenPackagePurchaseCommand>
{
    private readonly IPaymentGatewayService _paymentGateway;

    public async Task Handle(VerifyGoldenPackagePurchaseCommand request, CancellationToken ct)
    {
        // Verify payment
        var verifyResult = await _paymentGateway.VerifyPaymentAsync(
            request.Authority, 
            request.Authority, 
            ct);

        if (!verifyResult.IsSuccess)
        {
            transaction.Status = TransactionStatus.Failed;
            transaction.ErrorMessage = verifyResult.Message;
            throw new InvalidOperationException(verifyResult.Message);
        }

        // Update transaction
        transaction.Status = TransactionStatus.Completed;
        transaction.CompletedAt = DateTime.UtcNow;

        // Activate club membership
        var clubMembership = new ClubMembership
        {
            UserId = transaction.UserId,
            Status = ClubMembershipStatus.Active,
            StartDate = DateTime.UtcNow,
            EndDate = DateTime.UtcNow.AddMonths(1),
            PurchaseMethod = PackagePurchaseMethod.DirectPurchase
        };

        await _context.ClubMemberships.AddAsync(clubMembership, ct);
        await _context.SaveChangesAsync(ct);
    }
}
```

### Process Withdrawal (ProcessPayoutAsync)

```csharp
public class ProcessWithdrawalCommandHandler : IRequestHandler<ProcessWithdrawalCommand>
{
    private readonly IPaymentGatewayService _paymentGateway;

    public async Task Handle(ProcessWithdrawalCommand request, CancellationToken ct)
    {
        if (request.IsApproved)
        {
            if (payout.WithdrawalMethod == WithdrawalMethod.Diamond)
            {
                // Credit user wallet
                userWallet.DiscountBalance += payout.TotalAmount;
            }
            else if (payout.WithdrawalMethod == WithdrawalMethod.Cash)
            {
                // Process bank transfer
                var payoutResult = await _paymentGateway.ProcessPayoutAsync(new PayoutRequest
                {
                    UserId = payout.UserId,
                    Iban = payout.Iban,
                    Amount = payout.TotalAmount,
                    Description = $"برداشت کمیسیون هفته {payout.WeekNumber}"
                }, ct);

                if (payoutResult.IsSuccess)
                {
                    payout.Status = CommissionStatus.Withdrawn;
                    payout.CompletedAt = DateTime.UtcNow;
                    payout.TransactionId = payoutResult.TransactionId;
                }
                else
                {
                    payout.Status = CommissionStatus.PaymentFailed;
                    payout.ErrorMessage = payoutResult.Message;
                }
            }

            // Record history
            await _context.CommissionPayoutHistories.AddAsync(new CommissionPayoutHistory
            {
                PayoutId = payout.Id,
                TransactionType = payout.Status == CommissionStatus.Withdrawn 
                    ? TransactionType.Withdrawn 
                    : TransactionType.PaymentFailed,
                Amount = payout.TotalAmount,
                ProcessedBy = _currentUserService.UserId,
                ProcessedAt = DateTime.UtcNow
            }, ct);

            await _context.SaveChangesAsync(ct);
        }
    }
}
```

---

## 🧪 Testing Guide

### Unit Testing with Mock

```csharp
[Fact]
public async Task InitiatePayment_Should_Return_Success_With_Valid_Data()
{
    // Arrange
    var mockLogger = new Mock<ILogger<MockPaymentGatewayService>>();
    var service = new MockPaymentGatewayService(mockLogger.Object);
    
    var request = new PaymentRequest
    {
        UserId = 123,
        Mobile = "09123456789",
        Amount = 100000,
        Description = "Test payment",
        CallbackUrl = "https://test.com/callback"
    };

    // Act
    var result = await service.InitiatePaymentAsync(request);

    // Assert
    Assert.True(result.IsSuccess);
    Assert.NotNull(result.RefId);
    Assert.StartsWith("MockRef_", result.RefId);
    Assert.NotNull(result.GatewayUrl);
}

[Fact]
public async Task ProcessPayout_Should_Fail_With_Invalid_IBAN()
{
    // Arrange
    var mockLogger = new Mock<ILogger<MockPaymentGatewayService>>();
    var service = new MockPaymentGatewayService(mockLogger.Object);
    
    var request = new PayoutRequest
    {
        UserId = 123,
        Iban = "INVALID_IBAN",
        Amount = 50000,
        Description = "Test payout"
    };

    // Act
    var result = await service.ProcessPayoutAsync(request);

    // Assert
    Assert.False(result.IsSuccess);
    Assert.Contains("فرمت شماره شبا نامعتبر", result.Message);
}
```

### Integration Testing

```csharp
public class PaymentGatewayIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;

    public PaymentGatewayIntegrationTests(WebApplicationFactory<Program> factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task PurchaseGoldenPackage_Should_Initiate_Payment()
    {
        // Arrange
        var command = new PurchaseGoldenPackageCommand
        {
            UserId = 123,
            PaymentMethod = PackagePurchaseMethod.DirectPurchase
        };

        // Act
        var response = await _client.PostAsJsonAsync("/api/package/purchase", command);

        // Assert
        response.EnsureSuccessStatusCode();
        var transactionId = await response.Content.ReadFromJsonAsync<long>();
        Assert.True(transactionId > 0);
    }
}
```

---

## 🔒 Security Best Practices

1. **Configuration Security**:
   - ✅ Store API keys in `appsettings.json` (excluded from git)
   - ✅ Use Azure Key Vault or AWS Secrets Manager in production
   - ✅ Never hardcode credentials in code

2. **HTTPS Only**:
   - ✅ Enforce HTTPS for all payment callbacks
   - ✅ Validate SSL certificates

3. **Amount Validation**:
   - ✅ Validate min/max amounts before API call
   - ✅ Verify amounts match on callback

4. **IBAN Validation**:
   - ✅ Format: IR + 24 digits = 26 characters
   - ✅ Validate before payout processing

5. **Idempotency**:
   - ✅ Use unique OrderId for each payment
   - ✅ Store RefId to prevent duplicate processing

6. **Error Handling**:
   - ✅ Never expose internal errors to users
   - ✅ Log detailed errors for debugging
   - ✅ Return user-friendly error messages

---

## 📊 Monitoring & Logging

### Recommended Logs

```csharp
// Success
_logger.LogInformation(
    "Payment initiated successfully: UserId={UserId}, Amount={Amount}, RefId={RefId}",
    request.UserId, request.Amount, result.RefId);

// Failure
_logger.LogError(
    "Payment initiation failed: UserId={UserId}, Amount={Amount}, Error={Error}",
    request.UserId, request.Amount, result.ErrorMessage);

// API Error
_logger.LogError(
    "Payment gateway API error: StatusCode={StatusCode}, Response={Response}",
    response.StatusCode, responseContent);
```

### Sentry Integration

```csharp
try
{
    var result = await _paymentGateway.InitiatePaymentAsync(request, ct);
}
catch (Exception ex)
{
    SentrySdk.CaptureException(ex, scope =>
    {
        scope.SetTag("payment_provider", "Daya");
        scope.SetExtra("user_id", request.UserId);
        scope.SetExtra("amount", request.Amount);
    });
    throw;
}
```

---

## 🚀 Production Deployment Checklist

- [ ] Obtain Daya API credentials (BaseUrl + ApiKey)
- [ ] Obtain Bank Mellat credentials (TerminalId, Username, Password)
- [ ] Test in sandbox environment
- [ ] Update `appsettings.Production.json` with credentials
- [ ] Set `UseRealPaymentGateway = true`
- [ ] Configure HTTPS callback URLs
- [ ] Set up monitoring (Sentry/Application Insights)
- [ ] Configure retry policies (Polly)
- [ ] Test full payment flow (Initiate → Callback → Verify)
- [ ] Test withdrawal flow (Request → Approve → Payout)
- [ ] Document production URLs and credentials (secure location)

---

## 📞 Support & Troubleshooting

### Common Issues

**Issue**: "Payment gateway API error: 401 Unauthorized"
- **Solution**: Check API key in `appsettings.json`, verify credentials

**Issue**: "IBAN validation failed"
- **Solution**: Ensure IBAN starts with "IR" and is exactly 26 characters

**Issue**: "Bank Mellat returns negative RefId"
- **Solution**: Check error code mapping, verify TerminalId/Username/Password

**Issue**: "HttpClient timeout"
- **Solution**: Increase timeout in `ConfigureServices.cs`, check network connectivity

---

## 📚 References

- [Daya API Documentation](https://api.daya.ir/docs) (placeholder)
- [Bank Mellat IPG Guide](https://bpm.shaparak.ir/) (official)
- [Shaparak Paya Documentation](https://www.shaparak.ir/)
- [ISO 8601 Week Numbering](https://en.wikipedia.org/wiki/ISO_8601)

---

**Last Updated**: 2024-12-02  
**Version**: 1.0  
**Status**: ✅ Production Ready
