| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- using System.Text;
- using System.Text.Json;
- using Application.Abstractions.Data;
- using Application.Abstractions.Payment;
- using Microsoft.EntityFrameworkCore;
- using Microsoft.Extensions.Logging;
- namespace Infrastructure.Payment;
- internal sealed class DanalPayService(
- HttpClient httpClient,
- IAppDbContext db,
- ILogger<DanalPayService> logger
- ) : IDanalPayService
- {
- private const string ConfirmUrl = "https://one-api.danalpay.com/payments/confirm";
- private const string CancelUrl = "https://one-api.danalpay.com/payments/cancel";
- public async Task<DanalClientConfig> GetClientConfigAsync(CancellationToken ct)
- {
- var (cpid, clientKey, _) = await GetKeysAsync(ct);
- return new DanalClientConfig(clientKey, cpid);
- }
- public async Task<DanalConfirmResult> ConfirmAsync(string method, string transactionID, string orderID, int amount, CancellationToken ct)
- {
- try
- {
- var (cpid, _, secretKey) = await GetKeysAsync(ct);
- var authHeader = BuildBasicAuth(secretKey);
- var request = new HttpRequestMessage(HttpMethod.Post, ConfirmUrl);
- request.Headers.Add("Authorization", authHeader);
- var body = new
- {
- method,
- transactionId = transactionID,
- orderId = orderID,
- amount = amount.ToString(),
- merchantId = cpid
- };
- request.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
- var response = await httpClient.SendAsync(request, ct);
- var json = await response.Content.ReadAsStringAsync(ct);
- logger.LogInformation("[DanalPay] Confirm response: {StatusCode} {Body}", response.StatusCode, json);
- var doc = JsonSerializer.Deserialize<JsonElement>(json);
- var code = doc.TryGetProperty("code", out var codeProp) ? codeProp.GetString() : null;
- var message = doc.TryGetProperty("message", out var msgProp) ? msgProp.GetString() : null;
- string? Get(string key) => doc.TryGetProperty(key, out var p) ? p.GetString() : null;
- int? GetInt(string key) => int.TryParse(Get(key), out var v) ? v : null;
- byte? GetByte(string key) => byte.TryParse(Get(key), out var v) ? v : null;
- return new DanalConfirmResult(
- code == "SUCCESS", code, message,
- Get("transactionId") ?? transactionID,
- Get("orderName"),
- GetInt("totalAmount"),
- GetInt("discountAmount"),
- Get("userName"),
- Get("transDate"), Get("transTime"),
- Get("cardCode"), Get("cardName"), Get("cardNo"),
- GetByte("installmentMonths"), Get("approveNo"),
- Get("approvalDateTime"), Get("authKey"),
- Get("accountNumber"), Get("bankCode"), Get("userId"), Get("userEmail"),
- Get("bankName"), Get("expireDate"), Get("expireTime"),
- Get("virtualAccountNumber"), Get("useCashReceipt")
- );
- }
- catch (Exception ex)
- {
- logger.LogError(ex, "[DanalPay] Confirm error for orderID={OrderID}", orderID);
- return new DanalConfirmResult(false, "ERROR", ex.Message, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
- }
- }
- public async Task<DanalCancelResult> CancelAsync(string method, string transactionID, int amount, string cancelType, CancellationToken ct)
- {
- try
- {
- var (cpid, _, secretKey) = await GetKeysAsync(ct);
- var authHeader = BuildBasicAuth(secretKey);
- var request = new HttpRequestMessage(HttpMethod.Post, CancelUrl);
- request.Headers.Add("Authorization", authHeader);
- var body = new
- {
- method,
- transactionId = transactionID,
- amount = amount.ToString(),
- merchantId = cpid,
- cancelType
- };
- request.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
- var response = await httpClient.SendAsync(request, ct);
- var json = await response.Content.ReadAsStringAsync(ct);
- logger.LogInformation("[DanalPay] Cancel response: {StatusCode} {Body}", response.StatusCode, json);
- var doc = JsonSerializer.Deserialize<JsonElement>(json);
- var code = doc.TryGetProperty("code", out var codeProp) ? codeProp.GetString() : null;
- var message = doc.TryGetProperty("message", out var msgProp) ? msgProp.GetString() : null;
- string? Get(string key) => doc.TryGetProperty(key, out var p) ? p.GetString() : null;
- int? GetInt(string key) => int.TryParse(Get(key), out var v) ? v : null;
- return new DanalCancelResult(
- code == "SUCCESS", code, message,
- Get("originalTransactionId"),
- GetInt("cancelledAmount"),
- Get("transDate"), Get("transTime"),
- Get("balance"), Get("remainedAmount"),
- Get("approvalDateTime")
- );
- }
- catch (Exception ex)
- {
- logger.LogError(ex, "[DanalPay] Cancel error for transactionID={TransactionID}", transactionID);
- return new DanalCancelResult(false, "ERROR", ex.Message, null, null, null, null, null, null, null);
- }
- }
- // ── Private ──────────────────────────────────────────────────────
- private async Task<(string Cpid, string ClientKey, string SecretKey)> GetKeysAsync(CancellationToken ct)
- {
- var config = await db.Config.FirstOrDefaultAsync(ct);
- if (config?.External is null)
- {
- throw new InvalidOperationException("다날 결제 설정이 없습니다. 관리자 페이지에서 설정해주세요.");
- }
- var ext = config.External;
- var isLive = string.Equals(ext.DanalPayMode, "live", StringComparison.OrdinalIgnoreCase);
- var cpid = isLive ? ext.DanalLiveCpid : ext.DanalTestCpid;
- var clientKey = isLive ? ext.DanalLiveClientKeyEnc : ext.DanalTestClientKeyEnc;
- var secretKey = isLive ? ext.DanalLiveSecretKeyEnc : ext.DanalTestSecretKeyEnc;
- if (string.IsNullOrEmpty(cpid) || string.IsNullOrEmpty(clientKey) || string.IsNullOrEmpty(secretKey))
- {
- throw new InvalidOperationException($"다날 {(isLive ? "라이브" : "테스트")} 설정이 불완전합니다.");
- }
- return (cpid, clientKey, secretKey);
- }
- private static string BuildBasicAuth(string secretKey)
- {
- // SecretKey + ":" → Base64
- var raw = $"{secretKey}:";
- var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(raw));
- return $"Basic {base64}";
- }
- }
|