feat(payment): enhance TwentyCard payment integration and update configuration
Build and Deploy to Kubernetes / build-and-deploy (push) Has been cancelled
Build and Deploy to Kubernetes / build-and-deploy (push) Has been cancelled
- Added support for optional Email and NationalId fields in PaymentRequest for TwentyCard transactions. - Updated TwentyCardPaymentService to handle multipart/form-data requests and improved error handling. - Modified appsettings and configuration files for both production and staging environments to include updated BaseUrl and credentials for TwentyCard. - Updated README to reflect the new documentation location.
This commit is contained in:
@@ -24,6 +24,23 @@ stringData:
|
|||||||
"MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8",
|
"MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8",
|
||||||
"UseSandbox": false
|
"UseSandbox": false
|
||||||
},
|
},
|
||||||
|
"TwentyCard": {
|
||||||
|
"BaseUrl": "https://gateway.20card.ir/ipg",
|
||||||
|
"TerminalId": "165674",
|
||||||
|
"TerminalUsername": "ea5y1biehi",
|
||||||
|
"TerminalPassword": "SaLamAT@1404!",
|
||||||
|
"CreditPortalUrl": "https://userpanel.20card.ir/",
|
||||||
|
"Packages": {
|
||||||
|
"4": {
|
||||||
|
"GatewayAmountToman": 9700000,
|
||||||
|
"WalletCreditToman": 13000000
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"GatewayAmountToman": 97000000,
|
||||||
|
"WalletCreditToman": 130000000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"CmsBaseUrl": "https://cms.kbs2.ir",
|
"CmsBaseUrl": "https://cms.kbs2.ir",
|
||||||
"FrontOfficeBaseUrl": "https://kbs2.ir",
|
"FrontOfficeBaseUrl": "https://kbs2.ir",
|
||||||
"JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=",
|
"JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=",
|
||||||
|
|||||||
@@ -24,6 +24,23 @@ stringData:
|
|||||||
"MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8",
|
"MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8",
|
||||||
"UseSandbox": true
|
"UseSandbox": true
|
||||||
},
|
},
|
||||||
|
"TwentyCard": {
|
||||||
|
"BaseUrl": "https://gateway.20card.ir/ipg",
|
||||||
|
"TerminalId": "165674",
|
||||||
|
"TerminalUsername": "ea5y1biehi",
|
||||||
|
"TerminalPassword": "SaLamAT@1404!",
|
||||||
|
"CreditPortalUrl": "https://userpanel.20card.ir/",
|
||||||
|
"Packages": {
|
||||||
|
"4": {
|
||||||
|
"GatewayAmountToman": 9700000,
|
||||||
|
"WalletCreditToman": 13000000
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"GatewayAmountToman": 97000000,
|
||||||
|
"WalletCreditToman": 130000000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"CmsBaseUrl": "https://cms.se.kbs1.ir",
|
"CmsBaseUrl": "https://cms.se.kbs1.ir",
|
||||||
"FrontOfficeBaseUrl": "https://frontoffice.se.kbs1.ir",
|
"FrontOfficeBaseUrl": "https://frontoffice.se.kbs1.ir",
|
||||||
"FMS": {
|
"FMS": {
|
||||||
|
|||||||
@@ -108,6 +108,16 @@ public class PaymentRequest
|
|||||||
/// شماره فاکتور / سفارش
|
/// شماره فاکتور / سفارش
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Invoice { get; set; } = string.Empty;
|
public string Invoice { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ایمیل (اختیاری — برای ۲۰کارت)
|
||||||
|
/// </summary>
|
||||||
|
public string? Email { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// کد ملی (اختیاری — برای ۲۰کارت)
|
||||||
|
/// </summary>
|
||||||
|
public string? NationalId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using CMSMicroservice.Application.Common.Interfaces;
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
@@ -11,7 +10,7 @@ namespace CMSMicroservice.Infrastructure.Services.Payment;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// درگاه پرداخت بیستکارت (20Card IPG Merchant API).
|
/// درگاه پرداخت بیستکارت (20Card IPG Merchant API).
|
||||||
/// مبلغ ورودی به تومان است (مثل بقیهٔ سیستم).
|
/// مبلغ ورودی به تومان است. درخواستها بهصورت multipart/form-data ارسال میشوند.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TwentyCardPaymentService : IPaymentGatewayService
|
public class TwentyCardPaymentService : IPaymentGatewayService
|
||||||
{
|
{
|
||||||
@@ -55,12 +54,15 @@ public class TwentyCardPaymentService : IPaymentGatewayService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var amount = (long)request.Amount;
|
var amount = ((long)request.Amount).ToString();
|
||||||
var payerName = string.IsNullOrWhiteSpace(request.Name)
|
var payerName = string.IsNullOrWhiteSpace(request.Name)
|
||||||
? (string.IsNullOrWhiteSpace(request.Mobile) ? "کاربر" : request.Mobile)
|
? (string.IsNullOrWhiteSpace(request.Mobile) ? "کاربر" : request.Mobile)
|
||||||
: request.Name.Trim();
|
: request.Name.Trim();
|
||||||
|
var invoice = string.IsNullOrWhiteSpace(request.Invoice)
|
||||||
|
? request.UserId.ToString()
|
||||||
|
: request.Invoice;
|
||||||
|
|
||||||
var body = new Dictionary<string, object?>
|
using var content = BuildForm(new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
["terminal_id"] = _terminalId,
|
["terminal_id"] = _terminalId,
|
||||||
["terminal_username"] = _terminalUsername,
|
["terminal_username"] = _terminalUsername,
|
||||||
@@ -68,19 +70,19 @@ public class TwentyCardPaymentService : IPaymentGatewayService
|
|||||||
["amount"] = amount,
|
["amount"] = amount,
|
||||||
["name"] = payerName,
|
["name"] = payerName,
|
||||||
["mobile"] = request.Mobile ?? string.Empty,
|
["mobile"] = request.Mobile ?? string.Empty,
|
||||||
["invoice"] = string.IsNullOrWhiteSpace(request.Invoice)
|
["invoice"] = invoice,
|
||||||
? request.UserId.ToString()
|
|
||||||
: request.Invoice,
|
|
||||||
["callback"] = request.CallbackUrl,
|
["callback"] = request.CallbackUrl,
|
||||||
["description"] = request.Description
|
["description"] = request.Description ?? string.Empty
|
||||||
};
|
});
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.Email))
|
||||||
|
content.Add(new StringContent(request.Email), "email");
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.NationalId))
|
||||||
|
content.Add(new StringContent(request.NationalId), "national_id");
|
||||||
|
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
"TwentyCard payment request: Amount={Amount} Toman, User={UserId}, Invoice={Invoice}",
|
"TwentyCard payment request: Amount={Amount} Toman, User={UserId}, Invoice={Invoice}",
|
||||||
amount, request.UserId, body["invoice"]);
|
amount, request.UserId, invoice);
|
||||||
|
|
||||||
using var content = new StringContent(
|
|
||||||
JsonSerializer.Serialize(body, JsonOptions), Encoding.UTF8, "application/json");
|
|
||||||
|
|
||||||
var response = await _httpClient.PostAsync("api/pgw/merchant/request", content, cancellationToken);
|
var response = await _httpClient.PostAsync("api/pgw/merchant/request", content, cancellationToken);
|
||||||
var responseBody = await response.Content.ReadAsStringAsync(cancellationToken);
|
var responseBody = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
@@ -108,7 +110,9 @@ public class TwentyCardPaymentService : IPaymentGatewayService
|
|||||||
return new PaymentInitiateResult
|
return new PaymentInitiateResult
|
||||||
{
|
{
|
||||||
IsSuccess = false,
|
IsSuccess = false,
|
||||||
ErrorMessage = result?.Message ?? "ایجاد تراکنش ۲۰کارت ناموفق بود"
|
ErrorMessage = result?.Message
|
||||||
|
?? result?.Error?.Message
|
||||||
|
?? "ایجاد تراکنش ۲۰کارت ناموفق بود"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,17 +159,14 @@ public class TwentyCardPaymentService : IPaymentGatewayService
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var body = new Dictionary<string, object?>
|
using var content = BuildForm(new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
["terminal_id"] = _terminalId,
|
["terminal_id"] = _terminalId,
|
||||||
["terminal_username"] = _terminalUsername,
|
["terminal_username"] = _terminalUsername,
|
||||||
["terminal_password"] = _terminalPassword,
|
["terminal_password"] = _terminalPassword,
|
||||||
["authority"] = refId,
|
["authority"] = refId,
|
||||||
["amount"] = (long)amountInToman
|
["amount"] = ((long)amountInToman).ToString()
|
||||||
};
|
});
|
||||||
|
|
||||||
using var content = new StringContent(
|
|
||||||
JsonSerializer.Serialize(body, JsonOptions), Encoding.UTF8, "application/json");
|
|
||||||
|
|
||||||
var response = await _httpClient.PostAsync("api/pgw/merchant/verify", content, cancellationToken);
|
var response = await _httpClient.PostAsync("api/pgw/merchant/verify", content, cancellationToken);
|
||||||
var responseBody = await response.Content.ReadAsStringAsync(cancellationToken);
|
var responseBody = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
@@ -236,10 +237,25 @@ public class TwentyCardPaymentService : IPaymentGatewayService
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static MultipartFormDataContent BuildForm(Dictionary<string, string> fields)
|
||||||
|
{
|
||||||
|
var form = new MultipartFormDataContent();
|
||||||
|
foreach (var (key, value) in fields)
|
||||||
|
form.Add(new StringContent(value ?? string.Empty), key);
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class TwentyCardRequestResponse
|
private sealed class TwentyCardRequestResponse
|
||||||
{
|
{
|
||||||
public string? Message { get; set; }
|
public string? Message { get; set; }
|
||||||
public TwentyCardRequestData? Data { get; set; }
|
public TwentyCardRequestData? Data { get; set; }
|
||||||
|
public TwentyCardErrorBody? Error { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TwentyCardErrorBody
|
||||||
|
{
|
||||||
|
public string? Code { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class TwentyCardRequestData
|
private sealed class TwentyCardRequestData
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ public class PackageService : PackageContract.PackageContractBase
|
|||||||
var user = await _context.Users
|
var user = await _context.Users
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(u => u.Id == userId)
|
.Where(u => u.Id == userId)
|
||||||
.Select(u => new { u.Mobile, u.FirstName, u.LastName })
|
.Select(u => new { u.Mobile, u.FirstName, u.LastName, u.Email, u.NationalCode })
|
||||||
.FirstOrDefaultAsync(cancellationToken);
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
// Callback URL از config — نه از ورودی کاربر (امنیت)
|
// Callback URL از config — نه از ورودی کاربر (امنیت)
|
||||||
@@ -241,7 +241,9 @@ public class PackageService : PackageContract.PackageContractBase
|
|||||||
Name = payerName,
|
Name = payerName,
|
||||||
Invoice = purchase.Id.ToString(),
|
Invoice = purchase.Id.ToString(),
|
||||||
Description = $"خرید پکیج {package.Title}",
|
Description = $"خرید پکیج {package.Title}",
|
||||||
CallbackUrl = callbackUrl
|
CallbackUrl = callbackUrl,
|
||||||
|
Email = user?.Email,
|
||||||
|
NationalId = user?.NationalCode
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
if (!paymentResult.IsSuccess)
|
if (!paymentResult.IsSuccess)
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
"UseSandbox": true
|
"UseSandbox": true
|
||||||
},
|
},
|
||||||
"TwentyCard": {
|
"TwentyCard": {
|
||||||
"BaseUrl": "https://gateway.20card.ir",
|
"BaseUrl": "https://gateway.20card.ir/ipg",
|
||||||
"TerminalId": "165674",
|
"TerminalId": "165674",
|
||||||
"TerminalUsername": "ea5y1biehi",
|
"TerminalUsername": "ea5y1biehi",
|
||||||
"TerminalPassword": "ea5y1biehi",
|
"TerminalPassword": "SaLamAT@1404!",
|
||||||
"CreditPortalUrl": "https://20card.ir",
|
"CreditPortalUrl": "https://userpanel.20card.ir/",
|
||||||
"Packages": {
|
"Packages": {
|
||||||
"4": {
|
"4": {
|
||||||
"GatewayAmountToman": 9700000,
|
"GatewayAmountToman": 9700000,
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
"UseSandbox": true
|
"UseSandbox": true
|
||||||
},
|
},
|
||||||
"TwentyCard": {
|
"TwentyCard": {
|
||||||
"BaseUrl": "https://gateway.20card.ir",
|
"BaseUrl": "https://gateway.20card.ir/ipg",
|
||||||
"TerminalId": "165674",
|
"TerminalId": "165674",
|
||||||
"TerminalUsername": "ea5y1biehi",
|
"TerminalUsername": "ea5y1biehi",
|
||||||
"TerminalPassword": "ea5y1biehi",
|
"TerminalPassword": "SaLamAT@1404!",
|
||||||
"CreditPortalUrl": "https://20card.ir",
|
"CreditPortalUrl": "https://userpanel.20card.ir/",
|
||||||
"Packages": {
|
"Packages": {
|
||||||
"4": {
|
"4": {
|
||||||
"GatewayAmountToman": 9700000,
|
"GatewayAmountToman": 9700000,
|
||||||
|
|||||||
Reference in New Issue
Block a user