Skip to main content

Dependencias

Con dotnet:
dotnet add package System.Text.Json
HttpClient viene en el framework estándar.

Modelos

// Models.cs
using System.Text.Json.Serialization;

public record InformacionPago
{
    public decimal flt_total_con_iva { get; init; }
    public decimal flt_valor_iva { get; init; }
    public string str_id_pago { get; init; } = "";
    public string str_descripcion_pago { get; init; } = "";
    public string? str_email { get; init; }
    public string? str_id_cliente { get; init; }
    public string? str_tipo_id { get; init; }
    public string? str_nombre_cliente { get; init; }
    public string? str_apellido_cliente { get; init; }
    public string? str_telefono_cliente { get; init; }
    public string? str_opcional1 { get; init; }
    public string? str_opcional2 { get; init; }
    public string? str_opcional3 { get; init; }
    public string? str_opcional4 { get; init; }
    public string? str_opcional5 { get; init; }
}

public record InformacionSeguridad
{
    public int int_id_comercio { get; init; }
    public string str_usuario { get; init; } = "";
    public string str_clave { get; init; } = "";
    public int int_modalidad { get; init; } = -1;
}

public record AdicionalConfiguracion(int int_codigo, string str_valor);
public record AdicionalPago(int int_codigo, string str_valor);

public record SolicitudInicioPago
{
    public InformacionPago InformacionPago { get; init; } = new();
    public InformacionSeguridad InformacionSeguridad { get; init; } = new();
    public List<AdicionalPago> AdicionalesPago { get; init; } = new();
    public List<AdicionalConfiguracion> AdicionalesConfiguracion { get; init; } = new();
}

public record RespuestaInicioPago(
    int int_codigo,
    string? str_cod_error,
    string? str_descripcion_error,
    string? str_url
);

public record SolicitudVerificacion(
    int int_id_comercio,
    string str_usr_comercio,
    string str_pwd_Comercio,
    string str_id_pago,
    int int_no_pago = -1
);

public record RespuestaVerificacion(
    int int_estado,
    int int_error,
    string? str_detalle,
    int int_cantidad_pagos,
    string? str_res_pago
);

Cliente

// ZonaPagosClient.cs
using System.Net.Http.Json;
using System.Text.Json;

public class ZonaPagosClient
{
    private readonly HttpClient _http;
    private readonly string _apiUrl;
    private readonly int _idComercio;
    private readonly string _usuario;
    private readonly string _clave;
    private readonly string _codigoServicio;

    public ZonaPagosClient(HttpClient http, IConfiguration config)
    {
        _http = http;
        _http.Timeout = TimeSpan.FromSeconds(30);
        _apiUrl = config["ZonaPagos:ApiUrl"] ?? "https://www.zonapagos.com/Apis_CicloPago/api";
        _idComercio = int.Parse(config["ZonaPagos:IdComercio"]!);
        _usuario = config["ZonaPagos:Usuario"]!;
        _clave = config["ZonaPagos:Clave"]!;
        _codigoServicio = config["ZonaPagos:CodigoServicio"]!;
    }

    public async Task<string> IniciarPagoAsync(
        string idPago,
        decimal monto,
        decimal iva,
        string descripcion,
        string emailCliente,
        string documentoCliente,
        string tipoIdCliente = "1",
        string nombreCliente = "",
        string apellidoCliente = "",
        string telefonoCliente = "",
        List<AdicionalConfiguracion>? configExtra = null)
    {
        var request = new SolicitudInicioPago
        {
            InformacionPago = new InformacionPago
            {
                flt_total_con_iva = monto,
                flt_valor_iva = iva,
                str_id_pago = idPago,
                str_descripcion_pago = descripcion,
                str_email = emailCliente,
                str_id_cliente = documentoCliente,
                str_tipo_id = tipoIdCliente,
                str_nombre_cliente = nombreCliente,
                str_apellido_cliente = apellidoCliente,
                str_telefono_cliente = telefonoCliente
            },
            InformacionSeguridad = new InformacionSeguridad
            {
                int_id_comercio = _idComercio,
                str_usuario = _usuario,
                str_clave = _clave,
                int_modalidad = -1
            },
            AdicionalesConfiguracion = new List<AdicionalConfiguracion>
            {
                new(50, _codigoServicio)
            }
        };

        if (configExtra != null)
            request.AdicionalesConfiguracion.AddRange(configExtra);

        var response = await _http.PostAsJsonAsync($"{_apiUrl}/InicioPago", request);
        response.EnsureSuccessStatusCode();
        
        var data = await response.Content.ReadFromJsonAsync<RespuestaInicioPago>()
            ?? throw new Exception("Respuesta nula de ZonaPagos");

        if (data.int_codigo != 1)
            throw new ZonaPagosException(data.str_descripcion_error ?? "Error desconocido", data.int_codigo);

        return data.str_url!;
    }

    public async Task<RespuestaVerificacion> VerificarPagoAsync(string idPago, int noPago = -1)
    {
        var request = new SolicitudVerificacion(_idComercio, _usuario, _clave, idPago, noPago);
        var response = await _http.PostAsJsonAsync($"{_apiUrl}/VerificacionPago", request);
        response.EnsureSuccessStatusCode();
        
        var data = await response.Content.ReadFromJsonAsync<RespuestaVerificacion>()
            ?? throw new Exception("Respuesta nula");

        if (data.int_estado != 1)
            throw new ZonaPagosException(data.str_detalle ?? "Error", 2);

        return data;
    }
}

public class ZonaPagosException : Exception
{
    public int IntCodigo { get; }
    public ZonaPagosException(string mensaje, int intCodigo) : base(mensaje)
        => IntCodigo = intCodigo;
}

Parser

// StrResPagoParser.cs
public static class StrResPagoParser
{
    private static readonly string[] CamposBase = new[]
    {
        "int_ped_numero", "int_n_pago", "int_pago_parcial",
        "int_pago_terminado", "int_estado_pago",
        "dbl_valor_pagado", "dbl_total_pago", "dbl_valor_iva_pagado",
        "str_descripcion", "str_id_cliente",
        "str_nombre", "str_apellido", "str_telefono", "str_email",
        "str_campo1", "str_campo2", "str_campo3", "str_campo4", "str_campo5",
        "dat_fecha", "int_id_forma_pago"
    };

    private static readonly Dictionary<string, string[]> ExtrasPorMedio = new()
    {
        ["29"] = new[] { "str_ticketID", "int_codigo_servicio", "int_codigo_banco",
                          "str_nombre_banco", "str_codigo_transaccion", "int_ciclo_transaccion" },
        ["32"] = new[] { "str_ticketID", "int_numero_tarjeta", "str_franquicia",
                          "int_cod_aprobacion", "int_num_recibido" },
        ["47"] = new[] { "str_ticketID", "int_codigo_banco" },
        ["48"] = new[] { "str_ticketID" },
        ["51"] = new[] { "str_ticketID", "int_numero_tarjeta", "str_franquicia",
                          "int_cod_aprobacion", "int_num_recibido" }
    };

    public static List<Dictionary<string, string>> Parse(string? raw)
    {
        var result = new List<Dictionary<string, string>>();
        if (string.IsNullOrWhiteSpace(raw)) return result;

        var pagosRaw = raw
            .Split(new[] { "|;|" }, StringSplitOptions.None)
            .Select(p => p.Trim())
            .Where(p => p.Length > 0);

        foreach (var pagoRaw in pagosRaw)
        {
            var partes = pagoRaw.Split('|').Select(x => x.Trim()).ToArray();
            var pago = new Dictionary<string, string>();

            for (int i = 0; i < CamposBase.Length; i++)
                pago[CamposBase[i]] = i < partes.Length ? partes[i] : "";

            var medio = pago.GetValueOrDefault("int_id_forma_pago", "");
            if (ExtrasPorMedio.TryGetValue(medio, out var extras))
            {
                for (int i = 0; i < extras.Length; i++)
                {
                    var idx = CamposBase.Length + i;
                    pago[extras[i]] = idx < partes.Length ? partes[idx] : "";
                }
            }
            result.Add(pago);
        }

        return result;
    }
}

Uso en ASP.NET Core

// Program.cs
builder.Services.AddHttpClient<ZonaPagosClient>();

app.MapPost("/checkout", async (CheckoutDto dto, ZonaPagosClient zp) =>
{
    var idPago = $"ORDEN-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
    var url = await zp.IniciarPagoAsync(
        idPago, dto.Monto, dto.Iva, dto.Descripcion,
        dto.Cliente.Email, dto.Cliente.Documento);
    return Results.Redirect(url);
});

app.MapGet("/retorno", async (int id_comercio, string id_pago, ZonaPagosClient zp) =>
{
    var resp = await zp.VerificarPagoAsync(id_pago);
    var pagos = StrResPagoParser.Parse(resp.str_res_pago);
    var ultimo = pagos.LastOrDefault();
    
    if (ultimo?.GetValueOrDefault("int_estado_pago") == "1")
        return Results.Redirect($"/gracias?id={id_pago}");
    return Results.Redirect($"/pendiente?id={id_pago}");
});

Ver también

Node.js

Equivalente en JavaScript.